| # Test the runpy module | |
| import contextlib | |
| import importlib.machinery, importlib.util | |
| import os.path | |
| import pathlib | |
| import py_compile | |
| import re | |
| import signal | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import textwrap | |
| import unittest | |
| import warnings | |
| from test.support import ( | |
| force_not_colorized_test_class, | |
| infinite_recursion, | |
| no_tracing, | |
| requires_resource, | |
| requires_subprocess, | |
| verbose, | |
| ) | |
| from test import support | |
| from test.support.import_helper import forget, make_legacy_pyc, unload | |
| from test.support.os_helper import create_empty_file, temp_dir, FakePath | |
| from test.support.script_helper import make_script, make_zip_script | |
| from test.test_importlib.util import temporary_pycache_prefix | |
| import runpy | |
| from runpy import _run_code, _run_module_code, run_module, run_path | |
| # Note: This module can't safely test _run_module_as_main as it | |
| # runs its tests in the current process, which would mess with the | |
| # real __main__ module (usually test.regrtest) | |
| # See test_cmd_line_script for a test that executes that code path | |
| # Set up the test code and expected results | |
| example_source = """\ | |
| # Check basic code execution | |
| result = ['Top level assignment'] | |
| def f(): | |
| result.append('Lower level reference') | |
| f() | |
| del f | |
| # Check the sys module | |
| import sys | |
| run_argv0 = sys.argv[0] | |
| run_name_in_sys_modules = __name__ in sys.modules | |
| module_in_sys_modules = (run_name_in_sys_modules and | |
| globals() is sys.modules[__name__].__dict__) | |
| # Check nested operation | |
| import runpy | |
| nested = runpy._run_module_code('x=1\\n', mod_name='<run>') | |
| """ | |
| implicit_namespace = { | |
| "__name__": None, | |
| "__file__": None, | |
| "__package__": None, | |
| "__doc__": None, | |
| "__spec__": None | |
| } | |
| example_namespace = { | |
| "sys": sys, | |
| "runpy": runpy, | |
| "result": ["Top level assignment", "Lower level reference"], | |
| "run_argv0": sys.argv[0], | |
| "run_name_in_sys_modules": False, | |
| "module_in_sys_modules": False, | |
| "nested": dict(implicit_namespace, | |
| x=1, __name__="<run>", __loader__=None), | |
| } | |
| example_namespace.update(implicit_namespace) | |
| class CodeExecutionMixin: | |
| # Issue #15230 (run_path not handling run_name correctly) highlighted a | |
| # problem with the way arguments were being passed from higher level APIs | |
| # down to lower level code. This mixin makes it easier to ensure full | |
| # testing occurs at those upper layers as well, not just at the utility | |
| # layer | |
| # Figuring out the loader details in advance is hard to do, so we skip | |
| # checking the full details of loader and loader_state | |
| CHECKED_SPEC_ATTRIBUTES = ["name", "parent", "origin", "cached", | |
| "has_location", "submodule_search_locations"] | |
| def assertNamespaceMatches(self, result_ns, expected_ns): | |
| """Check two namespaces match. | |
| Ignores any unspecified interpreter created names | |
| """ | |
| # Avoid side effects | |
| result_ns = result_ns.copy() | |
| expected_ns = expected_ns.copy() | |
| # Impls are permitted to add extra names, so filter them out | |
| for k in list(result_ns): | |
| if k.startswith("__") and k.endswith("__"): | |
| if k not in expected_ns: | |
| result_ns.pop(k) | |
| if k not in expected_ns["nested"]: | |
| result_ns["nested"].pop(k) | |
| # Spec equality includes the loader, so we take the spec out of the | |
| # result namespace and check that separately | |
| result_spec = result_ns.pop("__spec__") | |
| expected_spec = expected_ns.pop("__spec__") | |
| if expected_spec is None: | |
| self.assertIsNone(result_spec) | |
| else: | |
| # If an expected loader is set, we just check we got the right | |
| # type, rather than checking for full equality | |
| if expected_spec.loader is not None: | |
| self.assertEqual(type(result_spec.loader), | |
| type(expected_spec.loader)) | |
| for attr in self.CHECKED_SPEC_ATTRIBUTES: | |
| k = "__spec__." + attr | |
| actual = (k, getattr(result_spec, attr)) | |
| expected = (k, getattr(expected_spec, attr)) | |
| self.assertEqual(actual, expected) | |
| # For the rest, we still don't use direct dict comparison on the | |
| # namespace, as the diffs are too hard to debug if anything breaks | |
| self.assertEqual(set(result_ns), set(expected_ns)) | |
| for k in result_ns: | |
| actual = (k, result_ns[k]) | |
| expected = (k, expected_ns[k]) | |
| self.assertEqual(actual, expected) | |
| def check_code_execution(self, create_namespace, expected_namespace): | |
| """Check that an interface runs the example code correctly | |
| First argument is a callable accepting the initial globals and | |
| using them to create the actual namespace | |
| Second argument is the expected result | |
| """ | |
| sentinel = object() | |
| expected_ns = expected_namespace.copy() | |
| run_name = expected_ns["__name__"] | |
| saved_argv0 = sys.argv[0] | |
| saved_mod = sys.modules.get(run_name, sentinel) | |
| # Check without initial globals | |
| result_ns = create_namespace(None) | |
| self.assertNamespaceMatches(result_ns, expected_ns) | |
| self.assertIs(sys.argv[0], saved_argv0) | |
| self.assertIs(sys.modules.get(run_name, sentinel), saved_mod) | |
| # And then with initial globals | |
| initial_ns = {"sentinel": sentinel} | |
| expected_ns["sentinel"] = sentinel | |
| result_ns = create_namespace(initial_ns) | |
| self.assertIsNot(result_ns, initial_ns) | |
| self.assertNamespaceMatches(result_ns, expected_ns) | |
| self.assertIs(sys.argv[0], saved_argv0) | |
| self.assertIs(sys.modules.get(run_name, sentinel), saved_mod) | |
| class ExecutionLayerTestCase(unittest.TestCase, CodeExecutionMixin): | |
| """Unit tests for runpy._run_code and runpy._run_module_code""" | |
| def test_run_code(self): | |
| expected_ns = example_namespace.copy() | |
| expected_ns.update({ | |
| "__loader__": None, | |
| }) | |
| def create_ns(init_globals): | |
| return _run_code(example_source, {}, init_globals) | |
| self.check_code_execution(create_ns, expected_ns) | |
| def test_run_module_code(self): | |
| mod_name = "<Nonsense>" | |
| mod_fname = "Some other nonsense" | |
| mod_loader = "Now you're just being silly" | |
| mod_package = '' # Treat as a top level module | |
| mod_spec = importlib.machinery.ModuleSpec(mod_name, | |
| origin=mod_fname, | |
| loader=mod_loader) | |
| expected_ns = example_namespace.copy() | |
| expected_ns.update({ | |
| "__name__": mod_name, | |
| "__file__": mod_fname, | |
| "__loader__": mod_loader, | |
| "__package__": mod_package, | |
| "__spec__": mod_spec, | |
| "run_argv0": mod_fname, | |
| "run_name_in_sys_modules": True, | |
| "module_in_sys_modules": True, | |
| }) | |
| def create_ns(init_globals): | |
| return _run_module_code(example_source, | |
| init_globals, | |
| mod_name, | |
| mod_spec) | |
| self.check_code_execution(create_ns, expected_ns) | |
| # TODO: Use self.addCleanup to get rid of a lot of try-finally blocks | |
| class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin): | |
| """Unit tests for runpy.run_module""" | |
| def expect_import_error(self, mod_name): | |
| try: | |
| run_module(mod_name) | |
| except ImportError: | |
| pass | |
| else: | |
| self.fail("Expected import error for " + mod_name) | |
| def test_invalid_names(self): | |
| # Builtin module | |
| self.expect_import_error("sys") | |
| # Non-existent modules | |
| self.expect_import_error("sys.imp.eric") | |
| self.expect_import_error("os.path.half") | |
| self.expect_import_error("a.bee") | |
| # Relative names not allowed | |
| self.expect_import_error(".howard") | |
| self.expect_import_error("..eaten") | |
| self.expect_import_error(".test_runpy") | |
| self.expect_import_error(".unittest") | |
| # Package without __main__.py | |
| self.expect_import_error("multiprocessing") | |
| def test_invalid_names_set_name_attribute(self): | |
| cases = [ | |
| # (mod_name, expected_name) -- comment indicates raise site | |
| ("nonexistent_runpy_test_module", | |
| "nonexistent_runpy_test_module"), # spec is None | |
| ("sys.imp.eric", "sys.imp.eric"), # find_spec error | |
| (".relative_name", ".relative_name"), # relative name rejected | |
| ("sys", "sys"), # builtin: no code object | |
| ("multiprocessing", "multiprocessing"), # package without __main__ | |
| ] | |
| for mod_name, expected_name in cases: | |
| with self.subTest(mod_name=mod_name): | |
| try: | |
| run_module(mod_name) | |
| except ImportError as exc: | |
| self.assertEqual(exc.name, expected_name) | |
| else: | |
| self.fail("Expected ImportError for %r" % mod_name) | |
| def test_library_module(self): | |
| self.assertEqual(run_module("runpy")["__name__"], "runpy") | |
| def _add_pkg_dir(self, pkg_dir, namespace=False): | |
| os.mkdir(pkg_dir) | |
| if namespace: | |
| return None | |
| pkg_fname = os.path.join(pkg_dir, "__init__.py") | |
| create_empty_file(pkg_fname) | |
| return pkg_fname | |
| def _make_pkg(self, source, depth, mod_base="runpy_test", | |
| *, namespace=False, parent_namespaces=False): | |
| # Enforce a couple of internal sanity checks on test cases | |
| if (namespace or parent_namespaces) and not depth: | |
| raise RuntimeError("Can't mark top level module as a " | |
| "namespace package") | |
| pkg_name = "__runpy_pkg__" | |
| test_fname = mod_base+os.extsep+"py" | |
| pkg_dir = sub_dir = os.path.realpath(tempfile.mkdtemp()) | |
| if verbose > 1: print(" Package tree in:", sub_dir) | |
| sys.path.insert(0, pkg_dir) | |
| if verbose > 1: print(" Updated sys.path:", sys.path[0]) | |
| if depth: | |
| namespace_flags = [parent_namespaces] * depth | |
| namespace_flags[-1] = namespace | |
| for namespace_flag in namespace_flags: | |
| sub_dir = os.path.join(sub_dir, pkg_name) | |
| pkg_fname = self._add_pkg_dir(sub_dir, namespace_flag) | |
| if verbose > 1: print(" Next level in:", sub_dir) | |
| if verbose > 1: print(" Created:", pkg_fname) | |
| mod_fname = os.path.join(sub_dir, test_fname) | |
| with open(mod_fname, "w") as mod_file: | |
| mod_file.write(source) | |
| if verbose > 1: print(" Created:", mod_fname) | |
| mod_name = (pkg_name+".")*depth + mod_base | |
| mod_spec = importlib.util.spec_from_file_location(mod_name, | |
| mod_fname) | |
| return pkg_dir, mod_fname, mod_name, mod_spec | |
| def _del_pkg(self, top): | |
| for entry in list(sys.modules): | |
| if entry.startswith("__runpy_pkg__"): | |
| del sys.modules[entry] | |
| if verbose > 1: print(" Removed sys.modules entries") | |
| del sys.path[0] | |
| if verbose > 1: print(" Removed sys.path entry") | |
| for root, dirs, files in os.walk(top, topdown=False): | |
| for name in files: | |
| try: | |
| os.remove(os.path.join(root, name)) | |
| except OSError as ex: | |
| if verbose > 1: print(ex) # Persist with cleaning up | |
| for name in dirs: | |
| fullname = os.path.join(root, name) | |
| try: | |
| os.rmdir(fullname) | |
| except OSError as ex: | |
| if verbose > 1: print(ex) # Persist with cleaning up | |
| try: | |
| os.rmdir(top) | |
| if verbose > 1: print(" Removed package tree") | |
| except OSError as ex: | |
| if verbose > 1: print(ex) # Persist with cleaning up | |
| def _fix_ns_for_legacy_pyc(self, ns, alter_sys): | |
| char_to_add = "c" | |
| ns["__file__"] += char_to_add | |
| spec = ns["__spec__"] | |
| new_spec = importlib.util.spec_from_file_location(spec.name, | |
| ns["__file__"]) | |
| ns["__spec__"] = new_spec | |
| if alter_sys: | |
| ns["run_argv0"] += char_to_add | |
| def _check_module(self, depth, alter_sys=False, | |
| *, namespace=False, parent_namespaces=False): | |
| pkg_dir, mod_fname, mod_name, mod_spec = ( | |
| self._make_pkg(example_source, depth, | |
| namespace=namespace, | |
| parent_namespaces=parent_namespaces)) | |
| forget(mod_name) | |
| expected_ns = example_namespace.copy() | |
| expected_ns.update({ | |
| "__name__": mod_name, | |
| "__file__": mod_fname, | |
| "__package__": mod_name.rpartition(".")[0], | |
| "__spec__": mod_spec, | |
| }) | |
| if alter_sys: | |
| expected_ns.update({ | |
| "run_argv0": mod_fname, | |
| "run_name_in_sys_modules": True, | |
| "module_in_sys_modules": True, | |
| }) | |
| def create_ns(init_globals): | |
| return run_module(mod_name, init_globals, alter_sys=alter_sys) | |
| try: | |
| if verbose > 1: print("Running from source:", mod_name) | |
| self.check_code_execution(create_ns, expected_ns) | |
| importlib.invalidate_caches() | |
| __import__(mod_name) | |
| if not sys.dont_write_bytecode: | |
| make_legacy_pyc(mod_fname, allow_compile=True) | |
| unload(mod_name) # In case loader caches paths | |
| os.remove(mod_fname) | |
| importlib.invalidate_caches() | |
| if verbose > 1: print("Running from compiled:", mod_name) | |
| self._fix_ns_for_legacy_pyc(expected_ns, alter_sys) | |
| self.check_code_execution(create_ns, expected_ns) | |
| else: | |
| os.remove(mod_fname) | |
| finally: | |
| self._del_pkg(pkg_dir) | |
| if verbose > 1: print("Module executed successfully") | |
| def _check_package(self, depth, alter_sys=False, | |
| *, namespace=False, parent_namespaces=False): | |
| pkg_dir, mod_fname, mod_name, mod_spec = ( | |
| self._make_pkg(example_source, depth, "__main__", | |
| namespace=namespace, | |
| parent_namespaces=parent_namespaces)) | |
| pkg_name = mod_name.rpartition(".")[0] | |
| forget(mod_name) | |
| expected_ns = example_namespace.copy() | |
| expected_ns.update({ | |
| "__name__": mod_name, | |
| "__file__": mod_fname, | |
| "__package__": pkg_name, | |
| "__spec__": mod_spec, | |
| }) | |
| if alter_sys: | |
| expected_ns.update({ | |
| "run_argv0": mod_fname, | |
| "run_name_in_sys_modules": True, | |
| "module_in_sys_modules": True, | |
| }) | |
| def create_ns(init_globals): | |
| return run_module(pkg_name, init_globals, alter_sys=alter_sys) | |
| try: | |
| if verbose > 1: print("Running from source:", pkg_name) | |
| self.check_code_execution(create_ns, expected_ns) | |
| importlib.invalidate_caches() | |
| __import__(mod_name) | |
| if not sys.dont_write_bytecode: | |
| make_legacy_pyc(mod_fname, allow_compile=True) | |
| unload(mod_name) # In case loader caches paths | |
| os.remove(mod_fname) | |
| if verbose > 1: print("Running from compiled:", pkg_name) | |
| importlib.invalidate_caches() | |
| self._fix_ns_for_legacy_pyc(expected_ns, alter_sys) | |
| self.check_code_execution(create_ns, expected_ns) | |
| else: | |
| os.remove(mod_fname) | |
| finally: | |
| self._del_pkg(pkg_dir) | |
| if verbose > 1: print("Package executed successfully") | |
| def _add_relative_modules(self, base_dir, source, depth): | |
| if depth <= 1: | |
| raise ValueError("Relative module test needs depth > 1") | |
| pkg_name = "__runpy_pkg__" | |
| module_dir = base_dir | |
| for i in range(depth): | |
| parent_dir = module_dir | |
| module_dir = os.path.join(module_dir, pkg_name) | |
| # Add sibling module | |
| sibling_fname = os.path.join(module_dir, "sibling.py") | |
| create_empty_file(sibling_fname) | |
| if verbose > 1: print(" Added sibling module:", sibling_fname) | |
| # Add nephew module | |
| uncle_dir = os.path.join(parent_dir, "uncle") | |
| self._add_pkg_dir(uncle_dir) | |
| if verbose > 1: print(" Added uncle package:", uncle_dir) | |
| cousin_dir = os.path.join(uncle_dir, "cousin") | |
| self._add_pkg_dir(cousin_dir) | |
| if verbose > 1: print(" Added cousin package:", cousin_dir) | |
| nephew_fname = os.path.join(cousin_dir, "nephew.py") | |
| create_empty_file(nephew_fname) | |
| if verbose > 1: print(" Added nephew module:", nephew_fname) | |
| def _check_relative_imports(self, depth, run_name=None): | |
| contents = r"""\ | |
| from __future__ import absolute_import | |
| from . import sibling | |
| from ..uncle.cousin import nephew | |
| """ | |
| pkg_dir, mod_fname, mod_name, mod_spec = ( | |
| self._make_pkg(contents, depth)) | |
| if run_name is None: | |
| expected_name = mod_name | |
| else: | |
| expected_name = run_name | |
| try: | |
| self._add_relative_modules(pkg_dir, contents, depth) | |
| pkg_name = mod_name.rpartition('.')[0] | |
| if verbose > 1: print("Running from source:", mod_name) | |
| d1 = run_module(mod_name, run_name=run_name) # Read from source | |
| self.assertEqual(d1["__name__"], expected_name) | |
| self.assertEqual(d1["__package__"], pkg_name) | |
| self.assertIn("sibling", d1) | |
| self.assertIn("nephew", d1) | |
| del d1 # Ensure __loader__ entry doesn't keep file open | |
| importlib.invalidate_caches() | |
| __import__(mod_name) | |
| os.remove(mod_fname) | |
| if not sys.dont_write_bytecode and sys.implementation.cache_tag: | |
| make_legacy_pyc(mod_fname) | |
| unload(mod_name) # In case the loader caches paths | |
| if verbose > 1: print("Running from compiled:", mod_name) | |
| importlib.invalidate_caches() | |
| d2 = run_module(mod_name, run_name=run_name) # Read from bytecode | |
| self.assertEqual(d2["__name__"], expected_name) | |
| self.assertEqual(d2["__package__"], pkg_name) | |
| self.assertIn("sibling", d2) | |
| self.assertIn("nephew", d2) | |
| del d2 # Ensure __loader__ entry doesn't keep file open | |
| finally: | |
| self._del_pkg(pkg_dir) | |
| if verbose > 1: print("Module executed successfully") | |
| def test_run_module(self): | |
| for depth in range(4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_module(depth) | |
| def test_run_module_in_namespace_package(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_module(depth, namespace=True, parent_namespaces=True) | |
| def test_run_package(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_package(depth) | |
| def test_run_package_init_exceptions(self): | |
| # These were previously wrapped in an ImportError; see Issue 14285 | |
| result = self._make_pkg("", 1, "__main__") | |
| pkg_dir, _, mod_name, _ = result | |
| mod_name = mod_name.replace(".__main__", "") | |
| self.addCleanup(self._del_pkg, pkg_dir) | |
| init = os.path.join(pkg_dir, "__runpy_pkg__", "__init__.py") | |
| exceptions = (ImportError, AttributeError, TypeError, ValueError) | |
| for exception in exceptions: | |
| name = exception.__name__ | |
| with self.subTest(name): | |
| source = "raise {0}('{0} in __init__.py.')".format(name) | |
| with open(init, "wt", encoding="ascii") as mod_file: | |
| mod_file.write(source) | |
| try: | |
| run_module(mod_name) | |
| except exception as err: | |
| self.assertNotIn("finding spec", format(err)) | |
| else: | |
| self.fail("Nothing raised; expected {}".format(name)) | |
| try: | |
| run_module(mod_name + ".submodule") | |
| except exception as err: | |
| self.assertNotIn("finding spec", format(err)) | |
| else: | |
| self.fail("Nothing raised; expected {}".format(name)) | |
| def test_submodule_imported_warning(self): | |
| pkg_dir, _, mod_name, _ = self._make_pkg("", 1) | |
| try: | |
| __import__(mod_name) | |
| with self.assertWarnsRegex(RuntimeWarning, | |
| r"found in sys\.modules"): | |
| run_module(mod_name) | |
| finally: | |
| self._del_pkg(pkg_dir) | |
| def test_package_imported_no_warning(self): | |
| pkg_dir, _, mod_name, _ = self._make_pkg("", 1, "__main__") | |
| self.addCleanup(self._del_pkg, pkg_dir) | |
| package = mod_name.replace(".__main__", "") | |
| # No warning should occur if we only imported the parent package | |
| __import__(package) | |
| self.assertIn(package, sys.modules) | |
| with warnings.catch_warnings(): | |
| warnings.simplefilter("error", RuntimeWarning) | |
| run_module(package) | |
| # But the warning should occur if we imported the __main__ submodule | |
| __import__(mod_name) | |
| with self.assertWarnsRegex(RuntimeWarning, r"found in sys\.modules"): | |
| run_module(package) | |
| def test_run_package_in_namespace_package(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_package(depth, parent_namespaces=True) | |
| def test_run_namespace_package(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_package(depth, namespace=True) | |
| def test_run_namespace_package_in_namespace_package(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_package(depth, namespace=True, parent_namespaces=True) | |
| def test_run_module_alter_sys(self): | |
| for depth in range(4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_module(depth, alter_sys=True) | |
| def test_run_package_alter_sys(self): | |
| for depth in range(1, 4): | |
| if verbose > 1: print("Testing package depth:", depth) | |
| self._check_package(depth, alter_sys=True) | |
| def test_explicit_relative_import(self): | |
| for depth in range(2, 5): | |
| if verbose > 1: print("Testing relative imports at depth:", depth) | |
| self._check_relative_imports(depth) | |
| def test_main_relative_import(self): | |
| for depth in range(2, 5): | |
| if verbose > 1: print("Testing main relative imports at depth:", depth) | |
| self._check_relative_imports(depth, "__main__") | |
| def test_run_name(self): | |
| depth = 1 | |
| run_name = "And now for something completely different" | |
| pkg_dir, mod_fname, mod_name, mod_spec = ( | |
| self._make_pkg(example_source, depth)) | |
| forget(mod_name) | |
| expected_ns = example_namespace.copy() | |
| expected_ns.update({ | |
| "__name__": run_name, | |
| "__file__": mod_fname, | |
| "__package__": mod_name.rpartition(".")[0], | |
| "__spec__": mod_spec, | |
| }) | |
| def create_ns(init_globals): | |
| return run_module(mod_name, init_globals, run_name) | |
| try: | |
| self.check_code_execution(create_ns, expected_ns) | |
| finally: | |
| self._del_pkg(pkg_dir) | |
| def test_pkgutil_walk_packages(self): | |
| # This is a dodgy hack to use the test_runpy infrastructure to test | |
| # issue #15343. Issue #15348 declares this is indeed a dodgy hack ;) | |
| import pkgutil | |
| max_depth = 4 | |
| base_name = "__runpy_pkg__" | |
| package_suffixes = ["uncle", "uncle.cousin"] | |
| module_suffixes = ["uncle.cousin.nephew", base_name + ".sibling"] | |
| expected_packages = set() | |
| expected_modules = set() | |
| for depth in range(1, max_depth): | |
| pkg_name = ".".join([base_name] * depth) | |
| expected_packages.add(pkg_name) | |
| for name in package_suffixes: | |
| expected_packages.add(pkg_name + "." + name) | |
| for name in module_suffixes: | |
| expected_modules.add(pkg_name + "." + name) | |
| pkg_name = ".".join([base_name] * max_depth) | |
| expected_packages.add(pkg_name) | |
| expected_modules.add(pkg_name + ".runpy_test") | |
| pkg_dir, mod_fname, mod_name, mod_spec = ( | |
| self._make_pkg("", max_depth)) | |
| self.addCleanup(self._del_pkg, pkg_dir) | |
| for depth in range(2, max_depth+1): | |
| self._add_relative_modules(pkg_dir, "", depth) | |
| for moduleinfo in pkgutil.walk_packages([pkg_dir]): | |
| self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo) | |
| self.assertIsInstance(moduleinfo.module_finder, | |
| importlib.machinery.FileFinder) | |
| if moduleinfo.ispkg: | |
| expected_packages.remove(moduleinfo.name) | |
| else: | |
| expected_modules.remove(moduleinfo.name) | |
| self.assertEqual(len(expected_packages), 0, expected_packages) | |
| self.assertEqual(len(expected_modules), 0, expected_modules) | |
| class RunPathTestCase(unittest.TestCase, CodeExecutionMixin): | |
| """Unit tests for runpy.run_path""" | |
| def _make_test_script(self, script_dir, script_basename, | |
| source=None, omit_suffix=False): | |
| if source is None: | |
| source = example_source | |
| return make_script(script_dir, script_basename, | |
| source, omit_suffix) | |
| def _check_script(self, script_name, expected_name, expected_file, | |
| expected_argv0, mod_name=None, | |
| expect_spec=True, check_loader=True): | |
| # First check is without run_name | |
| def create_ns(init_globals): | |
| return run_path(script_name, init_globals) | |
| expected_ns = example_namespace.copy() | |
| if mod_name is None: | |
| spec_name = expected_name | |
| else: | |
| spec_name = mod_name | |
| if expect_spec: | |
| mod_spec = importlib.util.spec_from_file_location(spec_name, | |
| expected_file) | |
| mod_cached = mod_spec.cached | |
| if not check_loader: | |
| mod_spec.loader = None | |
| else: | |
| mod_spec = mod_cached = None | |
| expected_ns.update({ | |
| "__name__": expected_name, | |
| "__file__": expected_file, | |
| "__package__": "", | |
| "__spec__": mod_spec, | |
| "run_argv0": expected_argv0, | |
| "run_name_in_sys_modules": True, | |
| "module_in_sys_modules": True, | |
| }) | |
| self.check_code_execution(create_ns, expected_ns) | |
| # Second check makes sure run_name works in all cases | |
| run_name = "prove.issue15230.is.fixed" | |
| def create_ns(init_globals): | |
| return run_path(script_name, init_globals, run_name) | |
| if expect_spec and mod_name is None: | |
| mod_spec = importlib.util.spec_from_file_location(run_name, | |
| expected_file) | |
| if not check_loader: | |
| mod_spec.loader = None | |
| expected_ns["__spec__"] = mod_spec | |
| expected_ns["__name__"] = run_name | |
| expected_ns["__package__"] = run_name.rpartition(".")[0] | |
| self.check_code_execution(create_ns, expected_ns) | |
| def _check_import_error(self, script_name, msg): | |
| msg = re.escape(msg) | |
| self.assertRaisesRegex(ImportError, msg, run_path, script_name) | |
| def test_basic_script(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'script' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| self._check_script(script_name, "<run_path>", script_name, | |
| script_name, expect_spec=False) | |
| def test_basic_script_with_pathlike_object(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'script' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| self._check_script(FakePath(script_name), "<run_path>", | |
| script_name, | |
| script_name, | |
| expect_spec=False) | |
| def test_basic_script_no_suffix(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'script' | |
| script_name = self._make_test_script(script_dir, mod_name, | |
| omit_suffix=True) | |
| self._check_script(script_name, "<run_path>", script_name, | |
| script_name, expect_spec=False) | |
| @unittest.skipIf(sys.implementation.cache_tag is None, | |
| 'requires sys.implementation.cache_tag') | |
| def test_script_compiled(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'script' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| compiled_name = py_compile.compile(script_name, doraise=True) | |
| os.remove(script_name) | |
| self._check_script(compiled_name, "<run_path>", compiled_name, | |
| compiled_name, expect_spec=False) | |
| def test_directory(self): | |
| with temp_dir() as script_dir: | |
| mod_name = '__main__' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| self._check_script(script_dir, "<run_path>", script_name, | |
| script_dir, mod_name=mod_name) | |
| def test_directory_compiled(self): | |
| with temp_dir() as script_dir: | |
| mod_name = '__main__' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| legacy_pyc = make_legacy_pyc(script_name, allow_compile=True) | |
| os.remove(script_name) | |
| self._check_script(script_dir, "<run_path>", legacy_pyc, | |
| script_dir, mod_name=mod_name) | |
| def test_directory_error(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'not_main' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| msg = "can't find '__main__' module in %r" % script_dir | |
| self._check_import_error(script_dir, msg) | |
| def test_directory_error_sets_name_attribute(self): | |
| with temp_dir() as script_dir: | |
| self._make_test_script(script_dir, 'not_main') | |
| try: | |
| run_path(script_dir) | |
| except ImportError as exc: | |
| self.assertEqual(exc.name, '__main__') | |
| else: | |
| self.fail("Expected ImportError for directory without " | |
| "__main__.py") | |
| def test_zipfile(self): | |
| with temp_dir() as script_dir: | |
| mod_name = '__main__' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) | |
| self._check_script(zip_name, "<run_path>", fname, zip_name, | |
| mod_name=mod_name, check_loader=False) | |
| def test_zipfile_compiled(self): | |
| with temp_dir() as script_dir: | |
| mod_name = '__main__' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| compiled_name = script_name + 'c' | |
| py_compile.compile(script_name, compiled_name, doraise=True) | |
| zip_name, fname = make_zip_script(script_dir, 'test_zip', | |
| compiled_name) | |
| self._check_script(zip_name, "<run_path>", fname, zip_name, | |
| mod_name=mod_name, check_loader=False) | |
| def test_zipfile_error(self): | |
| with temp_dir() as script_dir: | |
| mod_name = 'not_main' | |
| script_name = self._make_test_script(script_dir, mod_name) | |
| zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) | |
| msg = "can't find '__main__' module in %r" % zip_name | |
| self._check_import_error(zip_name, msg) | |
| @no_tracing | |
| @requires_resource('cpu') | |
| def test_main_recursion_error(self): | |
| with temp_dir() as script_dir, temp_dir() as dummy_dir: | |
| mod_name = '__main__' | |
| source = ("import runpy\n" | |
| "runpy.run_path(%r)\n") % dummy_dir | |
| script_name = self._make_test_script(script_dir, mod_name, source) | |
| zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) | |
| with infinite_recursion(25): | |
| self.assertRaises(RecursionError, run_path, zip_name) | |
| def test_encoding(self): | |
| with temp_dir() as script_dir: | |
| filename = os.path.join(script_dir, 'script.py') | |
| with open(filename, 'w', encoding='latin1') as f: | |
| f.write(""" | |
| #coding:latin1 | |
| s = "non-ASCII: h\xe9" | |
| """) | |
| result = run_path(filename) | |
| self.assertEqual(result['s'], "non-ASCII: h\xe9") | |
| def test_run_module_filter_syntax_warnings_by_module(self): | |
| module_re = r'test\.test_import\.data\.syntax_warnings\z' | |
| with (temp_dir() as tmpdir, | |
| temporary_pycache_prefix(tmpdir), | |
| warnings.catch_warnings(record=True) as wlog): | |
| warnings.simplefilter('error') | |
| warnings.filterwarnings('always', module=module_re) | |
| warnings.filterwarnings('error', module='syntax_warnings') | |
| ns = run_module('test.test_import.data.syntax_warnings') | |
| self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21]) | |
| filename = ns['__file__'] | |
| for wm in wlog: | |
| self.assertEqual(wm.filename, filename) | |
| self.assertIs(wm.category, SyntaxWarning) | |
| def test_run_path_filter_syntax_warnings_by_module(self): | |
| filename = support.findfile('test_import/data/syntax_warnings.py') | |
| with warnings.catch_warnings(record=True) as wlog: | |
| warnings.simplefilter('error') | |
| warnings.filterwarnings('always', module=r'<run_path>\z') | |
| warnings.filterwarnings('error', module='test') | |
| warnings.filterwarnings('error', module='syntax_warnings') | |
| warnings.filterwarnings('error', | |
| module=r'test\.test_import\.data\.syntax_warnings') | |
| run_path(filename) | |
| self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21]) | |
| for wm in wlog: | |
| self.assertEqual(wm.filename, filename) | |
| self.assertIs(wm.category, SyntaxWarning) | |
| with warnings.catch_warnings(record=True) as wlog: | |
| warnings.simplefilter('error') | |
| warnings.filterwarnings('always', module=r'package\.script\z') | |
| warnings.filterwarnings('error', module='<run_path>') | |
| warnings.filterwarnings('error', module='test') | |
| warnings.filterwarnings('error', module='syntax_warnings') | |
| warnings.filterwarnings('error', | |
| module=r'test\.test_import\.data\.syntax_warnings') | |
| run_path(filename, run_name='package.script') | |
| self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21]) | |
| @force_not_colorized_test_class | |
| 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) | |
| @requires_subprocess() | |
| def assertSigInt(self, cmd, *args, **kwargs): | |
| # Use -E to ignore PYTHONSAFEPATH | |
| cmd = [sys.executable, '-E', *cmd] | |
| proc = subprocess.run(cmd, *args, **kwargs, text=True, stderr=subprocess.PIPE) | |
| self.assertEndsWith(proc.stderr, "\nKeyboardInterrupt\n") | |
| self.assertEqual(proc.returncode, self.EXPECTED_CODE) | |
| def test_pymain_run_file(self): | |
| self.assertSigInt([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([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([run_module_as_main], cwd=tmp) | |
| def test_pymain_run_command_run_module(self): | |
| self.assertSigInt( | |
| ["-c", "import runpy; runpy.run_module('ham')"], | |
| cwd=self.ham.parent, | |
| ) | |
| def test_pymain_run_command(self): | |
| self.assertSigInt(["-c", "import ham"], cwd=self.ham.parent) | |
| def test_pymain_run_stdin(self): | |
| self.assertSigInt([], input="import ham", cwd=self.ham.parent) | |
| def test_pymain_run_module(self): | |
| ham = self.ham | |
| self.assertSigInt(["-m", ham.stem], cwd=ham.parent) | |
| if __name__ == "__main__": | |
| unittest.main() |