diff options
-rwxr-xr-x | Lib/test/regrtest.py | 48 | ||||
-rw-r--r-- | Lib/test/support.py | 52 | ||||
-rw-r--r-- | Lib/test/test_subprocess.py | 20 | ||||
-rw-r--r-- | Misc/NEWS | 5 |
4 files changed, 98 insertions, 27 deletions
diff --git a/Lib/test/regrtest.py b/Lib/test/regrtest.py index 4b9ef1a..551158c 100755 --- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -158,6 +158,24 @@ import traceback import warnings import unittest from inspect import isabstract +import tempfile + +# Some times __path__ and __file__ are not absolute (e.g. while running from +# Lib/) and, if we change the CWD to run the tests in a temporary dir, some +# imports might fail. This affects only the modules imported before os.chdir(). +# These modules are searched first in sys.path[0] (so '' -- the CWD) and if +# they are found in the CWD their __file__ and __path__ will be relative (this +# happens before the chdir). All the modules imported after the chdir, are +# not found in the CWD, and since the other paths in sys.path[1:] are absolute +# (site.py absolutize them), the __file__ and __path__ will be absolute too. +# Therefore it is necessary to absolutize manually the __file__ and __path__ of +# the packages to prevent later imports to fail when the CWD is different. +for module in sys.modules.values(): + if hasattr(module, '__path__'): + module.__path__ = [os.path.abspath(path) for path in module.__path__] + if hasattr(module, '__file__'): + module.__file__ = os.path.abspath(module.__file__) + # Ignore ImportWarnings that only occur in the source tree, # (because of modules with the same name as source-directories in Modules/) @@ -375,6 +393,9 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, resource_denieds = [] environment_changed = [] + if verbose: + print('The CWD is now', os.getcwd()) + if findleaks: try: import gc @@ -389,8 +410,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, found_garbage = [] if single: - from tempfile import gettempdir - filename = os.path.join(gettempdir(), 'pynexttest') + filename = 'pynexttest' try: fp = open(filename, 'r') next_test = fp.read().strip() @@ -401,7 +421,7 @@ def main(tests=None, testdir=None, verbose=0, quiet=False, if fromfile: tests = [] - fp = open(fromfile) + fp = open(os.path.join(support.SAVEDCWD, fromfile)) for line in fp: guts = line.split() # assuming no test has whitespace in its name if guts and not guts[0].startswith('#'): @@ -966,6 +986,7 @@ def dash_R(the_module, test, indirect_test, huntrleaks): deltas = [] nwarmup, ntracked, fname = huntrleaks + fname = os.path.join(support.SAVEDCWD, fname) repcount = nwarmup + ntracked print("beginning", repcount, "repetitions", file=sys.stderr) print(("1234567890"*(repcount//10 + 1))[:repcount], file=sys.stderr) @@ -1412,4 +1433,23 @@ if __name__ == '__main__': i -= 1 if os.path.abspath(os.path.normpath(sys.path[i])) == mydir: del sys.path[i] - main() + + # findtestdir() gets the dirname out of sys.argv[0], so we have to make it + # absolute before changing the CWD. + if sys.argv[0]: + sys.argv[0] = os.path.abspath(sys.argv[0]) + + + # Define a writable temp dir that will be used as cwd while running + # the tests. The name of the dir includes the pid to allow parallel + # testing (see the -j option). + TESTCWD = 'test_python_{}'.format(os.getpid()) + + TESTCWD = os.path.abspath(os.path.join(tempfile.gettempdir(), TESTCWD)) + + # Run the tests in a context manager that temporary changes the CWD to a + # temporary and writable directory. If it's not possible to create or + # change the CWD, the original CWD will be used. The original CWD is + # available from support.SAVEDCWD. + with support.temp_cwd(TESTCWD, quiet=True): + main() diff --git a/Lib/test/support.py b/Lib/test/support.py index 1308914..bf49bab 100644 --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -21,8 +21,8 @@ __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module", "verbose", "use_resources", "max_memuse", "record_original_stdout", "get_original_stdout", "unload", "unlink", "rmtree", "forget", "is_resource_enabled", "requires", "find_unused_port", "bind_port", - "fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", - "sortdict", "check_syntax_error", "open_urlresource", + "fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "SAVEDCWD", "temp_cwd", + "findfile", "sortdict", "check_syntax_error", "open_urlresource", "check_warnings", "CleanImport", "EnvironmentVarGuard", "TransientResource", "captured_output", "captured_stdout", "time_out", "socket_peer_reset", "ioerror_peer_reset", @@ -338,7 +338,7 @@ else: # Disambiguate TESTFN for parallel testing, while letting it remain a valid # module name. -TESTFN = "{0}_{1}_tmp".format(TESTFN, os.getpid()) +TESTFN = "{}_{}_tmp".format(TESTFN, os.getpid()) # Assuming sys.getfilesystemencoding()!=sys.getdefaultencoding() # TESTFN_UNICODE is a filename that can be encoded using the @@ -369,23 +369,37 @@ else: 'Unicode filename tests may not be effective' % TESTFN_UNICODE_UNENCODEABLE) -# Make sure we can write to TESTFN, try in /tmp if we can't -fp = None -try: - fp = open(TESTFN, 'w+') -except IOError: - TMP_TESTFN = os.path.join('/tmp', TESTFN) +# Save the initial cwd +SAVEDCWD = os.getcwd() + +@contextlib.contextmanager +def temp_cwd(name='tempcwd', quiet=False): + """ + Context manager that creates a temporary directory and set it as CWD. + + The new CWD is created in the current directory and it's named *name*. + If *quiet* is False (default) and it's not possible to create or change + the CWD, an error is raised. If it's True, only a warning is raised + and the original CWD is used. + """ + saved_dir = os.getcwd() + is_temporary = False try: - fp = open(TMP_TESTFN, 'w+') - TESTFN = TMP_TESTFN - del TMP_TESTFN - except IOError: - print(('WARNING: tests will fail, unable to write to: %s or %s' % - (TESTFN, TMP_TESTFN))) -if fp is not None: - fp.close() - unlink(TESTFN) -del fp + os.mkdir(name) + os.chdir(name) + is_temporary = True + except OSError: + if not quiet: + raise + warnings.warn('tests may fail, unable to change the CWD to ' + name, + RuntimeWarning, stacklevel=3) + try: + yield os.getcwd() + finally: + os.chdir(saved_dir) + if is_temporary: + rmtree(name) + def findfile(file, here=__file__): """Try to find a file on sys.path and the working directory. If it is not diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index dbde4fd..0b1fe25 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -7,6 +7,7 @@ import os import tempfile import time import re +import sysconfig mswindows = (sys.platform == "win32") @@ -141,10 +142,21 @@ class ProcessTestCase(unittest.TestCase): p.wait() self.assertEqual(p.stderr, None) - def test_executable(self): - arg0 = os.path.join(os.path.dirname(sys.executable), - "somethingyoudonthave") - p = subprocess.Popen([arg0, "-c", "import sys; sys.exit(47)"], + def test_executable_with_cwd(self): + python_dir = os.path.dirname(os.path.realpath(sys.executable)) + p = subprocess.Popen(["somethingyoudonthave", "-c", + "import sys; sys.exit(47)"], + executable=sys.executable, cwd=python_dir) + p.wait() + self.assertEqual(p.returncode, 47) + + @unittest.skipIf(sysconfig.is_python_build(), + "need an installed Python. See #7774") + def test_executable_without_cwd(self): + # For a normal installation, it should work without 'cwd' + # argument. For test runs in the build directory, see #7774. + p = subprocess.Popen(["somethingyoudonthave", "-c", + "import sys; sys.exit(47)"], executable=sys.executable) p.wait() self.assertEqual(p.returncode, 47) @@ -740,6 +740,11 @@ Documentation Tests ----- +- Issue #7712: test.support gained a new `temp_cwd` context manager which is + now also used by regrtest to run all the tests in a temporary directory. + The original CWD is saved in `support.SAVEDCWD`. + Thanks to Florent Xicluna who helped with the patch. + - Issue #7924: Fix an intermittent 'XXX undetected error' failure in test_capi (only seen so far on platforms where the curses module wasn't built), due to an uncleared exception. |