mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-07 04:53:47 +08:00
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
import ast
|
|
from contextlib import contextmanager
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def split_targets(root, targets):
|
|
unittest_targets, pytest_targets = [], []
|
|
for target in dict.fromkeys(targets):
|
|
path = root / target.split("::", 1)[0]
|
|
tree = ast.parse(path.read_text(), filename=str(path))
|
|
requires_pytest = any(
|
|
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith("test_") or
|
|
isinstance(node, ast.ClassDef) and node.name.startswith("Test") and (not node.bases or node.decorator_list)
|
|
for node in tree.body)
|
|
(pytest_targets if requires_pytest else unittest_targets).append(target)
|
|
return unittest_targets, pytest_targets
|
|
|
|
|
|
def test_id(nodeid):
|
|
path, *nodes = nodeid.split("::")
|
|
return ".".join((Path(path).with_suffix("").as_posix().replace("/", "."), *nodes))
|
|
|
|
|
|
class Recorder:
|
|
def __init__(self):
|
|
self.nodeids = []
|
|
self.errors = []
|
|
self.records = {}
|
|
|
|
def pytest_configure(self, config):
|
|
if hasattr(config, "workerinput") or getattr(config.option, "numprocesses", None) not in (None, 0) or getattr(config.option, "dist", "no") != "no":
|
|
import pytest
|
|
raise pytest.UsageError("vehicle tests require serial execution in the pinned native process")
|
|
|
|
def pytest_collection_modifyitems(self, session, config, items):
|
|
selected, imported = [], []
|
|
for item in items:
|
|
owner = item.cls if item.cls is not None else item.obj
|
|
(selected if getattr(owner, "__module__", None) == item.module.__name__ else imported).append(item)
|
|
if imported:
|
|
config.hook.pytest_deselected(items=imported)
|
|
items[:] = selected
|
|
|
|
def pytest_collection_finish(self, session):
|
|
self.nodeids = [item.nodeid for item in session.items]
|
|
|
|
def pytest_collectreport(self, report):
|
|
if report.failed:
|
|
self.errors.append(str(report.longrepr))
|
|
|
|
def pytest_runtest_logreport(self, report):
|
|
identity = test_id(report.nodeid)
|
|
record = self.records.setdefault(identity, {"id": identity, "status": "passed", "detail": "",
|
|
"time": 0.0, "stdout": "", "stderr": ""})
|
|
record["time"] += report.duration
|
|
xfail = getattr(report, "wasxfail", None)
|
|
if report.failed:
|
|
status = "xpassed" if report.when == "call" and str(report.longrepr).startswith("[XPASS(strict)]") else "failed" if report.when == "call" else "error"
|
|
elif report.skipped:
|
|
status = "xfailed" if xfail else "skipped"
|
|
elif report.when == "call" and xfail:
|
|
status = "xpassed"
|
|
else:
|
|
status = "passed"
|
|
priorities = {"passed": 0, "skipped": 1, "xfailed": 1, "xpassed": 2, "failed": 3, "error": 4}
|
|
if priorities[status] >= priorities[record["status"]]:
|
|
record["status"] = status
|
|
if status != "passed":
|
|
record["detail"] = str(report.longrepr) if report.longrepr else str(xfail or "")
|
|
for name, content in report.sections:
|
|
key = "stderr" if "stderr" in name else "stdout" if "stdout" in name else None
|
|
if key and content not in record[key]:
|
|
record[key] += content
|
|
|
|
|
|
@contextmanager
|
|
def serial_environment():
|
|
values = {"PYTEST_ADDOPTS": "", "PYTEST_PLUGINS": "", "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1"}
|
|
saved = {key: os.environ.get(key) for key in values}
|
|
os.environ.update(values)
|
|
try:
|
|
yield
|
|
finally:
|
|
for key, value in saved.items():
|
|
if value is None:
|
|
os.environ.pop(key, None)
|
|
else:
|
|
os.environ[key] = value
|
|
|
|
|
|
def invoke(arguments, recorder):
|
|
import pytest
|
|
with serial_environment():
|
|
return pytest.main(["-q", "--rootdir=.", "-o", "addopts=", "-p", "no:xdist", "-p", "no:xdist.plugin", *arguments], plugins=[recorder])
|
|
|
|
|
|
def collect(targets):
|
|
if not targets:
|
|
return [], []
|
|
recorder = Recorder()
|
|
code = invoke(["--collect-only", *targets], recorder)
|
|
errors = list(recorder.errors)
|
|
if code != 0:
|
|
errors.append(f"pytest collection exited {code}")
|
|
ids = [test_id(nodeid) for nodeid in recorder.nodeids]
|
|
if len(set(ids)) != len(ids):
|
|
errors.append("pytest collected duplicate test IDs")
|
|
return recorder.nodeids, errors
|
|
|
|
|
|
def run(nodeids):
|
|
if not nodeids:
|
|
return [], []
|
|
recorder = Recorder()
|
|
code = invoke(nodeids, recorder)
|
|
errors = list(recorder.errors)
|
|
if recorder.nodeids != nodeids:
|
|
errors.append("pytest execution collection changed from the selected IDs")
|
|
if code not in (0, 1):
|
|
errors.append(f"pytest execution exited {code}")
|
|
return list(recorder.records.values()), errors
|