Source code for checkon.results
import dataclasses
import datetime
import json
import pathlib
import platform
import textwrap
import typing as t
import attr
import marshmallow
import marshmallow_dataclass
import pyrsistent
import xmltodict
import checkon.tox
[docs]@dataclasses.dataclass(frozen=True)
class Failure:
message: str
lines: t.List[str]
[docs] @classmethod
def from_dict(cls, data):
return cls(**pyrsistent.freeze(data))
[docs]class FailureField(marshmallow.fields.Field):
def _deserialize(self, value, attr, data, **kw):
if isinstance(value, list):
[value] = value
return Failure(**value)
return None
[docs]@dataclasses.dataclass(frozen=True)
class TestCaseRun:
name: str
classname: str
file: t.Optional[str]
line: t.Optional[int]
time: str # TODO pendulum
failure: t.Optional[Failure] = dataclasses.field(
metadata={"marshmallow_field": FailureField()}, default=None
)
skipped: t.Any = None
system_err: t.Optional[t.Any] = dataclasses.field(
metadata={"data_key": "system-err"}, default=None
)
[docs]@attr.dataclass(frozen=True)
@dataclasses.dataclass(frozen=True)
class TestSuiteRun:
errors: int
failures: int
tests: int
time: str # TODO pendulum
timestamp: t.Optional[datetime.datetime] # TODO pendulum
hostname: t.Optional[str]
name: t.Optional[str]
test_cases: t.List[TestCaseRun] = dataclasses.field(
metadata={"data_key": "testcase"}
)
envname: t.Optional[str]
skipped: t.Optional[int] = None
[docs] @classmethod
def from_bytes(cls, data, envname):
schema = marshmallow_dataclass.class_schema(cls)(many=True)
parsed = xmltodict.parse(
data,
attr_prefix="",
dict_constructor=dict,
force_list=True,
cdata_key="lines",
)
if "testsuites" in parsed:
[suite] = parsed["testsuites"]
else:
suite = parsed
return schema.load([{**ts, "envname": envname} for ts in suite["testsuite"]])
[docs] @classmethod
def from_path(cls, path):
envname = path.parent.name
return cls.from_bytes(pathlib.Path(path).read_bytes(), envname=envname)
[docs]@attr.dataclass(frozen=True)
class ToxTestSuiteRun:
"""A toxenv result."""
suite: TestSuiteRun
tox_run: checkon.tox.ToxRun
envname: str
[docs] @classmethod
def from_dir(cls, toxenv_dir):
paths = toxenv_dir.glob("test_*.xml")
try:
[path] = paths
except ValueError:
suite = TestSuiteRun(
errors=0,
failures=0,
tests=0,
time="",
timestamp=datetime.datetime.utcnow(),
hostname=platform.node(),
name="",
test_cases=[],
envname=toxenv_dir.name,
)
else:
[suite] = TestSuiteRun.from_path(path)
[tox_data_path] = toxenv_dir.glob("tox_*.json")
tox_run = checkon.tox.ToxRun.from_path(tox_data_path)
return cls(suite, tox_run=tox_run, envname=toxenv_dir.name)
[docs]@attr.dataclass(frozen=True)
class DependentResult:
url: str
suite_runs: t.List[ToxTestSuiteRun]
[docs] @classmethod
def from_dir(cls, output_dir, url):
runs = []
for dir in pathlib.Path(output_dir).glob("*"):
if not dir.is_dir():
continue
runs.append(ToxTestSuiteRun.from_dir(dir))
return cls(url=url, suite_runs=runs)
[docs]@attr.dataclass(frozen=True)
class AppSuiteRun:
upstreamed: str
dependent_result: DependentResult
[docs]@attr.dataclass(frozen=True)
class FailedTest:
name: str
classname: str
file: t.Optional[str]
line: t.Optional[str]
failure: t.Sequence[Failure] = attr.ib(converter=Failure.from_dict)
[docs] @classmethod
def from_test_case(cls, test):
return cls(
**{
k: v
for k, v in dataclasses.asdict(test).items()
if k in attr.fields_dict(FailedTest).keys()
}
)
[docs]@attr.dataclass(frozen=True)
class Comparison:
base_requirement: str
new_requirement: str
base_failures: t.FrozenSet[FailedTest]
new_failures: t.FrozenSet[FailedTest]