diff --git a/_pytest/capture.py b/_pytest/capture.py index 844c9dc0e..0042b274b 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,8 +29,8 @@ def pytest_addoption(parser): help="shortcut for --capture=no.") -@pytest.mark.tryfirst -def pytest_load_initial_conftests(early_config, parser, args, __multicall__): +@pytest.mark.hookwrapper +def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace pluginmanager = early_config.pluginmanager capman = CaptureManager(ns.capture) @@ -47,15 +47,11 @@ def pytest_load_initial_conftests(early_config, parser, args, __multicall__): # finally trigger conftest loading but while capturing (issue93) capman.init_capturings() - try: - try: - return __multicall__.execute() - finally: - out, err = capman.suspendcapture() - except: + outcome = yield + out, err = capman.suspendcapture() + if outcome.excinfo is not None: sys.stdout.write(out) sys.stderr.write(err) - raise class CaptureManager: @@ -105,20 +101,19 @@ class CaptureManager: if capfuncarg is not None: capfuncarg.close() - @pytest.mark.tryfirst - def pytest_make_collect_report(self, __multicall__, collector): - if not isinstance(collector, pytest.File): - return - self.resumecapture() - try: - rep = __multicall__.execute() - finally: + @pytest.mark.hookwrapper + def pytest_make_collect_report(self, collector): + if isinstance(collector, pytest.File): + self.resumecapture() + outcome = yield out, err = self.suspendcapture() - if out: - rep.sections.append(("Captured stdout", out)) - if err: - rep.sections.append(("Captured stderr", err)) - return rep + rep = outcome.get_result() + if out: + rep.sections.append(("Captured stdout", out)) + if err: + rep.sections.append(("Captured stderr", err)) + else: + yield @pytest.mark.hookwrapper def pytest_runtest_setup(self, item): diff --git a/_pytest/core.py b/_pytest/core.py index 4921e97c7..f6377f912 100644 --- a/_pytest/core.py +++ b/_pytest/core.py @@ -95,7 +95,10 @@ def wrapped_call(wrap_controller, func): will trigger calling the function and receive an according CallOutcome object representing an exception or a result. """ - next(wrap_controller) # first yield + try: + next(wrap_controller) # first yield + except StopIteration: + return call_outcome = CallOutcome(func) try: wrap_controller.send(call_outcome) @@ -104,13 +107,7 @@ def wrapped_call(wrap_controller, func): (co.co_name, co.co_filename, co.co_firstlineno)) except StopIteration: pass - if call_outcome.excinfo is None: - return call_outcome.result - else: - ex = call_outcome.excinfo - if py3: - raise ex[1].with_traceback(ex[2]) - py.builtin._reraise(*ex) + return call_outcome.get_result() class CallOutcome: @@ -125,6 +122,15 @@ class CallOutcome: self.result = result self.excinfo = None + def get_result(self): + if self.excinfo is None: + return self.result + else: + ex = self.excinfo + if py3: + raise ex[1].with_traceback(ex[2]) + py.builtin._reraise(*ex) + class PluginManager(object): def __init__(self, hookspecs=None, prefix="pytest_"): diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index bffada8d4..d79fc671a 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -22,18 +22,21 @@ def pytest_addoption(parser): help="store internal tracing debug information in 'pytestdebug.log'.") -def pytest_cmdline_parse(__multicall__): - config = __multicall__.execute() +@pytest.mark.hookwrapper +def pytest_cmdline_parse(): + outcome = yield + config = outcome.get_result() if config.option.debug: path = os.path.abspath("pytestdebug.log") f = open(path, 'w') config._debugfile = f - f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %( - pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)), + f.write("versions pytest-%s, py-%s, " + "python-%s\ncwd=%s\nargs=%s\n\n" %( + pytest.__version__, py.__version__, + ".".join(map(str, sys.version_info)), os.getcwd(), config._origargs)) config.pluginmanager.set_tracing(f.write) sys.stderr.write("writing pytestdebug information to %s\n" % path) - return config @pytest.mark.trylast def pytest_unconfigure(config): diff --git a/_pytest/nose.py b/_pytest/nose.py index 203db9850..089807b66 100644 --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -16,7 +16,7 @@ def get_skip_exceptions(): return tuple(skip_classes) -def pytest_runtest_makereport(__multicall__, item, call): +def pytest_runtest_makereport(item, call): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): # let's substitute the excinfo with a pytest.skip one call2 = call.__class__(lambda: diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py index cc9d0b5f0..4151fbf0d 100644 --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -1,5 +1,7 @@ """ submit failure or test session information to a pastebin service. """ +import pytest import py, sys +import tempfile class url: base = "http://bpaste.net" @@ -13,9 +15,8 @@ def pytest_addoption(parser): choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") -def pytest_configure(__multicall__, config): - import tempfile - __multicall__.execute() +@pytest.mark.trylast +def pytest_configure(config): if config.option.pastebin == "all": config._pastebinfile = tempfile.TemporaryFile('w+') tr = config.pluginmanager.getplugin('terminalreporter') diff --git a/_pytest/python.py b/_pytest/python.py index 9e9d34183..93144815c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -183,17 +183,18 @@ def pytestconfig(request): return request.config -def pytest_pyfunc_call(__multicall__, pyfuncitem): - if not __multicall__.execute(): - testfunction = pyfuncitem.obj - if pyfuncitem._isyieldedfunction(): - testfunction(*pyfuncitem._args) - else: - funcargs = pyfuncitem.funcargs - testargs = {} - for arg in pyfuncitem._fixtureinfo.argnames: - testargs[arg] = funcargs[arg] - testfunction(**testargs) +@pytest.mark.trylast +def pytest_pyfunc_call(pyfuncitem): + testfunction = pyfuncitem.obj + if pyfuncitem._isyieldedfunction(): + testfunction(*pyfuncitem._args) + else: + funcargs = pyfuncitem.funcargs + testargs = {} + for arg in pyfuncitem._fixtureinfo.argnames: + testargs[arg] = funcargs[arg] + testfunction(**testargs) + return True def pytest_collect_file(path, parent): ext = path.ext @@ -210,30 +211,31 @@ def pytest_collect_file(path, parent): def pytest_pycollect_makemodule(path, parent): return Module(path, parent) -def pytest_pycollect_makeitem(__multicall__, collector, name, obj): - res = __multicall__.execute() +@pytest.mark.hookwrapper +def pytest_pycollect_makeitem(collector, name, obj): + outcome = yield + res = outcome.get_result() if res is not None: - return res + raise StopIteration + # nothing was collected elsewhere, let's do it here if isclass(obj): - #if hasattr(collector.obj, 'unittest'): - # return # we assume it's a mixin class for a TestCase derived one if collector.classnamefilter(name): Class = collector._getcustomclass("Class") - return Class(name, parent=collector) - elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and \ + outcome.force_result(Class(name, parent=collector)) + elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and\ getfixturemarker(obj) is None: - # mock seems to store unbound methods (issue473), let's normalize it + # mock seems to store unbound methods (issue473), normalize it obj = getattr(obj, "__func__", obj) if not isfunction(obj): collector.warn(code="C2", message= "cannot collect %r because it is not a function." % name, ) - return if getattr(obj, "__test__", True): if is_generator(obj): - return Generator(name, parent=collector) + res = Generator(name, parent=collector) else: - return list(collector._genfunctions(name, obj)) + res = list(collector._genfunctions(name, obj)) + outcome.force_result(res) def is_generator(func): try: diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 188db6122..4a77f72c8 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -57,7 +57,7 @@ class MarkEvaluator: @property def holder(self): - return self.item.keywords.get(self.name, None) + return self.item.keywords.get(self.name) def __bool__(self): return bool(self.holder) @@ -75,9 +75,7 @@ class MarkEvaluator: def istrue(self): try: return self._istrue() - except KeyboardInterrupt: - raise - except: + except Exception: self.exc = sys.exc_info() if isinstance(self.exc[1], SyntaxError): msg = [" " * (self.exc[1].offset + 4) + "^",] @@ -153,44 +151,32 @@ def check_xfail_no_run(item): if not evalxfail.get('run', True): pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) -def pytest_runtest_makereport(__multicall__, item, call): +@pytest.mark.hookwrapper +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + evalxfail = getattr(item, '_evalxfail', None) # unitttest special case, see setting of _unexpectedsuccess - if hasattr(item, '_unexpectedsuccess'): - rep = __multicall__.execute() - if rep.when == "call": - # we need to translate into how pytest encodes xpass - rep.wasxfail = "reason: " + repr(item._unexpectedsuccess) - rep.outcome = "failed" - return rep - if not (call.excinfo and - call.excinfo.errisinstance(pytest.xfail.Exception)): - evalxfail = getattr(item, '_evalxfail', None) - if not evalxfail: - return - if call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): - if not item.config.getvalue("runxfail"): - rep = __multicall__.execute() - rep.wasxfail = "reason: " + call.excinfo.value.msg - rep.outcome = "skipped" - return rep - rep = __multicall__.execute() - evalxfail = item._evalxfail - if not rep.skipped: - if not item.config.option.runxfail: - if evalxfail.wasvalid() and evalxfail.istrue(): - if call.excinfo: - if evalxfail.invalidraise(call.excinfo.value): - rep.outcome = "failed" - return rep - else: - rep.outcome = "skipped" - elif call.when == "call": - rep.outcome = "failed" - else: - return rep + if hasattr(item, '_unexpectedsuccess') and rep.when == "call": + # we need to translate into how pytest encodes xpass + rep.wasxfail = "reason: " + repr(item._unexpectedsuccess) + rep.outcome = "failed" + elif item.config.option.runxfail: + pass # don't interefere + elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): + rep.wasxfail = "reason: " + call.excinfo.value.msg + rep.outcome = "skipped" + elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \ + evalxfail.istrue(): + if call.excinfo: + if evalxfail.invalidraise(call.excinfo.value): + rep.outcome = "failed" + else: + rep.outcome = "skipped" rep.wasxfail = evalxfail.getexplanation() - return rep - return rep + elif call.when == "call": + rep.outcome = "failed" # xpass outcome + rep.wasxfail = evalxfail.getexplanation() # called by terminalreporter progress reporting def pytest_report_teststatus(report): diff --git a/_pytest/terminal.py b/_pytest/terminal.py index b73f3f113..2b8ca1510 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -345,8 +345,10 @@ class TerminalReporter: indent = (len(stack) - 1) * " " self._tw.line("%s%s" % (indent, col)) - def pytest_sessionfinish(self, exitstatus, __multicall__): - __multicall__.execute() + @pytest.mark.hookwrapper + def pytest_sessionfinish(self, exitstatus): + outcome = yield + outcome.get_result() self._tw.line("") if exitstatus in (0, 1, 2, 4): self.summary_errors() diff --git a/_pytest/unittest.py b/_pytest/unittest.py index af2aa9357..c035bdd1a 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -151,30 +151,33 @@ def pytest_runtest_makereport(item, call): pass # twisted trial support -def pytest_runtest_protocol(item, __multicall__): - if isinstance(item, TestCaseFunction): - if 'twisted.trial.unittest' in sys.modules: - ut = sys.modules['twisted.python.failure'] - Failure__init__ = ut.Failure.__init__ - check_testcase_implements_trial_reporter() - def excstore(self, exc_value=None, exc_type=None, exc_tb=None, - captureVars=None): - if exc_value is None: - self._rawexcinfo = sys.exc_info() - else: - if exc_type is None: - exc_type = type(exc_value) - self._rawexcinfo = (exc_type, exc_value, exc_tb) - try: - Failure__init__(self, exc_value, exc_type, exc_tb, - captureVars=captureVars) - except TypeError: - Failure__init__(self, exc_value, exc_type, exc_tb) - ut.Failure.__init__ = excstore + +@pytest.mark.hookwrapper +def pytest_runtest_protocol(item): + if isinstance(item, TestCaseFunction) and \ + 'twisted.trial.unittest' in sys.modules: + ut = sys.modules['twisted.python.failure'] + Failure__init__ = ut.Failure.__init__ + check_testcase_implements_trial_reporter() + def excstore(self, exc_value=None, exc_type=None, exc_tb=None, + captureVars=None): + if exc_value is None: + self._rawexcinfo = sys.exc_info() + else: + if exc_type is None: + exc_type = type(exc_value) + self._rawexcinfo = (exc_type, exc_value, exc_tb) try: - return __multicall__.execute() - finally: - ut.Failure.__init__ = Failure__init__ + Failure__init__(self, exc_value, exc_type, exc_tb, + captureVars=captureVars) + except TypeError: + Failure__init__(self, exc_value, exc_type, exc_tb) + ut.Failure.__init__ = excstore + yield + ut.Failure.__init__ = Failure__init__ + else: + yield + def check_testcase_implements_trial_reporter(done=[]): if done: diff --git a/testing/python/collect.py b/testing/python/collect.py index 8acd40ffa..15ff2b62b 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -525,12 +525,15 @@ class TestConftestCustomization: def test_customized_pymakeitem(self, testdir): b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write(py.code.Source(""" - def pytest_pycollect_makeitem(__multicall__): - result = __multicall__.execute() - if result: - for func in result: - func._some123 = "world" - return result + import pytest + @pytest.mark.hookwrapper + def pytest_pycollect_makeitem(): + outcome = yield + if outcome.excinfo is None: + result = outcome.result + if result: + for func in result: + func._some123 = "world" """)) b.join("test_module.py").write(py.code.Source(""" import pytest diff --git a/testing/test_mark.py b/testing/test_mark.py index af0b748b6..a7ee038ea 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -509,11 +509,13 @@ class TestKeywordSelection: pass """) testdir.makepyfile(conftest=""" - def pytest_pycollect_makeitem(__multicall__, name): + import pytest + @pytest.mark.hookwrapper + def pytest_pycollect_makeitem(name): + outcome = yield if name == "TestClass": - item = __multicall__.execute() + item = outcome.get_result() item.extra_keyword_matches.add("xxx") - return item """) reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword) py.builtin.print_("keyword", repr(keyword))