From f3332108a65bc2337254dbdeeaa47a502e89b2ec Mon Sep 17 00:00:00 2001
From: Benedek Racz <betontalpfa@gmail.com>
Date: Fri, 6 Dec 2019 11:05:55 +0100
Subject: [PATCH] [FIX]wait simply returns the previously determined exit
 status, in case of a non alive child; [ADD][TST] add isalive tests

---
 tests/needs_kill.py   |  11 +++++
 tests/test_isalive.py | 109 ++++++++++++++++++++++++++++++++++++++++++
 wexpect.py            |   3 --
 3 files changed, 120 insertions(+), 3 deletions(-)
 create mode 100644 tests/needs_kill.py
 create mode 100644 tests/test_isalive.py

diff --git a/tests/needs_kill.py b/tests/needs_kill.py
new file mode 100644
index 0000000..66373af
--- /dev/null
+++ b/tests/needs_kill.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+"""This script can only be killed by SIGKILL."""
+import signal, time
+
+# Ignore interrupt, hangup and continue signals - only SIGKILL will work
+signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+print('READY')
+while True:
+    time.sleep(10)
+    
\ No newline at end of file
diff --git a/tests/test_isalive.py b/tests/test_isalive.py
new file mode 100644
index 0000000..ca5817f
--- /dev/null
+++ b/tests/test_isalive.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+'''
+PEXPECT LICENSE
+
+    This license is approved by the OSI and FSF as GPL-compatible.
+        http://opensource.org/licenses/isc-license.txt
+
+    Copyright (c) 2012, Noah Spurrier <noah@noah.org>
+    PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
+    PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
+    COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''
+import wexpect
+import unittest
+import signal
+import sys
+import time
+from . import PexpectTestCase
+
+PYBIN = '"{}"'.format(sys.executable)
+
+class IsAliveTestCase(PexpectTestCase.PexpectTestCase):
+    """Various tests for the running status of processes."""
+
+    def test_expect_wait(self):
+        """Ensure consistency in wait() and isalive()."""
+        p = wexpect.spawn('sleep 1')
+        assert p.isalive()
+        self.assertEqual(p.wait(), 0)
+        assert not p.isalive()
+        # In previous versions of ptyprocess/wexpect, calling wait() a second
+        # time would raise an exception, but not since v4.0
+        self.assertEqual(p.wait(), 0)
+
+    def test_signal_wait(self):
+        '''Test calling wait with a process terminated by a signal.'''
+        if not hasattr(signal, 'SIGALRM'):
+            return 'SKIP'
+        p = wexpect.spawn(PYBIN, ['alarm_die.py'])
+        p.wait()
+        assert p.exitstatus is None
+        self.assertEqual(p.signalstatus, signal.SIGALRM)
+
+    def test_expect_isalive_dead_after_normal_termination (self):
+        p = wexpect.spawn('ls', timeout=15)
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+
+    def test_expect_isalive_dead_after_SIGHUP(self):
+        p = wexpect.spawn('cat', timeout=5)
+        assert p.isalive()
+        force = False
+        self.assertEqual(p.terminate(), True)
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+
+    def test_expect_isalive_dead_after_SIGINT(self):
+        p = wexpect.spawn('cat', timeout=5)
+        assert p.isalive()
+        force = False
+        if sys.platform.lower().startswith('sunos'):
+            # On Solaris (SmartOs), and only when executed from cron(1), SIGKILL
+            # is required to end the sub-process. This is done using force=True
+            force = True
+        self.assertEqual(p.terminate(force), True)
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+
+    def test_expect_isalive_dead_after_SIGKILL(self):
+        p = wexpect.spawn('cat', timeout=5)
+        assert p.isalive()
+        p.kill(9)
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+
+    def test_forced_terminate(self):
+    
+        p = wexpect.spawn(PYBIN + ' needs_kill.py')
+        p.expect('READY')
+        self.assertEqual(p.terminate(force=True), True)
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+
+### Some platforms allow this. Some reset status after call to waitpid.
+### probably not necessary, isalive() returns early when terminate is False.
+    def test_expect_isalive_consistent_multiple_calls (self):
+        '''This tests that multiple calls to isalive() return same value.
+        '''
+        p = wexpect.spawn('cat')
+        assert p.isalive()
+        assert p.isalive()
+        p.sendeof()
+        p.expect(wexpect.EOF)
+        assert not p.isalive()
+        assert not p.isalive()
+
+if __name__ == '__main__':
+    unittest.main()
+
+suite = unittest.makeSuite(IsAliveTestCase, 'test')
+
diff --git a/wexpect.py b/wexpect.py
index 71ebe82..7b81f70 100644
--- a/wexpect.py
+++ b/wexpect.py
@@ -914,9 +914,6 @@ class spawn_windows ():
         may have printed output then called exit(); but, technically, the child
         is still alive until its output is read."""
         
-        if not self.isalive():
-            raise ExceptionPexpect ('Cannot wait for dead child process.')
-        
         # We can't use os.waitpid under Windows because of 'permission denied' 
         # exception? Perhaps if not running as admin (or UAC enabled under 
         # Vista/7). Simply loop and wait for child to exit.