Skip to content

Colon-pair keywords #806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
2.8.0.dev (compared to 2.7.X)
-----------------------------

- Allow use of keywords like "-k myfile.py::MyClass::my_test",
as output by the pytest runner.

- fix issue713: JUnit XML reports for doctest failures.
Thanks Punyashloka Biswal.

Expand Down
15 changes: 13 additions & 2 deletions _pytest/mark.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
""" generic mechanism for marking and selecting python functions. """
import py

import re

class MarkerError(Exception):

Expand All @@ -24,7 +24,8 @@ def pytest_addoption(parser):
"contains 'test_method' or 'test_other'. "
"Additionally keywords are matched to classes and functions "
"containing extra names in their 'extra_keyword_matches' set, "
"as well as functions which have names assigned directly to them."
"as well as functions which have names assigned directly to them. "
"The notation \"MyClass::test_method\" is also recognized as a logical AND."
)

group._addoption(
Expand Down Expand Up @@ -71,6 +72,16 @@ def pytest_collection_modifyitems(items, config):
selectuntil = True
keywordexpr = keywordexpr[:-1]

# we recognize keywords in the form "[myfile.py::]MyClass::my_test", as
# output by py.test, although we ignore the "myfile.py" part and only consider
# class and function substrings, for now
def _colon_pair_replacer(match_obj):
return "(%s and %s)" % (match_obj.group(2), match_obj.group(3))
colon_pair_regex = r'(?iu)\b(\S*?::)?(\w+)::(\w+)(\b|\s)'
keywordexpr = re.sub(colon_pair_regex,
_colon_pair_replacer,
keywordexpr)

remaining = []
deselected = []
for colitem in items:
Expand Down
14 changes: 14 additions & 0 deletions doc/en/example/markers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ Or to select "http" and "quick" tests::
======= 2 tests deselected by '-khttp or quick' ========
======= 2 passed, 2 deselected in 0.12 seconds ========

Colon-pair notation (eg. copied from py.test output) also works, as a logical AND::

$ py.test -k "Class::test_meth" -v
======= test session starts ========
platform linux2 -- Python 2.7.9, pytest-2.8.0.dev4, py-1.4.28, pluggy-0.3.0 -- $PWD/.env/bin/python2.7
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items

test_server.py::TestClass::test_method PASSED

======= 3 tests deselected by '-kClass::test_meth' ========
======= 1 passed, 3 deselected in 0.12 seconds ========


.. note::

If you are using expressions such as "X and Y" then both X and Y
Expand Down
2 changes: 1 addition & 1 deletion doc/en/usage.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Several test run options::
py.test test_mod.py # run tests in module
py.test somepath # run all tests below somepath
py.test -k stringexpr # only run tests with names that match the
# the "string expression", e.g. "MyClass and not method"
# "string expression", e.g. "MyClass and not method"
# will select TestMyClass.test_something
# but not TestMyClass.test_method_simple
py.test test_mod.py::test_func # only run tests that match the "node ID",
Expand Down
35 changes: 32 additions & 3 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
class TestMark:
def test_markinfo_repr(self):
from _pytest.mark import MarkInfo
m = MarkInfo("hello", (1,2), {})
m = MarkInfo("hello", (1, 2), {})
repr(m)

def test_pytest_exists_in_namespace_all(self):
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_markers_option(testdir):
a1: this is a webtest marker
a1some: another marker
""")
result = testdir.runpytest("--markers", )
result = testdir.runpytest("--markers",)
result.stdout.fnmatch_lines([
"*a1*this is a webtest*",
"*a1some*another marker",
Expand Down Expand Up @@ -214,6 +214,8 @@ def test_nointer():
("not interface", ("test_nointer", "test_pass")),
("pass", ("test_pass",)),
("not pass", ("test_interface", "test_nointer")),
("inte and face", ("test_interface",)),
("inte and r and not face", ("test_nointer",)),
])
def test_keyword_option_custom(spec, testdir):
testdir.makepyfile("""
Expand All @@ -231,6 +233,33 @@ def test_pass():
assert len(passed) == len(passed_result)
assert list(passed) == list(passed_result)

@pytest.mark.parametrize("spec", [
("MyClass::test_ab", True, ("test_ab",)),
("lass::_a", False, ("test_ab",)),
("lass::b", True, ("test_ab", "test_bc")),
("badClass::test_ab", False, ()),
("test_ab", True, ()), # "file.py::function" doesn't work
("test_ab or MyClass::test_bc", False, ("test_ab", "test_bc")),
])
def test_keyword_with_colon_pairs(spec, testdir):
localfile = testdir.makepyfile("""
class TestMyClass:
def test_ab(self):
pass
def test_bc(self):
pass
def test_cd(self):
pass
""")
filename = localfile.basename
opt, add_filename, passed_result = spec
if add_filename:
opt = filename + "::" + opt
rec = testdir.inline_run("-k", opt)
passed, skipped, fail = rec.listoutcomes()
passed = [x.nodeid.split("::")[-1] for x in passed]
assert len(passed) == len(passed_result)
assert list(passed) == list(passed_result)

@pytest.mark.parametrize("spec", [
("None", ("test_func[None]",)),
Expand Down Expand Up @@ -348,7 +377,7 @@ def test_func(self):
assert len(l) == 3
assert l[0].args == ("pos0",)
assert l[1].args == ()
assert l[2].args == ("pos1", )
assert l[2].args == ("pos1",)

@pytest.mark.xfail(reason='unfixed')
def test_merging_markers_deep(self, testdir):
Expand Down