diff -r 3e2d0423b7c3 Doc/library/test.rst --- a/Doc/library/test.rst Mon Jul 08 11:30:11 2013 +1000 +++ b/Doc/library/test.rst Mon Jul 08 16:39:00 2013 +1000 @@ -375,18 +375,40 @@ assert s.getvalue() == "hello\n" -.. function:: temp_cwd(name='tempcwd', quiet=False, path=None) +.. function:: temp_dir(path=None, quiet=False) + + A context manager that creates a temporary directory at *path* and + yields the directory. + + If *path* is None, the temporary directory is created at an unspecified + location. If *quiet* is ``False``, the context manager raises an + exception on error. Otherwise, if *path* is specified and cannot be + created, only a warning is issued. + + +.. function:: change_cwd(path, quiet=False) A context manager that temporarily changes the current working - directory (CWD). + directory to *path* and yields the directory. - An existing path may be provided as *path*, in which case this function - makes no changes to the file system. + If *quiet* is ``False``, the context manager raises an exception + on error. Otherwise, it issues only a warning and keeps the current + working directory the same. - Otherwise, the new CWD is created in the current directory and it's named - *name*. If *quiet* is ``False`` 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. + +.. function:: temp_cwd(name='tempcwd', quiet=False) + + A context manager that temporarily creates a new directory and + changes the current working directory (CWD). + + The context manager creates a temporary directory in the current + directory with name *name* before temporarily changing the current + working directory. If *name* is None, the temporary directory is + created at an unspecified location. + + If *quiet* is ``False`` and it is not possible to create or change + the CWD, an error is raised. Otherwise, only a warning is raised + and the original CWD is used. .. function:: temp_umask(umask) diff -r 3e2d0423b7c3 Lib/test/script_helper.py --- a/Lib/test/script_helper.py Mon Jul 08 11:30:11 2013 +1000 +++ b/Lib/test/script_helper.py Mon Jul 08 16:39:00 2013 +1000 @@ -13,7 +13,7 @@ import zipfile from importlib.util import source_from_cache -from test.support import make_legacy_pyc, strip_python_stderr +from test.support import make_legacy_pyc, strip_python_stderr, temp_dir # Executing the interpreter in a subprocess def _assert_python(expected_success, *args, **env_vars): @@ -77,16 +77,6 @@ subprocess._cleanup() return data -# Script creation utilities -@contextlib.contextmanager -def temp_dir(): - dirname = tempfile.mkdtemp() - dirname = os.path.realpath(dirname) - try: - yield dirname - finally: - 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) diff -r 3e2d0423b7c3 Lib/test/support/__init__.py --- a/Lib/test/support/__init__.py Mon Jul 08 11:30:11 2013 +1000 +++ b/Lib/test/support/__init__.py Mon Jul 08 16:39:00 2013 +1000 @@ -740,45 +740,85 @@ SAVEDCWD = os.getcwd() @contextlib.contextmanager -def temp_cwd(name='tempcwd', quiet=False, path=None): +def temp_dir(path=None, quiet=False): + """Return a context manager that creates a temporary directory. + + Arguments: + + path: the directory to create temporarily. Defaults to creating + a temporary directory at an unspecified location. + + quiet: if False (the default), the context manager raises an exception + on error. Otherwise, if the path is specified and cannot be + created, only a warning is issued. + """ - Context manager that temporarily changes the CWD. - - An existing path may be provided as *path*, in which case this - function makes no changes to the file system. - - Otherwise, 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 + dir_created = False if path is None: - path = name + path = tempfile.mkdtemp() + dir_created = True + path = os.path.realpath(path) + else: try: - os.mkdir(name) - is_temporary = True + os.mkdir(path) + dir_created = True except OSError: if not quiet: raise - warnings.warn('tests may fail, unable to create temp CWD ' + name, + warnings.warn('tests may fail, unable to create temp dir: ' + path, RuntimeWarning, stacklevel=3) try: + yield path + finally: + if dir_created: + shutil.rmtree(path) + +@contextlib.contextmanager +def change_cwd(path, quiet=False): + """Return a context manager that changes the current working directory. + + Arguments: + + path: the directory to use as the temporary current working directory. + + quiet: if False (the default), the context manager raises an exception + on error. Otherwise, it issues only a warning and keeps the current + working directory the same. + + """ + saved_dir = os.getcwd() + try: os.chdir(path) except OSError: if not quiet: raise - warnings.warn('tests may fail, unable to change the CWD to ' + path, + warnings.warn('tests may fail, unable to change CWD to: ' + path, RuntimeWarning, stacklevel=3) try: yield os.getcwd() finally: os.chdir(saved_dir) - if is_temporary: - rmtree(name) +@contextlib.contextmanager +def temp_cwd(name='tempcwd', quiet=False): + """ + Context manager that temporarily creates and changes the CWD. + + The function temporarily changes the current working directory + after creating a temporary directory in the current directory with + name *name*. If *name* is None, the temporary directory is + created at an unspecified location. + + If *quiet* is False (default) and it is not possible to + create or change the CWD, an error is raised. If *quiet* is True, + only a warning is raised and the original CWD is used. + + """ + with temp_dir(path=name, quiet=quiet) as temp_path: + with change_cwd(temp_path, quiet=quiet) as cwd_dir: + yield cwd_dir + if hasattr(os, "umask"): @contextlib.contextmanager def temp_umask(umask): diff -r 3e2d0423b7c3 Lib/test/test_cmd_line_script.py --- a/Lib/test/test_cmd_line_script.py Mon Jul 08 11:30:11 2013 +1000 +++ b/Lib/test/test_cmd_line_script.py Mon Jul 08 16:39:00 2013 +1000 @@ -290,7 +290,7 @@ # Make sure package __init__ modules see "-m" in sys.argv0 while # searching for the module to execute with temp_dir() as script_dir: - with support.temp_cwd(path=script_dir): + with support.change_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])") script_name = _make_test_script(pkg_dir, 'script') @@ -307,7 +307,7 @@ # Make sure a "-c" file in the current directory # does not alter the value of sys.path[0] with temp_dir() as script_dir: - with support.temp_cwd(path=script_dir): + with support.change_cwd(path=script_dir): with open("-c", "w") as f: f.write("data") rc, out, err = assert_python_ok('-c', @@ -322,7 +322,7 @@ # does not alter the value of sys.path[0] with temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'other') - with support.temp_cwd(path=script_dir): + with support.change_cwd(path=script_dir): with open("-m", "w") as f: f.write("data") rc, out, err = assert_python_ok('-m', 'other', *example_args) @@ -335,7 +335,7 @@ # and results in an error that the return code to the # shell is '1' with temp_dir() as script_dir: - with support.temp_cwd(path=script_dir): + with support.change_cwd(path=script_dir): pkg_dir = os.path.join(script_dir, 'test_pkg') make_pkg(pkg_dir) script_name = _make_test_script(pkg_dir, 'other', diff -r 3e2d0423b7c3 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Mon Jul 08 11:30:11 2013 +1000 +++ b/Lib/test/test_shutil.py Mon Jul 08 16:39:00 2013 +1000 @@ -1318,18 +1318,18 @@ # that exists, it should be returned. base_dir, tail_dir = os.path.split(self.dir) relpath = os.path.join(tail_dir, self.file) - with support.temp_cwd(path=base_dir): + with support.change_cwd(path=base_dir): rv = shutil.which(relpath, path=self.temp_dir) self.assertEqual(rv, relpath) # But it shouldn't be searched in PATH directories (issue #16957). - with support.temp_cwd(path=self.dir): + with support.change_cwd(path=self.dir): rv = shutil.which(relpath, path=base_dir) self.assertIsNone(rv) def test_cwd(self): # Issue #16957 base_dir = os.path.dirname(self.dir) - with support.temp_cwd(path=self.dir): + with support.change_cwd(path=self.dir): rv = shutil.which(self.file, path=base_dir) if sys.platform == "win32": # Windows: current directory implicitly on PATH @@ -1350,7 +1350,7 @@ def test_relative_path(self): base_dir, tail_dir = os.path.split(self.dir) - with support.temp_cwd(path=base_dir): + with support.change_cwd(path=base_dir): rv = shutil.which(self.file, path=tail_dir) self.assertEqual(rv, os.path.join(tail_dir, self.file)) @@ -1375,7 +1375,7 @@ def test_empty_path(self): base_dir = os.path.dirname(self.dir) - with support.temp_cwd(path=self.dir), \ + with support.change_cwd(path=self.dir), \ support.EnvironmentVarGuard() as env: env['PATH'] = self.dir rv = shutil.which(self.file, path='') diff -r 3e2d0423b7c3 Lib/test/test_support.py --- a/Lib/test/test_support.py Mon Jul 08 11:30:11 2013 +1000 +++ b/Lib/test/test_support.py Mon Jul 08 16:39:00 2013 +1000 @@ -1,6 +1,7 @@ #!/usr/bin/env python import importlib +import shutil import sys import os import unittest @@ -88,6 +89,118 @@ s.listen(1) s.close() + # Tests for temp_dir() + + def test_temp_dir(self): + """Test that temp_dir() creates and destroys its directory.""" + parent_dir = tempfile.mkdtemp() + parent_dir = os.path.realpath(parent_dir) + + try: + path = os.path.join(parent_dir, 'temp') + self.assertFalse(os.path.isdir(path)) + with support.temp_dir(path) as temp_path: + self.assertEqual(temp_path, path) + self.assertTrue(os.path.isdir(path)) + self.assertFalse(os.path.isdir(path)) + finally: + shutil.rmtree(parent_dir) + + def test_temp_dir__path_none(self): + """Test passing no path.""" + with support.temp_dir() as temp_path: + self.assertTrue(os.path.isdir(temp_path)) + self.assertFalse(os.path.isdir(temp_path)) + + def test_temp_dir__existing_dir__quiet_default(self): + """Test passing a directory that already exists.""" + def call_temp_dir(path): + with support.temp_dir(path) as temp_path: + raise Exception("should not get here") + + path = tempfile.mkdtemp() + path = os.path.realpath(path) + try: + self.assertTrue(os.path.isdir(path)) + self.assertRaises(FileExistsError, call_temp_dir, path) + # Make sure temp_dir did not delete the original directory. + self.assertTrue(os.path.isdir(path)) + finally: + shutil.rmtree(path) + + def test_temp_dir__existing_dir__quiet_true(self): + """Test passing a directory that already exists with quiet=True.""" + path = tempfile.mkdtemp() + path = os.path.realpath(path) + + try: + with support.check_warnings() as recorder: + with support.temp_dir(path, quiet=True) as temp_path: + self.assertEqual(path, temp_path) + warnings = [str(w.message) for w in recorder.warnings] + # Make sure temp_dir did not delete the original directory. + self.assertTrue(os.path.isdir(path)) + finally: + shutil.rmtree(path) + + expected = ['tests may fail, unable to create temp dir: ' + path] + self.assertEqual(warnings, expected) + + # Tests for change_cwd() + + def test_change_cwd(self): + original_cwd = os.getcwd() + + with support.temp_dir() as temp_path: + with support.change_cwd(temp_path) as new_cwd: + self.assertEqual(new_cwd, temp_path) + self.assertEqual(os.getcwd(), new_cwd) + + self.assertEqual(os.getcwd(), original_cwd) + + def test_change_cwd__non_existent_dir(self): + """Test passing a non-existent directory.""" + original_cwd = os.getcwd() + + def call_change_cwd(path): + with support.change_cwd(path) as new_cwd: + raise Exception("should not get here") + + with support.temp_dir() as parent_dir: + non_existent_dir = os.path.join(parent_dir, 'does_not_exist') + self.assertRaises(FileNotFoundError, call_change_cwd, + non_existent_dir) + + self.assertEqual(os.getcwd(), original_cwd) + + def test_change_cwd__non_existent_dir__quiet_true(self): + """Test passing a non-existent directory with quiet=True.""" + original_cwd = os.getcwd() + + with support.temp_dir() as parent_dir: + bad_dir = os.path.join(parent_dir, 'does_not_exist') + with support.check_warnings() as recorder: + with support.change_cwd(bad_dir, quiet=True) as new_cwd: + self.assertEqual(new_cwd, original_cwd) + self.assertEqual(os.getcwd(), new_cwd) + warnings = [str(w.message) for w in recorder.warnings] + + expected = ['tests may fail, unable to change CWD to: ' + bad_dir] + self.assertEqual(warnings, expected) + + # Tests for change_cwd() + + def test_change_cwd__chdir_warning(self): + """Check the warning message when os.chdir() fails.""" + path = TESTFN + '_does_not_exist' + with support.check_warnings() as recorder: + with support.change_cwd(path=path, quiet=True): + pass + messages = [str(w.message) for w in recorder.warnings] + self.assertEqual(messages, ['tests may fail, unable to change CWD to: ' + path]) + + # Tests for temp_cwd() + def test_temp_cwd(self): here = os.getcwd() with support.temp_cwd(name=TESTFN): @@ -95,14 +208,15 @@ self.assertFalse(os.path.exists(TESTFN)) self.assertTrue(os.path.basename(os.getcwd()), here) - def test_temp_cwd__chdir_warning(self): - """Check the warning message when os.chdir() fails.""" - path = TESTFN + '_does_not_exist' - with support.check_warnings() as recorder: - with support.temp_cwd(path=path, quiet=True): - pass - messages = [str(w.message) for w in recorder.warnings] - self.assertEqual(messages, ['tests may fail, unable to change the CWD to ' + path]) + + def test_temp_cwd__name_none(self): + """Test passing None to temp_cwd().""" + original_cwd = os.getcwd() + with support.temp_cwd(name=None) as new_cwd: + self.assertNotEqual(new_cwd, original_cwd) + self.assertTrue(os.path.isdir(new_cwd)) + self.assertEqual(os.getcwd(), new_cwd) + self.assertEqual(os.getcwd(), original_cwd) def test_sortdict(self): self.assertEqual(support.sortdict({3:3, 2:2, 1:1}), "{1: 1, 2: 2, 3: 3}") diff -r 3e2d0423b7c3 Misc/NEWS --- a/Misc/NEWS Mon Jul 08 11:30:11 2013 +1000 +++ b/Misc/NEWS Mon Jul 08 16:39:00 2013 +1000 @@ -2567,6 +2567,8 @@ Tests ----- +- Issue #15415: Add new temp_dir() and change_cwd() context managers to + test.support, and refactor temp_cwd() to use them. Patch by Chris Jerdonek. - Issue #15467: Move helpers for `__sizeof__()` tests into test_support. Patch by Serhiy Storchaka.