diff --git a/AUTHORS b/AUTHORS index 12ac94b72..3e079b522 100644 --- a/AUTHORS +++ b/AUTHORS @@ -89,6 +89,7 @@ Ryan Wooden Samuele Pedroni Simon Gomizelj Stefano Taschini +Stefan Farmbauer Thomas Grainger Tom Viner Trevor Bekolay diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2ab8834b7..0a71de6b9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ * Text documents without any doctests no longer appear as "skipped". Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). -* Fix internal error issue when ``method`` argument is missing for +* Fix internal error issue when ``method`` argument is missing for ``teardown_method()``. Fixes (`#1605`_). * Fix exception visualization in case the current working directory (CWD) gets @@ -17,14 +17,21 @@ is specified on the command line together with the ``--pyargs`` option. Thanks to `@taschini`_ for the PR (`#1597`_). -* +* Raise helpful failure message, when requesting parametrized fixture at runtime, + e.g. with ``request.getfuncargvalue``. BACKWARD INCOMPAT: Previously these params + were simply never defined. So a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` + only ran once. Now a failure is raised. Fixes (`#460`_). Thanks to + `@nikratio`_ for bug report, `@RedBeardCode`_ and `@tomviner`_ for PR. .. _#1580: https://github.com/pytest-dev/pytest/pull/1580 .. _#1605: https://github.com/pytest-dev/pytest/issues/1605 .. _#1597: https://github.com/pytest-dev/pytest/pull/1597 +.. _#460: https://github.com/pytest-dev/pytest/pull/460 .. _@graingert: https://github.com/graingert .. _@taschini: https://github.com/taschini +.. _@nikratio: https://github.com/nikratio +.. _@RedBeardCode: https://github.com/RedBeardCode 2.9.2 diff --git a/_pytest/python.py b/_pytest/python.py index 21d78aea3..7246c253c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1651,6 +1651,25 @@ class FixtureRequest(FuncargnamesCompatAttr): except (AttributeError, ValueError): param = NOTSET param_index = 0 + if fixturedef.params is not None: + frame = inspect.stack()[3] + frameinfo = inspect.getframeinfo(frame[0]) + source_path = frameinfo.filename + source_lineno = frameinfo.lineno + source_path = py.path.local(source_path) + if source_path.relto(funcitem.config.rootdir): + source_path = source_path.relto(funcitem.config.rootdir) + msg = ( + "The requested fixture has no parameter defined for the " + "current test.\n\nRequested fixture '{0}' defined in:\n{1}" + "\n\nRequested here:\n{2}:{3}".format( + fixturedef.argname, + getlocation(fixturedef.func, funcitem.config.rootdir), + source_path, + source_lineno, + ) + ) + pytest.fail(msg) else: # indices might not be set if old-style metafunc.addcall() was used param_index = funcitem.callspec.indices.get(argname, 0) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 25e312098..d6e3250d0 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -2723,3 +2723,108 @@ class TestContextManagerFixtureFuncs: *def arg1* """) +class TestParameterizedSubRequest: + def test_call_from_fixture(self, testdir): + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + + @pytest.fixture + def get_named_fixture(request): + return request.getfuncargvalue('fix_with_param') + + def test_foo(request, get_named_fixture): + pass + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:9 + *1 error* + """.format(testfile.basename, testfile.basename)) + + def test_call_from_test(self, testdir): + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + + def test_foo(request): + request.getfuncargvalue('fix_with_param') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:8 + *1 failed* + """.format(testfile.basename, testfile.basename)) + + def test_external_fixture(self, testdir): + conffile = testdir.makeconftest(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + """) + + testfile = testdir.makepyfile(""" + def test_foo(request): + request.getfuncargvalue('fix_with_param') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:2 + *1 failed* + """.format(conffile.basename, testfile.basename)) + + def test_non_relative_path(self, testdir): + tests_dir = testdir.mkdir('tests') + fixdir = testdir.mkdir('fixtures') + fixfile = fixdir.join("fix.py") + fixfile.write(_pytest._code.Source(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + """)) + + testfile = tests_dir.join("test_foos.py") + testfile.write(_pytest._code.Source(""" + from fix import fix_with_param + + def test_foo(request): + request.getfuncargvalue('fix_with_param') + """)) + + tests_dir.chdir() + testdir.syspathinsert(fixdir) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:5 + E*Requested here: + E*{1}:5 + *1 failed* + """.format(fixfile.strpath, testfile.basename))