tmpdir.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # -*- coding: utf-8 -*-
  2. """ support for providing temporary directories to test functions. """
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. import os
  7. import re
  8. import tempfile
  9. import warnings
  10. import attr
  11. import py
  12. import six
  13. import pytest
  14. from .pathlib import ensure_reset_dir
  15. from .pathlib import LOCK_TIMEOUT
  16. from .pathlib import make_numbered_dir
  17. from .pathlib import make_numbered_dir_with_cleanup
  18. from .pathlib import Path
  19. from _pytest.monkeypatch import MonkeyPatch
  20. @attr.s
  21. class TempPathFactory(object):
  22. """Factory for temporary directories under the common base temp directory.
  23. The base directory can be configured using the ``--basetemp`` option."""
  24. _given_basetemp = attr.ib(
  25. # using os.path.abspath() to get absolute path instead of resolve() as it
  26. # does not work the same in all platforms (see #4427)
  27. # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
  28. converter=attr.converters.optional(
  29. lambda p: Path(os.path.abspath(six.text_type(p)))
  30. )
  31. )
  32. _trace = attr.ib()
  33. _basetemp = attr.ib(default=None)
  34. @classmethod
  35. def from_config(cls, config):
  36. """
  37. :param config: a pytest configuration
  38. """
  39. return cls(
  40. given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir")
  41. )
  42. def mktemp(self, basename, numbered=True):
  43. """makes a temporary directory managed by the factory"""
  44. if not numbered:
  45. p = self.getbasetemp().joinpath(basename)
  46. p.mkdir()
  47. else:
  48. p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
  49. self._trace("mktemp", p)
  50. return p
  51. def getbasetemp(self):
  52. """ return base temporary directory. """
  53. if self._basetemp is not None:
  54. return self._basetemp
  55. if self._given_basetemp is not None:
  56. basetemp = self._given_basetemp
  57. ensure_reset_dir(basetemp)
  58. basetemp = basetemp.resolve()
  59. else:
  60. from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
  61. temproot = Path(from_env or tempfile.gettempdir()).resolve()
  62. user = get_user() or "unknown"
  63. # use a sub-directory in the temproot to speed-up
  64. # make_numbered_dir() call
  65. rootdir = temproot.joinpath("pytest-of-{}".format(user))
  66. rootdir.mkdir(exist_ok=True)
  67. basetemp = make_numbered_dir_with_cleanup(
  68. prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
  69. )
  70. assert basetemp is not None, basetemp
  71. self._basetemp = t = basetemp
  72. self._trace("new basetemp", t)
  73. return t
  74. @attr.s
  75. class TempdirFactory(object):
  76. """
  77. backward comptibility wrapper that implements
  78. :class:``py.path.local`` for :class:``TempPathFactory``
  79. """
  80. _tmppath_factory = attr.ib()
  81. def ensuretemp(self, string, dir=1):
  82. """ (deprecated) return temporary directory path with
  83. the given string as the trailing part. It is usually
  84. better to use the 'tmpdir' function argument which
  85. provides an empty unique-per-test-invocation directory
  86. and is guaranteed to be empty.
  87. """
  88. # py.log._apiwarn(">1.1", "use tmpdir function argument")
  89. from .deprecated import PYTEST_ENSURETEMP
  90. warnings.warn(PYTEST_ENSURETEMP, stacklevel=2)
  91. return self.getbasetemp().ensure(string, dir=dir)
  92. def mktemp(self, basename, numbered=True):
  93. """Create a subdirectory of the base temporary directory and return it.
  94. If ``numbered``, ensure the directory is unique by adding a number
  95. prefix greater than any existing one.
  96. """
  97. return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve())
  98. def getbasetemp(self):
  99. """backward compat wrapper for ``_tmppath_factory.getbasetemp``"""
  100. return py.path.local(self._tmppath_factory.getbasetemp().resolve())
  101. def get_user():
  102. """Return the current user name, or None if getuser() does not work
  103. in the current environment (see #1010).
  104. """
  105. import getpass
  106. try:
  107. return getpass.getuser()
  108. except (ImportError, KeyError):
  109. return None
  110. def pytest_configure(config):
  111. """Create a TempdirFactory and attach it to the config object.
  112. This is to comply with existing plugins which expect the handler to be
  113. available at pytest_configure time, but ideally should be moved entirely
  114. to the tmpdir_factory session fixture.
  115. """
  116. mp = MonkeyPatch()
  117. tmppath_handler = TempPathFactory.from_config(config)
  118. t = TempdirFactory(tmppath_handler)
  119. config._cleanup.append(mp.undo)
  120. mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
  121. mp.setattr(config, "_tmpdirhandler", t, raising=False)
  122. mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
  123. @pytest.fixture(scope="session")
  124. def tmpdir_factory(request):
  125. """Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
  126. """
  127. return request.config._tmpdirhandler
  128. @pytest.fixture(scope="session")
  129. def tmp_path_factory(request):
  130. """Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
  131. """
  132. return request.config._tmp_path_factory
  133. def _mk_tmp(request, factory):
  134. name = request.node.name
  135. name = re.sub(r"[\W]", "_", name)
  136. MAXVAL = 30
  137. name = name[:MAXVAL]
  138. return factory.mktemp(name, numbered=True)
  139. @pytest.fixture
  140. def tmpdir(tmp_path):
  141. """Return a temporary directory path object
  142. which is unique to each test function invocation,
  143. created as a sub directory of the base temporary
  144. directory. The returned object is a `py.path.local`_
  145. path object.
  146. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
  147. """
  148. return py.path.local(tmp_path)
  149. @pytest.fixture
  150. def tmp_path(request, tmp_path_factory):
  151. """Return a temporary directory path object
  152. which is unique to each test function invocation,
  153. created as a sub directory of the base temporary
  154. directory. The returned object is a :class:`pathlib.Path`
  155. object.
  156. .. note::
  157. in python < 3.6 this is a pathlib2.Path
  158. """
  159. return _mk_tmp(request, tmp_path_factory)