Generic buffered read method test harness to validate EINTR behavior. Also validates that Python signal handlers are run during the read. Args: data_to_write: String to write to the child process for reading before sending it a signal, confirming the sig
(self, data_to_write, read_and_verify_code)
| 73 | (why, stdout.decode(), stderr.decode())) |
| 74 | |
| 75 | def _test_reading(self, data_to_write, read_and_verify_code): |
| 76 | """Generic buffered read method test harness to validate EINTR behavior. |
| 77 | |
| 78 | Also validates that Python signal handlers are run during the read. |
| 79 | |
| 80 | Args: |
| 81 | data_to_write: String to write to the child process for reading |
| 82 | before sending it a signal, confirming the signal was handled, |
| 83 | writing a final newline and closing the infile pipe. |
| 84 | read_and_verify_code: Single "line" of code to read from a file |
| 85 | object named 'infile' and validate the result. This will be |
| 86 | executed as part of a python subprocess fed data_to_write. |
| 87 | """ |
| 88 | infile_setup_code = self._generate_infile_setup_code() |
| 89 | # Total pipe IO in this function is smaller than the minimum posix OS |
| 90 | # pipe buffer size of 512 bytes. No writer should block. |
| 91 | assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' |
| 92 | |
| 93 | # Start a subprocess to call our read method while handling a signal. |
| 94 | self._process = subprocess.Popen( |
| 95 | [sys.executable, '-u', '-c', |
| 96 | 'import signal, sys ;' |
| 97 | 'signal.signal(signal.SIGINT, ' |
| 98 | 'lambda s, f: sys.stderr.write("$\\n")) ;' |
| 99 | + infile_setup_code + ' ;' + |
| 100 | 'sys.stderr.write("Worm Sign!\\n") ;' |
| 101 | + read_and_verify_code + ' ;' + |
| 102 | 'infile.close()' |
| 103 | ], |
| 104 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
| 105 | stderr=subprocess.PIPE) |
| 106 | |
| 107 | # Wait for the signal handler to be installed. |
| 108 | worm_sign = self._process.stderr.read(len(b'Worm Sign!\n')) |
| 109 | if worm_sign != b'Worm Sign!\n': # See also, Dune by Frank Herbert. |
| 110 | self.fail_with_process_info('while awaiting a sign', |
| 111 | stderr=worm_sign) |
| 112 | self._process.stdin.write(data_to_write) |
| 113 | |
| 114 | signals_sent = 0 |
| 115 | rlist = [] |
| 116 | # We don't know when the read_and_verify_code in our child is actually |
| 117 | # executing within the read system call we want to interrupt. This |
| 118 | # loop waits for a bit before sending the first signal to increase |
| 119 | # the likelihood of that. Implementations without correct EINTR |
| 120 | # and signal handling usually fail this test. |
| 121 | while not rlist: |
| 122 | rlist, _, _ = select.select([self._process.stderr], (), (), 0.05) |
| 123 | self._process.send_signal(signal.SIGINT) |
| 124 | signals_sent += 1 |
| 125 | if signals_sent > 200: |
| 126 | self._process.kill() |
| 127 | self.fail('reader process failed to handle our signals.') |
| 128 | # This assumes anything unexpected that writes to stderr will also |
| 129 | # write a newline. That is true of the traceback printing code. |
| 130 | signal_line = self._process.stderr.readline() |
| 131 | if signal_line != b'$\n': |
| 132 | self.fail_with_process_info('while awaiting signal', |
no test coverage detected