pytester: split asserts to a separate plugin, don't rewrite pytester itself

An upcoming commit wants to import from `_pytest.pytester` in the public
`pytest` module. This means that `_pytest.pytester` would start to get
imported during import time, which it hasn't up to now -- it was
imported by the plugin loader (if requested). When a plugin is loaded,
it is subjected to assertion rewriting, but only if the module isn't
imported yet, it issues a warning "Module already imported so cannot be
rewritten" and skips the rewriting. So we'd end up with the pytester
plugin not being rewritten, but it wants to be.

Absent better ideas, the solution here is to split the pytester
assertions to their own plugin (which will always only be imported by
the plugin loader) and exclude pytester itself from plugin rewriting.
This commit is contained in:
Ran Benita 2020-11-09 18:27:40 +02:00
parent e986d84466
commit b050578882
3 changed files with 89 additions and 27 deletions

View File

@ -256,6 +256,7 @@ default_plugins = essential_plugins + (
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
builtin_plugins.add("pytester_assertions")
def get_config(

View File

@ -1,4 +1,7 @@
"""(Disabled by default) support for testing pytest and pytest plugins."""
"""(Disabled by default) support for testing pytest and pytest plugins.
PYTEST_DONT_REWRITE
"""
import collections.abc
import contextlib
import gc
@ -66,6 +69,9 @@ if TYPE_CHECKING:
import pexpect
pytest_plugins = ["pytester_assertions"]
IGNORE_PAM = [ # filenames added when obtaining details about the current user
"/var/lib/sss/mc/passwd"
]
@ -408,16 +414,12 @@ class HookRecorder:
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
__tracebackhide__ = True
from _pytest.pytester_assertions import assertoutcome
outcomes = self.listoutcomes()
realpassed, realskipped, realfailed = outcomes
obtained = {
"passed": len(realpassed),
"skipped": len(realskipped),
"failed": len(realfailed),
}
expected = {"passed": passed, "skipped": skipped, "failed": failed}
assert obtained == expected, outcomes
assertoutcome(
outcomes, passed=passed, skipped=skipped, failed=failed,
)
def clear(self) -> None:
self.calls[:] = []
@ -574,25 +576,18 @@ class RunResult:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
__tracebackhide__ = True
from _pytest.pytester_assertions import assert_outcomes
d = self.parseoutcomes()
obtained = {
"passed": d.get("passed", 0),
"skipped": d.get("skipped", 0),
"failed": d.get("failed", 0),
"errors": d.get("errors", 0),
"xpassed": d.get("xpassed", 0),
"xfailed": d.get("xfailed", 0),
}
expected = {
"passed": passed,
"skipped": skipped,
"failed": failed,
"errors": errors,
"xpassed": xpassed,
"xfailed": xfailed,
}
assert obtained == expected
outcomes = self.parseoutcomes()
assert_outcomes(
outcomes,
passed=passed,
skipped=skipped,
failed=failed,
errors=errors,
xpassed=xpassed,
xfailed=xfailed,
)
class CwdSnapshot:

View File

@ -0,0 +1,66 @@
"""Helper plugin for pytester; should not be loaded on its own."""
# This plugin contains assertions used by pytester. pytester cannot
# contain them itself, since it is imported by the `pytest` module,
# hence cannot be subject to assertion rewriting, which requires a
# module to not be already imported.
from typing import Dict
from typing import Sequence
from typing import Tuple
from typing import Union
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
def assertoutcome(
outcomes: Tuple[
Sequence[TestReport],
Sequence[Union[CollectReport, TestReport]],
Sequence[Union[CollectReport, TestReport]],
],
passed: int = 0,
skipped: int = 0,
failed: int = 0,
) -> None:
__tracebackhide__ = True
realpassed, realskipped, realfailed = outcomes
obtained = {
"passed": len(realpassed),
"skipped": len(realskipped),
"failed": len(realfailed),
}
expected = {"passed": passed, "skipped": skipped, "failed": failed}
assert obtained == expected, outcomes
def assert_outcomes(
outcomes: Dict[str, int],
passed: int = 0,
skipped: int = 0,
failed: int = 0,
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
__tracebackhide__ = True
obtained = {
"passed": outcomes.get("passed", 0),
"skipped": outcomes.get("skipped", 0),
"failed": outcomes.get("failed", 0),
"errors": outcomes.get("errors", 0),
"xpassed": outcomes.get("xpassed", 0),
"xfailed": outcomes.get("xfailed", 0),
}
expected = {
"passed": passed,
"skipped": skipped,
"failed": failed,
"errors": errors,
"xpassed": xpassed,
"xfailed": xfailed,
}
assert obtained == expected