Commit 56e52da6 authored by Kirill Smelkov's avatar Kirill Smelkov

trun: Fix returncode when test run is canceled

To detect leaked processes in the end of the run, we are first waiting
for remaining test processes via procps. If the main waiting loop was
canceled without main test process first completed (p.poll calls never
returned !None), then the waitpid(pid=p.pid) system call will be done
via procps. Which leaves further waitpid(pid=p.pid) system call invoked
by subprocess to get -ECHILD error and artificially report 0 exit
status:

https://github.com/python/cpython/blob/2.7-0-g8d21aa21f2c/Lib/subprocess.py#L1094-L1107

-> Fix it by propagating .returncode from procps to Popen instance so
that it does not get lost.

On sample .nxdtest with

    TestCase('sleep', ['sleep', '10'])

Before the patch the output with CTRL+C was:

    $ nxdtest
    ...
    >>> sleep
    $ sleep 10
    ^C# Interrupt
    ok      sleep   0.604s  # 1t 0e 0f 0s       <-- NOTE
    # test run canceled
    # ran 1 test case:  1·ok                    <-- NOTE

After the patch the output becomes:

    $ nxdtest
    ...
    >>> sleep
    $ sleep 100
    ^C# Interrupt
    error   sleep   1.006s  # 1t 1e 0f 0s       <-- NOTE
    # test run canceled
    # ran 1 test case:  1·error                 <-- NOTE

/reviewed-by @jerome
/reviewed-on nexedi/nxdtest!16
parent cf300184
...@@ -360,6 +360,7 @@ TestCase('TEST1', ['%s']) ...@@ -360,6 +360,7 @@ TestCase('TEST1', ['%s'])
assert "# test run canceled" in captured.out assert "# test run canceled" in captured.out
assert "hang: terminating" in captured.out assert "hang: terminating" in captured.out
assert "leaked pid" not in captured.out assert "leaked pid" not in captured.out
assert u"ran 1 test case: 1·error" in captured.out
assert captured.err == '' assert captured.err == ''
...@@ -416,3 +417,4 @@ TestCase('TEST1', ['%s']) ...@@ -416,3 +417,4 @@ TestCase('TEST1', ['%s'])
assert b"# test run canceled" in out assert b"# test run canceled" in out
assert b"hang: terminating" in out assert b"hang: terminating" in out
assert b"leaked pid" not in out assert b"leaked pid" not in out
assert b("ran 1 test case: 1·error") in out
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2021 Nexedi SA and Contributors. # Copyright (C) 2021-2022 Nexedi SA and Contributors.
# #
# This program is free software: you can Use, Study, Modify and Redistribute # This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your # it under the terms of the GNU General Public License version 3, or (at your
...@@ -31,7 +31,7 @@ def main(): ...@@ -31,7 +31,7 @@ def main():
def _(sig, frame): def _(sig, frame):
print('%s: terminating' % job) print('%s: terminating' % job)
raise SystemExit raise SystemExit(1)
signal(SIGTERM, _) signal(SIGTERM, _)
while 1: while 1:
......
...@@ -135,6 +135,11 @@ def main(): ...@@ -135,6 +135,11 @@ def main():
print('# leaked pid=%d %r %s' % (proc.pid, proc.name(), proc.cmdline())) print('# leaked pid=%d %r %s' % (proc.pid, proc.name(), proc.cmdline()))
proc.terminate() proc.terminate()
gone, alive = psutil.wait_procs(procv, timeout=5) gone, alive = psutil.wait_procs(procv, timeout=5)
for proc in gone:
if proc.pid == p.pid:
# waitpid(pid=p.pid) done.
# Propagate returncode to p else ^^^ p.wait() will set it to 0
p.returncode = proc.returncode
for proc in alive: for proc in alive:
proc.kill() proc.kill()
defer(_) defer(_)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment