issue504: verbose output displays node IDs for each test
Replace the verbose per-test reporting format of `file:line test_name RESULT` with the node ID of the test, i.e. `file@line::class::method[param] RESULT`. This patch does not update the examples in the docs; @hpk42 has a script to regenerate those. --HG-- branch : issue504
This commit is contained in:
parent
d74f852fd6
commit
2ba23e8d08
1
AUTHORS
1
AUTHORS
|
@ -41,3 +41,4 @@ Jurko Gospodnetić
|
||||||
Marc Schlaich
|
Marc Schlaich
|
||||||
Christopher Gilling
|
Christopher Gilling
|
||||||
Daniel Grana
|
Daniel Grana
|
||||||
|
Andy Freeland
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
NEXT (2.6)
|
NEXT (2.6)
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
- change -v output to include full node IDs of tests. Users can copy
|
||||||
|
a node ID from a test run, including line number, and use it as a
|
||||||
|
positional argument in order to run only a single test.
|
||||||
|
|
||||||
- change XPASS colour to yellow rather then red when tests are run
|
- change XPASS colour to yellow rather then red when tests are run
|
||||||
with -v.
|
with -v.
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import py
|
import py
|
||||||
# DON't import pytest here because it causes import cycle troubles
|
# DON't import pytest here because it causes import cycle troubles
|
||||||
|
import re
|
||||||
import sys, os
|
import sys, os
|
||||||
from _pytest import hookspec # the extension point definitions
|
from _pytest import hookspec # the extension point definitions
|
||||||
from _pytest.core import PluginManager
|
from _pytest.core import PluginManager
|
||||||
|
@ -180,7 +181,7 @@ class Parser:
|
||||||
a = option.attrs()
|
a = option.attrs()
|
||||||
arggroup.add_argument(*n, **a)
|
arggroup.add_argument(*n, **a)
|
||||||
# bash like autocompletion for dirs (appending '/')
|
# bash like autocompletion for dirs (appending '/')
|
||||||
optparser.add_argument(FILE_OR_DIR, nargs='*'
|
optparser.add_argument(FILE_OR_DIR, nargs='*', type=node_with_line_number,
|
||||||
).completer=filescompleter
|
).completer=filescompleter
|
||||||
return optparser
|
return optparser
|
||||||
|
|
||||||
|
@ -699,7 +700,7 @@ class Config(object):
|
||||||
except ConftestImportFailure:
|
except ConftestImportFailure:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
if ns.help or ns.version:
|
if ns.help or ns.version:
|
||||||
# we don't want to prevent --help/--version to work
|
# we don't want to prevent --help/--version to work
|
||||||
# so just let is pass and print a warning at the end
|
# so just let is pass and print a warning at the end
|
||||||
self.pluginmanager._warnings.append(
|
self.pluginmanager._warnings.append(
|
||||||
"could not load initial conftests (%s)\n" % e.path)
|
"could not load initial conftests (%s)\n" % e.path)
|
||||||
|
@ -839,6 +840,12 @@ def getcfg(args, inibasenames):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def node_with_line_number(string):
|
||||||
|
split = string.split('[')
|
||||||
|
split[0] = re.sub(r'@\d+', '', split[0])
|
||||||
|
return '['.join(split)
|
||||||
|
|
||||||
|
|
||||||
def setns(obj, dic):
|
def setns(obj, dic):
|
||||||
import pytest
|
import pytest
|
||||||
for name, value in dic.items():
|
for name, value in dic.items():
|
||||||
|
|
|
@ -382,9 +382,11 @@ class TerminalReporter:
|
||||||
line = str(fspath)
|
line = str(fspath)
|
||||||
if lineno is not None:
|
if lineno is not None:
|
||||||
lineno += 1
|
lineno += 1
|
||||||
line += ":" + str(lineno)
|
line += "@" + str(lineno)
|
||||||
if domain:
|
if domain:
|
||||||
line += ": " + str(domain)
|
split = str(domain).split('[')
|
||||||
|
split[0] = split[0].replace('.', '::') # don't replace '.' in params
|
||||||
|
line += "::" + '['.join(split)
|
||||||
else:
|
else:
|
||||||
line = "[location]"
|
line = "[location]"
|
||||||
return line + " "
|
return line + " "
|
||||||
|
|
|
@ -1620,22 +1620,22 @@ class TestFixtureMarker:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("-v")
|
result = testdir.runpytest("-v")
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
test_mod1.py:1: test_func[s1] PASSED
|
test_mod1.py@1::test_func[s1] PASSED
|
||||||
test_mod2.py:1: test_func2[s1] PASSED
|
test_mod2.py@1::test_func2[s1] PASSED
|
||||||
test_mod2.py:3: test_func3[s1-m1] PASSED
|
test_mod2.py@3::test_func3[s1-m1] PASSED
|
||||||
test_mod2.py:5: test_func3b[s1-m1] PASSED
|
test_mod2.py@5::test_func3b[s1-m1] PASSED
|
||||||
test_mod2.py:3: test_func3[s1-m2] PASSED
|
test_mod2.py@3::test_func3[s1-m2] PASSED
|
||||||
test_mod2.py:5: test_func3b[s1-m2] PASSED
|
test_mod2.py@5::test_func3b[s1-m2] PASSED
|
||||||
test_mod1.py:1: test_func[s2] PASSED
|
test_mod1.py@1::test_func[s2] PASSED
|
||||||
test_mod2.py:1: test_func2[s2] PASSED
|
test_mod2.py@1::test_func2[s2] PASSED
|
||||||
test_mod2.py:3: test_func3[s2-m1] PASSED
|
test_mod2.py@3::test_func3[s2-m1] PASSED
|
||||||
test_mod2.py:5: test_func3b[s2-m1] PASSED
|
test_mod2.py@5::test_func3b[s2-m1] PASSED
|
||||||
test_mod2.py:7: test_func4[m1] PASSED
|
test_mod2.py@7::test_func4[m1] PASSED
|
||||||
test_mod2.py:3: test_func3[s2-m2] PASSED
|
test_mod2.py@3::test_func3[s2-m2] PASSED
|
||||||
test_mod2.py:5: test_func3b[s2-m2] PASSED
|
test_mod2.py@5::test_func3b[s2-m2] PASSED
|
||||||
test_mod2.py:7: test_func4[m2] PASSED
|
test_mod2.py@7::test_func4[m2] PASSED
|
||||||
test_mod1.py:3: test_func1[m1] PASSED
|
test_mod1.py@3::test_func1[m1] PASSED
|
||||||
test_mod1.py:3: test_func1[m2] PASSED
|
test_mod1.py@3::test_func1[m2] PASSED
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def test_class_ordering(self, testdir):
|
def test_class_ordering(self, testdir):
|
||||||
|
@ -1672,18 +1672,18 @@ class TestFixtureMarker:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("-vs")
|
result = testdir.runpytest("-vs")
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
test_class_ordering.py:4: TestClass2.test_1[1-a] PASSED
|
test_class_ordering.py@4::TestClass2::test_1[1-a] PASSED
|
||||||
test_class_ordering.py:4: TestClass2.test_1[2-a] PASSED
|
test_class_ordering.py@4::TestClass2::test_1[2-a] PASSED
|
||||||
test_class_ordering.py:6: TestClass2.test_2[1-a] PASSED
|
test_class_ordering.py@6::TestClass2::test_2[1-a] PASSED
|
||||||
test_class_ordering.py:6: TestClass2.test_2[2-a] PASSED
|
test_class_ordering.py@6::TestClass2::test_2[2-a] PASSED
|
||||||
test_class_ordering.py:4: TestClass2.test_1[1-b] PASSED
|
test_class_ordering.py@4::TestClass2::test_1[1-b] PASSED
|
||||||
test_class_ordering.py:4: TestClass2.test_1[2-b] PASSED
|
test_class_ordering.py@4::TestClass2::test_1[2-b] PASSED
|
||||||
test_class_ordering.py:6: TestClass2.test_2[1-b] PASSED
|
test_class_ordering.py@6::TestClass2::test_2[1-b] PASSED
|
||||||
test_class_ordering.py:6: TestClass2.test_2[2-b] PASSED
|
test_class_ordering.py@6::TestClass2::test_2[2-b] PASSED
|
||||||
test_class_ordering.py:9: TestClass.test_3[1-a] PASSED
|
test_class_ordering.py@9::TestClass::test_3[1-a] PASSED
|
||||||
test_class_ordering.py:9: TestClass.test_3[2-a] PASSED
|
test_class_ordering.py@9::TestClass::test_3[2-a] PASSED
|
||||||
test_class_ordering.py:9: TestClass.test_3[1-b] PASSED
|
test_class_ordering.py@9::TestClass::test_3[1-b] PASSED
|
||||||
test_class_ordering.py:9: TestClass.test_3[2-b] PASSED
|
test_class_ordering.py@9::TestClass::test_3[2-b] PASSED
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def test_parametrize_separated_order_higher_scope_first(self, testdir):
|
def test_parametrize_separated_order_higher_scope_first(self, testdir):
|
||||||
|
@ -2094,7 +2094,7 @@ class TestErrors:
|
||||||
def fix1(request):
|
def fix1(request):
|
||||||
def f():
|
def f():
|
||||||
raise KeyError
|
raise KeyError
|
||||||
request.addfinalizer(f)
|
request.addfinalizer(f)
|
||||||
return object()
|
return object()
|
||||||
|
|
||||||
l = []
|
l = []
|
||||||
|
@ -2113,7 +2113,7 @@ class TestErrors:
|
||||||
*KeyError*
|
*KeyError*
|
||||||
*3 pass*2 error*
|
*3 pass*2 error*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_setupfunc_missing_funcarg(self, testdir):
|
def test_setupfunc_missing_funcarg(self, testdir):
|
||||||
|
|
|
@ -145,6 +145,10 @@ class TestParser:
|
||||||
assert args.R == True
|
assert args.R == True
|
||||||
assert args.S == False
|
assert args.S == False
|
||||||
|
|
||||||
|
def test_parse_removes_line_number_from_positional_arguments(self, parser):
|
||||||
|
args = parser.parse(['path@2::func', 'path2@5::func2[param with @]'])
|
||||||
|
assert getattr(args, parseopt.FILE_OR_DIR) == ['path::func', 'path2::func2[param with @]']
|
||||||
|
|
||||||
def test_parse_defaultgetter(self):
|
def test_parse_defaultgetter(self):
|
||||||
def defaultget(option):
|
def defaultget(option):
|
||||||
if not hasattr(option, 'type'):
|
if not hasattr(option, 'type'):
|
||||||
|
|
|
@ -51,9 +51,9 @@ class TestTerminal:
|
||||||
result = testdir.runpytest(*option.args)
|
result = testdir.runpytest(*option.args)
|
||||||
if option.verbose:
|
if option.verbose:
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_pass_skip_fail.py:2: *test_ok*PASS*",
|
"*test_pass_skip_fail.py@2::test_ok PASS*",
|
||||||
"*test_pass_skip_fail.py:4: *test_skip*SKIP*",
|
"*test_pass_skip_fail.py@4::test_skip SKIP*",
|
||||||
"*test_pass_skip_fail.py:6: *test_func*FAIL*",
|
"*test_pass_skip_fail.py@6::test_func FAIL*",
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
@ -126,7 +126,7 @@ class TestTerminal:
|
||||||
])
|
])
|
||||||
result = testdir.runpytest("-v", p2)
|
result = testdir.runpytest("-v", p2)
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_p2.py <- *test_p1.py:2: TestMore.test_p1*",
|
"*test_p2.py <- *test_p1.py@2::TestMore::test_p1*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
|
def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
|
||||||
|
@ -450,17 +450,17 @@ class TestTerminalFunctional:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p1, '-v')
|
result = testdir.runpytest(p1, '-v')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_verbose_reporting.py:2: test_fail*FAIL*",
|
"*test_verbose_reporting.py@2::test_fail *FAIL*",
|
||||||
"*test_verbose_reporting.py:4: test_pass*PASS*",
|
"*test_verbose_reporting.py@4::test_pass *PASS*",
|
||||||
"*test_verbose_reporting.py:7: TestClass.test_skip*SKIP*",
|
"*test_verbose_reporting.py@7::TestClass::test_skip *SKIP*",
|
||||||
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
"*test_verbose_reporting.py@10::test_gen*0* *FAIL*",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||||
result = testdir.runpytest(p1, '-v', '-n 1')
|
result = testdir.runpytest(p1, '-v', '-n 1')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*FAIL*test_verbose_reporting.py:2: test_fail*",
|
"*FAIL*test_verbose_reporting.py@2::test_fail*",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ def test_runTest_method(testdir):
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("-v")
|
result = testdir.runpytest("-v")
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
*MyTestCaseWithRunTest.runTest*
|
*MyTestCaseWithRunTest::runTest*
|
||||||
*MyTestCaseWithoutRunTest.test_something*
|
*MyTestCaseWithoutRunTest::test_something*
|
||||||
*2 passed*
|
*2 passed*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue