summaryrefslogtreecommitdiff
path: root/src/_pytest
diff options
context:
space:
mode:
authorRan Benita <ran@unusedvar.com>2020-11-09 18:27:40 +0200
committerRan Benita <ran@unusedvar.com>2020-11-13 11:25:09 +0200
commitb0505788821604f0b0787683d47a0ca693fd0426 (patch)
tree53dac24f3c94d2859ba01e8dde73a8cb15b502f8 /src/_pytest
parente986d84466dfa98dbbc55cc1bf5fcb99075f4ac3 (diff)
downloadpytest-b0505788821604f0b0787683d47a0ca693fd0426.tar.gz
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.
Diffstat (limited to 'src/_pytest')
-rw-r--r--src/_pytest/config/__init__.py1
-rw-r--r--src/_pytest/pytester.py51
-rw-r--r--src/_pytest/pytester_assertions.py66
3 files changed, 90 insertions, 28 deletions
diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index 6c1d9c69a..74168efd5 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -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(
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index 0e1dffb9d..158b71b3a 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -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
-
- 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
+ from _pytest.pytester_assertions import assert_outcomes
+
+ outcomes = self.parseoutcomes()
+ assert_outcomes(
+ outcomes,
+ passed=passed,
+ skipped=skipped,
+ failed=failed,
+ errors=errors,
+ xpassed=xpassed,
+ xfailed=xfailed,
+ )
class CwdSnapshot:
diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py
new file mode 100644
index 000000000..630c1d333
--- /dev/null
+++ b/src/_pytest/pytester_assertions.py
@@ -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