1 new commit in pytest: https://bitbucket.org/hpk42/pytest/commits/bd9f28f011ea/ Changeset: bd9f28f011ea Branch: yield-test-run-inline User: RonnyPfannschmidt Date: 2015-02-10 23:33:18+00:00 Summary: addresses #16 - replace Generator with pytest_pyfunc_interpret_result Affected #: 10 files
diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,16 @@ 2.7.0.dev (compared to 2.6.4) ----------------------------- +- introduce the hook pytest_pyfunc_interpret_result + this eases interpreting test functions results like generators, + twisted inline defereds, futures and asyncio generators + +- replace the Generator concept with the pytest_pyfunc_interpret_result hook + (Note: this change reduces reporting detail for generator tests, + that will be addressed in a later release) + + the Generator class is now a alias to Function + - add ability to set command line options by environment variable PYTEST_ADDOPTS. - fix issue655: work around different ways that cause python2/3 diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -136,6 +136,10 @@ """ call underlying test function. """ pytest_pyfunc_call.firstresult = True + +def pytest_pyfunc_interpret_result(pyfuncitem, result): + """ interpret the return value of the underlying test function. """ + def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -27,13 +27,6 @@ @pytest.mark.trylast def pytest_runtest_setup(item): if is_potential_nosetest(item): - if isinstance(item.parent, pytest.Generator): - gen = item.parent - if not hasattr(gen, '_nosegensetup'): - call_optional(gen.obj, 'setup') - if isinstance(gen.parent, pytest.Instance): - call_optional(gen.parent.obj, 'setup') - gen._nosegensetup = True if not call_optional(item.obj, 'setup'): # call module level setup if there is no object level one call_optional(item.parent.obj, 'setup') @@ -49,11 +42,6 @@ # del item.parent._nosegensetup -def pytest_make_collect_report(collector): - if isinstance(collector, pytest.Generator): - call_optional(collector.obj, 'setup') - - def is_potential_nosetest(item): # extra check needed since we do not do nose style setup/teardown # on direct unittest style classes diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -177,7 +177,9 @@ 'raises' : raises, 'collect': { 'Module': Module, 'Class': Class, 'Instance': Instance, - 'Function': Function, 'Generator': Generator, + 'Function': Function, + # TODO: backward compatibility check + 'Generator': Function, '_fillfuncargs': fillfixtures} } @@ -188,16 +190,38 @@ @pytest.mark.trylast +def pytest_pyfunc_interpret_result(pyfuncitem, result): + if inspect.isgenerator(result): + pyfuncitem.warn( + code='G01', + message='generator test, reporting is limited') + for check in result: + # TODO(ronny): subtest support needed + if callable(check): + check() + elif not isinstance(check, tuple): + pytest.fail('%r is not a check' % (check,)) + elif callable(check[0]): + check[0](*check[1:]) + elif callable(check[1]): + check[1](*check[2:]) + else: + pytest.fail( + 'generator test check not callable \n%r' % (check,)) + + +@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) + funcargs = pyfuncitem.funcargs + testargs = {} + for arg in pyfuncitem._fixtureinfo.argnames: + testargs[arg] = funcargs[arg] + result = testfunction(**testargs) + if result is not None: + pyfuncitem.ihook.pytest_pyfunc_interpret_result( + pyfuncitem=pyfuncitem, + result=result) return True def pytest_collect_file(path, parent): @@ -235,10 +259,7 @@ "cannot collect %r because it is not a function." % name, ) if getattr(obj, "__test__", True): - if is_generator(obj): - res = Generator(name, parent=collector) - else: - res = list(collector._genfunctions(name, obj)) + res = list(collector._genfunctions(name, obj)) outcome.force_result(res) def is_generator(func): @@ -621,43 +642,6 @@ return self._repr_failure_py(excinfo, style=style) -class Generator(FunctionMixin, PyCollector): - def collect(self): - # test generators are seen as collectors but they also - # invoke setup/teardown on popular request - # (induced by the common "test_*" naming shared with normal tests) - self.session._setupstate.prepare(self) - # see FunctionMixin.setup and test_setupstate_is_preserved_134 - self._preservedparent = self.parent.obj - l = [] - seen = {} - for i, x in enumerate(self.obj()): - name, call, args = self.getcallargs(x) - if not callable(call): - raise TypeError("%r yielded non callable test %r" %(self.obj, call,)) - if name is None: - name = "[%d]" % i - else: - name = "['%s']" % name - if name in seen: - raise ValueError("%r generated tests with non-unique name %r" %(self, name)) - seen[name] = True - l.append(self.Function(name, self, args=args, callobj=call)) - return l - - def getcallargs(self, obj): - if not isinstance(obj, (tuple, list)): - obj = (obj,) - # explict naming - if isinstance(obj[0], py.builtin._basestring): - name = obj[0] - obj = obj[1:] - else: - name = None - call, args = obj[0], obj[1:] - return name, call, args - - def hasinit(obj): init = getattr(obj, '__init__', None) if init: diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -76,199 +76,6 @@ ]) -class TestGenerator: - def test_generative_functions(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == '[0]' - assert gencolitems[0].obj.__name__ == 'func1' - - def test_generative_methods(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods: - def test_gen(self): - yield func1, 17, 3*5 - yield func1, 42, 6*7 - """) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == '[0]' - assert gencolitems[0].obj.__name__ == 'func1' - - def test_generative_functions_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - - def test_gen(): - yield "seventeen", func1, 17, 3*5 - yield "fortytwo", func1, 42, 6*7 - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "['seventeen']" - assert gencolitems[0].obj.__name__ == 'func1' - assert gencolitems[1].name == "['fortytwo']" - assert gencolitems[1].obj.__name__ == 'func1' - - def test_generative_functions_unique_explicit_names(self, testdir): - # generative - modcol = testdir.getmodulecol(""" - def func(): pass - def test_gen(): - yield "name", func - yield "name", func - """) - colitems = modcol.collect() - assert len(colitems) == 1 - gencol = colitems[0] - assert isinstance(gencol, pytest.Generator) - pytest.raises(ValueError, "gencol.collect()") - - def test_generative_methods_with_explicit_names(self, testdir): - modcol = testdir.getmodulecol(""" - def func1(arg, arg2): - assert arg == arg2 - class TestGenMethods: - def test_gen(self): - yield "m1", func1, 17, 3*5 - yield "m2", func1, 42, 6*7 - """) - gencol = modcol.collect()[0].collect()[0].collect()[0] - assert isinstance(gencol, pytest.Generator) - gencolitems = gencol.collect() - assert len(gencolitems) == 2 - assert isinstance(gencolitems[0], pytest.Function) - assert isinstance(gencolitems[1], pytest.Function) - assert gencolitems[0].name == "['m1']" - assert gencolitems[0].obj.__name__ == 'func1' - assert gencolitems[1].name == "['m2']" - assert gencolitems[1].obj.__name__ == 'func1' - - def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): - o = testdir.makepyfile(""" - def test_generative_order_of_execution(): - import py, pytest - test_list = [] - expected_list = list(range(6)) - - def list_append(item): - test_list.append(item) - - def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) - assert test_list == expected_list - - for i in expected_list: - yield list_append, i - yield assert_order_of_execution - """) - reprec = testdir.inline_run(o) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 7 - assert not skipped and not failed - - def test_order_of_execution_generator_different_codeline(self, testdir): - o = testdir.makepyfile(""" - def test_generative_tests_different_codeline(): - import py, pytest - test_list = [] - expected_list = list(range(3)) - - def list_append_2(): - test_list.append(2) - - def list_append_1(): - test_list.append(1) - - def list_append_0(): - test_list.append(0) - - def assert_order_of_execution(): - py.builtin.print_('expected order', expected_list) - py.builtin.print_('but got ', test_list) - assert test_list == expected_list - - yield list_append_0 - yield list_append_1 - yield list_append_2 - yield assert_order_of_execution - """) - reprec = testdir.inline_run(o) - passed, skipped, failed = reprec.countoutcomes() - assert passed == 4 - assert not skipped and not failed - - def test_setupstate_is_preserved_134(self, testdir): - # yield-based tests are messy wrt to setupstate because - # during collection they already invoke setup functions - # and then again when they are run. For now, we want to make sure - # that the old 1.3.4 behaviour is preserved such that all - # yielded functions all share the same "self" instance that - # has been used during collection. - o = testdir.makepyfile(""" - setuplist = [] - class TestClass: - def setup_method(self, func): - #print "setup_method", self, func - setuplist.append(self) - self.init = 42 - - def teardown_method(self, func): - self.init = None - - def test_func1(self): - pass - - def test_func2(self): - yield self.func2 - yield self.func2 - - def func2(self): - assert self.init - - def test_setuplist(): - # once for test_func2 during collection - # once for test_func1 during test run - # once for test_func2 during test run - #print setuplist - assert len(setuplist) == 3, len(setuplist) - assert setuplist[0] == setuplist[2], setuplist - assert setuplist[1] != setuplist[2], setuplist - """) - reprec = testdir.inline_run(o, '-v') - passed, skipped, failed = reprec.countoutcomes() - assert passed == 4 - assert not skipped and not failed - - class TestFunction: def test_getmodulecollector(self, testdir): item = testdir.getitem("def test_func(): pass") @@ -690,37 +497,6 @@ assert lineno == 1 assert msg == "TestClass" - def test_generator_reportinfo(self, testdir): - modcol = testdir.getmodulecol(""" - # lineno 0 - def test_gen(): - def check(x): - assert x - yield check, 3 - """) - gencol = testdir.collect_by_name(modcol, "test_gen") - fspath, lineno, modpath = gencol.reportinfo() - assert fspath == modcol.fspath - assert lineno == 1 - assert modpath == "test_gen" - - genitem = gencol.collect()[0] - fspath, lineno, modpath = genitem.reportinfo() - assert fspath == modcol.fspath - assert lineno == 2 - assert modpath == "test_gen[0]" - """ - def test_func(): - pass - def test_genfunc(): - def check(x): - pass - yield check, 3 - class TestClass: - def test_method(self): - pass - """ - def test_customized_python_discovery(testdir): testdir.makeini(""" diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/python/fixture.py --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1246,9 +1246,7 @@ yield f, -3 """) reprec = testdir.inline_run() - reprec.assertoutcome(passed=2) - - + reprec.assertoutcome(passed=1) def test_funcarg_and_setup(self, testdir): testdir.makepyfile(""" diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/test_nose.py --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -146,6 +146,7 @@ ]) +@pytest.mark.xfail(reason='generator tests dont hook into setup/teardown') def test_nose_test_generator_fixtures(testdir): p = testdir.makepyfile(""" # taken from nose-0.11.1 unit_tests/test_generator_fixtures.py diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/test_runner_xunit.py --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -1,6 +1,7 @@ # # test correct setup/teardowns at # module, class, and instance level +import pytest def test_module_and_function_setup(testdir): reprec = testdir.inline_runsource(""" @@ -141,6 +142,8 @@ """) reprec.assertoutcome(failed=1, passed=1) + +@pytest.mark.xfail(reason='generator check reporting detail got reduced') def test_method_generator_setup(testdir): reprec = testdir.inline_runsource(""" class TestSetupTeardownOnInstance: diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/test_session.py --- a/testing/test_session.py +++ b/testing/test_session.py @@ -61,9 +61,10 @@ def test_1(): yield None """) - failures = reprec.getfailedcollections() - out = failures[0].longrepr.reprcrash.message - i = out.find('TypeError') + passed, skipped, failed = reprec.listoutcomes() + assert len(failed) == 1 + out = failed[0].longrepr.reprcrash.message + i = out.find('not a check') assert i != -1 def test_syntax_error_module(self, testdir): diff -r 46d5ac0ba396af887969a3cc9940fccba5ff1955 -r bd9f28f011eafe48ddf95b6a3e9dd260aa69c99f testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -453,7 +453,7 @@ "*test_verbose_reporting.py::test_fail *FAIL*", "*test_verbose_reporting.py::test_pass *PASS*", "*test_verbose_reporting.py::TestClass::test_skip *SKIP*", - "*test_verbose_reporting.py::test_gen*0* *FAIL*", + "*test_verbose_reporting.py::test_gen *FAIL*", ]) assert result.ret == 1 Repository URL: https://bitbucket.org/hpk42/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit