diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-22 19:14:52 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-04-22 19:14:52 +0000 |
| commit | b02e400b63849991c1590a3572e711740758583f (patch) | |
| tree | 517eed03d0bb1f1d4b05039c6dcd9baef611f82f | |
| parent | 5f512359af5d02c037f85887bf5a428af05d4fb3 (diff) | |
| download | meta-virtualization-b02e400b63849991c1590a3572e711740758583f.tar.gz | |
tests: fix memres start hanging in subprocess.run
memres start spawns background processes (QEMU VM, idle watchdog)
that persist after the vrunner script exits. When invoked via
subprocess.run(capture_output=True), these background processes
inherit the pipe file descriptors, preventing communicate() from
returning until all pipe holders exit — which can be 30+ minutes
(the idle timeout).
Fix by using Popen with:
- stdin=subprocess.DEVNULL (no inherited stdin pipe)
- file-based stdout (no pipe FDs to inherit)
- start_new_session=True (new process group, so wait() only
waits for the parent script, not the background children)
This matches the behavior when running from a shell, where the
daemon processes are fully detached from the caller's FD table.
Applied to both VdkrRunner and VpdmnRunner memres_start methods.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
| -rw-r--r-- | tests/conftest.py | 60 |
1 files changed, 58 insertions, 2 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index 56047929..8e1e245f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py | |||
| @@ -444,7 +444,35 @@ class VdkrRunner: | |||
| 444 | if port_forwards: | 444 | if port_forwards: |
| 445 | for pf in port_forwards: | 445 | for pf in port_forwards: |
| 446 | args.extend(["-p", pf]) | 446 | args.extend(["-p", pf]) |
| 447 | return self.run(*args, timeout=timeout) | 447 | # memres start spawns background processes (QEMU VM, idle watchdog) |
| 448 | # that can inherit pipe FDs from subprocess.run(capture_output=True), | ||
| 449 | # causing communicate() to hang indefinitely. Use Popen with | ||
| 450 | # file-based output, DEVNULL stdin, and start_new_session to fully | ||
| 451 | # isolate the daemon process tree from the test harness. | ||
| 452 | cmd = [str(self.binary)] | ||
| 453 | if self._needs_arch_flag: | ||
| 454 | cmd.extend(["--arch", self.arch]) | ||
| 455 | cmd.extend(["--state-dir", str(self.state_dir)]) | ||
| 456 | cmd.extend(args) | ||
| 457 | import tempfile | ||
| 458 | with tempfile.TemporaryFile(mode='w+') as out: | ||
| 459 | proc = subprocess.Popen( | ||
| 460 | cmd, env=self.env, | ||
| 461 | stdin=subprocess.DEVNULL, | ||
| 462 | stdout=out, stderr=subprocess.STDOUT, | ||
| 463 | start_new_session=True, | ||
| 464 | ) | ||
| 465 | try: | ||
| 466 | proc.wait(timeout=timeout) | ||
| 467 | except subprocess.TimeoutExpired: | ||
| 468 | proc.kill() | ||
| 469 | proc.wait() | ||
| 470 | raise | ||
| 471 | out.seek(0) | ||
| 472 | output = out.read() | ||
| 473 | result = subprocess.CompletedProcess( | ||
| 474 | cmd, proc.returncode, stdout=output, stderr="") | ||
| 475 | return result | ||
| 448 | 476 | ||
| 449 | def memres_stop(self, timeout=30): | 477 | def memres_stop(self, timeout=30): |
| 450 | """Stop memory resident mode.""" | 478 | """Stop memory resident mode.""" |
| @@ -705,7 +733,35 @@ class VpdmnRunner: | |||
| 705 | if port_forwards: | 733 | if port_forwards: |
| 706 | for pf in port_forwards: | 734 | for pf in port_forwards: |
| 707 | args.extend(["-p", pf]) | 735 | args.extend(["-p", pf]) |
| 708 | return self.run(*args, timeout=timeout) | 736 | # memres start spawns background processes (QEMU VM, idle watchdog) |
| 737 | # that can inherit pipe FDs from subprocess.run(capture_output=True), | ||
| 738 | # causing communicate() to hang indefinitely. Use Popen with | ||
| 739 | # file-based output, DEVNULL stdin, and start_new_session to fully | ||
| 740 | # isolate the daemon process tree from the test harness. | ||
| 741 | cmd = [str(self.binary)] | ||
| 742 | if self._needs_arch_flag: | ||
| 743 | cmd.extend(["--arch", self.arch]) | ||
| 744 | cmd.extend(["--state-dir", str(self.state_dir)]) | ||
| 745 | cmd.extend(args) | ||
| 746 | import tempfile | ||
| 747 | with tempfile.TemporaryFile(mode='w+') as out: | ||
| 748 | proc = subprocess.Popen( | ||
| 749 | cmd, env=self.env, | ||
| 750 | stdin=subprocess.DEVNULL, | ||
| 751 | stdout=out, stderr=subprocess.STDOUT, | ||
| 752 | start_new_session=True, | ||
| 753 | ) | ||
| 754 | try: | ||
| 755 | proc.wait(timeout=timeout) | ||
| 756 | except subprocess.TimeoutExpired: | ||
| 757 | proc.kill() | ||
| 758 | proc.wait() | ||
| 759 | raise | ||
| 760 | out.seek(0) | ||
| 761 | output = out.read() | ||
| 762 | result = subprocess.CompletedProcess( | ||
| 763 | cmd, proc.returncode, stdout=output, stderr="") | ||
| 764 | return result | ||
| 709 | 765 | ||
| 710 | def memres_stop(self, timeout=30): | 766 | def memres_stop(self, timeout=30): |
| 711 | """Stop memory resident mode.""" | 767 | """Stop memory resident mode.""" |
