Skip to content

Commit ca8d46d

Browse files
ambvgraingert
andauthored
[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 a68a2ad) Co-authored-by: Thomas Grainger <tagrain@gmail.com>
1 parent 57e7d5c commit ca8d46d

File tree

3 files changed

+91
-6
lines changed

3 files changed

+91
-6
lines changed

Lib/test/test_runpy.py

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
# Test the runpy module
2-
import unittest
3-
import os
2+
import contextlib
3+
import importlib.machinery, importlib.util
44
import os.path
5-
import sys
5+
import pathlib
6+
import py_compile
67
import re
8+
import signal
9+
import subprocess
10+
import sys
711
import tempfile
8-
import importlib, importlib.machinery, importlib.util
9-
import py_compile
12+
import textwrap
13+
import unittest
1014
import warnings
11-
import pathlib
1215
from test.support import (
1316
forget, make_legacy_pyc, unload, verbose, no_tracing,
1417
create_empty_file, temp_dir)
@@ -752,5 +755,82 @@ def test_encoding(self):
752755
self.assertEqual(result['s'], "non-ASCII: h\xe9")
753756

754757

758+
class TestExit(unittest.TestCase):
759+
STATUS_CONTROL_C_EXIT = 0xC000013A
760+
EXPECTED_CODE = (
761+
STATUS_CONTROL_C_EXIT
762+
if sys.platform == "win32"
763+
else -signal.SIGINT
764+
)
765+
@staticmethod
766+
@contextlib.contextmanager
767+
def tmp_path(*args, **kwargs):
768+
with temp_dir() as tmp_fn:
769+
yield pathlib.Path(tmp_fn)
770+
771+
772+
def run(self, *args, **kwargs):
773+
with self.tmp_path() as tmp:
774+
self.ham = ham = tmp / "ham.py"
775+
ham.write_text(
776+
textwrap.dedent(
777+
"""\
778+
raise KeyboardInterrupt
779+
"""
780+
)
781+
)
782+
super().run(*args, **kwargs)
783+
784+
def assertSigInt(self, *args, **kwargs):
785+
proc = subprocess.run(*args, **kwargs, text=True, stderr=subprocess.PIPE)
786+
self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n"))
787+
self.assertEqual(proc.returncode, self.EXPECTED_CODE)
788+
789+
def test_pymain_run_file(self):
790+
self.assertSigInt([sys.executable, self.ham])
791+
792+
def test_pymain_run_file_runpy_run_module(self):
793+
tmp = self.ham.parent
794+
run_module = tmp / "run_module.py"
795+
run_module.write_text(
796+
textwrap.dedent(
797+
"""\
798+
import runpy
799+
runpy.run_module("ham")
800+
"""
801+
)
802+
)
803+
self.assertSigInt([sys.executable, run_module], cwd=tmp)
804+
805+
def test_pymain_run_file_runpy_run_module_as_main(self):
806+
tmp = self.ham.parent
807+
run_module_as_main = tmp / "run_module_as_main.py"
808+
run_module_as_main.write_text(
809+
textwrap.dedent(
810+
"""\
811+
import runpy
812+
runpy._run_module_as_main("ham")
813+
"""
814+
)
815+
)
816+
self.assertSigInt([sys.executable, run_module_as_main], cwd=tmp)
817+
818+
def test_pymain_run_command_run_module(self):
819+
self.assertSigInt(
820+
[sys.executable, "-c", "import runpy; runpy.run_module('ham')"],
821+
cwd=self.ham.parent,
822+
)
823+
824+
def test_pymain_run_command(self):
825+
self.assertSigInt([sys.executable, "-c", "import ham"], cwd=self.ham.parent)
826+
827+
def test_pymain_run_stdin(self):
828+
self.assertSigInt([sys.executable], input="import ham", cwd=self.ham.parent)
829+
830+
def test_pymain_run_module(self):
831+
ham = self.ham
832+
self.assertSigInt([sys.executable, "-m", ham.stem], cwd=ham.parent)
833+
834+
755835
if __name__ == "__main__":
756836
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tests for SIGINT handling in the runpy module.

Modules/main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,11 @@ pymain_run_module(const wchar_t *modname, int set_argv0)
287287
Py_DECREF(module);
288288
return pymain_exit_err_print();
289289
}
290+
_Py_UnhandledKeyboardInterrupt = 0;
290291
result = PyObject_Call(runmodule, runargs, NULL);
292+
if (!result && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
293+
_Py_UnhandledKeyboardInterrupt = 1;
294+
}
291295
Py_DECREF(runpy);
292296
Py_DECREF(runmodule);
293297
Py_DECREF(module);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy