]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
kunit: tool: Terminate kernel under test on SIGINT
authorDavid Gow <david@davidgow.net>
Sat, 28 Feb 2026 10:07:22 +0000 (18:07 +0800)
committerShuah Khan <skhan@linuxfoundation.org>
Mon, 6 Apr 2026 20:09:04 +0000 (14:09 -0600)
kunit.py will attempt to catch SIGINT / ^C in order to ensure the TTY isn't
messed up, but never actually attempts to terminate the running kernel (be
it UML or QEMU). This can lead to a bit of frustration if the kernel has
crashed or hung.

Terminate the kernel process in the signal handler, if it's running. This
requires plumbing through the process handle in a few more places (and
having some checks to see if the kernel is still running in places where it
may have already been killed).

Reported-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Closes: https://lore.kernel.org/all/aaFmiAmg9S18EANA@smile.fi.intel.com/
Signed-off-by: David Gow <david@davidgow.net>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Tested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
tools/testing/kunit/kunit_kernel.py

index b610fcf0715a09ab1c8d134fedf54db0cec50ab3..2869fcb199ff7a6fbb8e23fc4b3c3ab2ca024598 100644 (file)
@@ -16,7 +16,7 @@ import shutil
 import signal
 import sys
 import threading
-from typing import Iterator, List, Optional, Tuple
+from typing import Iterator, List, Optional, Tuple, Any
 from types import FrameType
 
 import kunit_config
@@ -265,6 +265,7 @@ class LinuxSourceTree:
                if kconfig_add:
                        kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add))
                        self._kconfig.merge_in_entries(kconfig)
+               self._process : Optional[subprocess.Popen[Any]] = None
 
        def arch(self) -> str:
                return self._arch
@@ -364,36 +365,45 @@ class LinuxSourceTree:
                        args.append('kunit.filter_action=' + filter_action)
                args.append('kunit.enable=1')
 
-               process = self._ops.start(args, build_dir)
-               assert process.stdout is not None  # tell mypy it's set
+               self._process = self._ops.start(args, build_dir)
+               assert self._process is not None # tell mypy it's set
+               assert self._process.stdout is not None  # tell mypy it's set
 
                # Enforce the timeout in a background thread.
                def _wait_proc() -> None:
                        try:
-                               process.wait(timeout=timeout)
+                               if self._process:
+                                       self._process.wait(timeout=timeout)
                        except Exception as e:
                                print(e)
-                               process.terminate()
-                               process.wait()
+                               if self._process:
+                                       self._process.terminate()
+                                       self._process.wait()
                waiter = threading.Thread(target=_wait_proc)
                waiter.start()
 
                output = open(get_outfile_path(build_dir), 'w')
                try:
                        # Tee the output to the file and to our caller in real time.
-                       for line in process.stdout:
+                       for line in self._process.stdout:
                                output.write(line)
                                yield line
                # This runs even if our caller doesn't consume every line.
                finally:
                        # Flush any leftover output to the file
-                       output.write(process.stdout.read())
+                       if self._process:
+                               if self._process.stdout:
+                                       output.write(self._process.stdout.read())
+                                       self._process.stdout.close()
+                               self._process = None
                        output.close()
-                       process.stdout.close()
 
                        waiter.join()
                        self._restore_terminal_if_tty()
 
        def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
                logging.error('Build interruption occurred. Cleaning console.')
+               if self._process:
+                               self._process.terminate()
+                               self._process.wait()
                self._restore_terminal_if_tty()