diff --git a/Lib/test/script_helper.py b/Lib/test/script_helper.py --- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -13,7 +13,7 @@ import zipfile from imp import source_from_cache -from test.support import make_legacy_pyc, strip_python_stderr +from test.support import make_legacy_pyc, strip_python_stderr, Module # Executing the interpreter in a subprocess def _assert_python(expected_success, *args, **env_vars): @@ -88,14 +88,10 @@ shutil.rmtree(dirname) def make_script(script_dir, script_basename, source): - script_filename = script_basename+os.extsep+'py' - script_name = os.path.join(script_dir, script_filename) - # The script should be encoded to UTF-8, the default string encoding - script_file = open(script_name, 'w', encoding='utf-8') - script_file.write(source) - script_file.close() + mod = Module(script_basename, src=source) + script_path = mod.write(parent_dir=script_dir) importlib.invalidate_caches() - return script_name + return script_path def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None): zip_filename = zip_basename+os.extsep+'zip' @@ -118,10 +114,6 @@ # zip_file.close() return zip_name, os.path.join(zip_name, name_in_zip) -def make_pkg(pkg_dir, init_source=''): - os.mkdir(pkg_dir) - make_script(pkg_dir, '__init__', init_source) - def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename, source, depth=1, compiled=False): unlink = [] diff --git a/Lib/test/support.py b/Lib/test/support.py --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -25,6 +25,8 @@ import logging.handlers import struct import tempfile +from test.supportlib.pkgcreator import ( + create_module, create_package, Module, Package) try: import _thread, threading diff --git a/Lib/test/supportlib/__init__.py b/Lib/test/supportlib/__init__.py new file mode 100644 --- /dev/null +++ b/Lib/test/supportlib/__init__.py @@ -0,0 +1,7 @@ +""" +A subpackage of helper functionality to support writing regression tests. + +XXX TODO: move test/script_helper.py into this subpackage. +XXX TODO: move test/support.py functionality into this subpackage. + +""" diff --git a/Lib/test/supportlib/pkgcreator.py b/Lib/test/supportlib/pkgcreator.py new file mode 100644 --- /dev/null +++ b/Lib/test/supportlib/pkgcreator.py @@ -0,0 +1,118 @@ +""" +Support for creating modules and packages for testing purposes. + +""" + +import os + + +class _Namespace(object): + + """A container for storing attributes.""" + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + +class Module(_Namespace): + + """Encapsulates the information needed to create a module.""" + + def __init__(self, name, src=''): + super(Module, self).__init__(name=name, src=src) + + def write(self, parent_dir=None): + """Create the module, and return its path.""" + return create_module(self, parent_dir=parent_dir) + + +class Package(_Namespace): + + """Encapsulates the information needed to create a package. + + Sample usage: + + pkg = Package('pkg', subs=[Package('subpkg'), 'mod1', + Module('mod2', src='# Code...')]) + + """ + + def __init__(self, name, init_src='', subs=None): + """ + Arguments (selected): + + subs: an iterable of Package instances, Module instances, and + string module names. Defaults to an empty list. + + """ + if subs is None: + subs = [] + super(Package, self).__init__(name=name, init_src=init_src, subs=subs) + + def write(self, parent_dir=None): + """Create the package, and return the path to its directory.""" + return create_package(self, parent_dir=parent_dir) + + +def create_file(path, text='', encoding=None): + if encoding is None: + encoding = 'utf-8' + with open(path, 'w', encoding=encoding) as f: + f.write(text) + + +def create_module(mod, parent_dir=None): + """Create a module inside the given parent directory. + + Returns the path to the module created. + + Arguments: + + mod: a string module name or Module instance. + + parent_dir: defaults to the current working directory. + + """ + if parent_dir is None: + parent_dir = os.getcwd() + + if isinstance(mod, str): + mod = Module(mod) + + mod_filename = mod.name + os.extsep + "py" + mod_path = os.path.join(parent_dir, mod_filename) + create_file(mod_path, text=mod.src) + + return mod_path + + +def create_package(pkg, parent_dir=None): + """Create a package inside the given parent directory. + + Returns the path to the package directory created. + + Arguments: + + pkg: a string package name or Package instance. + + parent_dir: defaults to the current working directory. + + """ + if parent_dir is None: + parent_dir = os.getcwd() + + if isinstance(pkg, str): + pkg = Package(pkg) + + pkg_dir = os.path.join(parent_dir, pkg.name) + os.mkdir(pkg_dir) + init_path = os.path.join(pkg_dir, '__init__.py') + create_file(init_path, text=pkg.init_src) + + for sub in pkg.subs: + if isinstance(sub, str): + sub = Module(sub) + sub.write(parent_dir=pkg_dir) + + return pkg_dir diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -12,7 +12,7 @@ import textwrap from test import support from test.script_helper import ( - make_pkg, make_script, make_zip_pkg, make_zip_script, + make_script, make_zip_pkg, make_zip_script, assert_python_ok, assert_python_failure, temp_dir, spawn_python, kill_python) @@ -56,6 +56,13 @@ print('cwd==%a' % os.getcwd()) """ +def _make_pkg(name, parent_dir, init_source=''): + pkg = support.Package(name, init_src=init_source) + pkg_dir = pkg.write(parent_dir=parent_dir) + # XXX TODO: can we remove this? + importlib.invalidate_caches() + return pkg_dir + def _make_test_script(script_dir, script_basename, source=test_source): to_return = make_script(script_dir, script_basename, source) importlib.invalidate_caches() @@ -219,8 +226,7 @@ def test_module_in_package(self): with temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) script_name = _make_test_script(pkg_dir, 'script') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script') self._check_script(launch_name, script_name, script_name, @@ -244,8 +250,7 @@ def test_package(self): with temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) script_name = _make_test_script(pkg_dir, '__main__') launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_script(launch_name, script_name, @@ -254,8 +259,7 @@ def test_package_compiled(self): with temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) script_name = _make_test_script(pkg_dir, '__main__') compiled_name = py_compile.compile(script_name, doraise=True) os.remove(script_name) @@ -267,8 +271,7 @@ def test_package_error(self): with temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) msg = ("'test_pkg' is a package and cannot " "be directly executed") launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') @@ -276,10 +279,8 @@ def test_package_recursion(self): with temp_dir() as script_dir: - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) - main_dir = os.path.join(pkg_dir, '__main__') - make_pkg(main_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) + main_dir = _make_pkg('__main__', parent_dir=pkg_dir) msg = ("Cannot use package as __main__ module; " "'test_pkg' is a package and cannot " "be directly executed") @@ -291,8 +292,8 @@ # searching for the module to execute with temp_dir() as script_dir: with support.temp_cwd(path=script_dir): - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir, "import sys; print('init_argv0==%r' % sys.argv[0])") + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir, + init_source="import sys; print('init_argv0==%r' % sys.argv[0])") script_name = _make_test_script(pkg_dir, 'script') rc, out, err = assert_python_ok('-m', 'test_pkg.script', *example_args) if verbose > 1: @@ -336,8 +337,7 @@ # shell is '1' with temp_dir() as script_dir: with support.temp_cwd(path=script_dir): - pkg_dir = os.path.join(script_dir, 'test_pkg') - make_pkg(pkg_dir) + pkg_dir = _make_pkg('test_pkg', parent_dir=script_dir) script_name = _make_test_script(pkg_dir, 'other', "if __name__ == '__main__': raise ValueError") rc, out, err = assert_python_failure('-m', 'test_pkg.other', *example_args) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -9,9 +9,9 @@ import py_compile from test.support import ( forget, make_legacy_pyc, run_unittest, unload, verbose, no_tracing, - create_empty_file) + create_module, create_package, Module, Package) from test.script_helper import ( - make_pkg, make_script, make_zip_pkg, make_zip_script, temp_dir) + make_script, make_zip_pkg, make_zip_script, temp_dir) import runpy @@ -176,31 +176,25 @@ def test_library_module(self): self.assertEqual(run_module("runpy")["__name__"], "runpy") - def _add_pkg_dir(self, pkg_dir): - os.mkdir(pkg_dir) - 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"): 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) + parent_dir = os.path.realpath(tempfile.mkdtemp()) + if verbose > 1: print(" Package tree in:", parent_dir) + sys.path.insert(0, parent_dir) if verbose > 1: print(" Updated sys.path:", sys.path[0]) + + sub_dir = parent_dir for i in range(depth): - sub_dir = os.path.join(sub_dir, pkg_name) - pkg_fname = self._add_pkg_dir(sub_dir) - 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) - mod_file = open(mod_fname, "w") - mod_file.write(source) - mod_file.close() + sub_dir = create_package(pkg_name, parent_dir=sub_dir) + if verbose > 1: print(" Created next level in:", sub_dir) + + mod = Module(mod_base, src=source) + mod_fname = mod.write(parent_dir=sub_dir) if verbose > 1: print(" Created:", mod_fname) - mod_name = (pkg_name+".")*depth + mod_base - return pkg_dir, mod_fname, mod_name + + mod_name = ".".join(depth * [pkg_name] + [mod_base]) + + return parent_dir, mod_fname, mod_name def _del_pkg(self, top, depth, mod_name): for entry in list(sys.modules): @@ -303,7 +297,7 @@ self._del_pkg(pkg_dir, depth, pkg_name) if verbose > 1: print("Package executed successfully") - def _add_relative_modules(self, base_dir, source, depth): + def _add_relative_modules(self, base_dir, depth): if depth <= 1: raise ValueError("Relative module test needs depth > 1") pkg_name = "__runpy_pkg__" @@ -311,20 +305,12 @@ 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) + sibling_fname = create_module("sibling", parent_dir=module_dir) 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) + cousin_pkg = Package("cousin", subs=['nephew']) + uncle_pkg = Package("uncle", subs=[cousin_pkg]) + uncle_dir = uncle_pkg.write(parent_dir=parent_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"""\ @@ -339,7 +325,7 @@ else: expected_name = run_name try: - self._add_relative_modules(pkg_dir, contents, depth) + self._add_relative_modules(pkg_dir, 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 @@ -438,7 +424,7 @@ self._make_pkg("", max_depth)) self.addCleanup(self._del_pkg, pkg_dir, max_depth, mod_name) for depth in range(2, max_depth+1): - self._add_relative_modules(pkg_dir, "", depth) + self._add_relative_modules(pkg_dir, depth) for finder, mod_name, ispkg in pkgutil.walk_packages([pkg_dir]): self.assertIsInstance(finder, importlib.machinery.FileFinder) diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -43,7 +43,7 @@ - Issue #15229: An OSError subclass whose __init__ doesn't call back OSError.__init__ could produce incomplete instances, leading to crashes when calling str() on them. - + Library ------- @@ -176,6 +176,9 @@ Tests ----- +- Issue #15403: Refactor test_runpy and test_cmd_line_script to use common + package-creation support code. Patch by Chris Jerdonek. + - Issue #15230: Adopted a more systematic approach in the runpy tests - Issue #15300: Ensure the temporary test working directories are in the same