From ec2856cb052c0b61645cea81dab0ac605560cd29 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Aug 2020 20:23:57 +0100 Subject: [PATCH 1/9] bpo-41602: add tests for sigint handling in runpy --- Lib/test/test_runpy.py | 87 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index f8274a981cb1c0..06f0a0c2b9c66e 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -1,15 +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 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 verbose, no_tracing +from test.support import no_tracing, verbose from test.support.import_helper import forget, make_legacy_pyc, unload from test.support.os_helper import create_empty_file, temp_dir from test.support.script_helper import make_script, make_zip_script @@ -752,5 +754,76 @@ def test_encoding(self): self.assertEqual(result['s'], "non-ASCII: h\xe9") +class TestExit(unittest.TestCase): + @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, -2) + + 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() From 78ba11c1d43c11cb748d56f36ccefeaa79db1ecb Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2020 19:25:38 +0000 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst 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..195b4c8a9074e6 --- /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 runpy \ No newline at end of file From ce4601a1dda21a8facf02554004dfcc58300898c Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Aug 2020 21:25:23 +0100 Subject: [PATCH 3/9] check for STATUS_CONTROL_C_EXIT on windows --- Lib/test/test_runpy.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 06f0a0c2b9c66e..2954dfedc7e428 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -5,6 +5,7 @@ import pathlib import py_compile import re +import signal import subprocess import sys import tempfile @@ -755,6 +756,12 @@ def test_encoding(self): 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): @@ -777,7 +784,7 @@ def run(self, *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, -2) + self.assertEqual(proc.returncode, self.EXPECTED_CODE) def test_pymain_run_file(self): self.assertSigInt([sys.executable, self.ham]) From a42916bbc696e33b900a4655d8dee2fc3a58b5c0 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Aug 2020 22:42:26 +0100 Subject: [PATCH 4/9] REVERTME: flip the assertion to make the tests pass --- Lib/test/test_runpy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 2954dfedc7e428..501cf6b54bc51f 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -781,10 +781,10 @@ def run(self, *args, **kwargs): ) super().run(*args, **kwargs) - def assertSigInt(self, *args, **kwargs): + def assertSigInt(self, *args, expected_code=EXPECTED_CODE, **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) + self.assertEqual(proc.returncode, expected_code) def test_pymain_run_file(self): self.assertSigInt([sys.executable, self.ham]) @@ -829,7 +829,11 @@ def test_pymain_run_stdin(self): def test_pymain_run_module(self): ham = self.ham - self.assertSigInt([sys.executable, "-m", ham.stem], cwd=ham.parent) + self.assertSigInt( + [sys.executable, "-m", ham.stem], + cwd=ham.parent, + expected_code=1, # TODO: should be self.EXPECTED_CODE + ) if __name__ == "__main__": From c483f653ec4f02e408051343e329fe37bd414e68 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 25 Aug 2020 23:47:21 +0100 Subject: [PATCH 5/9] bpo-41635: noop to work around flaky test From de65294551d8af254e77a1c3ba4edab8e7f6b44e Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 16 Sep 2020 21:51:13 +0100 Subject: [PATCH 6/9] raise SIGINT exit code on KeyboardInterrupt from pymain_run_module Co-Authored-By: Guido van Rossum --- Modules/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/main.c b/Modules/main.c index 4a76f4461bf610..5b63f2a5b847f9 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -288,6 +288,9 @@ pymain_run_module(const wchar_t *modname, int set_argv0) return pymain_exit_err_print(); } result = PyObject_Call(runmodule, runargs, NULL); + if (!result && PyErr_Occurred() == PyExc_KeyboardInterrupt) { + _Py_UnhandledKeyboardInterrupt = 1; + } Py_DECREF(runpy); Py_DECREF(runmodule); Py_DECREF(module); From 0b325b62af7fbaee2bbce85607cd0efae4f687f5 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Wed, 16 Sep 2020 21:51:55 +0100 Subject: [PATCH 7/9] Revert "REVERTME: flip the assertion to make the tests pass" This reverts commit a42916bbc696e33b900a4655d8dee2fc3a58b5c0. --- Lib/test/test_runpy.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 501cf6b54bc51f..2954dfedc7e428 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -781,10 +781,10 @@ def run(self, *args, **kwargs): ) super().run(*args, **kwargs) - def assertSigInt(self, *args, expected_code=EXPECTED_CODE, **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, expected_code) + self.assertEqual(proc.returncode, self.EXPECTED_CODE) def test_pymain_run_file(self): self.assertSigInt([sys.executable, self.ham]) @@ -829,11 +829,7 @@ def test_pymain_run_stdin(self): def test_pymain_run_module(self): ham = self.ham - self.assertSigInt( - [sys.executable, "-m", ham.stem], - cwd=ham.parent, - expected_code=1, # TODO: should be self.EXPECTED_CODE - ) + self.assertSigInt([sys.executable, "-m", ham.stem], cwd=ham.parent) if __name__ == "__main__": From 4486dcd7f60eb70987d88dc65f838dfe97fe76c7 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Tue, 22 Sep 2020 10:09:14 +0100 Subject: [PATCH 8/9] reset _Py_UnhandledKeyboardInterrupt before calling runpy._run_module_as_main --- Modules/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/main.c b/Modules/main.c index 5b63f2a5b847f9..2cc891f61aadd1 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -287,6 +287,7 @@ 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; From 19f40f7e4a15877a2dc45d35cb2cffa51f0c7bd7 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 22 Sep 2020 07:25:44 -0700 Subject: [PATCH 9/9] Capitalization and punctuation in blurb. Co-authored-by: Victor Stinner --- Misc/NEWS.d/next/Tests/2020-08-25-19-25-36.bpo-41602.Z64s0I.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 195b4c8a9074e6..fa3d2f1aa374ec 100644 --- 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 @@ -1 +1 @@ -add tests for sigint handling in runpy \ No newline at end of file +Add tests for SIGINT handling in the runpy module.