Use a subdirectory in the TEMP directory to speed up tmpdir creation

Fix #105
This commit is contained in:
Bruno Oliveira 2015-07-15 20:03:58 -03:00
parent 8f4f2c665d
commit 0f52856f99
7 changed files with 109 additions and 33 deletions

View File

@ -4,6 +4,13 @@
- fix issue768: docstrings found in python modules were not setting up session - fix issue768: docstrings found in python modules were not setting up session
fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR. fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
- added `tmpdir_factory`, a session-scoped fixture that can be used to create
directories under the base temporary directory. Previously this object was
installed as a `_tmpdirhandler` attribute of the `config` object, but now it
is part of the official API and using `config._tmpdirhandler` is
deprecated.
Thanks Bruno Oliveira for the PR.
- fix issue 808: pytest's internal assertion rewrite hook now implements the - fix issue 808: pytest's internal assertion rewrite hook now implements the
optional PEP302 get_data API so tests can access data files next to them. optional PEP302 get_data API so tests can access data files next to them.
Thanks xmo-odoo for request and example and Bruno Oliveira for Thanks xmo-odoo for request and example and Bruno Oliveira for

View File

@ -309,16 +309,18 @@ class HookRecorder:
self.calls[:] = [] self.calls[:] = []
def pytest_funcarg__linecomp(request): @pytest.fixture
def linecomp(request):
return LineComp() return LineComp()
def pytest_funcarg__LineMatcher(request): def pytest_funcarg__LineMatcher(request):
return LineMatcher return LineMatcher
def pytest_funcarg__testdir(request):
tmptestdir = Testdir(request)
return tmptestdir
@pytest.fixture
def testdir(request, tmpdir_factory):
return Testdir(request, tmpdir_factory)
rex_outcome = re.compile("(\d+) (\w+)") rex_outcome = re.compile("(\d+) (\w+)")
@ -388,10 +390,10 @@ class Testdir:
""" """
def __init__(self, request): def __init__(self, request, tmpdir_factory):
self.request = request self.request = request
# XXX remove duplication with tmpdir plugin # XXX remove duplication with tmpdir plugin
basetmp = request.config._tmpdirhandler.ensuretemp("testdir") basetmp = tmpdir_factory.ensuretemp("testdir")
name = request.function.__name__ name = request.function.__name__
for i in range(100): for i in range(100):
try: try:

View File

@ -6,7 +6,12 @@ import py
from _pytest.monkeypatch import monkeypatch from _pytest.monkeypatch import monkeypatch
class TempdirHandler: class TempdirFactory:
"""Factory for temporary directories under the common base temp directory.
The base directory can be configured using the ``--basetemp`` option.
"""
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.trace = config.trace.get("tmpdir") self.trace = config.trace.get("tmpdir")
@ -22,6 +27,10 @@ class TempdirHandler:
return self.getbasetemp().ensure(string, dir=dir) return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True): def mktemp(self, basename, numbered=True):
"""Create a subdirectory of the base temporary directory and return it.
If ``numbered``, ensure the directory is unique by adding a number
prefix greater than any existing one.
"""
basetemp = self.getbasetemp() basetemp = self.getbasetemp()
if not numbered: if not numbered:
p = basetemp.mkdir(basename) p = basetemp.mkdir(basename)
@ -51,15 +60,33 @@ class TempdirHandler:
def finish(self): def finish(self):
self.trace("finish") self.trace("finish")
# backward compatibility
TempdirHandler = TempdirFactory
def pytest_configure(config): def pytest_configure(config):
"""Create a TempdirFactory and attach it to the config object.
This is to comply with existing plugins which expect the handler to be
available at pytest_configure time, but ideally should be moved entirely
to the tmpdir_factory session fixture.
"""
mp = monkeypatch() mp = monkeypatch()
t = TempdirHandler(config) t = TempdirFactory(config)
config._cleanup.extend([mp.undo, t.finish]) config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False) mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False) mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
@pytest.fixture(scope='session')
def tmpdir_factory(request):
"""Return a TempdirFactory instance for the test session.
"""
return request.config._tmpdirhandler
@pytest.fixture @pytest.fixture
def tmpdir(request): def tmpdir(request, tmpdir_factory):
"""return a temporary directory path object """return a temporary directory path object
which is unique to each test function invocation, which is unique to each test function invocation,
created as a sub directory of the base temporary created as a sub directory of the base temporary
@ -71,5 +98,5 @@ def tmpdir(request):
MAXVAL = 30 MAXVAL = 30
if len(name) > MAXVAL: if len(name) > MAXVAL:
name = name[:MAXVAL] name = name[:MAXVAL]
x = request.config._tmpdirhandler.mktemp(name, numbered=True) x = tmpdir_factory.mktemp(name, numbered=True)
return x return x

View File

@ -5,10 +5,10 @@
Temporary directories and files Temporary directories and files
================================================ ================================================
The 'tmpdir' test function argument The 'tmpdir' fixture
----------------------------------- --------------------
You can use the ``tmpdir`` function argument which will You can use the ``tmpdir`` fixture which will
provide a temporary directory unique to the test invocation, provide a temporary directory unique to the test invocation,
created in the `base temporary directory`_. created in the `base temporary directory`_.
@ -51,6 +51,44 @@ Running this would result in a passed test except for the last
test_tmpdir.py:7: AssertionError test_tmpdir.py:7: AssertionError
======= 1 failed in 0.12 seconds ======== ======= 1 failed in 0.12 seconds ========
The 'tmpdir_factory' fixture
----------------------------
.. versionadded:: 2.8
The ``tmpdir_factory`` is a session-scoped fixture which can be used
to create arbitrary temporary directories from any other fixture or test.
For example, suppose your test suite needs a large image on disk, which is
generated procedurally. Instead of computing the same image for each test
that uses it into its own ``tmpdir``, you can generate it once per-session
to save time:
.. code-block:: python
# contents of conftest.py
import pytest
@pytest.fixture(scope='session')
def image_file(tmpdir_factory):
img = compute_expensive_image()
fn = tmpdir_factory.mktemp('data').join('img.png')
img.save(str(fn))
return fn
# contents of test_image.py
def test_histogram(image_file):
img = load_image(image_file)
# compute and test histogram
``tmpdir_factory`` instances have the following methods:
.. currentmodule:: _pytest.tmpdir
.. automethod:: TempdirFactory.mktemp
.. automethod:: TempdirFactory.getbasetemp
.. _`base temporary directory`: .. _`base temporary directory`:
The default base temporary directory The default base temporary directory

View File

@ -555,7 +555,8 @@ class TestRequestBasic:
pass pass
def test_function(request, farg): def test_function(request, farg):
assert set(get_public_names(request.fixturenames)) == \ assert set(get_public_names(request.fixturenames)) == \
set(["tmpdir", "sarg", "arg1", "request", "farg"]) set(["tmpdir", "sarg", "arg1", "request", "farg",
"tmpdir_factory"])
""") """)
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)

View File

@ -4,9 +4,9 @@ from _pytest.config import PytestPluginManager
@pytest.fixture(scope="module", params=["global", "inpackage"]) @pytest.fixture(scope="module", params=["global", "inpackage"])
def basedir(request): def basedir(request, tmpdir_factory):
from _pytest.tmpdir import tmpdir from _pytest.tmpdir import tmpdir
tmpdir = tmpdir(request) tmpdir = tmpdir(request, tmpdir_factory)
tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3") tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5") tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
if request.param == "inpackage": if request.param == "inpackage":

View File

@ -1,7 +1,7 @@
import py import py
import pytest import pytest
from _pytest.tmpdir import tmpdir, TempdirHandler from _pytest.tmpdir import tmpdir, TempdirFactory
def test_funcarg(testdir): def test_funcarg(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -13,16 +13,15 @@ def test_funcarg(testdir):
reprec = testdir.inline_run() reprec = testdir.inline_run()
calls = reprec.getcalls("pytest_runtest_setup") calls = reprec.getcalls("pytest_runtest_setup")
item = calls[0].item item = calls[0].item
# pytest_unconfigure has deleted the TempdirHandler already
config = item.config config = item.config
config._tmpdirhandler = TempdirHandler(config) tmpdirhandler = TempdirFactory(config)
item._initrequest() item._initrequest()
p = tmpdir(item._request) p = tmpdir(item._request, tmpdirhandler)
assert p.check() assert p.check()
bn = p.basename.strip("0123456789") bn = p.basename.strip("0123456789")
assert bn.endswith("test_func_a_") assert bn.endswith("test_func_a_")
item.name = "qwe/\\abc" item.name = "qwe/\\abc"
p = tmpdir(item._request) p = tmpdir(item._request, tmpdirhandler)
assert p.check() assert p.check()
bn = p.basename.strip("0123456789") bn = p.basename.strip("0123456789")
assert bn == "qwe__abc" assert bn == "qwe__abc"
@ -38,7 +37,7 @@ class TestTempdirHandler:
def test_mktemp(self, testdir): def test_mktemp(self, testdir):
config = testdir.parseconfig() config = testdir.parseconfig()
config.option.basetemp = testdir.mkdir("hello") config.option.basetemp = testdir.mkdir("hello")
t = TempdirHandler(config) t = TempdirFactory(config)
tmp = t.mktemp("world") tmp = t.mktemp("world")
assert tmp.relto(t.getbasetemp()) == "world0" assert tmp.relto(t.getbasetemp()) == "world0"
tmp = t.mktemp("this") tmp = t.mktemp("this")
@ -49,17 +48,19 @@ class TestTempdirHandler:
class TestConfigTmpdir: class TestConfigTmpdir:
def test_getbasetemp_custom_removes_old(self, testdir): def test_getbasetemp_custom_removes_old(self, testdir):
p = testdir.tmpdir.join("xyz") mytemp = testdir.tmpdir.join("xyz")
config = testdir.parseconfigure("--basetemp=xyz") p = testdir.makepyfile("""
b = config._tmpdirhandler.getbasetemp() def test_1(tmpdir):
assert b == p pass
h = b.ensure("hello") """)
config._tmpdirhandler.getbasetemp() testdir.runpytest(p, '--basetemp=%s' % mytemp)
assert h.check() mytemp.check()
config = testdir.parseconfigure("--basetemp=xyz") mytemp.ensure("hello")
b2 = config._tmpdirhandler.getbasetemp()
assert b2.check() testdir.runpytest(p, '--basetemp=%s' % mytemp)
assert not h.check() mytemp.check()
assert not mytemp.join("hello").check()
def test_basetemp(testdir): def test_basetemp(testdir):
mytemp = testdir.tmpdir.mkdir("mytemp") mytemp = testdir.tmpdir.mkdir("mytemp")