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