Index: Lib/tempfile.py =================================================================== --- Lib/tempfile.py (revision 87169) +++ Lib/tempfile.py (working copy) @@ -29,6 +29,8 @@ # Imports. +import warnings as _warnings +import sys as _sys import io as _io import os as _os import errno as _errno @@ -617,23 +619,41 @@ """ def __init__(self, suffix="", prefix=template, dir=None): + self._closed = False + self.name = None # Handle mkdtemp throwing an exception self.name = mkdtemp(suffix, prefix, dir) - self._closed = False + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.name) + def __enter__(self): return self.name - def cleanup(self): - if not self._closed: - self._rmtree(self.name) + def cleanup(self, _warn=False): + if self.name and not self._closed: + try: + self._rmtree(self.name) + except (TypeError, AttributeError) as ex: + # Issue 10888: Emit a warning on stderr + # if the directory could not be cleaned + # up due to missing globals + if "None" not in str(ex): + raise + print("ERROR: {!r} while cleaning up {!r}".format(ex, self,), + file=_sys.stderr) + return self._closed = True + if _warn: + self._warn("Implicitly cleaning up {!r}".format(self), + ResourceWarning) def __exit__(self, exc, value, tb): self.cleanup() - __del__ = cleanup + def __del__(self): + # Issue a ResourceWarning if implicit cleanup needed + self.cleanup(_warn=True) - # XXX (ncoghlan): The following code attempts to make # this class tolerant of the module nulling out process # that happens during CPython interpreter shutdown @@ -644,6 +664,7 @@ _remove = staticmethod(_os.remove) _rmdir = staticmethod(_os.rmdir) _os_error = _os.error + _warn = _warnings.warn def _rmtree(self, path): # Essentially a stripped down version of shutil.rmtree. We can't Index: Lib/test/test_tempfile.py =================================================================== --- Lib/test/test_tempfile.py (revision 87169) +++ Lib/test/test_tempfile.py (working copy) @@ -925,6 +925,13 @@ f.write(b"Hello world!") return tmp + def test_mkdtemp_failure(self): + # Check no additional exception if mkdtemp fails + # Previously would raise AttributeError instead + # (noted as part of Issue #10888) + #with self.assertRaises(os.error): + tempfile.TemporaryDirectory(prefix="[]<>?*!:") + def test_explicit_cleanup(self): # A TemporaryDirectory is deleted when cleaned up dir = tempfile.mkdtemp() @@ -955,8 +962,7 @@ def test_del_on_shutdown(self): # A TemporaryDirectory may be cleaned up during shutdown # Make sure it works with the relevant modules nulled out - dir = tempfile.mkdtemp() - try: + with self.do_create() as dir: d = self.do_create(dir=dir) # Mimic the nulling out of modules that # occurs during system shutdown @@ -967,9 +973,32 @@ d.cleanup() self.assertFalse(os.path.exists(d.name), "TemporaryDirectory %s exists after cleanup" % d.name) - finally: - os.rmdir(dir) + def test_warnings_on_cleanup(self): + # Two kinds of warning on shutdown + # Issue 10888: may write to stderr if modules are nulled out + # ResourceWarning will be triggered by __del__ + with self.do_create() as dir: + d = self.do_create(dir=dir) + + #Check for the Issue 10888 message + modules = [os, os.path] + if has_stat: + modules.append(stat) + with support.captured_stderr() as err: + with NulledModules(*modules): + d.cleanup() + message = err.getvalue() + self.assertIn("while cleaning up", message) + self.assertIn(d.name, message) + + # Check for the resource warning + with support.check_warnings(('Implicitly', ResourceWarning), quiet=False): + warnings.filterwarnings("always", category=ResourceWarning) + d.__del__() + self.assertFalse(os.path.exists(d.name), + "TemporaryDirectory %s exists after __del__" % d.name) + def test_multiple_close(self): # Can be cleaned-up many times without error d = self.do_create() Index: Lib/test/support.py =================================================================== --- Lib/test/support.py (revision 87169) +++ Lib/test/support.py (working copy) @@ -874,6 +874,9 @@ def captured_stdout(): return captured_output("stdout") +def captured_stderr(): + return captured_output("stderr") + def captured_stdin(): return captured_output("stdin")