1# Test the runpy module 2import contextlib 3import importlib.machinery, importlib.util 4import os.path 5import pathlib 6import py_compile 7import re 8import signal 9import subprocess 10import sys 11import tempfile 12import textwrap 13import unittest 14import warnings 15from test.support import (infinite_recursion, no_tracing, verbose, 16 requires_subprocess, requires_resource) 17from test.support.import_helper import forget, make_legacy_pyc, unload 18from test.support.os_helper import create_empty_file, temp_dir, FakePath 19from test.support.script_helper import make_script, make_zip_script 20 21 22import runpy 23from runpy import _run_code, _run_module_code, run_module, run_path 24# Note: This module can't safely test _run_module_as_main as it 25# runs its tests in the current process, which would mess with the 26# real __main__ module (usually test.regrtest) 27# See test_cmd_line_script for a test that executes that code path 28 29 30# Set up the test code and expected results 31example_source = """\ 32# Check basic code execution 33result = ['Top level assignment'] 34def f(): 35 result.append('Lower level reference') 36f() 37del f 38# Check the sys module 39import sys 40run_argv0 = sys.argv[0] 41run_name_in_sys_modules = __name__ in sys.modules 42module_in_sys_modules = (run_name_in_sys_modules and 43 globals() is sys.modules[__name__].__dict__) 44# Check nested operation 45import runpy 46nested = runpy._run_module_code('x=1\\n', mod_name='<run>') 47""" 48 49implicit_namespace = { 50 "__name__": None, 51 "__file__": None, 52 "__cached__": None, 53 "__package__": None, 54 "__doc__": None, 55 "__spec__": None 56} 57example_namespace = { 58 "sys": sys, 59 "runpy": runpy, 60 "result": ["Top level assignment", "Lower level reference"], 61 "run_argv0": sys.argv[0], 62 "run_name_in_sys_modules": False, 63 "module_in_sys_modules": False, 64 "nested": dict(implicit_namespace, 65 x=1, __name__="<run>", __loader__=None), 66} 67example_namespace.update(implicit_namespace) 68 69class CodeExecutionMixin: 70 # Issue #15230 (run_path not handling run_name correctly) highlighted a 71 # problem with the way arguments were being passed from higher level APIs 72 # down to lower level code. This mixin makes it easier to ensure full 73 # testing occurs at those upper layers as well, not just at the utility 74 # layer 75 76 # Figuring out the loader details in advance is hard to do, so we skip 77 # checking the full details of loader and loader_state 78 CHECKED_SPEC_ATTRIBUTES = ["name", "parent", "origin", "cached", 79 "has_location", "submodule_search_locations"] 80 81 def assertNamespaceMatches(self, result_ns, expected_ns): 82 """Check two namespaces match. 83 84 Ignores any unspecified interpreter created names 85 """ 86 # Avoid side effects 87 result_ns = result_ns.copy() 88 expected_ns = expected_ns.copy() 89 # Impls are permitted to add extra names, so filter them out 90 for k in list(result_ns): 91 if k.startswith("__") and k.endswith("__"): 92 if k not in expected_ns: 93 result_ns.pop(k) 94 if k not in expected_ns["nested"]: 95 result_ns["nested"].pop(k) 96 # Spec equality includes the loader, so we take the spec out of the 97 # result namespace and check that separately 98 result_spec = result_ns.pop("__spec__") 99 expected_spec = expected_ns.pop("__spec__") 100 if expected_spec is None: 101 self.assertIsNone(result_spec) 102 else: 103 # If an expected loader is set, we just check we got the right 104 # type, rather than checking for full equality 105 if expected_spec.loader is not None: 106 self.assertEqual(type(result_spec.loader), 107 type(expected_spec.loader)) 108 for attr in self.CHECKED_SPEC_ATTRIBUTES: 109 k = "__spec__." + attr 110 actual = (k, getattr(result_spec, attr)) 111 expected = (k, getattr(expected_spec, attr)) 112 self.assertEqual(actual, expected) 113 # For the rest, we still don't use direct dict comparison on the 114 # namespace, as the diffs are too hard to debug if anything breaks 115 self.assertEqual(set(result_ns), set(expected_ns)) 116 for k in result_ns: 117 actual = (k, result_ns[k]) 118 expected = (k, expected_ns[k]) 119 self.assertEqual(actual, expected) 120 121 def check_code_execution(self, create_namespace, expected_namespace): 122 """Check that an interface runs the example code correctly 123 124 First argument is a callable accepting the initial globals and 125 using them to create the actual namespace 126 Second argument is the expected result 127 """ 128 sentinel = object() 129 expected_ns = expected_namespace.copy() 130 run_name = expected_ns["__name__"] 131 saved_argv0 = sys.argv[0] 132 saved_mod = sys.modules.get(run_name, sentinel) 133 # Check without initial globals 134 result_ns = create_namespace(None) 135 self.assertNamespaceMatches(result_ns, expected_ns) 136 self.assertIs(sys.argv[0], saved_argv0) 137 self.assertIs(sys.modules.get(run_name, sentinel), saved_mod) 138 # And then with initial globals 139 initial_ns = {"sentinel": sentinel} 140 expected_ns["sentinel"] = sentinel 141 result_ns = create_namespace(initial_ns) 142 self.assertIsNot(result_ns, initial_ns) 143 self.assertNamespaceMatches(result_ns, expected_ns) 144 self.assertIs(sys.argv[0], saved_argv0) 145 self.assertIs(sys.modules.get(run_name, sentinel), saved_mod) 146 147 148class ExecutionLayerTestCase(unittest.TestCase, CodeExecutionMixin): 149 """Unit tests for runpy._run_code and runpy._run_module_code""" 150 151 def test_run_code(self): 152 expected_ns = example_namespace.copy() 153 expected_ns.update({ 154 "__loader__": None, 155 }) 156 def create_ns(init_globals): 157 return _run_code(example_source, {}, init_globals) 158 self.check_code_execution(create_ns, expected_ns) 159 160 def test_run_module_code(self): 161 mod_name = "<Nonsense>" 162 mod_fname = "Some other nonsense" 163 mod_loader = "Now you're just being silly" 164 mod_package = '' # Treat as a top level module 165 mod_spec = importlib.machinery.ModuleSpec(mod_name, 166 origin=mod_fname, 167 loader=mod_loader) 168 expected_ns = example_namespace.copy() 169 expected_ns.update({ 170 "__name__": mod_name, 171 "__file__": mod_fname, 172 "__loader__": mod_loader, 173 "__package__": mod_package, 174 "__spec__": mod_spec, 175 "run_argv0": mod_fname, 176 "run_name_in_sys_modules": True, 177 "module_in_sys_modules": True, 178 }) 179 def create_ns(init_globals): 180 return _run_module_code(example_source, 181 init_globals, 182 mod_name, 183 mod_spec) 184 self.check_code_execution(create_ns, expected_ns) 185 186# TODO: Use self.addCleanup to get rid of a lot of try-finally blocks 187class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin): 188 """Unit tests for runpy.run_module""" 189 190 def expect_import_error(self, mod_name): 191 try: 192 run_module(mod_name) 193 except ImportError: 194 pass 195 else: 196 self.fail("Expected import error for " + mod_name) 197 198 def test_invalid_names(self): 199 # Builtin module 200 self.expect_import_error("sys") 201 # Non-existent modules 202 self.expect_import_error("sys.imp.eric") 203 self.expect_import_error("os.path.half") 204 self.expect_import_error("a.bee") 205 # Relative names not allowed 206 self.expect_import_error(".howard") 207 self.expect_import_error("..eaten") 208 self.expect_import_error(".test_runpy") 209 self.expect_import_error(".unittest") 210 # Package without __main__.py 211 self.expect_import_error("multiprocessing") 212 213 def test_library_module(self): 214 self.assertEqual(run_module("runpy")["__name__"], "runpy") 215 216 def _add_pkg_dir(self, pkg_dir, namespace=False): 217 os.mkdir(pkg_dir) 218 if namespace: 219 return None 220 pkg_fname = os.path.join(pkg_dir, "__init__.py") 221 create_empty_file(pkg_fname) 222 return pkg_fname 223 224 def _make_pkg(self, source, depth, mod_base="runpy_test", 225 *, namespace=False, parent_namespaces=False): 226 # Enforce a couple of internal sanity checks on test cases 227 if (namespace or parent_namespaces) and not depth: 228 raise RuntimeError("Can't mark top level module as a " 229 "namespace package") 230 pkg_name = "__runpy_pkg__" 231 test_fname = mod_base+os.extsep+"py" 232 pkg_dir = sub_dir = os.path.realpath(tempfile.mkdtemp()) 233 if verbose > 1: print(" Package tree in:", sub_dir) 234 sys.path.insert(0, pkg_dir) 235 if verbose > 1: print(" Updated sys.path:", sys.path[0]) 236 if depth: 237 namespace_flags = [parent_namespaces] * depth 238 namespace_flags[-1] = namespace 239 for namespace_flag in namespace_flags: 240 sub_dir = os.path.join(sub_dir, pkg_name) 241 pkg_fname = self._add_pkg_dir(sub_dir, namespace_flag) 242 if verbose > 1: print(" Next level in:", sub_dir) 243 if verbose > 1: print(" Created:", pkg_fname) 244 mod_fname = os.path.join(sub_dir, test_fname) 245 with open(mod_fname, "w") as mod_file: 246 mod_file.write(source) 247 if verbose > 1: print(" Created:", mod_fname) 248 mod_name = (pkg_name+".")*depth + mod_base 249 mod_spec = importlib.util.spec_from_file_location(mod_name, 250 mod_fname) 251 return pkg_dir, mod_fname, mod_name, mod_spec 252 253 def _del_pkg(self, top): 254 for entry in list(sys.modules): 255 if entry.startswith("__runpy_pkg__"): 256 del sys.modules[entry] 257 if verbose > 1: print(" Removed sys.modules entries") 258 del sys.path[0] 259 if verbose > 1: print(" Removed sys.path entry") 260 for root, dirs, files in os.walk(top, topdown=False): 261 for name in files: 262 try: 263 os.remove(os.path.join(root, name)) 264 except OSError as ex: 265 if verbose > 1: print(ex) # Persist with cleaning up 266 for name in dirs: 267 fullname = os.path.join(root, name) 268 try: 269 os.rmdir(fullname) 270 except OSError as ex: 271 if verbose > 1: print(ex) # Persist with cleaning up 272 try: 273 os.rmdir(top) 274 if verbose > 1: print(" Removed package tree") 275 except OSError as ex: 276 if verbose > 1: print(ex) # Persist with cleaning up 277 278 def _fix_ns_for_legacy_pyc(self, ns, alter_sys): 279 char_to_add = "c" 280 ns["__file__"] += char_to_add 281 ns["__cached__"] = ns["__file__"] 282 spec = ns["__spec__"] 283 new_spec = importlib.util.spec_from_file_location(spec.name, 284 ns["__file__"]) 285 ns["__spec__"] = new_spec 286 if alter_sys: 287 ns["run_argv0"] += char_to_add 288 289 290 def _check_module(self, depth, alter_sys=False, 291 *, namespace=False, parent_namespaces=False): 292 pkg_dir, mod_fname, mod_name, mod_spec = ( 293 self._make_pkg(example_source, depth, 294 namespace=namespace, 295 parent_namespaces=parent_namespaces)) 296 forget(mod_name) 297 expected_ns = example_namespace.copy() 298 expected_ns.update({ 299 "__name__": mod_name, 300 "__file__": mod_fname, 301 "__cached__": mod_spec.cached, 302 "__package__": mod_name.rpartition(".")[0], 303 "__spec__": mod_spec, 304 }) 305 if alter_sys: 306 expected_ns.update({ 307 "run_argv0": mod_fname, 308 "run_name_in_sys_modules": True, 309 "module_in_sys_modules": True, 310 }) 311 def create_ns(init_globals): 312 return run_module(mod_name, init_globals, alter_sys=alter_sys) 313 try: 314 if verbose > 1: print("Running from source:", mod_name) 315 self.check_code_execution(create_ns, expected_ns) 316 importlib.invalidate_caches() 317 __import__(mod_name) 318 os.remove(mod_fname) 319 if not sys.dont_write_bytecode: 320 make_legacy_pyc(mod_fname) 321 unload(mod_name) # In case loader caches paths 322 importlib.invalidate_caches() 323 if verbose > 1: print("Running from compiled:", mod_name) 324 self._fix_ns_for_legacy_pyc(expected_ns, alter_sys) 325 self.check_code_execution(create_ns, expected_ns) 326 finally: 327 self._del_pkg(pkg_dir) 328 if verbose > 1: print("Module executed successfully") 329 330 def _check_package(self, depth, alter_sys=False, 331 *, namespace=False, parent_namespaces=False): 332 pkg_dir, mod_fname, mod_name, mod_spec = ( 333 self._make_pkg(example_source, depth, "__main__", 334 namespace=namespace, 335 parent_namespaces=parent_namespaces)) 336 pkg_name = mod_name.rpartition(".")[0] 337 forget(mod_name) 338 expected_ns = example_namespace.copy() 339 expected_ns.update({ 340 "__name__": mod_name, 341 "__file__": mod_fname, 342 "__cached__": importlib.util.cache_from_source(mod_fname), 343 "__package__": pkg_name, 344 "__spec__": mod_spec, 345 }) 346 if alter_sys: 347 expected_ns.update({ 348 "run_argv0": mod_fname, 349 "run_name_in_sys_modules": True, 350 "module_in_sys_modules": True, 351 }) 352 def create_ns(init_globals): 353 return run_module(pkg_name, init_globals, alter_sys=alter_sys) 354 try: 355 if verbose > 1: print("Running from source:", pkg_name) 356 self.check_code_execution(create_ns, expected_ns) 357 importlib.invalidate_caches() 358 __import__(mod_name) 359 os.remove(mod_fname) 360 if not sys.dont_write_bytecode: 361 make_legacy_pyc(mod_fname) 362 unload(mod_name) # In case loader caches paths 363 if verbose > 1: print("Running from compiled:", pkg_name) 364 importlib.invalidate_caches() 365 self._fix_ns_for_legacy_pyc(expected_ns, alter_sys) 366 self.check_code_execution(create_ns, expected_ns) 367 finally: 368 self._del_pkg(pkg_dir) 369 if verbose > 1: print("Package executed successfully") 370 371 def _add_relative_modules(self, base_dir, source, depth): 372 if depth <= 1: 373 raise ValueError("Relative module test needs depth > 1") 374 pkg_name = "__runpy_pkg__" 375 module_dir = base_dir 376 for i in range(depth): 377 parent_dir = module_dir 378 module_dir = os.path.join(module_dir, pkg_name) 379 # Add sibling module 380 sibling_fname = os.path.join(module_dir, "sibling.py") 381 create_empty_file(sibling_fname) 382 if verbose > 1: print(" Added sibling module:", sibling_fname) 383 # Add nephew module 384 uncle_dir = os.path.join(parent_dir, "uncle") 385 self._add_pkg_dir(uncle_dir) 386 if verbose > 1: print(" Added uncle package:", uncle_dir) 387 cousin_dir = os.path.join(uncle_dir, "cousin") 388 self._add_pkg_dir(cousin_dir) 389 if verbose > 1: print(" Added cousin package:", cousin_dir) 390 nephew_fname = os.path.join(cousin_dir, "nephew.py") 391 create_empty_file(nephew_fname) 392 if verbose > 1: print(" Added nephew module:", nephew_fname) 393 394 def _check_relative_imports(self, depth, run_name=None): 395 contents = r"""\ 396from __future__ import absolute_import 397from . import sibling 398from ..uncle.cousin import nephew 399""" 400 pkg_dir, mod_fname, mod_name, mod_spec = ( 401 self._make_pkg(contents, depth)) 402 if run_name is None: 403 expected_name = mod_name 404 else: 405 expected_name = run_name 406 try: 407 self._add_relative_modules(pkg_dir, contents, depth) 408 pkg_name = mod_name.rpartition('.')[0] 409 if verbose > 1: print("Running from source:", mod_name) 410 d1 = run_module(mod_name, run_name=run_name) # Read from source 411 self.assertEqual(d1["__name__"], expected_name) 412 self.assertEqual(d1["__package__"], pkg_name) 413 self.assertIn("sibling", d1) 414 self.assertIn("nephew", d1) 415 del d1 # Ensure __loader__ entry doesn't keep file open 416 importlib.invalidate_caches() 417 __import__(mod_name) 418 os.remove(mod_fname) 419 if not sys.dont_write_bytecode: 420 make_legacy_pyc(mod_fname) 421 unload(mod_name) # In case the loader caches paths 422 if verbose > 1: print("Running from compiled:", mod_name) 423 importlib.invalidate_caches() 424 d2 = run_module(mod_name, run_name=run_name) # Read from bytecode 425 self.assertEqual(d2["__name__"], expected_name) 426 self.assertEqual(d2["__package__"], pkg_name) 427 self.assertIn("sibling", d2) 428 self.assertIn("nephew", d2) 429 del d2 # Ensure __loader__ entry doesn't keep file open 430 finally: 431 self._del_pkg(pkg_dir) 432 if verbose > 1: print("Module executed successfully") 433 434 def test_run_module(self): 435 for depth in range(4): 436 if verbose > 1: print("Testing package depth:", depth) 437 self._check_module(depth) 438 439 def test_run_module_in_namespace_package(self): 440 for depth in range(1, 4): 441 if verbose > 1: print("Testing package depth:", depth) 442 self._check_module(depth, namespace=True, parent_namespaces=True) 443 444 def test_run_package(self): 445 for depth in range(1, 4): 446 if verbose > 1: print("Testing package depth:", depth) 447 self._check_package(depth) 448 449 def test_run_package_init_exceptions(self): 450 # These were previously wrapped in an ImportError; see Issue 14285 451 result = self._make_pkg("", 1, "__main__") 452 pkg_dir, _, mod_name, _ = result 453 mod_name = mod_name.replace(".__main__", "") 454 self.addCleanup(self._del_pkg, pkg_dir) 455 init = os.path.join(pkg_dir, "__runpy_pkg__", "__init__.py") 456 457 exceptions = (ImportError, AttributeError, TypeError, ValueError) 458 for exception in exceptions: 459 name = exception.__name__ 460 with self.subTest(name): 461 source = "raise {0}('{0} in __init__.py.')".format(name) 462 with open(init, "wt", encoding="ascii") as mod_file: 463 mod_file.write(source) 464 try: 465 run_module(mod_name) 466 except exception as err: 467 self.assertNotIn("finding spec", format(err)) 468 else: 469 self.fail("Nothing raised; expected {}".format(name)) 470 try: 471 run_module(mod_name + ".submodule") 472 except exception as err: 473 self.assertNotIn("finding spec", format(err)) 474 else: 475 self.fail("Nothing raised; expected {}".format(name)) 476 477 def test_submodule_imported_warning(self): 478 pkg_dir, _, mod_name, _ = self._make_pkg("", 1) 479 try: 480 __import__(mod_name) 481 with self.assertWarnsRegex(RuntimeWarning, 482 r"found in sys\.modules"): 483 run_module(mod_name) 484 finally: 485 self._del_pkg(pkg_dir) 486 487 def test_package_imported_no_warning(self): 488 pkg_dir, _, mod_name, _ = self._make_pkg("", 1, "__main__") 489 self.addCleanup(self._del_pkg, pkg_dir) 490 package = mod_name.replace(".__main__", "") 491 # No warning should occur if we only imported the parent package 492 __import__(package) 493 self.assertIn(package, sys.modules) 494 with warnings.catch_warnings(): 495 warnings.simplefilter("error", RuntimeWarning) 496 run_module(package) 497 # But the warning should occur if we imported the __main__ submodule 498 __import__(mod_name) 499 with self.assertWarnsRegex(RuntimeWarning, r"found in sys\.modules"): 500 run_module(package) 501 502 def test_run_package_in_namespace_package(self): 503 for depth in range(1, 4): 504 if verbose > 1: print("Testing package depth:", depth) 505 self._check_package(depth, parent_namespaces=True) 506 507 def test_run_namespace_package(self): 508 for depth in range(1, 4): 509 if verbose > 1: print("Testing package depth:", depth) 510 self._check_package(depth, namespace=True) 511 512 def test_run_namespace_package_in_namespace_package(self): 513 for depth in range(1, 4): 514 if verbose > 1: print("Testing package depth:", depth) 515 self._check_package(depth, namespace=True, parent_namespaces=True) 516 517 def test_run_module_alter_sys(self): 518 for depth in range(4): 519 if verbose > 1: print("Testing package depth:", depth) 520 self._check_module(depth, alter_sys=True) 521 522 def test_run_package_alter_sys(self): 523 for depth in range(1, 4): 524 if verbose > 1: print("Testing package depth:", depth) 525 self._check_package(depth, alter_sys=True) 526 527 def test_explicit_relative_import(self): 528 for depth in range(2, 5): 529 if verbose > 1: print("Testing relative imports at depth:", depth) 530 self._check_relative_imports(depth) 531 532 def test_main_relative_import(self): 533 for depth in range(2, 5): 534 if verbose > 1: print("Testing main relative imports at depth:", depth) 535 self._check_relative_imports(depth, "__main__") 536 537 def test_run_name(self): 538 depth = 1 539 run_name = "And now for something completely different" 540 pkg_dir, mod_fname, mod_name, mod_spec = ( 541 self._make_pkg(example_source, depth)) 542 forget(mod_name) 543 expected_ns = example_namespace.copy() 544 expected_ns.update({ 545 "__name__": run_name, 546 "__file__": mod_fname, 547 "__cached__": importlib.util.cache_from_source(mod_fname), 548 "__package__": mod_name.rpartition(".")[0], 549 "__spec__": mod_spec, 550 }) 551 def create_ns(init_globals): 552 return run_module(mod_name, init_globals, run_name) 553 try: 554 self.check_code_execution(create_ns, expected_ns) 555 finally: 556 self._del_pkg(pkg_dir) 557 558 def test_pkgutil_walk_packages(self): 559 # This is a dodgy hack to use the test_runpy infrastructure to test 560 # issue #15343. Issue #15348 declares this is indeed a dodgy hack ;) 561 import pkgutil 562 max_depth = 4 563 base_name = "__runpy_pkg__" 564 package_suffixes = ["uncle", "uncle.cousin"] 565 module_suffixes = ["uncle.cousin.nephew", base_name + ".sibling"] 566 expected_packages = set() 567 expected_modules = set() 568 for depth in range(1, max_depth): 569 pkg_name = ".".join([base_name] * depth) 570 expected_packages.add(pkg_name) 571 for name in package_suffixes: 572 expected_packages.add(pkg_name + "." + name) 573 for name in module_suffixes: 574 expected_modules.add(pkg_name + "." + name) 575 pkg_name = ".".join([base_name] * max_depth) 576 expected_packages.add(pkg_name) 577 expected_modules.add(pkg_name + ".runpy_test") 578 pkg_dir, mod_fname, mod_name, mod_spec = ( 579 self._make_pkg("", max_depth)) 580 self.addCleanup(self._del_pkg, pkg_dir) 581 for depth in range(2, max_depth+1): 582 self._add_relative_modules(pkg_dir, "", depth) 583 for moduleinfo in pkgutil.walk_packages([pkg_dir]): 584 self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo) 585 self.assertIsInstance(moduleinfo.module_finder, 586 importlib.machinery.FileFinder) 587 if moduleinfo.ispkg: 588 expected_packages.remove(moduleinfo.name) 589 else: 590 expected_modules.remove(moduleinfo.name) 591 self.assertEqual(len(expected_packages), 0, expected_packages) 592 self.assertEqual(len(expected_modules), 0, expected_modules) 593 594class RunPathTestCase(unittest.TestCase, CodeExecutionMixin): 595 """Unit tests for runpy.run_path""" 596 597 def _make_test_script(self, script_dir, script_basename, 598 source=None, omit_suffix=False): 599 if source is None: 600 source = example_source 601 return make_script(script_dir, script_basename, 602 source, omit_suffix) 603 604 def _check_script(self, script_name, expected_name, expected_file, 605 expected_argv0, mod_name=None, 606 expect_spec=True, check_loader=True): 607 # First check is without run_name 608 def create_ns(init_globals): 609 return run_path(script_name, init_globals) 610 expected_ns = example_namespace.copy() 611 if mod_name is None: 612 spec_name = expected_name 613 else: 614 spec_name = mod_name 615 if expect_spec: 616 mod_spec = importlib.util.spec_from_file_location(spec_name, 617 expected_file) 618 mod_cached = mod_spec.cached 619 if not check_loader: 620 mod_spec.loader = None 621 else: 622 mod_spec = mod_cached = None 623 624 expected_ns.update({ 625 "__name__": expected_name, 626 "__file__": expected_file, 627 "__cached__": mod_cached, 628 "__package__": "", 629 "__spec__": mod_spec, 630 "run_argv0": expected_argv0, 631 "run_name_in_sys_modules": True, 632 "module_in_sys_modules": True, 633 }) 634 self.check_code_execution(create_ns, expected_ns) 635 # Second check makes sure run_name works in all cases 636 run_name = "prove.issue15230.is.fixed" 637 def create_ns(init_globals): 638 return run_path(script_name, init_globals, run_name) 639 if expect_spec and mod_name is None: 640 mod_spec = importlib.util.spec_from_file_location(run_name, 641 expected_file) 642 if not check_loader: 643 mod_spec.loader = None 644 expected_ns["__spec__"] = mod_spec 645 expected_ns["__name__"] = run_name 646 expected_ns["__package__"] = run_name.rpartition(".")[0] 647 self.check_code_execution(create_ns, expected_ns) 648 649 def _check_import_error(self, script_name, msg): 650 msg = re.escape(msg) 651 self.assertRaisesRegex(ImportError, msg, run_path, script_name) 652 653 def test_basic_script(self): 654 with temp_dir() as script_dir: 655 mod_name = 'script' 656 script_name = self._make_test_script(script_dir, mod_name) 657 self._check_script(script_name, "<run_path>", script_name, 658 script_name, expect_spec=False) 659 660 def test_basic_script_with_pathlike_object(self): 661 with temp_dir() as script_dir: 662 mod_name = 'script' 663 script_name = self._make_test_script(script_dir, mod_name) 664 self._check_script(FakePath(script_name), "<run_path>", 665 script_name, 666 script_name, 667 expect_spec=False) 668 669 def test_basic_script_no_suffix(self): 670 with temp_dir() as script_dir: 671 mod_name = 'script' 672 script_name = self._make_test_script(script_dir, mod_name, 673 omit_suffix=True) 674 self._check_script(script_name, "<run_path>", script_name, 675 script_name, expect_spec=False) 676 677 def test_script_compiled(self): 678 with temp_dir() as script_dir: 679 mod_name = 'script' 680 script_name = self._make_test_script(script_dir, mod_name) 681 compiled_name = py_compile.compile(script_name, doraise=True) 682 os.remove(script_name) 683 self._check_script(compiled_name, "<run_path>", compiled_name, 684 compiled_name, expect_spec=False) 685 686 def test_directory(self): 687 with temp_dir() as script_dir: 688 mod_name = '__main__' 689 script_name = self._make_test_script(script_dir, mod_name) 690 self._check_script(script_dir, "<run_path>", script_name, 691 script_dir, mod_name=mod_name) 692 693 def test_directory_compiled(self): 694 with temp_dir() as script_dir: 695 mod_name = '__main__' 696 script_name = self._make_test_script(script_dir, mod_name) 697 compiled_name = py_compile.compile(script_name, doraise=True) 698 os.remove(script_name) 699 if not sys.dont_write_bytecode: 700 legacy_pyc = make_legacy_pyc(script_name) 701 self._check_script(script_dir, "<run_path>", legacy_pyc, 702 script_dir, mod_name=mod_name) 703 704 def test_directory_error(self): 705 with temp_dir() as script_dir: 706 mod_name = 'not_main' 707 script_name = self._make_test_script(script_dir, mod_name) 708 msg = "can't find '__main__' module in %r" % script_dir 709 self._check_import_error(script_dir, msg) 710 711 def test_zipfile(self): 712 with temp_dir() as script_dir: 713 mod_name = '__main__' 714 script_name = self._make_test_script(script_dir, mod_name) 715 zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) 716 self._check_script(zip_name, "<run_path>", fname, zip_name, 717 mod_name=mod_name, check_loader=False) 718 719 def test_zipfile_compiled(self): 720 with temp_dir() as script_dir: 721 mod_name = '__main__' 722 script_name = self._make_test_script(script_dir, mod_name) 723 compiled_name = py_compile.compile(script_name, doraise=True) 724 zip_name, fname = make_zip_script(script_dir, 'test_zip', 725 compiled_name) 726 self._check_script(zip_name, "<run_path>", fname, zip_name, 727 mod_name=mod_name, check_loader=False) 728 729 def test_zipfile_error(self): 730 with temp_dir() as script_dir: 731 mod_name = 'not_main' 732 script_name = self._make_test_script(script_dir, mod_name) 733 zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) 734 msg = "can't find '__main__' module in %r" % zip_name 735 self._check_import_error(zip_name, msg) 736 737 @no_tracing 738 @requires_resource('cpu') 739 def test_main_recursion_error(self): 740 with temp_dir() as script_dir, temp_dir() as dummy_dir: 741 mod_name = '__main__' 742 source = ("import runpy\n" 743 "runpy.run_path(%r)\n") % dummy_dir 744 script_name = self._make_test_script(script_dir, mod_name, source) 745 zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) 746 with infinite_recursion(25): 747 self.assertRaises(RecursionError, run_path, zip_name) 748 749 def test_encoding(self): 750 with temp_dir() as script_dir: 751 filename = os.path.join(script_dir, 'script.py') 752 with open(filename, 'w', encoding='latin1') as f: 753 f.write(""" 754#coding:latin1 755s = "non-ASCII: h\xe9" 756""") 757 result = run_path(filename) 758 self.assertEqual(result['s'], "non-ASCII: h\xe9") 759 760 761class TestExit(unittest.TestCase): 762 STATUS_CONTROL_C_EXIT = 0xC000013A 763 EXPECTED_CODE = ( 764 STATUS_CONTROL_C_EXIT 765 if sys.platform == "win32" 766 else -signal.SIGINT 767 ) 768 @staticmethod 769 @contextlib.contextmanager 770 def tmp_path(*args, **kwargs): 771 with temp_dir() as tmp_fn: 772 yield pathlib.Path(tmp_fn) 773 774 775 def run(self, *args, **kwargs): 776 with self.tmp_path() as tmp: 777 self.ham = ham = tmp / "ham.py" 778 ham.write_text( 779 textwrap.dedent( 780 """\ 781 raise KeyboardInterrupt 782 """ 783 ) 784 ) 785 super().run(*args, **kwargs) 786 787 @requires_subprocess() 788 def assertSigInt(self, cmd, *args, **kwargs): 789 # Use -E to ignore PYTHONSAFEPATH 790 cmd = [sys.executable, '-E', *cmd] 791 proc = subprocess.run(cmd, *args, **kwargs, text=True, stderr=subprocess.PIPE) 792 self.assertTrue(proc.stderr.endswith("\nKeyboardInterrupt\n"), proc.stderr) 793 self.assertEqual(proc.returncode, self.EXPECTED_CODE) 794 795 def test_pymain_run_file(self): 796 self.assertSigInt([self.ham]) 797 798 def test_pymain_run_file_runpy_run_module(self): 799 tmp = self.ham.parent 800 run_module = tmp / "run_module.py" 801 run_module.write_text( 802 textwrap.dedent( 803 """\ 804 import runpy 805 runpy.run_module("ham") 806 """ 807 ) 808 ) 809 self.assertSigInt([run_module], cwd=tmp) 810 811 def test_pymain_run_file_runpy_run_module_as_main(self): 812 tmp = self.ham.parent 813 run_module_as_main = tmp / "run_module_as_main.py" 814 run_module_as_main.write_text( 815 textwrap.dedent( 816 """\ 817 import runpy 818 runpy._run_module_as_main("ham") 819 """ 820 ) 821 ) 822 self.assertSigInt([run_module_as_main], cwd=tmp) 823 824 def test_pymain_run_command_run_module(self): 825 self.assertSigInt( 826 ["-c", "import runpy; runpy.run_module('ham')"], 827 cwd=self.ham.parent, 828 ) 829 830 def test_pymain_run_command(self): 831 self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) 832 833 def test_pymain_run_stdin(self): 834 self.assertSigInt([], input="import ham", cwd=self.ham.parent) 835 836 def test_pymain_run_module(self): 837 ham = self.ham 838 self.assertSigInt(["-m", ham.stem], cwd=ham.parent) 839 840 841if __name__ == "__main__": 842 unittest.main() 843