diff --git a/pytest/_core.py b/pytest/_core.py index 82adcac9f..650aa5b1c 100644 --- a/pytest/_core.py +++ b/pytest/_core.py @@ -4,7 +4,7 @@ import sys, os default_plugins = ( "session terminal python runner pdb capture mark skipping tmpdir monkeypatch " "recwarn pastebin unittest helpconfig nose assertion genscript " - "junitxml doctest keyword").split() + "junitxml doctest").split() def main(args=None): import sys diff --git a/pytest/plugin/keyword.py b/pytest/plugin/keyword.py deleted file mode 100644 index 68d0f5d03..000000000 --- a/pytest/plugin/keyword.py +++ /dev/null @@ -1,65 +0,0 @@ - -def pytest_addoption(parser): - group = parser.getgroup("general") - group._addoption('-k', - action="store", dest="keyword", default='', - help="only run test items matching the given " - "space separated keywords. precede a keyword with '-' to negate. " - "Terminate the expression with ':' to treat a match as a signal " - "to run all subsequent tests. ") - -def pytest_collection_modifyitems(items, config): - keywordexpr = config.option.keyword - if not keywordexpr: - return - selectuntil = False - if keywordexpr[-1] == ":": - selectuntil = True - keywordexpr = keywordexpr[:-1] - - remaining = [] - deselected = [] - for colitem in items: - if keywordexpr and skipbykeyword(colitem, keywordexpr): - deselected.append(colitem) - else: - remaining.append(colitem) - if selectuntil: - keywordexpr = None - - if deselected: - config.hook.pytest_deselected(items=deselected) - items[:] = remaining - -def skipbykeyword(colitem, keywordexpr): - """ return True if they given keyword expression means to - skip this collector/item. - """ - if not keywordexpr: - return - chain = colitem.listchain() - for key in filter(None, keywordexpr.split()): - eor = key[:1] == '-' - if eor: - key = key[1:] - if not (eor ^ matchonekeyword(key, chain)): - return True - -def matchonekeyword(key, chain): - elems = key.split(".") - # XXX O(n^2), anyone cares? - chain = [item.keywords for item in chain if item.keywords] - for start, _ in enumerate(chain): - if start + len(elems) > len(chain): - return False - for num, elem in enumerate(elems): - for keyword in chain[num + start]: - ok = False - if elem in keyword: - ok = True - break - if not ok: - break - if num == len(elems) - 1 and ok: - return True - return False diff --git a/pytest/plugin/mark.py b/pytest/plugin/mark.py index 35ba91541..f49c74786 100644 --- a/pytest/plugin/mark.py +++ b/pytest/plugin/mark.py @@ -87,6 +87,71 @@ import py def pytest_namespace(): return {'mark': MarkGenerator()} +def pytest_addoption(parser): + group = parser.getgroup("general") + group._addoption('-k', + action="store", dest="keyword", default='', + help="only run test items matching the given " + "space separated keywords. precede a keyword with '-' to negate. " + "Terminate the expression with ':' to treat a match as a signal " + "to run all subsequent tests. ") + +def pytest_collection_modifyitems(items, config): + keywordexpr = config.option.keyword + if not keywordexpr: + return + selectuntil = False + if keywordexpr[-1] == ":": + selectuntil = True + keywordexpr = keywordexpr[:-1] + + remaining = [] + deselected = [] + for colitem in items: + if keywordexpr and skipbykeyword(colitem, keywordexpr): + deselected.append(colitem) + else: + remaining.append(colitem) + if selectuntil: + keywordexpr = None + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + +def skipbykeyword(colitem, keywordexpr): + """ return True if they given keyword expression means to + skip this collector/item. + """ + if not keywordexpr: + return + chain = colitem.listchain() + for key in filter(None, keywordexpr.split()): + eor = key[:1] == '-' + if eor: + key = key[1:] + if not (eor ^ matchonekeyword(key, chain)): + return True + +def matchonekeyword(key, chain): + elems = key.split(".") + # XXX O(n^2), anyone cares? + chain = [item.keywords for item in chain if item.keywords] + for start, _ in enumerate(chain): + if start + len(elems) > len(chain): + return False + for num, elem in enumerate(elems): + for keyword in chain[num + start]: + ok = False + if elem in keyword: + ok = True + break + if not ok: + break + if num == len(elems) - 1 and ok: + return True + return False + class MarkGenerator: """ non-underscore attributes of this object can be used as decorators for marking test functions. Example: @py.test.mark.slowtest in front of a diff --git a/testing/plugin/test_keyword.py b/testing/plugin/test_keyword.py deleted file mode 100644 index 2f97c28a3..000000000 --- a/testing/plugin/test_keyword.py +++ /dev/null @@ -1,127 +0,0 @@ -import py - -class Test_genitems: - def test_check_collect_hashes(self, testdir): - p = testdir.makepyfile(""" - def test_1(): - pass - - def test_2(): - pass - """) - p.copy(p.dirpath(p.purebasename + "2" + ".py")) - items, reprec = testdir.inline_genitems(p.dirpath()) - assert len(items) == 4 - for numi, i in enumerate(items): - for numj, j in enumerate(items): - if numj != numi: - assert hash(i) != hash(j) - assert i != j - - def test_root_conftest_syntax_error(self, testdir): - # do we want to unify behaviour with - # test_subdir_conftest_error? - p = testdir.makepyfile(conftest="raise SyntaxError\n") - py.test.raises(SyntaxError, testdir.inline_genitems, p.dirpath()) - - def test_example_items1(self, testdir): - p = testdir.makepyfile(''' - def testone(): - pass - - class TestX: - def testmethod_one(self): - pass - - class TestY(TestX): - pass - ''') - items, reprec = testdir.inline_genitems(p) - assert len(items) == 3 - assert items[0].name == 'testone' - assert items[1].name == 'testmethod_one' - assert items[2].name == 'testmethod_one' - - # let's also test getmodpath here - assert items[0].getmodpath() == "testone" - assert items[1].getmodpath() == "TestX.testmethod_one" - assert items[2].getmodpath() == "TestY.testmethod_one" - - s = items[0].getmodpath(stopatmodule=False) - assert s.endswith("test_example_items1.testone") - print(s) - - -class TestKeywordSelection: - def test_select_simple(self, testdir): - file_test = testdir.makepyfile(""" - def test_one(): assert 0 - class TestClass(object): - def test_method_one(self): - assert 42 == 43 - """) - def check(keyword, name): - reprec = testdir.inline_run("-s", "-k", keyword, file_test) - passed, skipped, failed = reprec.listoutcomes() - assert len(failed) == 1 - assert failed[0].nodeid.split("::")[-1] == name - assert len(reprec.getcalls('pytest_deselected')) == 1 - - for keyword in ['test_one', 'est_on']: - #yield check, keyword, 'test_one' - check(keyword, 'test_one') - check('TestClass.test', 'test_method_one') - - def test_select_extra_keywords(self, testdir): - p = testdir.makepyfile(test_select=""" - def test_1(): - pass - class TestClass: - def test_2(self): - pass - """) - testdir.makepyfile(conftest=""" - import py - class Class(py.test.collect.Class): - def _keywords(self): - return ['xxx', self.name] - """) - for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1', - 'TestClass test_2', 'xxx TestClass test_2',): - reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword) - py.builtin.print_("keyword", repr(keyword)) - passed, skipped, failed = reprec.listoutcomes() - assert len(passed) == 1 - assert passed[0].nodeid.endswith("test_2") - dlist = reprec.getcalls("pytest_deselected") - assert len(dlist) == 1 - assert dlist[0].items[0].name == 'test_1' - - def test_select_starton(self, testdir): - threepass = testdir.makepyfile(test_threepass=""" - def test_one(): assert 1 - def test_two(): assert 1 - def test_three(): assert 1 - """) - reprec = testdir.inline_run("-k", "test_two:", threepass) - passed, skipped, failed = reprec.listoutcomes() - assert len(passed) == 2 - assert not failed - dlist = reprec.getcalls("pytest_deselected") - assert len(dlist) == 1 - item = dlist[0].items[0] - assert item.name == "test_one" - - - def test_keyword_extra(self, testdir): - p = testdir.makepyfile(""" - def test_one(): - assert 0 - test_one.mykeyword = True - """) - reprec = testdir.inline_run("-k", "-mykeyword", p) - passed, skipped, failed = reprec.countoutcomes() - assert passed + skipped + failed == 0 - reprec = testdir.inline_run("-k", "mykeyword", p) - passed, skipped, failed = reprec.countoutcomes() - assert failed == 1 diff --git a/testing/plugin/test_mark.py b/testing/plugin/test_mark.py index c3e795129..a340f9b73 100644 --- a/testing/plugin/test_mark.py +++ b/testing/plugin/test_mark.py @@ -173,3 +173,130 @@ class TestFunctional: result.stdout.fnmatch_lines([ "keyword: *hello*" ]) + + +class Test_genitems: + def test_check_collect_hashes(self, testdir): + p = testdir.makepyfile(""" + def test_1(): + pass + + def test_2(): + pass + """) + p.copy(p.dirpath(p.purebasename + "2" + ".py")) + items, reprec = testdir.inline_genitems(p.dirpath()) + assert len(items) == 4 + for numi, i in enumerate(items): + for numj, j in enumerate(items): + if numj != numi: + assert hash(i) != hash(j) + assert i != j + + def test_root_conftest_syntax_error(self, testdir): + # do we want to unify behaviour with + # test_subdir_conftest_error? + p = testdir.makepyfile(conftest="raise SyntaxError\n") + py.test.raises(SyntaxError, testdir.inline_genitems, p.dirpath()) + + def test_example_items1(self, testdir): + p = testdir.makepyfile(''' + def testone(): + pass + + class TestX: + def testmethod_one(self): + pass + + class TestY(TestX): + pass + ''') + items, reprec = testdir.inline_genitems(p) + assert len(items) == 3 + assert items[0].name == 'testone' + assert items[1].name == 'testmethod_one' + assert items[2].name == 'testmethod_one' + + # let's also test getmodpath here + assert items[0].getmodpath() == "testone" + assert items[1].getmodpath() == "TestX.testmethod_one" + assert items[2].getmodpath() == "TestY.testmethod_one" + + s = items[0].getmodpath(stopatmodule=False) + assert s.endswith("test_example_items1.testone") + print(s) + + +class TestKeywordSelection: + def test_select_simple(self, testdir): + file_test = testdir.makepyfile(""" + def test_one(): assert 0 + class TestClass(object): + def test_method_one(self): + assert 42 == 43 + """) + def check(keyword, name): + reprec = testdir.inline_run("-s", "-k", keyword, file_test) + passed, skipped, failed = reprec.listoutcomes() + assert len(failed) == 1 + assert failed[0].nodeid.split("::")[-1] == name + assert len(reprec.getcalls('pytest_deselected')) == 1 + + for keyword in ['test_one', 'est_on']: + #yield check, keyword, 'test_one' + check(keyword, 'test_one') + check('TestClass.test', 'test_method_one') + + def test_select_extra_keywords(self, testdir): + p = testdir.makepyfile(test_select=""" + def test_1(): + pass + class TestClass: + def test_2(self): + pass + """) + testdir.makepyfile(conftest=""" + import py + class Class(py.test.collect.Class): + def _keywords(self): + return ['xxx', self.name] + """) + for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1', + 'TestClass test_2', 'xxx TestClass test_2',): + reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword) + py.builtin.print_("keyword", repr(keyword)) + passed, skipped, failed = reprec.listoutcomes() + assert len(passed) == 1 + assert passed[0].nodeid.endswith("test_2") + dlist = reprec.getcalls("pytest_deselected") + assert len(dlist) == 1 + assert dlist[0].items[0].name == 'test_1' + + def test_select_starton(self, testdir): + threepass = testdir.makepyfile(test_threepass=""" + def test_one(): assert 1 + def test_two(): assert 1 + def test_three(): assert 1 + """) + reprec = testdir.inline_run("-k", "test_two:", threepass) + passed, skipped, failed = reprec.listoutcomes() + assert len(passed) == 2 + assert not failed + dlist = reprec.getcalls("pytest_deselected") + assert len(dlist) == 1 + item = dlist[0].items[0] + assert item.name == "test_one" + + + def test_keyword_extra(self, testdir): + p = testdir.makepyfile(""" + def test_one(): + assert 0 + test_one.mykeyword = True + """) + reprec = testdir.inline_run("-k", "-mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert passed + skipped + failed == 0 + reprec = testdir.inline_run("-k", "mykeyword", p) + passed, skipped, failed = reprec.countoutcomes() + assert failed == 1