Skip to content

Commit 5186635

Browse files
authored
Introduce no_fnmatch_line/no_re_match_line in pytester (pytest-dev#5914)
Introduce no_fnmatch_line/no_re_match_line in pytester
2 parents cd398e2 + 47c2091 commit 5186635

24 files changed

+181
-80
lines changed

changelog/5914.feature.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
``pytester`` learned two new functions, `no_fnmatch_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_fnmatch_line>`_ and
2+
`no_re_match_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_re_match_line>`_.
3+
4+
The functions are used to ensure the captured text *does not* match the given
5+
pattern.
6+
7+
The previous idiom was to use ``re.match``:
8+
9+
.. code-block:: python
10+
11+
assert re.match(pat, result.stdout.str()) is None
12+
13+
Or the ``in`` operator:
14+
15+
.. code-block:: python
16+
17+
assert text in result.stdout.str()
18+
19+
But the new functions produce best output on failure.

src/_pytest/pytester.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,8 +1318,7 @@ def fnmatch_lines(self, lines2):
13181318
13191319
The argument is a list of lines which have to match and can use glob
13201320
wildcards. If they do not match a pytest.fail() is called. The
1321-
matches and non-matches are also printed on stdout.
1322-
1321+
matches and non-matches are also shown as part of the error message.
13231322
"""
13241323
__tracebackhide__ = True
13251324
self._match_lines(lines2, fnmatch, "fnmatch")
@@ -1330,8 +1329,7 @@ def re_match_lines(self, lines2):
13301329
The argument is a list of lines which have to match using ``re.match``.
13311330
If they do not match a pytest.fail() is called.
13321331
1333-
The matches and non-matches are also printed on stdout.
1334-
1332+
The matches and non-matches are also shown as part of the error message.
13351333
"""
13361334
__tracebackhide__ = True
13371335
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
@@ -1374,3 +1372,40 @@ def _match_lines(self, lines2, match_func, match_nickname):
13741372
else:
13751373
self._log("remains unmatched: {!r}".format(line))
13761374
pytest.fail(self._log_text)
1375+
1376+
def no_fnmatch_line(self, pat):
1377+
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
1378+
1379+
:param str pat: the pattern to match lines.
1380+
"""
1381+
__tracebackhide__ = True
1382+
self._no_match_line(pat, fnmatch, "fnmatch")
1383+
1384+
def no_re_match_line(self, pat):
1385+
"""Ensure captured lines do not match the given pattern, using ``re.match``.
1386+
1387+
:param str pat: the regular expression to match lines.
1388+
"""
1389+
__tracebackhide__ = True
1390+
self._no_match_line(pat, lambda name, pat: re.match(pat, name), "re.match")
1391+
1392+
def _no_match_line(self, pat, match_func, match_nickname):
1393+
"""Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``
1394+
1395+
:param str pat: the pattern to match lines
1396+
"""
1397+
__tracebackhide__ = True
1398+
nomatch_printed = False
1399+
try:
1400+
for line in self.lines:
1401+
if match_func(line, pat):
1402+
self._log("%s:" % match_nickname, repr(pat))
1403+
self._log(" with:", repr(line))
1404+
pytest.fail(self._log_text)
1405+
else:
1406+
if not nomatch_printed:
1407+
self._log("nomatch:", repr(pat))
1408+
nomatch_printed = True
1409+
self._log(" and:", repr(line))
1410+
finally:
1411+
self._log_output = []

testing/acceptance_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def test_issue93_initialnode_importing_capturing(self, testdir):
246246
)
247247
result = testdir.runpytest()
248248
assert result.ret == ExitCode.NO_TESTS_COLLECTED
249-
assert "should not be seen" not in result.stdout.str()
249+
result.stdout.no_fnmatch_line("*should not be seen*")
250250
assert "stderr42" not in result.stderr.str()
251251

252252
def test_conftest_printing_shows_if_error(self, testdir):
@@ -954,7 +954,7 @@ def test_with_failing_collection(self, testdir):
954954
result.stdout.fnmatch_lines(["*Interrupted: 1 errors during collection*"])
955955
# Collection errors abort test execution, therefore no duration is
956956
# output
957-
assert "duration" not in result.stdout.str()
957+
result.stdout.no_fnmatch_line("*duration*")
958958

959959
def test_with_not(self, testdir):
960960
testdir.makepyfile(self.source)
@@ -1008,7 +1008,7 @@ def main():
10081008
result = testdir.runpython(target)
10091009
assert result.ret == 0
10101010
result.stderr.fnmatch_lines(["*not found*foo*"])
1011-
assert "INTERNALERROR>" not in result.stdout.str()
1011+
result.stdout.no_fnmatch_line("*INTERNALERROR>*")
10121012

10131013

10141014
def test_import_plugin_unicode_name(testdir):

testing/code/test_excinfo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def test_division_zero():
399399
result = testdir.runpytest()
400400
assert result.ret != 0
401401
result.stdout.fnmatch_lines(["*AssertionError*Pattern*[123]*not found*"])
402-
assert "__tracebackhide__ = True" not in result.stdout.str()
402+
result.stdout.no_fnmatch_line("*__tracebackhide__ = True*")
403403

404404
result = testdir.runpytest("--fulltrace")
405405
assert result.ret != 0
@@ -1343,7 +1343,8 @@ def test(tmpdir):
13431343
)
13441344
result = testdir.runpytest()
13451345
result.stdout.fnmatch_lines(["* 1 failed in *"])
1346-
assert "INTERNALERROR" not in result.stdout.str() + result.stderr.str()
1346+
result.stdout.no_fnmatch_line("*INTERNALERROR*")
1347+
result.stderr.no_fnmatch_line("*INTERNALERROR*")
13471348

13481349

13491350
@pytest.mark.usefixtures("limited_recursion_depth")

testing/logging/test_fixture.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test2(caplog):
4646
)
4747
result = testdir.runpytest()
4848
result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"])
49-
assert "log from test2" not in result.stdout.str()
49+
result.stdout.no_fnmatch_line("*log from test2*")
5050

5151

5252
def test_with_statement(caplog):

testing/logging/test_reporting.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_foo():
109109
"=* 1 failed in *=",
110110
]
111111
)
112-
assert "DEBUG" not in result.stdout.str()
112+
result.stdout.no_re_match_line("DEBUG")
113113

114114

115115
def test_setup_logging(testdir):
@@ -282,7 +282,7 @@ def test_log_cli(request):
282282
"WARNING*test_log_cli_default_level.py* message will be shown*",
283283
]
284284
)
285-
assert "INFO message won't be shown" not in result.stdout.str()
285+
result.stdout.no_fnmatch_line("*INFO message won't be shown*")
286286
# make sure that that we get a '0' exit code for the testsuite
287287
assert result.ret == 0
288288

@@ -566,7 +566,7 @@ def test_log_cli(request):
566566
"PASSED", # 'PASSED' on its own line because the log message prints a new line
567567
]
568568
)
569-
assert "This log message won't be shown" not in result.stdout.str()
569+
result.stdout.no_fnmatch_line("*This log message won't be shown*")
570570

571571
# make sure that that we get a '0' exit code for the testsuite
572572
assert result.ret == 0
@@ -580,7 +580,7 @@ def test_log_cli(request):
580580
"PASSED", # 'PASSED' on its own line because the log message prints a new line
581581
]
582582
)
583-
assert "This log message won't be shown" not in result.stdout.str()
583+
result.stdout.no_fnmatch_line("*This log message won't be shown*")
584584

585585
# make sure that that we get a '0' exit code for the testsuite
586586
assert result.ret == 0
@@ -616,7 +616,7 @@ def test_log_cli(request):
616616
"PASSED", # 'PASSED' on its own line because the log message prints a new line
617617
]
618618
)
619-
assert "This log message won't be shown" not in result.stdout.str()
619+
result.stdout.no_fnmatch_line("*This log message won't be shown*")
620620

621621
# make sure that that we get a '0' exit code for the testsuite
622622
assert result.ret == 0
@@ -942,15 +942,15 @@ def test_simple():
942942
]
943943
)
944944
elif verbose == "-q":
945-
assert "collected 1 item*" not in result.stdout.str()
945+
result.stdout.no_fnmatch_line("*collected 1 item**")
946946
expected_lines.extend(
947947
[
948948
"*test_collection_collect_only_live_logging.py::test_simple*",
949949
"no tests ran in 0.[0-9][0-9]s",
950950
]
951951
)
952952
elif verbose == "-qq":
953-
assert "collected 1 item*" not in result.stdout.str()
953+
result.stdout.no_fnmatch_line("*collected 1 item**")
954954
expected_lines.extend(["*test_collection_collect_only_live_logging.py: 1*"])
955955

956956
result.stdout.fnmatch_lines(expected_lines)
@@ -983,7 +983,7 @@ def test_simple():
983983

984984
result = testdir.runpytest()
985985

986-
assert "--- live log collection ---" not in result.stdout.str()
986+
result.stdout.no_fnmatch_line("*--- live log collection ---*")
987987

988988
assert result.ret == 0
989989
assert os.path.isfile(log_file)

testing/python/collect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ class Test(object):
11391139
"""
11401140
)
11411141
result = testdir.runpytest()
1142-
assert "TypeError" not in result.stdout.str()
1142+
result.stdout.no_fnmatch_line("*TypeError*")
11431143
assert result.ret == ExitCode.NO_TESTS_COLLECTED
11441144

11451145

testing/python/fixtures.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def test_lookup_error(unknown):
455455
"*1 error*",
456456
]
457457
)
458-
assert "INTERNAL" not in result.stdout.str()
458+
result.stdout.no_fnmatch_line("*INTERNAL*")
459459

460460
def test_fixture_excinfo_leak(self, testdir):
461461
# on python2 sys.excinfo would leak into fixture executions
@@ -2647,7 +2647,7 @@ def test_finish():
26472647
*3 passed*
26482648
"""
26492649
)
2650-
assert "error" not in result.stdout.str()
2650+
result.stdout.no_fnmatch_line("*error*")
26512651

26522652
def test_fixture_finalizer(self, testdir):
26532653
testdir.makeconftest(
@@ -3151,7 +3151,7 @@ def arg1():
31513151
*hello world*
31523152
"""
31533153
)
3154-
assert "arg0" not in result.stdout.str()
3154+
result.stdout.no_fnmatch_line("*arg0*")
31553155

31563156
@pytest.mark.parametrize("testmod", [True, False])
31573157
def test_show_fixtures_conftest(self, testdir, testmod):

testing/python/setup_only.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_arg1(arg1):
2727
result.stdout.fnmatch_lines(
2828
["*SETUP F arg1*", "*test_arg1 (fixtures used: arg1)*", "*TEARDOWN F arg1*"]
2929
)
30-
assert "_arg0" not in result.stdout.str()
30+
result.stdout.no_fnmatch_line("*_arg0*")
3131

3232

3333
def test_show_different_scopes(testdir, mode):

testing/python/show_fixtures_per_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
def test_no_items_should_not_show_output(testdir):
22
result = testdir.runpytest("--fixtures-per-test")
3-
assert "fixtures used by" not in result.stdout.str()
3+
result.stdout.no_fnmatch_line("*fixtures used by*")
44
assert result.ret == 0
55

66

@@ -30,7 +30,7 @@ def test_arg1(arg1):
3030
" arg1 docstring",
3131
]
3232
)
33-
assert "_arg0" not in result.stdout.str()
33+
result.stdout.no_fnmatch_line("*_arg0*")
3434

3535

3636
def test_fixtures_in_conftest(testdir):

0 commit comments

Comments
 (0)