|
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