From c26a666e6751a9fad766ef83432b893f9b15ecaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Thu, 24 Sep 2020 16:34:21 +0200 Subject: [PATCH] [3.9] bpo-41602: raise SIGINT exit code on KeyboardInterrupt from pymain_run_module (GH-21956) (#22397) Closes bpo issue 41602. (cherry picked from commit a68a2ad19c891faa891904b3da537911cc77df21) Co-authored-by: Thomas Grainger --- Lib/test/test_runpy.py | 92 +++++++++++++++++-- .../2020-08-25-19-25-36.bpo-41602.Z64s0I.rst | 1 + Modules/main.c | 4 + 3 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 84834d39b1c216..4aaf75b1bceb68 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -1,14 +1,17 @@ # Test the runpy module -import unittest -import os +import contextlib +import importlib.machinery, importlib.util import os.path -import sys +import pathlib +import py_compile import re +import signal +import subprocess +import sys import tempfile -import importlib, importlib.machinery, importlib.util -import py_compile +import textwrap +import unittest import warnings -import pathlib from test.support import ( forget, make_legacy_pyc, unload, verbose, no_tracing, create_empty_file, temp_dir) @@ -752,5 +755,82 @@ def test_encoding(self): self.assertEqual(result['s'], "non-ASCII: h\xe9") +class TestExit(unittest.TestCase): + STATUS_CONTROL_C_EXIT = 0xC000013A + EXPECTED_CODE = ( + STATUS_CONTROL_C_EXIT + if sys.platform == "win32" + else -signal.SIGINT + ) + @staticmethod + @contextlib.contextmanager + def tmp_path(*args, **kwargs): + with temp_dir() as tmp_fn: + yield pathlib.Path(tmp_fn) + + + def run(self, *args, **kwargs): + with self.tmp_path() as tmp: + self.ham = ham = tmp / "ham.py" + ham.write_text( + textwrap.dedent( + """\ + raise KeyboardInterrupt + """ + ) + ) + super().run(*args, **kwargs) + + def assertSigInt(self, *args, **kwargs): + proc = subprocess.run(*args, **kwargs, text=True, stderr=subprocess.PIPE) + self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n")) + self.assertEqual(proc.returncode, self.EXPECTED_CODE) + + def test_pymain_run_file(self): + self.assertSigInt([sys.executable, self.ham]) + + def test_pymain_run_file_runpy_run_module(self): + tmp = self.ham.parent + run_module = tmp / "run_module.py" + run_module.write_text( + textwrap.dedent( + """\ + import runpy + runpy.run_module("ham") + """ + ) + ) + self.assertSigInt([sys.executable, run_module], cwd=tmp) + + def test_pymain_run_file_runpy_run_module_as_main(self): + tmp = self.ham.parent + run_module_as_main = tmp / "run_module_as_main.py" + run_module_as_main.write_text( + textwrap.dedent( + """\ + import runpy + runpy._run_module_as_main("ham") + """ + ) + ) + self.assertSigInt([sys.executable, run_module_as_main], cwd=tmp) + + def test_pymain_run_command_run_module(self): + self.assertSigInt( + [sys.executable, "-c", "import runpy; runpy.run_module('ham')"], + cwd=self.ham.parent, + ) + + def test_pymain_run_command(self): + self.assertSigInt([sys.executable, "-c", "import ham"], cwd=self.ham.parent) + + def test_pymain_run_stdin(self): + self.assertSigInt([sys.executable], input="import ham", cwd=self.ham.parent) + + def test_pymain_run_module(self): + ham = self.ham + self.assertSigInt([sys.executable, "-m", ham.stem], cwd=ham.parent) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst b/Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst new file mode 100644 index 00000000000000..fa3d2f1aa374ec --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst @@ -0,0 +1 @@ +Add tests for SIGINT handling in the runpy module. diff --git a/Modules/main.c b/Modules/main.c index 4a76f4461bf610..2cc891f61aadd1 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -287,7 +287,11 @@ pymain_run_module(const wchar_t *modname, int set_argv0) Py_DECREF(module); return pymain_exit_err_print(); } + _Py_UnhandledKeyboardInterrupt = 0; result = PyObject_Call(runmodule, runargs, NULL); + if (!result && PyErr_Occurred() == PyExc_KeyboardInterrupt) { + _Py_UnhandledKeyboardInterrupt = 1; + } Py_DECREF(runpy); Py_DECREF(runmodule); Py_DECREF(module);