diff --git a/.hgtags b/.hgtags index e5cfd8b7a..973aa6d7d 100644 --- a/.hgtags +++ b/.hgtags @@ -6,3 +6,4 @@ 8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4 8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4 0000000000000000000000000000000000000000 1.0.0b4 +2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6 diff --git a/CHANGELOG b/CHANGELOG index f0a0e9e91..7fb6ca16e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ -Changes between 1.0.0b3 and 1.0.0 +Changes between 1.0.0b3 and 1.0.0b6 ============================================= +* renamed py.test.xfail back to py.test.mark.xfail to avoid + two ways to decorate for xfail + +* re-added py.test.mark decorator for setting keywords on functions + (it was actually documented so removing it was not nice) + * remove scope-argument from request.addfinalizer() because request.cached_setup has the scope arg. TOOWTDI. diff --git a/MANIFEST b/MANIFEST index 8d686843d..35398168c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -325,6 +325,7 @@ py/test/plugin/pytest_execnetcleanup.py py/test/plugin/pytest_figleaf.py py/test/plugin/pytest_hooklog.py py/test/plugin/pytest_iocapture.py +py/test/plugin/pytest_keyword.py py/test/plugin/pytest_monkeypatch.py py/test/plugin/pytest_pdb.py py/test/plugin/pytest_pocoo.py diff --git a/doc/test/features.txt b/doc/test/features.txt index 7cd9babba..5898e2b0e 100644 --- a/doc/test/features.txt +++ b/doc/test/features.txt @@ -237,13 +237,15 @@ keyword. By default, all filename parts and class/function names of a test function are put into the set -of keywords for a given test. You may specify additional +of keywords for a given test. You can specify additional kewords like this:: @py.test.mark(webtest=True) def test_send_http(): ... +and then use those keywords to select tests. + disabling a test class ---------------------- diff --git a/py/execnet/testing/test_gateway.py b/py/execnet/testing/test_gateway.py index f5bba5b90..7b8514e38 100644 --- a/py/execnet/testing/test_gateway.py +++ b/py/execnet/testing/test_gateway.py @@ -543,7 +543,7 @@ class TestPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution): ret = channel.receive() assert ret == 42 - @py.test.xfail # "fix needed: dying remote process does not cause waitclose() to fail" + @py.test.mark.xfail # "fix needed: dying remote process does not cause waitclose() to fail" def test_waitclose_on_remote_killed(self): gw = py.execnet.PopenGateway() channel = gw.remote_exec(""" @@ -616,12 +616,12 @@ class TestSshGateway(BasicRemoteExecution): def test_sshaddress(self): assert self.gw.remoteaddress == self.sshhost - @py.test.xfail # XXX ssh-gateway error handling + @py.test.mark.xfail # XXX ssh-gateway error handling def test_connexion_failes_on_non_existing_hosts(self): py.test.raises(IOError, "py.execnet.SshGateway('nowhere.codespeak.net')") - @py.test.xfail # "XXX ssh-gateway error handling" + @py.test.mark.xfail # "XXX ssh-gateway error handling" def test_deprecated_identity(self): py.test.deprecated_call( py.test.raises, IOError, diff --git a/py/test/defaultconftest.py b/py/test/defaultconftest.py index 78596f81a..6c7459df5 100644 --- a/py/test/defaultconftest.py +++ b/py/test/defaultconftest.py @@ -10,5 +10,5 @@ Generator = py.test.collect.Generator Function = py.test.collect.Function Instance = py.test.collect.Instance -pytest_plugins = "default runner terminal xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split() +pytest_plugins = "default runner terminal keyword xfail tmpdir execnetcleanup monkeypatch recwarn pdb".split() diff --git a/py/test/dist/testing/test_dsession.py b/py/test/dist/testing/test_dsession.py index 542ae9ba8..02679a75c 100644 --- a/py/test/dist/testing/test_dsession.py +++ b/py/test/dist/testing/test_dsession.py @@ -367,7 +367,7 @@ class TestDSession: assert node.gateway.spec.popen #XXX eq.geteventargs("pytest_sessionfinish") - @py.test.xfail + @py.test.mark.xfail def test_collected_function_causes_remote_skip_at_module_level(self, testdir): p = testdir.makepyfile(""" import py diff --git a/py/test/dist/testing/test_nodemanage.py b/py/test/dist/testing/test_nodemanage.py index b95a1c4c8..da4fd979b 100644 --- a/py/test/dist/testing/test_nodemanage.py +++ b/py/test/dist/testing/test_nodemanage.py @@ -11,7 +11,7 @@ class pytest_funcarg__mysetup: request.getfuncargvalue("_pytest") class TestNodeManager: - @py.test.xfail + @py.test.mark.xfail def test_rsync_roots_no_roots(self, mysetup): mysetup.source.ensure("dir1", "file1").write("hello") config = py.test.config._reparse([source]) diff --git a/py/test/plugin/hookspec.py b/py/test/plugin/hookspec.py index 07c65ce99..f1cfc108b 100644 --- a/py/test/plugin/hookspec.py +++ b/py/test/plugin/hookspec.py @@ -90,7 +90,7 @@ def pytest_runtest_protocol(item): pytest_runtest_protocol.firstresult = True def pytest_runtest_makereport(item, call): - """ make ItemTestReport for the specified test outcome. """ + """ make ItemTestReport for the given item and call outcome. """ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(rep): diff --git a/py/test/plugin/pytest_keyword.py b/py/test/plugin/pytest_keyword.py new file mode 100644 index 000000000..290187226 --- /dev/null +++ b/py/test/plugin/pytest_keyword.py @@ -0,0 +1,79 @@ +""" + py.test.mark / keyword plugin + +""" +import py + +def pytest_namespace(config): + mark = KeywordDecorator({}) + return {'mark': mark} + +class KeywordDecorator: + """ decorator for setting function attributes. """ + def __init__(self, keywords, lastname=None): + self._keywords = keywords + self._lastname = lastname + + def __call__(self, func=None, **kwargs): + if func is None: + kw = self._keywords.copy() + kw.update(kwargs) + return KeywordDecorator(kw) + elif not hasattr(func, 'func_dict'): + kw = self._keywords.copy() + name = self._lastname + if name is None: + name = "mark" + kw[name] = func + return KeywordDecorator(kw) + func.func_dict.update(self._keywords) + return func + + def __getattr__(self, name): + if name[0] == "_": + raise AttributeError(name) + kw = self._keywords.copy() + kw[name] = True + return self.__class__(kw, lastname=name) + +def test_pytest_mark_getattr(): + mark = KeywordDecorator({}) + def f(): pass + + mark.hello(f) + assert f.hello == True + + mark.hello("test")(f) + assert f.hello == "test" + + py.test.raises(AttributeError, "mark._hello") + py.test.raises(AttributeError, "mark.__str__") + +def test_pytest_mark_call(): + mark = KeywordDecorator({}) + def f(): pass + mark(x=3)(f) + assert f.x == 3 + def g(): pass + mark(g) + assert not g.func_dict + + mark.hello(f) + assert f.hello == True + + mark.hello("test")(f) + assert f.hello == "test" + + mark("x1")(f) + assert f.mark == "x1" + +def test_mark_plugin(testdir): + p = testdir.makepyfile(""" + import py + pytest_plugins = "keyword" + @py.test.mark.hello + def test_hello(): + assert hasattr(test_hello, 'hello') + """) + result = testdir.runpytest(p) + assert result.stdout.fnmatch_lines(["*passed*"]) diff --git a/py/test/plugin/pytest_xfail.py b/py/test/plugin/pytest_xfail.py index 77e9274b0..d53bd1a6b 100644 --- a/py/test/plugin/pytest_xfail.py +++ b/py/test/plugin/pytest_xfail.py @@ -3,13 +3,15 @@ mark tests as expected-to-fail and report them separately. example: - @py.test.xfail + @py.test.mark.xfail def test_hello(): ... assert 0 """ import py +pytest_plugins = ['keyword'] + def pytest_runtest_makereport(__call__, item, call): if call.when != "call": return @@ -52,12 +54,6 @@ def pytest_terminal_summary(terminalreporter): for event in xpassed: tr._tw.line("%s: xpassed" %(event.item,)) -def xfail_decorator(func): - func.xfail = True - return func - -def pytest_namespace(config): - return dict(xfail=xfail_decorator) # =============================================================================== # @@ -68,11 +64,11 @@ def pytest_namespace(config): def test_xfail(testdir, linecomp): p = testdir.makepyfile(test_one=""" import py - @py.test.xfail + @py.test.mark.xfail def test_this(): assert 0 - @py.test.xfail + @py.test.mark.xfail def test_that(): assert 1 """) diff --git a/py/test/testing/test_pluginmanager.py b/py/test/testing/test_pluginmanager.py index e1d4ec3e8..b1fc23f28 100644 --- a/py/test/testing/test_pluginmanager.py +++ b/py/test/testing/test_pluginmanager.py @@ -221,7 +221,7 @@ class TestPytestPluginInteractions: assert not pluginmanager.listattr("hello") assert pluginmanager.listattr("x") == [42] - @py.test.xfail # setup call methods + @py.test.mark.xfail # setup call methods def test_call_setup_participants(self, testdir): testdir.makepyfile( conftest="""