From 215f68856260b2c15149e0a15bb1155bdad78ff6 Mon Sep 17 00:00:00 2001 From: Benedek Racz Date: Sun, 5 Apr 2020 09:10:15 +0200 Subject: [PATCH] [FIX] use flag_eof, and make more robust terminating process --- tests/test_expect.py | 29 ++++++++++++++++++++ wexpect/__main__.py | 1 + wexpect/console_reader.py | 2 ++ wexpect/host.py | 58 ++++++++++++++++++++++----------------- 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/tests/test_expect.py b/tests/test_expect.py index 1eefae5..ecd664f 100644 --- a/tests/test_expect.py +++ b/tests/test_expect.py @@ -148,6 +148,10 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): wexpect.EOF]) self.assertEqual(index, 3, (index, p.before, p.after)) + # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + def test_expect_index (self): '''This tests that mixed list of regex strings, TIMEOUT, and EOF all return the correct index when matched. @@ -161,6 +165,9 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): p = wexpect.spawn('cat', timeout=5, echo=False) p.expect = p.expect_exact self._expect_index(p) + # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() def _expect_index (self, p): p.sendline ('1234') @@ -218,6 +225,10 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): the_old_way = the_old_way.replace('\r\n', '\n' ).replace('\r', '\n').replace('\n\n', '\n').rstrip() self.assertEqual(the_old_way, the_new_way) + # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + p = wexpect.spawn('echo hello.?world') i = p.expect_exact('.?') self.assertEqual(p.before, 'hello') @@ -236,6 +247,10 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): ).replace('\r', '\n').replace('\n\n', '\n').rstrip() self.assertEqual(the_old_way, the_new_way) + # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + def test_expect_timeout (self): p = wexpect.spawn('cat', timeout=5) p.expect(wexpect.TIMEOUT) # This tells it to wait for timeout. @@ -250,6 +265,9 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): else: self.fail ('Expected an EOF exception.') + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + def test_buffer_interface(self): p = wexpect.spawn('cat', timeout=5) p.sendline ('Hello') @@ -257,6 +275,8 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): assert len(p.buffer) p.buffer = 'Testing' p.sendeof () + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() def _before_after(self, p): p.timeout = 5 @@ -275,6 +295,9 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): assert p.before.startswith(', 51, 52'), p.before[:20] assert p.before.endswith(', 99]\r\n'), p.before[-20:] + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + def test_before_after(self): '''This tests expect() for some simple before/after things. ''' @@ -313,6 +336,8 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): p.sendline('list(range(4*5))') self.assertEqual(p.expect(['12,', '2,']), 1) + p.sendline('exit()') + def test_ordering(self): '''This tests expect() for which pattern is returned when many may eventually match. I (Grahn) am a bit @@ -333,6 +358,10 @@ class ExpectTestCase (PexpectTestCase.PexpectTestCase): p.expect = p.expect_exact self._ordering(p) + # Termination of the SpawnSocket is slow. We have to wait to prevent the failure of the next test. + if wexpect.spawn_class_name == 'SpawnSocket': + p.wait() + def _greed(self, expect): # End at the same point: the one with the earliest start should win self.assertEqual(expect(['3, 4', '2, 3, 4']), 1) diff --git a/wexpect/__main__.py b/wexpect/__main__.py index 8211fa0..48f461b 100644 --- a/wexpect/__main__.py +++ b/wexpect/__main__.py @@ -63,6 +63,7 @@ def main(): buffer_size_x=args.buffer_size_x, buffer_size_y=args.buffer_size_y, local_echo=str2bool(args.local_echo), interact=str2bool(args.interact)) + logger.info(f'Exiting with status: {cons.child_exitstatus}') sys.exit(cons.child_exitstatus) except Exception as e: # pragma: no cover diff --git a/wexpect/console_reader.py b/wexpect/console_reader.py index ffde67d..2da6c1a 100644 --- a/wexpect/console_reader.py +++ b/wexpect/console_reader.py @@ -469,7 +469,9 @@ class ConsoleReaderSocket(ConsoleReaderBase): def close_connection(self): if self.connection: + self.connection.shutdown(socket.SHUT_RDWR) self.connection.close() + self.connection = None def send_to_host(self, msg): # convert to bytes diff --git a/wexpect/host.py b/wexpect/host.py index d0b0653..f04f0b8 100644 --- a/wexpect/host.py +++ b/wexpect/host.py @@ -411,7 +411,7 @@ class SpawnBase: if force or self.console_process is None: self.child_process = self.get_console_process() self.child_pid = self.child_process.pid - return self.child_process + return self.child_process def close(self): # File-like object. """ Closes the child console.""" @@ -441,6 +441,9 @@ class SpawnBase: # Child process has not been started... Not alive return False + if self.exitstatus is not None: + return False + try: self.exitstatus = self.child_process.wait(timeout=0) logger.info(f'exitstatus: {self.exitstatus}') @@ -450,11 +453,17 @@ class SpawnBase: def kill(self, sig=signal.SIGTERM): """Sig == sigint for ctrl-c otherwise the child is terminated.""" try: - self.child_process.send_signal(sig) - except psutil.NoSuchProcess as e: - logger.info('Child has already died. %s', e) + logger.info(f'Sending kill signal: {sig}') + self.send(SIGNAL_CHARS[sig]) + self.terminated = True + except EOF as e: + logger.info(e) def wait(self, child=True, console=False): + + if self.exitstatus is not None: + return self.exitstatus + if child: self.exitstatus = self.child_process.wait() logger.info(f'exitstatus: {self.exitstatus}') @@ -589,6 +598,10 @@ class SpawnBase: def send(self, s, delaybeforesend=None): """Virtual definition """ + if self.flag_eof: + logger.info('EOF: End of file has been already detected.') + raise EOF('End of file has been already detected.') + if delaybeforesend is None: delaybeforesend = self.delaybeforesend @@ -837,8 +850,8 @@ class SpawnBase: else: self.match = None self.match_index = None - logger.info(f'EOF: {e}\n{self}') - raise EOF(f'{e}\n{self}') + logger.info('Raise EOF again') + raise except TIMEOUT as e: self.buffer = incoming self.before = incoming @@ -968,26 +981,19 @@ class SpawnPipe(SpawnBase): except pywintypes.error as e: if e.args[0] == winerror.ERROR_BROKEN_PIPE: # 109 logger.info("EOF: broken pipe, bye bye") + self.flag_eof = True raise EOF("broken pipe, bye bye") elif e.args[0] == winerror.ERROR_NO_DATA: '''232 (0xE8) The pipe is being closed. ''' logger.info("The pipe is being closed.") + self.flag_eof = True raise EOF("The pipe is being closed.") else: raise return len(s) - def kill(self, sig=signal.SIGTERM): - """Sig == sigint for ctrl-c otherwise the child is terminated.""" - try: - logger.info(f'Sending kill signal: {sig}') - self.send(SIGNAL_CHARS[sig]) - self.terminated = True - except EOF as e: - logger.info(e) - class SpawnSocket(SpawnBase): @@ -1015,8 +1021,16 @@ class SpawnSocket(SpawnBase): the log. """ if isinstance(s, str): s = str.encode(s) - self.sock.sendall(s) - return len(s) + try: + if s: + logger.debug(f"Writing: {s}") + self.sock.sendall(s) + logger.spam(f"WriteFile finished.") + return len(s) + except ConnectionResetError as e: + logger.info("ConnectionResetError") + self.flag_eof = True + raise EOF("ConnectionResetError") def connect_to_child(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -1024,7 +1038,9 @@ class SpawnSocket(SpawnBase): self.sock.settimeout(.2) def disconnect_from_child(self): + logger.info('disconnect_from_child') if self.sock: + self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() self.sock = None @@ -1065,14 +1081,6 @@ class SpawnSocket(SpawnBase): return s.decode() - def kill(self, sig=signal.SIGTERM): - """Sig == sigint for ctrl-c otherwise the child is terminated.""" - try: - logger.info(f'Sending kill signal: {sig}') - self.send(SIGNAL_CHARS[sig]) - except EOF as e: - logger.info(e) - class searcher_re (object): """This is regular expression string search helper for the