|
1 | 1 |
|
2 | 2 | import os |
3 | 3 | import re |
4 | | -from signal import SIGINT, SIGTERM |
| 4 | +import signal |
5 | 5 | import subprocess |
6 | 6 | import sys |
7 | 7 | import tempfile |
|
15 | 15 | OBJECT_NAME = 'data_get_issue__722' |
16 | 16 | LOCAL_TEMPFILE_NAME = 'data_object_for_issue_722.dat' |
17 | 17 |
|
18 | | -_clock_resolution = max(.01, *(time.clock_getres(getattr(time,symbol)) |
19 | | - for symbol in dir(time) if symbol.startswith('CLOCK_'))) |
| 18 | + |
| 19 | +_clock_polling_interval = max(.01, time.clock_getres(time.CLOCK_BOOTTIME)) |
| 20 | + |
| 21 | + |
20 | 22 | def wait_till_true(function, timeout=None): |
21 | 23 | start_time = time.clock_gettime_ns(time.CLOCK_BOOTTIME) |
22 | 24 | while not (truth_value := function()): |
23 | 25 | if timeout is not None and (time.clock_gettime_ns(time.CLOCK_BOOTTIME)-start_time)*1e-9 > timeout: |
24 | 26 | break |
25 | | - time.sleep(_clock_resolution) |
| 27 | + time.sleep(_clock_polling_interval) |
26 | 28 | return truth_value |
27 | 29 |
|
28 | 30 |
|
29 | | -def test(test_case, sigs = (SIGINT,SIGTERM)): |
| 31 | +def test(test_case, signal_names = ("SIGTERM",#"SIGINT" |
| 32 | + )): |
30 | 33 | """Creates a child process executing a long get() and ensures the process can be |
31 | 34 | terminated using SIGINT or SIGTERM. |
32 | 35 | """ |
33 | 36 | program = os.path.join(test_modules.__path__[0], os.path.basename(__file__)) |
34 | 37 |
|
35 | | - # Call into this same module as a command. This will initiate another Python process that |
36 | | - # performs a lengthy data object "get" operation (see the main body of the script, below.) |
37 | | - process = subprocess.Popen([sys.executable, program], |
38 | | - stderr=subprocess.PIPE, |
39 | | - stdout=subprocess.PIPE, |
40 | | - text = True) |
41 | | - |
42 | | - # Wait for download process to reach the point of spawning data transfer threads. In Python 3.9+ versions |
43 | | - # of the concurrent.futures module, these are nondaemon threads and will block the exit of the main thread |
44 | | - # unless measures are taken (#722). |
45 | | - localfile = process.stdout.readline().strip() |
46 | | - test_case.assertTrue(wait_till_true(lambda:os.path.exists(localfile) and os.stat(localfile).st_size > OBJECT_SIZE//2), |
47 | | - "Parallel download from data_objects.get() probably experienced a fatal error before spawning auxiliary data transfer threads." |
48 | | - ) |
49 | | - |
50 | | - for sig in sigs: |
51 | | - # Interrupt the sub-process with the given signal. |
| 38 | + for signal_name in signal_names: |
| 39 | + # Call into this same module as a command. This will initiate another Python process that |
| 40 | + # performs a lengthy data object "get" operation (see the main body of the script, below.) |
| 41 | + process = subprocess.Popen([sys.executable, program], |
| 42 | + stderr=subprocess.PIPE, |
| 43 | + stdout=subprocess.PIPE, |
| 44 | + text = True) |
| 45 | + |
| 46 | + # Wait for download process to reach the point of spawning data transfer threads. In Python 3.9+ versions |
| 47 | + # of the concurrent.futures module, these are nondaemon threads and will block the exit of the main thread |
| 48 | + # unless measures are taken (#722). |
| 49 | + localfile = process.stdout.readline().strip() |
| 50 | + test_case.assertTrue(wait_till_true(lambda:os.path.exists(localfile) and os.stat(localfile).st_size > OBJECT_SIZE//2), |
| 51 | + "Parallel download from data_objects.get() probably experienced a fatal error before spawning auxiliary data transfer threads." |
| 52 | + ) |
| 53 | + |
| 54 | + signal_message_info = f"While testing signal {signal_name}" |
| 55 | + sig = getattr(signal, signal_name) |
| 56 | + |
| 57 | + # Interrupt the subprocess with the given signal. |
52 | 58 | process.send_signal(sig) |
53 | | - # Assert that this signal is what killed the sub-process, rather than a timed out process "wait" or a natural exit |
| 59 | + # Assert that this signal is what killed the subprocess, rather than a timed out process "wait" or a natural exit |
54 | 60 | # due to misproper or incomplete handling of the signal. |
55 | 61 | try: |
56 | | - test_case.assertEqual(process.wait(timeout = 15), -sig) |
| 62 | + test_case.assertEqual(process.wait(timeout = 15), -sig, "{signal_message_info}: unexpected subprocess return code.") |
57 | 63 | except subprocess.TimeoutExpired as timeout_exc: |
58 | | - test_case.fail("Sub-process timed out before exit. Non-daemon thread(s) probably prevented subprocess's main thread from exiting") |
| 64 | + test_case.fail(f"{signal_message_info}: subprocess timed out before terminating. " |
| 65 | + "Non-daemon thread(s) probably prevented subprocess's main thread from exiting.") |
59 | 66 | # Assert that in the case of SIGINT, the process registered a KeyboardInterrupt. |
60 | | - if sig == SIGINT: |
61 | | - test_case.assertTrue(re.search('KeyboardInterrupt',process.stderr.read())) |
| 67 | + if sig == signal.SIGINT: |
| 68 | + test_case.assertTrue(re.search('KeyboardInterrupt', process.stderr.read()), |
| 69 | + "{signal_message_info}: Expected 'KeyboardInterrupt' in log output.") |
62 | 70 |
|
63 | 71 |
|
64 | 72 | if __name__ == "__main__": |
|
0 commit comments