Skip to content

Commit c96acc4

Browse files
committed
Cleanup tests to be more focused and assert clearly expected entropy use
1 parent 6aec2f7 commit c96acc4

2 files changed

Lines changed: 122 additions & 141 deletions

File tree

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/runtime/PythonContextEntropyTests.java

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import java.util.Random;
5252

5353
import org.graalvm.polyglot.PolyglotException;
54-
import org.graalvm.polyglot.Context;
5554
import org.junit.After;
5655
import org.junit.Before;
5756
import org.junit.Test;
@@ -124,26 +123,4 @@ public void deviceInitializationEntropySourceThrowsProviderExceptionWhenExhauste
124123
Files.deleteIfExists(tempFile);
125124
}
126125
}
127-
128-
@Test
129-
public void randomSeedNoneConsumesInitializationEntropyBytes() {
130-
long seed = 0x1234ABCDL;
131-
Context polyglotContext = PythonTests.enterContext(Map.of("python.InitializationEntropySource", "fixed:0x1234ABCD"), new String[]{"-S"});
132-
try {
133-
polyglotContext.eval("python", "import _random; _random.Random()");
134-
PythonContext context = PythonContext.get(null);
135-
byte[] actual = new byte[16];
136-
context.fillInitializationEntropyBytes(actual);
137-
138-
Random expectedRandom = new Random(seed);
139-
expectedRandom.nextBytes(new byte[24]);
140-
expectedRandom.nextBytes(new byte[624 * Integer.BYTES]);
141-
byte[] expected = new byte[16];
142-
expectedRandom.nextBytes(expected);
143-
144-
assertArrayEquals(expected, actual);
145-
} finally {
146-
PythonTests.closeContext();
147-
}
148-
}
149126
}

graalpython/com.oracle.graal.python.test/src/tests/test_entropy_subprocess.py

Lines changed: 122 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,53 @@
4242
import sys
4343
import tempfile
4444
import textwrap
45+
import threading
4546
import unittest
4647

4748

4849
@unittest.skipUnless(sys.implementation.name == "graalpy" and sys.platform.startswith("linux"), "Linux GraalPy-specific test")
4950
class EntropySubprocessTests(unittest.TestCase):
5051
HASH_SECRET_BYTES = 24
5152
RANDOM_SEED_BYTES = 624 * 4
52-
HASH_AND_RANDOM_IMPORT_BYTES = HASH_SECRET_BYTES + RANDOM_SEED_BYTES
53+
RANDOM_INSTANCE_BYTES = HASH_SECRET_BYTES + RANDOM_SEED_BYTES
54+
RANDOM_MODULE_BYTES = HASH_SECRET_BYTES + (2 * RANDOM_SEED_BYTES)
55+
TEMPFILE_CANDIDATE_NAME_BYTES = HASH_SECRET_BYTES + (4 * RANDOM_SEED_BYTES)
5356
SSL_DATA_DIR = os.path.join(os.path.dirname(__file__), "ssldata")
5457

55-
def _run_with_init_device(self, data: bytes, code: str):
56-
with tempfile.NamedTemporaryFile(delete=False) as f:
57-
f.write(data)
58-
path = f.name
59-
try:
60-
return self._run_with_init_source(f"device:{path}", code)
61-
finally:
62-
os.unlink(path)
58+
def _run_with_init_pipe(self, byte_count: int, code: str):
59+
with tempfile.TemporaryDirectory() as temp_dir:
60+
path = os.path.join(temp_dir, "initrandom")
61+
subprocess.run(["mkfifo", path], check=True)
62+
writer_error = []
63+
bytes_written = 0
64+
65+
def feed_pipe():
66+
nonlocal bytes_written
67+
try:
68+
fd = os.open(path, os.O_WRONLY)
69+
try:
70+
remaining = byte_count
71+
chunk = b"\x00" * min(4096, byte_count)
72+
while remaining > 0:
73+
written = os.write(fd, chunk[:remaining])
74+
bytes_written += written
75+
remaining -= written
76+
finally:
77+
os.close(fd)
78+
except BaseException as exc: # re-raised in the main thread
79+
writer_error.append(exc)
80+
81+
writer = threading.Thread(target=feed_pipe)
82+
writer.start()
83+
try:
84+
result = self._run_with_init_source(f"device:{path}", code)
85+
finally:
86+
writer.join(timeout=10)
87+
if writer.is_alive():
88+
self.fail("initrandom pipe writer thread did not finish")
89+
if writer_error:
90+
raise writer_error[0]
91+
return result, bytes_written
6392

6493
def _run_with_init_source(self, source: str, code: str):
6594
env = os.environ.copy()
@@ -86,65 +115,64 @@ def assert_initrandom_exhausted(self, result):
86115
def assert_subprocess_ok(self, result):
87116
self.assertEqual(0, result.returncode, result)
88117

118+
def assert_initrandom_bytes_used(self, byte_count: int, code: str, stdout: str):
119+
result, bytes_written = self._run_with_init_pipe(byte_count, code)
120+
self.assert_subprocess_ok(result)
121+
self.assertEqual(byte_count, bytes_written)
122+
self.assertEqual(stdout, result.stdout.strip())
123+
124+
exhausted_result, exhausted_written = self._run_with_init_pipe(byte_count - 1, code)
125+
self.assert_initrandom_exhausted(exhausted_result)
126+
self.assertEqual(byte_count - 1, exhausted_written)
127+
89128
def test_startup_hash_secret_uses_initrandom(self):
90-
result = self._run_with_init_device(b"\x00" * 4, "print('ok')")
91-
self.assert_initrandom_exhausted(result)
129+
self.assert_initrandom_bytes_used(self.HASH_SECRET_BYTES, "print('ok')", "ok")
92130

93131
def test__random_random_uses_initrandom(self):
94-
result = self._run_with_init_device(
95-
b"\x00" * self.HASH_SECRET_BYTES,
132+
self.assert_initrandom_bytes_used(
133+
self.RANDOM_INSTANCE_BYTES,
96134
"import _random; _random.Random(); print('ok')",
135+
"ok",
97136
)
98-
self.assert_initrandom_exhausted(result)
99137

100138
def test_random_module_import_uses_initrandom(self):
101-
result = self._run_with_init_device(
102-
b"\x00" * self.HASH_SECRET_BYTES,
139+
self.assert_initrandom_bytes_used(
140+
self.RANDOM_MODULE_BYTES,
103141
"import random; print('ok')",
142+
"ok",
104143
)
105-
self.assert_initrandom_exhausted(result)
106-
107-
def test_systemrandom_does_not_mutate_random_state(self):
108-
result = self._run_with_init_source(
109-
"fixed:0x1234ABCD",
110-
"import random; before = random.getstate(); "
111-
"random.SystemRandom().getrandbits(32); "
112-
"after = random.getstate(); "
113-
"print(before == after)",
144+
145+
def test_systemrandom_does_not_use_additional_initrandom(self):
146+
self.assert_initrandom_bytes_used(
147+
self.RANDOM_MODULE_BYTES,
148+
"import random; random.SystemRandom().getrandbits(32); print('ok')",
149+
"ok",
114150
)
115-
self.assert_subprocess_ok(result)
116-
self.assertEqual("True", result.stdout.strip())
117-
118-
def test_secrets_does_not_mutate_random_state(self):
119-
result = self._run_with_init_source(
120-
"fixed:0x1234ABCD",
121-
"import random; before = random.getstate(); "
122-
"import secrets; secrets.token_hex(8); "
123-
"after = random.getstate(); "
124-
"print(before == after)",
151+
152+
def test_secrets_does_not_use_additional_initrandom(self):
153+
self.assert_initrandom_bytes_used(
154+
self.RANDOM_MODULE_BYTES,
155+
"import random; import secrets; secrets.token_hex(8); print('ok')",
156+
"ok",
125157
)
126-
self.assert_subprocess_ok(result)
127-
self.assertEqual("True", result.stdout.strip())
128158

129159
def test_os_urandom_does_not_use_initrandom(self):
130-
result = self._run_with_init_device(
131-
b"\x00" * self.HASH_SECRET_BYTES,
160+
self.assert_initrandom_bytes_used(
161+
self.HASH_SECRET_BYTES,
132162
"import os; print(len(os.urandom(16)))",
163+
"16",
133164
)
134-
self.assert_subprocess_ok(result)
135-
self.assertEqual("16", result.stdout.strip())
136165

137166
def test_multiprocessing_process_import_does_not_use_initrandom(self):
138-
result = self._run_with_init_device(
139-
b"\x00" * self.HASH_SECRET_BYTES,
167+
self.assert_initrandom_bytes_used(
168+
self.HASH_SECRET_BYTES,
140169
"import multiprocessing.process; print('ok')",
170+
"ok",
141171
)
142-
self.assert_subprocess_ok(result)
143-
self.assertEqual("ok", result.stdout.strip())
144172

145-
def test_multiprocessing_deliver_challenge_does_not_mutate_random_state(self):
146-
result = self._run_with_init_source(
147-
"fixed:0x1234ABCD",
173+
def test_multiprocessing_deliver_challenge_does_not_use_additional_initrandom(self):
174+
self.assert_initrandom_bytes_used(
175+
self.RANDOM_MODULE_BYTES,
148176
textwrap.dedent("""
149177
import random
150178
import multiprocessing.connection as mc
@@ -161,48 +189,37 @@ def recv_bytes(self, _max):
161189
msg = self.sent[-1][len(mc._CHALLENGE):]
162190
return mc._create_response(auth, msg)
163191
164-
before = random.getstate()
165192
d = Dummy()
166193
mc.deliver_challenge(d, auth)
167-
after = random.getstate()
168-
print(before == after)
194+
print('ok')
169195
"""),
196+
"ok",
170197
)
171-
self.assert_subprocess_ok(result)
172-
self.assertEqual("True", result.stdout.strip())
173198

174199
def test_tempfile_candidate_names_use_initrandom(self):
175-
result = self._run_with_init_device(
176-
b"\x00" * self.HASH_AND_RANDOM_IMPORT_BYTES,
200+
self.assert_initrandom_bytes_used(
201+
self.TEMPFILE_CANDIDATE_NAME_BYTES,
177202
"import tempfile; next(tempfile._get_candidate_names()); print('ok')",
203+
"ok",
178204
)
179-
self.assert_initrandom_exhausted(result)
180-
181-
def test_tempfile_does_not_mutate_random_state(self):
182-
result = self._run_with_init_source(
183-
"fixed:0x1234ABCD",
184-
"import random; before = random.getstate(); "
185-
"import tempfile; next(tempfile._get_candidate_names()); "
186-
"after = random.getstate(); "
187-
"print(before == after)",
205+
206+
def test_tempfile_after_random_import_uses_initrandom(self):
207+
self.assert_initrandom_bytes_used(
208+
self.TEMPFILE_CANDIDATE_NAME_BYTES,
209+
"import random; import tempfile; next(tempfile._get_candidate_names()); print('ok')",
210+
"ok",
188211
)
189-
self.assert_subprocess_ok(result)
190-
self.assertEqual("True", result.stdout.strip())
191-
192-
def test_email_generator_boundary_mutates_random_state(self):
193-
result = self._run_with_init_source(
194-
"fixed:0x1234ABCD",
195-
"import random; before = random.getstate(); "
196-
"from email.generator import Generator; Generator._make_boundary(); "
197-
"after = random.getstate(); "
198-
"print(before == after)",
212+
213+
def test_email_generator_boundary_does_not_use_additional_initrandom(self):
214+
self.assert_initrandom_bytes_used(
215+
self.RANDOM_MODULE_BYTES,
216+
"import random; from email.generator import Generator; Generator._make_boundary(); print('ok')",
217+
"ok",
199218
)
200-
self.assert_subprocess_ok(result)
201-
self.assertEqual("False", result.stdout.strip())
202219

203-
def test_imaplib_connect_mutates_random_state(self):
204-
result = self._run_with_init_source(
205-
"fixed:0x1234ABCD",
220+
def test_imaplib_connect_does_not_use_additional_initrandom(self):
221+
self.assert_initrandom_bytes_used(
222+
self.RANDOM_MODULE_BYTES,
206223
textwrap.dedent("""
207224
import random
208225
import imaplib
@@ -221,7 +238,6 @@ def _get_capabilities(self):
221238
def shutdown(self):
222239
pass
223240
224-
before = random.getstate()
225241
d = Dummy.__new__(Dummy)
226242
d.debug = imaplib.Debug
227243
d.state = 'LOGOUT'
@@ -234,74 +250,62 @@ def shutdown(self):
234250
d._tls_established = False
235251
d._mode_ascii()
236252
d._connect()
237-
after = random.getstate()
238-
print(before == after)
253+
print('ok')
239254
"""),
255+
"ok",
240256
)
241-
self.assert_subprocess_ok(result)
242-
self.assertEqual("False", result.stdout.strip())
243257

244258
def test_pyexpat_import_does_not_use_additional_initrandom(self):
245-
result = self._run_with_init_device(
246-
b"\x00" * self.HASH_SECRET_BYTES,
259+
self.assert_initrandom_bytes_used(
260+
self.HASH_SECRET_BYTES,
247261
"import pyexpat; print(pyexpat.__name__)",
262+
"pyexpat",
248263
)
249-
self.assert_subprocess_ok(result)
250-
self.assertEqual("pyexpat", result.stdout.strip())
251264

252265
def test_pyexpat_parsercreate_does_not_use_additional_initrandom(self):
253-
result = self._run_with_init_device(
254-
b"\x00" * self.HASH_SECRET_BYTES,
266+
self.assert_initrandom_bytes_used(
267+
self.HASH_SECRET_BYTES,
255268
"import pyexpat; p = pyexpat.ParserCreate(); print(type(p).__name__)",
269+
"xmlparser",
256270
)
257-
self.assert_subprocess_ok(result)
258-
self.assertEqual("xmlparser", result.stdout.strip())
259271

260272
def test_sqlite3_import_does_not_use_additional_initrandom(self):
261-
result = self._run_with_init_device(
262-
b"\x00" * self.HASH_SECRET_BYTES,
273+
self.assert_initrandom_bytes_used(
274+
self.HASH_SECRET_BYTES,
263275
"import _sqlite3; print(_sqlite3.__name__)",
276+
"_sqlite3",
264277
)
265-
self.assert_subprocess_ok(result)
266-
self.assertEqual("_sqlite3", result.stdout.strip())
267278

268279
def test_sqlite3_randomblob_does_not_use_initrandom(self):
269-
result = self._run_with_init_device(
270-
b"\x00" * self.HASH_SECRET_BYTES,
280+
self.assert_initrandom_bytes_used(
281+
self.HASH_SECRET_BYTES,
271282
"import sqlite3; "
272283
"conn = sqlite3.connect(':memory:'); "
273284
"print(conn.execute('select length(randomblob(16))').fetchone()[0])",
285+
"16",
274286
)
275-
self.assert_subprocess_ok(result)
276-
self.assertEqual("16", result.stdout.strip())
277287

278288
def test_ssl_load_cert_chain_does_not_use_initrandom(self):
279289
cert = os.path.join(self.SSL_DATA_DIR, "signed_cert.pem")
280-
result = self._run_with_init_device(
281-
b"\x00" * self.HASH_SECRET_BYTES,
290+
self.assert_initrandom_bytes_used(
291+
self.HASH_SECRET_BYTES,
282292
f"import ssl; "
283293
f"ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER); "
284294
f"ctx.load_cert_chain(r'{cert}'); "
285295
f"print('ok')",
296+
"ok",
286297
)
287-
self.assert_subprocess_ok(result)
288-
self.assertEqual("ok", result.stdout.strip())
289-
290-
def test_uuid1_mutates_random_state(self):
291-
result = self._run_with_init_source(
292-
"fixed:0x1234ABCD",
293-
"import random; before = random.getstate(); "
294-
"import uuid; uuid.uuid1(node=1); "
295-
"after = random.getstate(); "
296-
"print(before == after)",
298+
299+
def test_uuid1_does_not_use_additional_initrandom(self):
300+
self.assert_initrandom_bytes_used(
301+
self.RANDOM_MODULE_BYTES,
302+
"import random; import uuid; uuid.uuid1(node=1); print('ok')",
303+
"ok",
297304
)
298-
self.assert_subprocess_ok(result)
299-
self.assertEqual("False", result.stdout.strip())
300305

301306
def test_uuid4_does_not_use_initrandom(self):
302-
result = self._run_with_init_device(
303-
b"\x00" * self.HASH_SECRET_BYTES,
307+
self.assert_initrandom_bytes_used(
308+
self.HASH_SECRET_BYTES,
304309
"import uuid; print(uuid.uuid4().version)",
310+
"4",
305311
)
306-
self.assert_subprocess_ok(result)
307-
self.assertEqual("4", result.stdout.strip())

0 commit comments

Comments
 (0)