Skip to content

Commit 3151219

Browse files
committed
assertoutcomes() only accepts plural forms
Fix #6505
1 parent f551cab commit 3151219

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed

changelog/6505.breaking.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
``Testdir.run().parseoutcomes()`` now always returns the parsed nouns in plural form.
2+
3+
Originally ``parseoutcomes()`` would always returns the nouns in plural form, but a change
4+
meant to improve the terminal summary by using singular form single items (``1 warning`` or ``1 error``)
5+
caused an unintended regression by changing the keys returned by ``parseoutcomes()``.
6+
7+
Now the API guarantees to always return the plural form, so calls like this:
8+
9+
.. code-block:: python
10+
11+
result = testdir.runpytest()
12+
result.assert_outcomes(error=1)
13+
14+
Need to be changed to:
15+
16+
17+
.. code-block:: python
18+
19+
result = testdir.runpytest()
20+
result.assert_outcomes(errors=1)

src/_pytest/pytester.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -452,28 +452,47 @@ def __repr__(self) -> str:
452452
)
453453

454454
def parseoutcomes(self) -> Dict[str, int]:
455-
"""Return a dictionary of outcomestring->num from parsing the terminal
455+
"""Return a dictionary of outcome noun -> count from parsing the terminal
456456
output that the test process produced.
457457
458+
The returned nouns will always be in plural form::
459+
460+
======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
461+
462+
Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``
463+
"""
464+
return self.parse_summary_nouns(self.outlines)
465+
466+
@classmethod
467+
def parse_summary_nouns(cls, lines) -> Dict[str, int]:
468+
"""Extracts the nouns from a pytest terminal summary line.
469+
470+
It always returns the plural noun for consistency::
471+
472+
======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====
473+
474+
Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``
458475
"""
459-
for line in reversed(self.outlines):
476+
for line in reversed(lines):
460477
if rex_session_duration.search(line):
461478
outcomes = rex_outcome.findall(line)
462479
ret = {noun: int(count) for (count, noun) in outcomes}
463480
break
464481
else:
465482
raise ValueError("Pytest terminal summary report not found")
466-
if "errors" in ret:
467-
assert "error" not in ret
468-
ret["error"] = ret.pop("errors")
469-
return ret
483+
484+
to_plural = {
485+
"warning": "warnings",
486+
"error": "errors",
487+
}
488+
return {to_plural.get(k, k): v for k, v in ret.items()}
470489

471490
def assert_outcomes(
472491
self,
473492
passed: int = 0,
474493
skipped: int = 0,
475494
failed: int = 0,
476-
error: int = 0,
495+
errors: int = 0,
477496
xpassed: int = 0,
478497
xfailed: int = 0,
479498
) -> None:
@@ -487,15 +506,15 @@ def assert_outcomes(
487506
"passed": d.get("passed", 0),
488507
"skipped": d.get("skipped", 0),
489508
"failed": d.get("failed", 0),
490-
"error": d.get("error", 0),
509+
"errors": d.get("errors", 0),
491510
"xpassed": d.get("xpassed", 0),
492511
"xfailed": d.get("xfailed", 0),
493512
}
494513
expected = {
495514
"passed": passed,
496515
"skipped": skipped,
497516
"failed": failed,
498-
"error": error,
517+
"errors": errors,
499518
"xpassed": xpassed,
500519
"xfailed": xfailed,
501520
}

testing/python/fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4342,6 +4342,6 @@ def test_fixt(custom):
43424342
)
43434343
expected = "E ValueError: custom did not yield a value"
43444344
result = testdir.runpytest()
4345-
result.assert_outcomes(error=1)
4345+
result.assert_outcomes(errors=1)
43464346
result.stdout.fnmatch_lines([expected])
43474347
assert result.ret == ExitCode.TESTS_FAILED

testing/test_pytester.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,9 +763,38 @@ def test_error2(bad_fixture):
763763
"""
764764
)
765765
result = testdir.runpytest(str(p1))
766-
result.assert_outcomes(error=2)
766+
result.assert_outcomes(errors=2)
767767

768-
assert result.parseoutcomes() == {"error": 2}
768+
assert result.parseoutcomes() == {"errors": 2}
769+
770+
771+
def test_parse_summary_line_always_plural():
772+
"""Parsing summaries always returns plural nouns (#6505)"""
773+
lines = [
774+
"some output 1",
775+
"some output 2",
776+
"======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====",
777+
"done.",
778+
]
779+
assert pytester.RunResult.parse_summary_nouns(lines) == {
780+
"errors": 1,
781+
"failed": 1,
782+
"passed": 1,
783+
"warnings": 1,
784+
}
785+
786+
lines = [
787+
"some output 1",
788+
"some output 2",
789+
"======= 1 failed, 1 passed, 2 warnings, 2 errors in 0.13s ====",
790+
"done.",
791+
]
792+
assert pytester.RunResult.parse_summary_nouns(lines) == {
793+
"errors": 2,
794+
"failed": 1,
795+
"passed": 1,
796+
"warnings": 2,
797+
}
769798

770799

771800
def test_makefile_joins_absolute_path(testdir: Testdir) -> None:

0 commit comments

Comments
 (0)