From 086d4e4ced1fdc6dad036fc727679fb543371e0d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 30 Sep 2013 13:42:39 +0200 Subject: [PATCH] strike keyword argument in favour of new pytest.yield_fixture decorator --- CHANGELOG | 9 ++++++--- _pytest/python.py | 30 ++++++++++++++++++++++-------- doc/en/fixture.txt | 4 ++-- doc/en/yieldfixture.txt | 13 ++++++------- testing/python/fixture.py | 17 ++++++++--------- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 314f92aa5..57f8b8369 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,9 +16,12 @@ known incompatibilities: new features: -- experimentally allow fixture functions to "yield" instead of "return" - a fixture value, allowing direct integration with with-context managers - in fixture functions and avoiding registration of finalization callbacks. +- experimentally introduce a new pytest.yield_fixture decorator which + has exactly the same parameters as pytest.fixture but expects + a ``yield`` statement instead of a ``return statement`` from fixture functions. + This allows direct integration with with-context managers + in fixture functions and generally avoids registering of finalization callbacks + in favour of treating the "after-yield" as teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris Bruynooghe, Ronny Pfannschmidt and many others for discussions. diff --git a/_pytest/python.py b/_pytest/python.py index 7cfa7b051..7557ef10c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -40,7 +40,7 @@ class FixtureFunctionMarker: return function -def fixture(scope="function", params=None, autouse=False, yieldctx=False): +def fixture(scope="function", params=None, autouse=False): """ (return a) decorator to mark a fixture factory function. This decorator can be used (with or or without parameters) to define @@ -62,16 +62,29 @@ def fixture(scope="function", params=None, autouse=False, yieldctx=False): can see it. If False (the default) then an explicit reference is needed to activate the fixture. - :arg yieldctx: if True, the fixture function yields a fixture value. - Code after such a ``yield`` statement is treated as - teardown code. """ if callable(scope) and params is None and autouse == False: # direct decoration return FixtureFunctionMarker( - "function", params, autouse, yieldctx)(scope) + "function", params, autouse)(scope) else: - return FixtureFunctionMarker(scope, params, autouse, yieldctx) + return FixtureFunctionMarker(scope, params, autouse) + +def yield_fixture(scope="function", params=None, autouse=False): + """ (return a) decorator to mark a yield-fixture factory function + (EXPERIMENTAL). + + This takes the same arguments as :py:func:`pytest.fixture` but + expects a fixture function to use a ``yield`` instead of a ``return`` + statement to provide a fixture. See + http://pytest.org/en/latest/yieldfixture.html for more info. + """ + if callable(scope) and params is None and autouse == False: + # direct decoration + return FixtureFunctionMarker( + "function", params, autouse, yieldctx=True)(scope) + else: + return FixtureFunctionMarker(scope, params, autouse, yieldctx=True) defaultfuncargprefixmarker = fixture() @@ -136,6 +149,7 @@ def pytest_namespace(): raises.Exception = pytest.fail.Exception return { 'fixture': fixture, + 'yield_fixture': yield_fixture, 'raises' : raises, 'collect': { 'Module': Module, 'Class': Class, 'Instance': Instance, @@ -1675,7 +1689,7 @@ def call_fixture_func(fixturefunc, request, kwargs, yieldctx): if yieldctx: if not is_generator(fixturefunc): fail_fixturefunc(fixturefunc, - msg="yieldctx=True requires yield statement") + msg="yield_fixture requires yield statement in function") iter = fixturefunc(**kwargs) next = getattr(iter, "__next__", None) if next is None: @@ -1688,7 +1702,7 @@ def call_fixture_func(fixturefunc, request, kwargs, yieldctx): pass else: fail_fixturefunc(fixturefunc, - "fixture function has more than one 'yield'") + "yield_fixture function has more than one 'yield'") request.addfinalizer(teardown) else: res = fixturefunc(**kwargs) diff --git a/doc/en/fixture.txt b/doc/en/fixture.txt index c64a29137..c04f5818c 100644 --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -276,8 +276,8 @@ module itself does not need to change or know about these details of fixture setup. Note that pytest-2.4 introduced an experimental alternative -:ref:`yield fixture mechanism ` for easier context manager integration -and more linear writing of teardown code. +:ref:`yield fixture mechanism ` for easier context manager +integration and more linear writing of teardown code. .. _`request-context`: diff --git a/doc/en/yieldfixture.txt b/doc/en/yieldfixture.txt index f4df53155..585cbc175 100644 --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -1,5 +1,5 @@ -.. _yieldctx: +.. _yieldfixture: Fixture functions using "yield" / context manager integration --------------------------------------------------------------- @@ -16,10 +16,9 @@ fully supporting all other fixture features. "yielding" fixture values is an experimental feature and its exact declaration may change later but earliest in a 2.5 release. You can thus - safely use this feature in the 2.4 series but may need to adapt your - fixtures later. Test functions themselves will not need to change - (they can be completely ignorant of the return/yield modes of - fixture functions). + safely use this feature in the 2.4 series but may need to adapt later. + Test functions themselves will not need to change (as a general + feature, they are ignorant of how fixtures are setup). Let's look at a simple standalone-example using the new ``yield`` syntax:: @@ -27,7 +26,7 @@ Let's look at a simple standalone-example using the new ``yield`` syntax:: import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def passwd(): print ("\nsetup before yield") f = open("/etc/passwd") @@ -62,7 +61,7 @@ Let's simplify the above ``passwd`` fixture:: import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def passwd(): with open("/etc/passwd") as f: yield f.readlines() diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 076b41a6e..8e1970913 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1980,7 +1980,7 @@ class TestContextManagerFixtureFuncs: def test_simple(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(yieldctx=True) + @pytest.yield_fixture def arg1(): print ("setup") yield 1 @@ -2004,7 +2004,7 @@ class TestContextManagerFixtureFuncs: def test_scoped(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): print ("setup") yield 1 @@ -2025,7 +2025,7 @@ class TestContextManagerFixtureFuncs: def test_setup_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): pytest.fail("setup") yield 1 @@ -2041,7 +2041,7 @@ class TestContextManagerFixtureFuncs: def test_teardown_exception(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): yield 1 pytest.fail("teardown") @@ -2057,7 +2057,7 @@ class TestContextManagerFixtureFuncs: def test_yields_more_than_one(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): yield 1 yield 2 @@ -2074,7 +2074,7 @@ class TestContextManagerFixtureFuncs: def test_no_yield(self, testdir): testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module", yieldctx=True) + @pytest.yield_fixture(scope="module") def arg1(): return 1 def test_1(arg1): @@ -2082,9 +2082,8 @@ class TestContextManagerFixtureFuncs: """) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" - *yieldctx*requires*yield* - *yieldctx=True* + *yield_fixture*requires*yield* + *yield_fixture* *def arg1* """) -