import importlib
import os
import pytest
import supercov_runtime
_runtime = supercov_runtime.install()
_worker = os.environ.get("PYTEST_XDIST_WORKER", os.environ.get(supercov_runtime.WORKER_ENV, "main"))
_xdist_controller = False
def _retry_from_item(item) -> int:
execution_count = getattr(item, "execution_count", 1)
try:
return max(int(execution_count) - 1, 0)
except (TypeError, ValueError):
return 0
def _switch(item, phase: str) -> None:
if _runtime is None or _xdist_controller:
return
_runtime.switch(
{
"worker": _worker,
"test": item.nodeid,
"retry": _retry_from_item(item),
"phase": phase,
}
)
@pytest.hookimpl(tryfirst=True)
def pytest_load_initial_conftests(early_config, parser, args):
del parser, args
if _runtime is None:
return
try:
enabled = bool(early_config.getini("enable_assertion_pass_hook"))
from _pytest.assertion import rewrite
tail = rewrite.PYC_TAIL
if enabled and "-supercov" not in tail:
stem, extension = tail.rsplit(".", 1)
rewrite.PYC_TAIL = f"{stem}-supercov.{extension}"
except Exception as error: _runtime.limitation(
"python-pytest-assertion-hook-unavailable",
f"this pytest exposes no rewrite cache Supercov can name ({error!r}); plain assert statements are not linked to assertions",
)
def _hook_expectation_contexts() -> None:
if _runtime is None:
return
for module_name, class_name in (
("_pytest.raises", "RaisesExc"),
("_pytest.raises", "RaisesGroup"),
("_pytest.python_api", "RaisesContext"),
("_pytest.recwarn", "WarningsChecker"),
):
try:
cls = getattr(importlib.import_module(module_name), class_name)
original = cls.__dict__["__exit__"]
except Exception: continue
def exit_and_mark(self, exc_type, exc_val, exc_tb, _original=original):
__tracebackhide__ = True
result = _original(self, exc_type, exc_val, exc_tb)
if result is True or (result is None and exc_type is None):
_runtime.assertion()
return result
cls.__exit__ = exit_and_mark
_hook_expectation_contexts()
def _item_file(item):
try:
path = getattr(item, "path", None)
return str(path) if path is not None else getattr(item, "fspath", None) and str(item.fspath)
except Exception:
return None
def _report_file(report):
try:
location = getattr(report, "location", None)
return location[0] if location else None
except Exception:
return None
def pytest_assertion_pass(item, lineno, orig, expl):
del orig, expl
if _runtime is None or _xdist_controller:
return
_runtime.assertion_site(_item_file(item), lineno)
_runtime.assertion()
def pytest_configure(config):
global _worker, _xdist_controller
worker_input = getattr(config, "workerinput", None)
if isinstance(worker_input, dict):
_worker = str(worker_input.get("workerid", _worker))
workers = getattr(config.option, "numprocesses", None)
_xdist_controller = _worker == "main" and workers not in (None, 0, "0")
if _runtime is not None:
_runtime.set_worker(_worker)
_runtime.pytest_active = True
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
_switch(item, "setup")
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_call(item):
_switch(item, "call")
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_teardown(item):
_switch(item, "teardown")
def pytest_runtest_logreport(report):
if _runtime is None:
return
if _xdist_controller:
if report.when != "???":
return
node = getattr(report, "node", None)
gateway = getattr(node, "gateway", None)
worker = getattr(gateway, "id", None) or "unknown-worker"
_runtime.outcome(worker, report.nodeid, 0, "call", "failed", False)
return
_runtime.outcome(
_worker,
report.nodeid,
max(int(getattr(report, "rerun", 0) or 0), 0),
report.when,
report.outcome,
bool(getattr(report, "wasxfail", False)),
file=_report_file(report),
)
def pytest_runtest_logfinish(nodeid, location):
del nodeid, location
if _runtime is not None and not _xdist_controller:
_runtime.switch(None)
def pytest_unconfigure(config):
del config
if _runtime is not None:
_runtime.flush()