Index: Lib/test/regrtest.py =================================================================== --- Lib/test/regrtest.py (revision 78100) +++ Lib/test/regrtest.py (working copy) @@ -158,9 +158,29 @@ import traceback import warnings import unittest +import tempfile import imp +# 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 the 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 +# these packages to prevent later imports to fail when the CWD is different. +# Note that all the other modules already imported still have a relative +# __file__, but this is not a problem. +for module in sys.modules.itervalues(): + 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/) for mod in ("ctypes", "gzip", "zipfile", "tarfile", "encodings.zlib_codec", @@ -200,6 +220,14 @@ 'xpickle') +# 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 = "{}_python_{}".format(test_support.TESTFN, os.getpid()) + +TESTCWD = os.path.abspath(os.path.join(tempfile.gettempdir(), TESTCWD)) + + def usage(code, msg=''): print __doc__ if msg: print msg @@ -350,6 +378,9 @@ resource_denieds = [] environment_changed = [] + if verbose: + print 'The CWD is now', os.getcwd() + if findleaks: try: import gc @@ -364,8 +395,7 @@ 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() @@ -376,7 +406,7 @@ if fromfile: tests = [] - fp = open(fromfile) + fp = open(os.path.join(test_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('#'): @@ -518,8 +548,8 @@ print test sys.stdout.flush() if trace: - # If we're tracing code coverage, then we don't exit with status - # if on a false return value from main. + # If we're tracing code coverage, then we don't exit with + # status if on a false return value from main. tracer.runctx('runtest(test, verbose, quiet, testdir)', globals=globals(), locals=vars()) else: @@ -541,8 +571,8 @@ if gc.garbage: print "Warning: test created", len(gc.garbage), print "uncollectable object(s)." - # move the uncollectable objects somewhere so we don't see - # them again + # move the uncollectable objects somewhere so we don't + # see them again found_garbage.extend(gc.garbage) del gc.garbage[:] # Unload the newly imported modules (best effort finalization) @@ -959,6 +989,7 @@ deltas = [] nwarmup, ntracked, fname = huntrleaks + fname = os.path.join(test_support.SAVEDCWD, fname) repcount = nwarmup + ntracked print >> sys.stderr, "beginning", repcount, "repetitions" print >> sys.stderr, ("1234567890"*(repcount//10 + 1))[:repcount] @@ -1501,4 +1532,12 @@ i -= 1 if os.path.abspath(os.path.normpath(sys.path[i])) == mydir: del sys.path[i] - main() + if sys.argv[0]: + sys.argv[0] = os.path.abspath(sys.argv[0]) + # Create a context manager that temporary sets TESTCWD as current working + # directory. This context manager is used to run the tests, and provides + # a writable CWD. If it's not possible to create or change the CWD the + # original CWD will be used. The original CWD is also saved in + # test_support.SAVEDCWD. + with test_support.temp_cwd(TESTCWD, quiet=True): + main() Index: Lib/test/test_subprocess.py =================================================================== --- Lib/test/test_subprocess.py (revision 78100) +++ Lib/test/test_subprocess.py (working copy) @@ -32,12 +32,17 @@ # doesn't crash on some buildbots (Alphas in particular). if hasattr(test_support, "reap_children"): test_support.reap_children() + # This test should be run from the original working directory + self.save_cwd = os.getcwd() + os.chdir(test_support.SAVEDCWD) def tearDown(self): # Try to minimize the number of children we have so this test # doesn't crash on some buildbots (Alphas in particular). if hasattr(test_support, "reap_children"): test_support.reap_children() + # Restore the working directory + os.chdir(self.save_cwd) def mkstemp(self): """wrapper for mkstemp, calling mktemp if mkstemp is not available""" Index: Lib/test/test_support.py =================================================================== --- Lib/test/test_support.py (revision 78100) +++ Lib/test/test_support.py (working copy) @@ -22,7 +22,7 @@ "get_original_stdout", "unload", "unlink", "rmtree", "forget", "is_resource_enabled", "requires", "find_unused_port", "bind_port", "fcmp", "have_unicode", "is_jython", "TESTFN", "HOST", "FUZZ", - "findfile", "sortdict", "check_syntax_error", + "SAVEDCWD", "temp_cwd", "findfile", "sortdict", "check_syntax_error", "open_urlresource", "check_warnings", "CleanImport", "EnvironmentVarGuard", "captured_output", "captured_stdout", "TransientResource", "transient_internet", @@ -379,28 +379,38 @@ 'Unicode filename tests may not be effective' \ % TESTFN_UNICODE_UNENCODEABLE -# Disambiguate TESTFN for parallel testing, while letting it remain a valid -# module name. -TESTFN = "{0}_{1}_tmp".format(TESTFN, os.getpid()) +# Save the initial cwd +SAVEDCWD = os.getcwd() -# 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) +@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 found the argument passed to the function is returned (this does not