changeset 1222:88e61e45026d

tests: import killdaemons.py from Mercurial 1cfded2fa1a9 This contains updated functionality to kill the server process on Windows, which is needed to run the test-*server.t tests. This version requires the pids to be passed as an argument instead of via $DAEMON_PIDS.
author Matt Harbison <matt_harbison@yahoo.com>
date Sat, 21 Mar 2015 18:42:51 -0400
parents 524dbc8ffeac
children 4e7da688a066
files tests/killdaemons.py tests/test-simple4server-bundle2.t tests/test-simple4server.t
diffstat 3 files changed, 83 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/tests/killdaemons.py	Fri Mar 20 12:51:57 2015 -0700
+++ b/tests/killdaemons.py	Sat Mar 21 18:42:51 2015 -0400
@@ -1,25 +1,91 @@
 #!/usr/bin/env python
 
-import os, time, errno, signal
+import os, sys, time, errno, signal
+
+if os.name =='nt':
+    import ctypes
+
+    def _check(ret, expectederr=None):
+        if ret == 0:
+            winerrno = ctypes.GetLastError()
+            if winerrno == expectederr:
+                return True
+            raise ctypes.WinError(winerrno)
 
-# Kill off any leftover daemon processes
-try:
-    fp = open(os.environ['DAEMON_PIDS'])
-    for line in fp:
+    def kill(pid, logfn, tryhard=True):
+        logfn('# Killing daemon process %d' % pid)
+        PROCESS_TERMINATE = 1
+        PROCESS_QUERY_INFORMATION = 0x400
+        SYNCHRONIZE = 0x00100000
+        WAIT_OBJECT_0 = 0
+        WAIT_TIMEOUT = 258
+        handle = ctypes.windll.kernel32.OpenProcess(
+                PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION,
+                False, pid)
+        if handle == 0:
+            _check(0, 87) # err 87 when process not found
+            return # process not found, already finished
         try:
-            pid = int(line)
-        except ValueError:
-            continue
+            r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+            if r == WAIT_OBJECT_0:
+                pass # terminated, but process handle still available
+            elif r == WAIT_TIMEOUT:
+                _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
+            else:
+                _check(r)
+
+            # TODO?: forcefully kill when timeout
+            #        and ?shorter waiting time? when tryhard==True
+            r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
+                                                       # timeout = 100 ms
+            if r == WAIT_OBJECT_0:
+                pass # process is terminated
+            elif r == WAIT_TIMEOUT:
+                logfn('# Daemon process %d is stuck')
+            else:
+                _check(r) # any error
+        except: #re-raises
+            ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
+            raise
+        _check(ctypes.windll.kernel32.CloseHandle(handle))
+
+else:
+    def kill(pid, logfn, tryhard=True):
         try:
             os.kill(pid, 0)
+            logfn('# Killing daemon process %d' % pid)
             os.kill(pid, signal.SIGTERM)
-            for i in range(10):
-                time.sleep(0.05)
+            if tryhard:
+                for i in range(10):
+                    time.sleep(0.05)
+                    os.kill(pid, 0)
+            else:
+                time.sleep(0.1)
                 os.kill(pid, 0)
+            logfn('# Daemon process %d is stuck - really killing it' % pid)
             os.kill(pid, signal.SIGKILL)
         except OSError, err:
             if err.errno != errno.ESRCH:
                 raise
-    fp.close()
-except IOError:
-    pass
+
+def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
+    if not logfn:
+        logfn = lambda s: s
+    # Kill off any leftover daemon processes
+    try:
+        fp = open(pidfile)
+        for line in fp:
+            try:
+                pid = int(line)
+            except ValueError:
+                continue
+            kill(pid, logfn, tryhard)
+        fp.close()
+        if remove:
+            os.unlink(pidfile)
+    except IOError:
+        pass
+
+if __name__ == '__main__':
+    path, = sys.argv[1:]
+    killdaemons(path)
--- a/tests/test-simple4server-bundle2.t	Fri Mar 20 12:51:57 2015 -0700
+++ b/tests/test-simple4server-bundle2.t	Sat Mar 21 18:42:51 2015 -0400
@@ -140,7 +140,7 @@
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -154,7 +154,7 @@
   lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2-exp=HG2Y%0Ab2x%253Achangegroup%3D01%2C02%0Ab2x%253Alistkeys%0Ab2x%253Aobsmarkers%3DV0%2CV1%0Ab2x%253Apushkey%0Ab2x%253Aremote-changegroup%3Dhttp%2Chttps%0Adigests%3Dmd5%2Csha1%2Csha512 unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
--- a/tests/test-simple4server.t	Fri Mar 20 12:51:57 2015 -0700
+++ b/tests/test-simple4server.t	Sat Mar 21 18:42:51 2015 -0400
@@ -141,7 +141,7 @@
 
   $ echo '[__temporary__]' >> server/.hg/hgrc
   $ echo 'advertiseobsolete=False' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -155,7 +155,7 @@
   lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (no-eol)
 
   $ echo 'advertiseobsolete=True' >> server/.hg/hgrc
-  $ $TESTDIR/killdaemons.py
+  $ $TESTDIR/killdaemons.py $DAEMON_PIDS
   $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS