From 499c9551c8969719155be86c5b1f215d0ad737e6 Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Tue, 21 Jun 2016 18:28:40 +0200 Subject: [PATCH 01/20] Implement working version of --setuponly --- _pytest/main.py | 2 ++ _pytest/python.py | 15 +++++++++++++++ _pytest/runner.py | 12 +++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/_pytest/main.py b/_pytest/main.py index 47b6cb694..ce0bb4918 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -48,6 +48,8 @@ def pytest_addoption(parser): help="run pytest in strict mode, warnings become errors.") group._addoption("-c", metavar="file", type=str, dest="inifilename", help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") + group.addoption('--setuponly', '--setup-only', action="store_true", + help="only setup fixtures, don't execute the tests.") group = parser.getgroup("collect", "collection") group.addoption('--collectonly', '--collect-only', action="store_true", diff --git a/_pytest/python.py b/_pytest/python.py index 526f4be09..244a4ce50 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2459,6 +2459,14 @@ class FixtureDef: # even if finalization fails, we invalidate # the cached fixture value if hasattr(self, "cached_result"): + if self._fixturemanager.config.option.setuponly: + tw = self._fixturemanager.config.get_terminal_writer() + tw.line() + tw.write(' ' * 2 * self.scopenum) + tw.write('TEARDOWN {} {}'.format(self.scope[0].upper(), self.argname)) + if hasattr(self, "cached_param"): + tw.write('[{}]'.format(self.cached_param)) + del self.cached_param del self.cached_result def execute(self, request): @@ -2504,6 +2512,13 @@ class FixtureDef: try: result = call_fixture_func(fixturefunc, request, kwargs) + tw = request.config.get_terminal_writer() + tw.line() + tw.write(' ' * 2 * self.scopenum) + tw.write('SETUP {} {}'.format(self.scope[0].upper(), fixturefunc.__name__)) + if hasattr(request, 'param'): + tw.write('[{}]'.format(request.param)) + self.cached_param = request.param except Exception: self.cached_result = (None, my_cache_key, sys.exc_info()) raise diff --git a/_pytest/runner.py b/_pytest/runner.py index 4cc2ef6ac..1f7100977 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -73,7 +73,17 @@ def runtestprotocol(item, log=True, nextitem=None): rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: - reports.append(call_and_report(item, "call", log)) + if item.config.option.setuponly: + tw = item.config.get_terminal_writer() + tw.line() + tw.write(' ' * 8) + tw.write('{} '.format(item._nodeid)) + used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) + if used_fixtures: + tw.write('fixtures: ') + tw.write(', '.join(used_fixtures)) + else: + reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # after all teardown hooks have been called From 6874c3a3cc9d66c4573a474e5e36b8b99f7fe071 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 11:09:54 +0200 Subject: [PATCH 02/20] Factor setuponly code out of runtestprotocol(). --- _pytest/runner.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/_pytest/runner.py b/_pytest/runner.py index 1f7100977..82e2a0dde 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -74,14 +74,7 @@ def runtestprotocol(item, log=True, nextitem=None): reports = [rep] if rep.passed: if item.config.option.setuponly: - tw = item.config.get_terminal_writer() - tw.line() - tw.write(' ' * 8) - tw.write('{} '.format(item._nodeid)) - used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) - if used_fixtures: - tw.write('fixtures: ') - tw.write(', '.join(used_fixtures)) + show_test_item(item) else: reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "teardown", log, @@ -93,6 +86,17 @@ def runtestprotocol(item, log=True, nextitem=None): item.funcargs = None return reports +def show_test_item(item): + """Show test function, parameters and the fixtures of the test item.""" + tw = item.config.get_terminal_writer() + tw.line() + tw.write(' ' * 8) + tw.write('{} '.format(item._nodeid)) + used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) + if used_fixtures: + tw.write('fixtures: ') + tw.write(', '.join(used_fixtures)) + def pytest_runtest_setup(item): item.session._setupstate.prepare(item) From 7d19f839829d8f6ffa23b1dd03702766bae4ff49 Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Wed, 22 Jun 2016 12:00:45 +0200 Subject: [PATCH 03/20] Add test for setuponly option --- testing/python/setup_only.py | 151 +++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 testing/python/setup_only.py diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py new file mode 100644 index 000000000..9b23c3d91 --- /dev/null +++ b/testing/python/setup_only.py @@ -0,0 +1,151 @@ +def test_show_only_active_fixtures(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def _arg0(): + """hidden arg0 fixture""" + @pytest.fixture + def arg1(): + """arg1 docstring""" + def test_arg1(arg1): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F arg1*', + '*test_arg1 fixtures: arg1', + '*TEARDOWN F arg1*', + ]) + assert "_arg0" not in result.stdout.str() + + +def test_show_different_scopes(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session') + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_session, arg_function): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_session*', + '*SETUP F arg_function*', + '*test_arg1 fixtures: arg_function, arg_session', + '*TEARDOWN F arg_function*', + 'TEARDOWN S arg_session*', + ]) + + +def test_show_nested_fixtures(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture(scope='session') + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_same(arg_same): + """function scoped fixture""" + def test_arg1(arg_same): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same*', + '*SETUP F arg_same*', + '*test_arg1 fixtures: arg_same', + '*TEARDOWN F arg_same*', + 'TEARDOWN S arg_same*', + ]) + + +def test_show_fixtures_with_autouse(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session', autouse=True) + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_function): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_session*', + '*SETUP F arg_function*', + '*test_arg1 fixtures: arg_function, arg_session', + ]) + + +def test_show_fixtures_with_parameters(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture(scope='session', params=['foo', 'bar']) + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same?foo?', + 'TEARDOWN S arg_same?foo?', + 'SETUP S arg_same?bar?', + 'TEARDOWN S arg_same?bar?', + ]) + + +def test_show_fixtures_with_parameter_ids(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture( + scope='session', params=['foo', 'bar'], ids=['spam', 'ham']) + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''') + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same?spam?', + 'SETUP S arg_same?ham?', + ]) From 92bcc36266e996cc845d185a11c5bdaf564e486f Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Wed, 22 Jun 2016 12:01:51 +0200 Subject: [PATCH 04/20] Refactor logging of fixtures --- _pytest/python.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index 244a4ce50..142a07acf 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2460,12 +2460,8 @@ class FixtureDef: # the cached fixture value if hasattr(self, "cached_result"): if self._fixturemanager.config.option.setuponly: - tw = self._fixturemanager.config.get_terminal_writer() - tw.line() - tw.write(' ' * 2 * self.scopenum) - tw.write('TEARDOWN {} {}'.format(self.scope[0].upper(), self.argname)) + self._log_fixture_stack('TEARDOWN') if hasattr(self, "cached_param"): - tw.write('[{}]'.format(self.cached_param)) del self.cached_param del self.cached_result @@ -2512,19 +2508,33 @@ class FixtureDef: try: result = call_fixture_func(fixturefunc, request, kwargs) - tw = request.config.get_terminal_writer() - tw.line() - tw.write(' ' * 2 * self.scopenum) - tw.write('SETUP {} {}'.format(self.scope[0].upper(), fixturefunc.__name__)) - if hasattr(request, 'param'): - tw.write('[{}]'.format(request.param)) - self.cached_param = request.param + # We want to access the params of ids if they exist also in during + # the finish() method. + if self._fixturemanager.config.option.setuponly: + if hasattr(request, 'param'): + if self.ids: + ind = self.params.index(request.param) + self.cached_param = self.ids[ind] + else: + self.cached_param = request.param + self._log_fixture_stack('SETUP') except Exception: self.cached_result = (None, my_cache_key, sys.exc_info()) raise self.cached_result = (result, my_cache_key, None) return result + def _log_fixture_stack(self, what): + tw = self._fixturemanager.config.get_terminal_writer() + tw.line() + tw.write(' ' * 2 * self.scopenum) + tw.write('{step} {scope} {fixture}'.format( + step=what.ljust(8), # align the output to TEARDOWN + scope=self.scope[0].upper(), + fixture=self.argname)) + if hasattr(self, 'cached_param'): + tw.write('[{}]'.format(self.cached_param)) + def __repr__(self): return ("" % (self.argname, self.scope, self.baseid)) From 2c6cfa42faac3e1ac996fb6932d275a82c28397f Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Wed, 22 Jun 2016 12:14:35 +0200 Subject: [PATCH 05/20] Disable capturing for setuponly output --- _pytest/python.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/_pytest/python.py b/_pytest/python.py index 142a07acf..f02c54441 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2525,7 +2525,12 @@ class FixtureDef: return result def _log_fixture_stack(self, what): - tw = self._fixturemanager.config.get_terminal_writer() + config = self._fixturemanager.config + capman = config.pluginmanager.getplugin('capturemanager') + if capman: + capman.suspendcapture() + + tw = config.get_terminal_writer() tw.line() tw.write(' ' * 2 * self.scopenum) tw.write('{step} {scope} {fixture}'.format( @@ -2535,6 +2540,9 @@ class FixtureDef: if hasattr(self, 'cached_param'): tw.write('[{}]'.format(self.cached_param)) + if capman: + capman.resumecapture() + def __repr__(self): return ("" % (self.argname, self.scope, self.baseid)) From 2c5c4f3f78bf71da50629b7ee2a7e0dbe9bccfe9 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 12:54:36 +0200 Subject: [PATCH 06/20] Add printing of fixture dependencies --- _pytest/python.py | 6 ++++++ _pytest/runner.py | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index f02c54441..a644807ce 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2537,6 +2537,12 @@ class FixtureDef: step=what.ljust(8), # align the output to TEARDOWN scope=self.scope[0].upper(), fixture=self.argname)) + + if what == 'SETUP': + deps = sorted(arg for arg in self.argnames if arg != 'request') + if deps: + tw.write(' (fixtures used: {})'.format(', '.join(deps))) + if hasattr(self, 'cached_param'): tw.write('[{}]'.format(self.cached_param)) diff --git a/_pytest/runner.py b/_pytest/runner.py index 82e2a0dde..1d3ae78ab 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -91,11 +91,10 @@ def show_test_item(item): tw = item.config.get_terminal_writer() tw.line() tw.write(' ' * 8) - tw.write('{} '.format(item._nodeid)) + tw.write('{}'.format(item._nodeid)) used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) if used_fixtures: - tw.write('fixtures: ') - tw.write(', '.join(used_fixtures)) + tw.write(' (fixtures used: {})'.format(', '.join(used_fixtures))) def pytest_runtest_setup(item): item.session._setupstate.prepare(item) From 1a75139f72510754398218f69cf67a8bef70e063 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 13:25:46 +0200 Subject: [PATCH 07/20] Fix the tests --- testing/python/setup_only.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index 9b23c3d91..fcb2b9a7d 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -16,7 +16,7 @@ def test_show_only_active_fixtures(testdir): result.stdout.fnmatch_lines([ '*SETUP F arg1*', - '*test_arg1 fixtures: arg1', + '*test_arg1 (fixtures used: arg1)', '*TEARDOWN F arg1*', ]) assert "_arg0" not in result.stdout.str() @@ -41,7 +41,7 @@ def test_show_different_scopes(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_session*', '*SETUP F arg_function*', - '*test_arg1 fixtures: arg_function, arg_session', + '*test_arg1 (fixtures used: arg_function, arg_session)', '*TEARDOWN F arg_function*', 'TEARDOWN S arg_session*', ]) @@ -68,8 +68,8 @@ def test_show_nested_fixtures(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_same*', - '*SETUP F arg_same*', - '*test_arg1 fixtures: arg_same', + '*SETUP F arg_same (fixtures used: arg_same)*', + '*test_arg1 (fixtures used: arg_same)', '*TEARDOWN F arg_same*', 'TEARDOWN S arg_same*', ]) @@ -94,7 +94,7 @@ def test_show_fixtures_with_autouse(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_session*', '*SETUP F arg_function*', - '*test_arg1 fixtures: arg_function, arg_session', + '*test_arg1 (fixtures used: arg_function, arg_session)', ]) From 5e0d78f4f134ddc3830bff4f8871e0947892f756 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 14:53:37 +0200 Subject: [PATCH 08/20] Fix .format string failures on python 2.6 --- _pytest/python.py | 4 ++-- _pytest/runner.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index a644807ce..6c7be14de 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2541,10 +2541,10 @@ class FixtureDef: if what == 'SETUP': deps = sorted(arg for arg in self.argnames if arg != 'request') if deps: - tw.write(' (fixtures used: {})'.format(', '.join(deps))) + tw.write(' (fixtures used: {0})'.format(', '.join(deps))) if hasattr(self, 'cached_param'): - tw.write('[{}]'.format(self.cached_param)) + tw.write('[{0}]'.format(self.cached_param)) if capman: capman.resumecapture() diff --git a/_pytest/runner.py b/_pytest/runner.py index 1d3ae78ab..d385027ad 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -91,10 +91,10 @@ def show_test_item(item): tw = item.config.get_terminal_writer() tw.line() tw.write(' ' * 8) - tw.write('{}'.format(item._nodeid)) + tw.write('{0}'.format(item._nodeid)) used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) if used_fixtures: - tw.write(' (fixtures used: {})'.format(', '.join(used_fixtures))) + tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures))) def pytest_runtest_setup(item): item.session._setupstate.prepare(item) From f59d8f7720e408df41a060fd9cb89cd92186fb1e Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 16:23:58 +0200 Subject: [PATCH 09/20] Fix the tests (#3) * Fix the tests * Fix .format string failures on python 2.6 --- _pytest/python.py | 4 ++-- _pytest/runner.py | 4 ++-- testing/python/setup_only.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index a644807ce..6c7be14de 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2541,10 +2541,10 @@ class FixtureDef: if what == 'SETUP': deps = sorted(arg for arg in self.argnames if arg != 'request') if deps: - tw.write(' (fixtures used: {})'.format(', '.join(deps))) + tw.write(' (fixtures used: {0})'.format(', '.join(deps))) if hasattr(self, 'cached_param'): - tw.write('[{}]'.format(self.cached_param)) + tw.write('[{0}]'.format(self.cached_param)) if capman: capman.resumecapture() diff --git a/_pytest/runner.py b/_pytest/runner.py index 1d3ae78ab..d385027ad 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -91,10 +91,10 @@ def show_test_item(item): tw = item.config.get_terminal_writer() tw.line() tw.write(' ' * 8) - tw.write('{}'.format(item._nodeid)) + tw.write('{0}'.format(item._nodeid)) used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) if used_fixtures: - tw.write(' (fixtures used: {})'.format(', '.join(used_fixtures))) + tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures))) def pytest_runtest_setup(item): item.session._setupstate.prepare(item) diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index 9b23c3d91..fcb2b9a7d 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -16,7 +16,7 @@ def test_show_only_active_fixtures(testdir): result.stdout.fnmatch_lines([ '*SETUP F arg1*', - '*test_arg1 fixtures: arg1', + '*test_arg1 (fixtures used: arg1)', '*TEARDOWN F arg1*', ]) assert "_arg0" not in result.stdout.str() @@ -41,7 +41,7 @@ def test_show_different_scopes(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_session*', '*SETUP F arg_function*', - '*test_arg1 fixtures: arg_function, arg_session', + '*test_arg1 (fixtures used: arg_function, arg_session)', '*TEARDOWN F arg_function*', 'TEARDOWN S arg_session*', ]) @@ -68,8 +68,8 @@ def test_show_nested_fixtures(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_same*', - '*SETUP F arg_same*', - '*test_arg1 fixtures: arg_same', + '*SETUP F arg_same (fixtures used: arg_same)*', + '*test_arg1 (fixtures used: arg_same)', '*TEARDOWN F arg_same*', 'TEARDOWN S arg_same*', ]) @@ -94,7 +94,7 @@ def test_show_fixtures_with_autouse(testdir): result.stdout.fnmatch_lines([ 'SETUP S arg_session*', '*SETUP F arg_function*', - '*test_arg1 fixtures: arg_function, arg_session', + '*test_arg1 (fixtures used: arg_function, arg_session)', ]) From 61992b4e22c73bf0131b1298f20743bb0bb8dbf2 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 16:45:36 +0200 Subject: [PATCH 10/20] Implement --setup-plan option --- _pytest/main.py | 3 +++ _pytest/python.py | 15 ++++++++++----- _pytest/runner.py | 2 +- testing/python/setup_only.py | 32 ++++++++++++++++++++------------ testing/python/setup_plan.py | 18 ++++++++++++++++++ 5 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 testing/python/setup_plan.py diff --git a/_pytest/main.py b/_pytest/main.py index ce0bb4918..ee99fa1e7 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -50,6 +50,9 @@ def pytest_addoption(parser): help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") group.addoption('--setuponly', '--setup-only', action="store_true", help="only setup fixtures, don't execute the tests.") + group.addoption('--setupplan', '--setup-plan', action="store_true", + help="show what fixtures and tests would be executed but don't" + " execute anything.") group = parser.getgroup("collect", "collection") group.addoption('--collectonly', '--collect-only', action="store_true", diff --git a/_pytest/python.py b/_pytest/python.py index 6c7be14de..4c5614318 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2459,7 +2459,8 @@ class FixtureDef: # even if finalization fails, we invalidate # the cached fixture value if hasattr(self, "cached_result"): - if self._fixturemanager.config.option.setuponly: + config = self._fixturemanager.config + if config.option.setuponly or config.option.setupplan: self._log_fixture_stack('TEARDOWN') if hasattr(self, "cached_param"): del self.cached_param @@ -2507,10 +2508,14 @@ class FixtureDef: fixturefunc = fixturefunc.__get__(request.instance) try: - result = call_fixture_func(fixturefunc, request, kwargs) - # We want to access the params of ids if they exist also in during - # the finish() method. - if self._fixturemanager.config.option.setuponly: + config = request.config + if config.option.setupplan: + result = None + else: + result = call_fixture_func(fixturefunc, request, kwargs) + if config.option.setuponly or config.option.setupplan: + # We want to access the params of ids if they exist also in during + # the finish() method. if hasattr(request, 'param'): if self.ids: ind = self.params.index(request.param) diff --git a/_pytest/runner.py b/_pytest/runner.py index d385027ad..efcb28108 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -73,7 +73,7 @@ def runtestprotocol(item, log=True, nextitem=None): rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: - if item.config.option.setuponly: + if item.config.option.setuponly or item.config.option.setupplan: show_test_item(item) else: reports.append(call_and_report(item, "call", log)) diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index fcb2b9a7d..2508ca74e 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -1,4 +1,12 @@ -def test_show_only_active_fixtures(testdir): +import pytest + + +@pytest.fixture(params=['--setup-only', '--setup-plan'], scope='module') +def mode(request): + return request.param + + +def test_show_only_active_fixtures(testdir, mode): p = testdir.makepyfile(''' import pytest @pytest.fixture @@ -11,7 +19,7 @@ def test_show_only_active_fixtures(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ @@ -22,7 +30,7 @@ def test_show_only_active_fixtures(testdir): assert "_arg0" not in result.stdout.str() -def test_show_different_scopes(testdir): +def test_show_different_scopes(testdir, mode): p = testdir.makepyfile(''' import pytest @pytest.fixture @@ -35,7 +43,7 @@ def test_show_different_scopes(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ @@ -47,7 +55,7 @@ def test_show_different_scopes(testdir): ]) -def test_show_nested_fixtures(testdir): +def test_show_nested_fixtures(testdir, mode): testdir.makeconftest(''' import pytest @pytest.fixture(scope='session') @@ -63,7 +71,7 @@ def test_show_nested_fixtures(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ @@ -75,7 +83,7 @@ def test_show_nested_fixtures(testdir): ]) -def test_show_fixtures_with_autouse(testdir): +def test_show_fixtures_with_autouse(testdir, mode): p = testdir.makepyfile(''' import pytest @pytest.fixture @@ -88,7 +96,7 @@ def test_show_fixtures_with_autouse(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ @@ -98,7 +106,7 @@ def test_show_fixtures_with_autouse(testdir): ]) -def test_show_fixtures_with_parameters(testdir): +def test_show_fixtures_with_parameters(testdir, mode): testdir.makeconftest(''' import pytest @pytest.fixture(scope='session', params=['foo', 'bar']) @@ -114,7 +122,7 @@ def test_show_fixtures_with_parameters(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ @@ -125,7 +133,7 @@ def test_show_fixtures_with_parameters(testdir): ]) -def test_show_fixtures_with_parameter_ids(testdir): +def test_show_fixtures_with_parameter_ids(testdir, mode): testdir.makeconftest(''' import pytest @pytest.fixture( @@ -142,7 +150,7 @@ def test_show_fixtures_with_parameter_ids(testdir): pass ''') - result = testdir.runpytest("--setup-only", p) + result = testdir.runpytest(mode, p) assert result.ret == 0 result.stdout.fnmatch_lines([ diff --git a/testing/python/setup_plan.py b/testing/python/setup_plan.py new file mode 100644 index 000000000..1618fa7b3 --- /dev/null +++ b/testing/python/setup_plan.py @@ -0,0 +1,18 @@ +def test_show_fixtures_and_test(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg(): + assert False + def test_arg(arg): + assert False + ''') + + result = testdir.runpytest("--setup-plan", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F arg*', + '*test_arg (fixtures used: arg)', + '*TEARDOWN F arg*', + ]) From f7d50dfa91e37aaffe566c400a9590b2c79cc50d Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Wed, 22 Jun 2016 17:24:55 +0200 Subject: [PATCH 11/20] Add a test for handling of dynamic requests for fixtures from other fixtures --- testing/python/setup_only.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index 2508ca74e..11329775c 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -157,3 +157,25 @@ def test_show_fixtures_with_parameter_ids(testdir, mode): 'SETUP S arg_same?spam?', 'SETUP S arg_same?ham?', ]) + + +def test_dynamic_fixture_request(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture() + def dynamically_requested_fixture(): + pass + @pytest.fixture() + def dependent_fixture(request): + request.getfuncargvalue('dynamically_requested_fixture') + def test_dyn(dependent_fixture): + pass + ''') + + result = testdir.runpytest('--setup-only', p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F dynamically_requested_fixture', + '*TEARDOWN F dynamically_requested_fixture' + ]) From 0dbf77e08e0d0f6b364a4cec219b584191d6a27f Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Wed, 22 Jun 2016 17:37:58 +0200 Subject: [PATCH 12/20] Add to changelog and authors. --- AUTHORS | 3 +++ CHANGELOG.rst | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/AUTHORS b/AUTHORS index 0c40517ce..4ef9b9201 100644 --- a/AUTHORS +++ b/AUTHORS @@ -27,6 +27,7 @@ Christopher Gilling Daniel Grana Daniel Hahler Daniel Nuri +Danielle Jenkins Dave Hunt David Mohr David Vierra @@ -84,9 +85,11 @@ Ronny Pfannschmidt Ross Lawley Ryan Wooden Samuele Pedroni +Steffen Allner Tareq Alayan Tom Viner Trevor Bekolay +Vasily Kuznetsov Wouter van Ackooy David Díaz-Barquero Eric Hunsberger diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 26f78cec1..ecb9e682d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -43,6 +43,13 @@ Can also show where fixtures are defined if combined with ``-v``. Thanks `@hackebrot`_ for the PR. +* New cli flags: + ``--setup-plan`` performs normal collection and reports the potential setup + and teardown, does not execute any fixtures and tests + ``--setup-only`` performs normal collection, executes setup and teardown of + fixtures and reports them. Thanks `@d6e`_, `@kvas-it`_, `@sallner`_ + and `@omarkohl`_. + **Changes** * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like From ecc97aa3b9678b4c60a51ac5154d846c9fdc7bff Mon Sep 17 00:00:00 2001 From: Steffen Allner Date: Wed, 22 Jun 2016 17:52:13 +0200 Subject: [PATCH 13/20] Use correct links in changelog. --- CHANGELOG.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 18754ebc6..f3f034cf4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -47,12 +47,11 @@ `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR (`#1633`_) -* New cli flags: - ``--setup-plan`` performs normal collection and reports the potential setup - and teardown, does not execute any fixtures and tests +* New cli flags: (1) ``--setup-plan`` performs normal collection and reports + the potential setup and teardown, does not execute any fixtures and tests (2) ``--setup-only`` performs normal collection, executes setup and teardown of - fixtures and reports them. Thanks `@d6e`_, `@kvas-it`_, `@sallner`_ - and `@omarkohl`_. + fixtures and reports them. Thanks `@d6e`_, `@kvas-it`_, `@sallner`_ + and `@omarkohl`_ for the PR. **Changes** @@ -148,6 +147,9 @@ .. _@olegpidsadnyi: https://github.com/olegpidsadnyi .. _@obestwalter: https://github.com/obestwalter .. _@davehunt: https://github.com/davehunt +.. _@sallner: https://github.com/sallner +.. _@d6e: https://github.com/d6e +.. _@kvas-it: https://github.com/kvas-it .. _#1421: https://github.com/pytest-dev/pytest/issues/1421 .. _#1426: https://github.com/pytest-dev/pytest/issues/1426 From 1a5e530b982dfe6f485a18f6861cc860a7d3ce73 Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Thu, 23 Jun 2016 10:23:04 +0200 Subject: [PATCH 14/20] Fix capturing with --setup-only/--setup-plan --- _pytest/python.py | 4 +++- _pytest/runner.py | 2 +- testing/python/setup_only.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index a17cc29dc..c5418b4d7 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2552,7 +2552,7 @@ class FixtureDef: config = self._fixturemanager.config capman = config.pluginmanager.getplugin('capturemanager') if capman: - capman.suspendcapture() + out, err = capman.suspendcapture() tw = config.get_terminal_writer() tw.line() @@ -2572,6 +2572,8 @@ class FixtureDef: if capman: capman.resumecapture() + sys.stdout.write(out) + sys.stderr.write(err) def __repr__(self): return ("" % diff --git a/_pytest/runner.py b/_pytest/runner.py index efcb28108..dff321a75 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -91,7 +91,7 @@ def show_test_item(item): tw = item.config.get_terminal_writer() tw.line() tw.write(' ' * 8) - tw.write('{0}'.format(item._nodeid)) + tw.write(item._nodeid) used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) if used_fixtures: tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures))) diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index 11329775c..eef2857bb 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -179,3 +179,24 @@ def test_dynamic_fixture_request(testdir): '*SETUP F dynamically_requested_fixture', '*TEARDOWN F dynamically_requested_fixture' ]) + + +def test_capturing(testdir): + p = testdir.makepyfile(''' + import pytest, sys + @pytest.fixture() + def one(): + sys.stdout.write('this should be captured') + sys.stderr.write('this should also be captured') + @pytest.fixture() + def two(one): + assert 0 + def test_capturing(two): + pass + ''') + + result = testdir.runpytest('--setup-only', p) + result.stdout.fnmatch_lines([ + 'this should be captured', + 'this should also be captured' + ]) From c6af737d4e5bc8da8f12a43028b5ac7be764794b Mon Sep 17 00:00:00 2001 From: Vasily Kuznetsov Date: Thu, 23 Jun 2016 10:54:22 +0200 Subject: [PATCH 15/20] Fix fixture parameter display when ids is a function --- _pytest/python.py | 16 +++++++++------- testing/python/setup_only.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index c5418b4d7..edc81e923 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2480,7 +2480,7 @@ class FixtureDef: if hasattr(self, "cached_result"): config = self._fixturemanager.config if config.option.setuponly or config.option.setupplan: - self._log_fixture_stack('TEARDOWN') + self._show_fixture_action('TEARDOWN') if hasattr(self, "cached_param"): del self.cached_param del self.cached_result @@ -2533,22 +2533,24 @@ class FixtureDef: else: result = call_fixture_func(fixturefunc, request, kwargs) if config.option.setuponly or config.option.setupplan: - # We want to access the params of ids if they exist also in during - # the finish() method. if hasattr(request, 'param'): + # Save the fixture parameter so ._show_fixture_action() can + # display it now and during the teardown (in .finish()). if self.ids: - ind = self.params.index(request.param) - self.cached_param = self.ids[ind] + if callable(self.ids): + self.cached_param = self.ids(request.param) + else: + self.cached_param = self.ids[request.param_index] else: self.cached_param = request.param - self._log_fixture_stack('SETUP') + self._show_fixture_action('SETUP') except Exception: self.cached_result = (None, my_cache_key, sys.exc_info()) raise self.cached_result = (result, my_cache_key, None) return result - def _log_fixture_stack(self, what): + def _show_fixture_action(self, what): config = self._fixturemanager.config capman = config.pluginmanager.getplugin('capturemanager') if capman: diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py index eef2857bb..e7403420b 100644 --- a/testing/python/setup_only.py +++ b/testing/python/setup_only.py @@ -159,6 +159,25 @@ def test_show_fixtures_with_parameter_ids(testdir, mode): ]) +def test_show_fixtures_with_parameter_ids_function(testdir, mode): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(params=['foo', 'bar'], ids=lambda p: p.upper()) + def foobar(): + pass + def test_foobar(foobar): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F foobar?FOO?', + '*SETUP F foobar?BAR?', + ]) + + def test_dynamic_fixture_request(testdir): p = testdir.makepyfile(''' import pytest From 032ce8baf6d0c0425f3949a630ec43fb1c2bce75 Mon Sep 17 00:00:00 2001 From: Danielle Jenkins Date: Sat, 25 Jun 2016 12:19:46 +0200 Subject: [PATCH 16/20] Switch setuponly and setupplan options to a hook-based implementation. --- _pytest/config.py | 2 +- _pytest/hookspec.py | 13 ++++++ _pytest/python.py | 109 ++++++++++++++----------------------------- _pytest/setuponly.py | 54 +++++++++++++++++++++ _pytest/setupplan.py | 14 ++++++ 5 files changed, 117 insertions(+), 75 deletions(-) create mode 100644 _pytest/setuponly.py create mode 100644 _pytest/setupplan.py diff --git a/_pytest/config.py b/_pytest/config.py index 9a308df2b..cce159623 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -65,7 +65,7 @@ _preinit = [] default_plugins = ( "mark main terminal runner python pdb unittest capture skipping " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript " - "junitxml resultlog doctest cacheprovider").split() + "junitxml resultlog doctest cacheprovider setuponly setupplan").split() builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index 639e316d0..2c6d8ba51 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -218,6 +218,19 @@ def pytest_runtest_logreport(report): """ process a test setup/call/teardown report relating to the respective phase of executing a test. """ +# ------------------------------------------------------------------------- +# Fixture related hooks +# ------------------------------------------------------------------------- + +@hookspec(firstresult=True) +def pytest_fixture_setup(fixturedef, request): + """ performs fixture setup execution. """ + +def pytest_fixture_post_finalizer(fixturedef): + """ called after fixture teardown, but before the cache is cleared so + the fixture result cache ``fixturedef.cached_result`` can + still be accessed.""" + # ------------------------------------------------------------------------- # test session related hooks # ------------------------------------------------------------------------- diff --git a/_pytest/python.py b/_pytest/python.py index edc81e923..0aa8dee0e 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -2475,25 +2475,18 @@ class FixtureDef: func = self._finalizer.pop() func() finally: + ihook = self._fixturemanager.session.ihook + ihook.pytest_fixture_post_finalizer(fixturedef=self) # even if finalization fails, we invalidate # the cached fixture value if hasattr(self, "cached_result"): - config = self._fixturemanager.config - if config.option.setuponly or config.option.setupplan: - self._show_fixture_action('TEARDOWN') - if hasattr(self, "cached_param"): - del self.cached_param del self.cached_result def execute(self, request): # get required arguments and register our own finish() # with their finalization - kwargs = {} for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) - result, arg_cache_key, exc = fixturedef.cached_result - request._check_scope(argname, request.scope, fixturedef.scope) - kwargs[argname] = result if argname != "request": fixturedef.addfinalizer(self.finish) @@ -2511,76 +2504,44 @@ class FixtureDef: self.finish() assert not hasattr(self, "cached_result") - fixturefunc = self.func - - if self.unittest: - if request.instance is not None: - # bind the unbound method to the TestCase instance - fixturefunc = self.func.__get__(request.instance) - else: - # the fixture function needs to be bound to the actual - # request.instance so that code working with "self" behaves - # as expected. - if request.instance is not None: - fixturefunc = getimfunc(self.func) - if fixturefunc != self.func: - fixturefunc = fixturefunc.__get__(request.instance) - - try: - config = request.config - if config.option.setupplan: - result = None - else: - result = call_fixture_func(fixturefunc, request, kwargs) - if config.option.setuponly or config.option.setupplan: - if hasattr(request, 'param'): - # Save the fixture parameter so ._show_fixture_action() can - # display it now and during the teardown (in .finish()). - if self.ids: - if callable(self.ids): - self.cached_param = self.ids(request.param) - else: - self.cached_param = self.ids[request.param_index] - else: - self.cached_param = request.param - self._show_fixture_action('SETUP') - except Exception: - self.cached_result = (None, my_cache_key, sys.exc_info()) - raise - self.cached_result = (result, my_cache_key, None) - return result - - def _show_fixture_action(self, what): - config = self._fixturemanager.config - capman = config.pluginmanager.getplugin('capturemanager') - if capman: - out, err = capman.suspendcapture() - - tw = config.get_terminal_writer() - tw.line() - tw.write(' ' * 2 * self.scopenum) - tw.write('{step} {scope} {fixture}'.format( - step=what.ljust(8), # align the output to TEARDOWN - scope=self.scope[0].upper(), - fixture=self.argname)) - - if what == 'SETUP': - deps = sorted(arg for arg in self.argnames if arg != 'request') - if deps: - tw.write(' (fixtures used: {0})'.format(', '.join(deps))) - - if hasattr(self, 'cached_param'): - tw.write('[{0}]'.format(self.cached_param)) - - if capman: - capman.resumecapture() - sys.stdout.write(out) - sys.stderr.write(err) + ihook = self._fixturemanager.session.ihook + ihook.pytest_fixture_setup(fixturedef=self, request=request) def __repr__(self): return ("" % (self.argname, self.scope, self.baseid)) +def pytest_fixture_setup(fixturedef, request): + """ Execution of fixture setup. """ + kwargs = {} + for argname in fixturedef.argnames: + fixdef = request._get_active_fixturedef(argname) + result, arg_cache_key, exc = fixdef.cached_result + request._check_scope(argname, request.scope, fixdef.scope) + kwargs[argname] = result + + fixturefunc = fixturedef.func + if fixturedef.unittest: + if request.instance is not None: + # bind the unbound method to the TestCase instance + fixturefunc = fixturedef.func.__get__(request.instance) + else: + # the fixture function needs to be bound to the actual + # request.instance so that code working with "fixturedef" behaves + # as expected. + if request.instance is not None: + fixturefunc = getimfunc(fixturedef.func) + if fixturefunc != fixturedef.func: + fixturefunc = fixturefunc.__get__(request.instance) + my_cache_key = request.param_index + try: + result = call_fixture_func(fixturefunc, request, kwargs) + except Exception: + fixturedef.cached_result = (None, my_cache_key, sys.exc_info()) + raise + fixturedef.cached_result = (result, my_cache_key, None) + return result + def num_mock_patch_args(function): """ return number of arguments used up by mock arguments (if any) """ patchings = getattr(function, "patchings", None) diff --git a/_pytest/setuponly.py b/_pytest/setuponly.py new file mode 100644 index 000000000..98830c76b --- /dev/null +++ b/_pytest/setuponly.py @@ -0,0 +1,54 @@ +import pytest +import sys + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef, request): + yield + config = request.config + if config.option.setuponly: + if hasattr(request, 'param'): + # Save the fixture parameter so ._show_fixture_action() can + # display it now and during the teardown (in .finish()). + if fixturedef.ids: + if callable(fixturedef.ids): + fixturedef.cached_param = fixturedef.ids(request.param) + else: + fixturedef.cached_param = fixturedef.ids[request.param_index] + else: + fixturedef.cached_param = request.param + _show_fixture_action(fixturedef, 'SETUP') + +def pytest_fixture_post_finalizer(fixturedef): + if hasattr(fixturedef, "cached_result"): + config = fixturedef._fixturemanager.config + if config.option.setuponly: + _show_fixture_action(fixturedef, 'TEARDOWN') + if hasattr(fixturedef, "cached_param"): + del fixturedef.cached_param + +def _show_fixture_action(fixturedef, msg): + config = fixturedef._fixturemanager.config + capman = config.pluginmanager.getplugin('capturemanager') + if capman: + out, err = capman.suspendcapture() + + tw = config.get_terminal_writer() + tw.line() + tw.write(' ' * 2 * fixturedef.scopenum) + tw.write('{step} {scope} {fixture}'.format( + step=msg.ljust(8), # align the output to TEARDOWN + scope=fixturedef.scope[0].upper(), + fixture=fixturedef.argname)) + + if msg == 'SETUP': + deps = sorted(arg for arg in fixturedef.argnames if arg != 'request') + if deps: + tw.write(' (fixtures used: {0})'.format(', '.join(deps))) + + if hasattr(fixturedef, 'cached_param'): + tw.write('[{0}]'.format(fixturedef.cached_param)) + + if capman: + capman.resumecapture() + sys.stdout.write(out) + sys.stderr.write(err) diff --git a/_pytest/setupplan.py b/_pytest/setupplan.py new file mode 100644 index 000000000..65df65d18 --- /dev/null +++ b/_pytest/setupplan.py @@ -0,0 +1,14 @@ +import pytest + + +@pytest.hookimpl(tryfirst=True) +def pytest_fixture_setup(fixturedef, request): + # Will return a dummy fixture if the setuponly option is provided. + if request.config.option.setupplan: + fixturedef.cached_result = (None, None, None) + return fixturedef.cached_result + +@pytest.hookimpl(tryfirst=True) +def pytest_cmdline_main(config): + if config.option.setupplan: + config.option.setuponly = True From da5c579d827ce61e2124e004ae90230711dac29b Mon Sep 17 00:00:00 2001 From: Danielle Jenkins Date: Sat, 25 Jun 2016 12:20:56 +0200 Subject: [PATCH 17/20] Move setupplan and setuponly options to their respective modules. Also, changed their group from "general" to "debugconfig". --- _pytest/main.py | 5 ----- _pytest/setuponly.py | 5 +++++ _pytest/setupplan.py | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index 063c71660..845d5dd00 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -44,11 +44,6 @@ def pytest_addoption(parser): help="run pytest in strict mode, warnings become errors.") group._addoption("-c", metavar="file", type=str, dest="inifilename", help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") - group.addoption('--setuponly', '--setup-only', action="store_true", - help="only setup fixtures, don't execute the tests.") - group.addoption('--setupplan', '--setup-plan', action="store_true", - help="show what fixtures and tests would be executed but don't" - " execute anything.") group._addoption("--continue-on-collection-errors", action="store_true", default=False, dest="continue_on_collection_errors", help="Force test execution even if collection errors occur.") diff --git a/_pytest/setuponly.py b/_pytest/setuponly.py index 98830c76b..abb578da7 100644 --- a/_pytest/setuponly.py +++ b/_pytest/setuponly.py @@ -1,6 +1,11 @@ import pytest import sys +def pytest_addoption(parser): + group = parser.getgroup("debugconfig") + group.addoption('--setuponly', '--setup-only', action="store_true", + help="only setup fixtures, don't execute the tests.") + @pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup(fixturedef, request): yield diff --git a/_pytest/setupplan.py b/_pytest/setupplan.py index 65df65d18..c7c8ff60d 100644 --- a/_pytest/setupplan.py +++ b/_pytest/setupplan.py @@ -1,5 +1,10 @@ import pytest +def pytest_addoption(parser): + group = parser.getgroup("debugconfig") + group.addoption('--setupplan', '--setup-plan', action="store_true", + help="show what fixtures and tests would be executed but don't" + " execute anything.") @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup(fixturedef, request): From 7a3daac85b871594c194433b64832ff2e0d1132e Mon Sep 17 00:00:00 2001 From: Danielle Jenkins Date: Sat, 25 Jun 2016 12:21:12 +0200 Subject: [PATCH 18/20] Add docs for setuponly and setupplan options. --- doc/en/writing_plugins.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index aeb30724c..bcd795d82 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -498,6 +498,8 @@ Session related reporting hooks: .. autofunction:: pytest_report_header .. autofunction:: pytest_report_teststatus .. autofunction:: pytest_terminal_summary +.. autofunction:: pytest_fixture_setup +.. autofunction:: pytest_fixture_post_finalizer And here is the central hook for reporting about test execution: @@ -554,6 +556,10 @@ Reference of objects involved in hooks :members: :show-inheritance: +.. autoclass:: _pytest.python.FixtureDef() + :members: + :show-inheritance: + .. autoclass:: _pytest.runner.CallInfo() :members: From 9877bf47e3cd11070bac6377ea734ca20ff364ba Mon Sep 17 00:00:00 2001 From: Danielle Jenkins Date: Sat, 25 Jun 2016 12:21:31 +0200 Subject: [PATCH 19/20] Improve commenting for setupplan unittest. --- testing/python/setup_plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/python/setup_plan.py b/testing/python/setup_plan.py index 1618fa7b3..8c9822469 100644 --- a/testing/python/setup_plan.py +++ b/testing/python/setup_plan.py @@ -1,4 +1,5 @@ def test_show_fixtures_and_test(testdir): + """ Verifies that fixtures are not executed. """ p = testdir.makepyfile(''' import pytest @pytest.fixture From 32ca5cdb09dd47547aebb2ceadfbdf511af27b69 Mon Sep 17 00:00:00 2001 From: Danielle Jenkins Date: Sat, 25 Jun 2016 12:33:31 +0200 Subject: [PATCH 20/20] Update changelog for new fixture hooks. --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3f034cf4..06df82b45 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -53,6 +53,12 @@ fixtures and reports them. Thanks `@d6e`_, `@kvas-it`_, `@sallner`_ and `@omarkohl`_ for the PR. +* Added two new hooks: ``pytest_fixture_setup`` which executes the fixture + setup and ``pytest_fixture_post_finalizer`` which is called after the fixture's + finalizer and has access to the fixture's result cache. + Thanks `@d6e`_, `@sallner`_ + + **Changes** * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like