Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

py.test: Temporary folder for the session scope

Tags:

python

pytest

The tmpdir fixture in py.test uses the function scope and thus isn't available in a fixture with a broader scope such as session. However, this would be useful for some cases such as setting up a temporary PostgreSQL server (which of course shouldn't be recreated for each test).

Is there any clean way to get a temporary folder for a broader scope that does not involve writing my own fixture and accessing internal APIs of py.test?

like image 460
ThiefMaster Avatar asked Aug 27 '14 10:08

ThiefMaster


People also ask

Where does Conftest py go?

You can put fixtures into individual test files, but to share fixtures among multiple test files, you need to use a conftest.py file somewhere centrally located for all of the tests. For the Tasks project, all of the fixtures will be in tasks_proj/tests/conftest.py. From there, the fixtures can be shared by any test.

Are pytest fixtures cached?

Pytest only caches one instance of a fixture at a time, which means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope.

What is Autouse in pytest?

It is possible to apply a fixture to all of the tests in a hierarchy, even if the tests don't explicitly request a fixture, by passing autouse=True to the @pytest. fixture decorator. This is useful when we need to apply a side-effect before and/or after each test unconditionally. @pytest.fixture(autouse=True)

What is scope in pytest fixture?

Fixtures include an optional parameter called scope, which controls how often a fixture gets set up and torn down. The scope parameter to @pytest. fixture() can have the values of function, class, module, or session. The default scope is function.


3 Answers

Since pytest release 2.8 and above the session-scoped tmpdir_factory fixture is available. See the example below from the documentation.

# 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 
like image 168
itsafire Avatar answered Sep 23 '22 05:09

itsafire


Unfortunately there is currently no way (2014) of doing this nicely. In the future py.test will introduce a new "any" scope or something similar for this, but that's the future.

Right now you have to do this manually yourself. However as you note you lose quite a few nice features: symlinks in /tmp to the last test, auto cleanup after a few test runs, sensibly named directories etc. If the directory is not too expensive I usually combine a session and function scoped fixture in the following way:

@pytest.fixture(scope='session') def session_dir(request):     temp_dir = py.path.local(tempfile.mkdtemp())     request.addfinalizer(lambda: folder.remove(rec=1))     # Any extra setup here     return temp_dir  @pytest.fixture def temp_dir(session_dir, tmpdir):     session_dir.copy(tmpdir)     return tmpdir 

This creates a temporary directory which gets cleaned up after a test run, however for each test which actually needs it (by requesting temp_dir) gets a copy which is saved with the tmpdir semantics.

If tests actually need to share state via this directory then the finalizer of temp_dir would have to copy things back to the session_dir. This is however not a very good idea since it makes the tests reliant on the execution order and would also cause problems when using pytest-xdist.

like image 25
flub Avatar answered Sep 21 '22 05:09

flub


I add a finalizer when I want to delete all temporary folders created in session.

_tmp_factory = None
@pytest.fixture(scope="session")
def tmp_factory(request, tmpdir_factory):
    global _tmp_factory
    if _tmp_factory is None:
        _tmp_factory = tmpdir_factory
        request.addfinalizer(cleanup)
    return _tmp_factory

def cleanup():
    root = _tmp_factory.getbasetemp().strpath
    print "Cleaning all temporary folders from %s" % root
    shutil.rmtree(root)

def test_deleting_temp(tmp_factory):
    root_a = tmp_factory.mktemp('A')
    root_a.join('foo.txt').write('hello world A')

    root_b = tmp_factory.mktemp('B')
    root_b.join('bar.txt').write('hello world B')

    for root, _, files in os.walk(tmp_factory.getbasetemp().strpath):
        for name in files:
            print(os.path.join(root, name))

The output should be like:

/tmp/pytest-of-agp/pytest-0/.lock
/tmp/pytest-of-agp/pytest-0/A0/foo.txt
/tmp/pytest-of-agp/pytest-0/B0/bar.txt
Cleaning all temporary folders from /tmp/pytest-of-agp/pytest-0
like image 33
asterio gonzalez Avatar answered Sep 24 '22 05:09

asterio gonzalez