Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor/streamline testplan report - part 1 #1163

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion dodo.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ def task_test():

def task_build():
return {
"actions": ["python -m build -w"],
"actions": [
CmdAction(
"python -m build -w", env=updated_env({"DEV_BUILD": "0"})
)
],
"task_dep": ["build_ui"],
"doc": "Build a wheel package",
}
Expand Down
6 changes: 3 additions & 3 deletions testplan/common/exporters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from testplan.common.config import Config, Configurable
from testplan.common.utils import strings
from testplan.common.utils.timing import utcnow
from testplan.common.utils.timing import now
from testplan.report import TestReport


Expand All @@ -17,7 +17,7 @@ class ExporterResult:
result: Dict = None
traceback: str = None
uid: str = strings.uuid4()
start_time: datetime = utcnow()
start_time: datetime = now()
end_time: datetime = None

@property
Expand Down Expand Up @@ -153,7 +153,7 @@ def run_exporter(
except Exception:
exp_result.traceback = traceback.format_exc()
finally:
exp_result.end_time = utcnow()
exp_result.end_time = now()
if not exp_result.success:
exporter.logger.error(exp_result.traceback)
if result:
Expand Down
18 changes: 17 additions & 1 deletion testplan/common/report/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ class ReportCategories:
# use for before/after_start/stop, setup, teardown, etc
SYNTHESIZED = "synthesized"

@classmethod
def is_test_level(cls, cat):
return cat in (
cls.MULTITEST,
cls.TASK_RERUN,
cls.GTEST,
cls.CPPUNIT,
cls.BOOST_TEST,
cls.HOBBESTEST,
cls.PYTEST,
cls.PYUNIT,
cls.UNITTEST,
cls.QUNIT,
cls.JUNIT,
cls.ERROR,
)


class Report:
"""
Expand Down Expand Up @@ -510,7 +527,6 @@ def __init__(self, name, **kwargs):
super(BaseReportGroup, self).__init__(name=name, **kwargs)

self._index: Dict = {}
self.host: Optional[str] = None
self.children = []

self.build_index()
Expand Down
4 changes: 2 additions & 2 deletions testplan/common/report/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def emit(self, record):
if hasattr(record, "report_obj_id"):
report = REPORT_MAP.get(record.report_obj_id)
if report is not None:
created = datetime.datetime.utcfromtimestamp(
created = datetime.datetime.fromtimestamp(
record.created
).replace(tzinfo=timezone.utc)
).astimezone()
report.logs.append(
{
"message": self.format(record),
Expand Down
16 changes: 12 additions & 4 deletions testplan/common/report/schemas.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Base schemas for report serialization.
"""
from marshmallow import Schema, fields, post_load
from marshmallow import Schema, fields, post_load, post_dump
from marshmallow.utils import EXCLUDE

from testplan.common.report.base import (
Expand All @@ -22,8 +22,8 @@
class IntervalSchema(Schema):
"""Schema for ``timer.Interval``"""

start = custom_fields.UTCDateTime()
end = custom_fields.UTCDateTime(allow_none=True)
start = fields.DateTime("iso")
end = fields.DateTime("iso", allow_none=True)

@post_load
def make_interval(self, data, **kwargs):
Expand Down Expand Up @@ -68,7 +68,7 @@ class ReportLogSchema(Schema):
message = fields.String()
levelname = fields.String()
levelno = fields.Integer()
created = custom_fields.UTCDateTime()
created = fields.DateTime("iso")
funcName = fields.String()
lineno = fields.Integer()
uid = fields.UUID()
Expand Down Expand Up @@ -126,6 +126,14 @@ def make_report(self, data, **kwargs):
rep.timer = timer
return rep

@post_dump
def strip_none(self, data, **kwargs):
if data["status_override"] is None:
del data["status_override"]
if data["status_reason"] is None:
del data["status_reason"]
return data


class BaseReportGroupSchema(ReportSchema):
"""Schema for ``base.BaseReportGroup``."""
Expand Down
21 changes: 12 additions & 9 deletions testplan/common/utils/timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ def retry_until_timeout(

def utcnow() -> datetime.datetime:
"""Timezone aware UTC now."""
return datetime.datetime.utcnow().replace(tzinfo=timezone.utc)
return datetime.datetime.now(tz=timezone.utc)


def now() -> datetime.datetime:
"""Timezone aware local time."""
return datetime.datetime.now().astimezone()


_Interval = collections.namedtuple("_Interval", "start end")
Expand Down Expand Up @@ -299,17 +304,15 @@ def __init__(self, timer, key):
self.start_ts = None

def __enter__(self):
self.start_ts = utcnow()
self.start_ts = now()

def __exit__(self, exc_type, exc_value, _):
if self.key in self.timer:
self.timer[self.key].append(
Interval(start=self.start_ts, end=utcnow())
Interval(start=self.start_ts, end=now())
)
else:
self.timer[self.key] = [
Interval(start=self.start_ts, end=utcnow())
]
self.timer[self.key] = [Interval(start=self.start_ts, end=now())]


class Timer(dict):
Expand All @@ -333,9 +336,9 @@ def record(self, key):
def start(self, key):
"""Record the start timestamp for the given key."""
if key in self:
self[key].append(Interval(utcnow(), None))
self[key].append(Interval(now(), None))
else:
self[key] = [Interval(utcnow(), None)]
self[key] = [Interval(now(), None)]

def end(self, key):
"""
Expand All @@ -344,7 +347,7 @@ def end(self, key):
if key not in self or self.last(key).end is not None:
raise KeyError(f"`start` missing for {key}, cannot record end.")

self[key][-1] = Interval(self[key][-1].start, utcnow())
self[key][-1] = Interval(self[key][-1].start, now())

def merge(self, timer):
for key in timer:
Expand Down
1 change: 1 addition & 0 deletions testplan/exporters/testing/xml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ def export(
)
xml_dir = pathlib.Path(self.cfg.xml_dir).resolve()

# TODO: what if xml_dir is a file?
if xml_dir.exists():
shutil.rmtree(xml_dir)

Expand Down
49 changes: 20 additions & 29 deletions testplan/report/testing/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,26 @@ def __init__(
self.label = label
self.information = information or []
self.resource_meta_path: Optional[str] = None
try:
user = getpass.getuser()
except (ImportError, OSError):
# if the USERNAME env variable is unset on Windows, this fails
# with ImportError
user = "unknown"
self.information.extend(
[
("user", user),
("command_line_string", " ".join(sys.argv)),
("python_version", platform.python_version()),
]
)
if self.label:

# reports coming from tpr already have certain info set
info_keys = [info[0] for info in self.information]
if "user" not in info_keys:
try:
user = getpass.getuser()
except (ImportError, OSError):
# if the USERNAME env variable is unset on Windows, this fails
# with ImportError
user = "unknown"
self.information.append(("user", user))
if "command_line_string" not in info_keys:
self.information.append(
("command_line_string", " ".join(sys.argv))
)
if "python_version" not in info_keys:
self.information.append(
("python_version", platform.python_version())
)
if self.label and "label" not in info_keys:
self.information.append(("label", label))

# Report attachments: Dict[dst: str, src: str].
Expand Down Expand Up @@ -145,21 +151,9 @@ def bubble_up_attachments(self):
will be used by Exporters to export attachments as well as the report.
"""
for child in self:
if getattr(child, "fix_spec_path", None):
self._bubble_up_fix_spec(child)
for attachment in child.attachments:
self.attachments[attachment.dst_path] = attachment.source_path

def _bubble_up_fix_spec(self, child):
"""Bubble up a "fix_spec_path" from a child report."""
real_path = child.fix_spec_path
hash_dir = hashlib.md5(real_path.encode("utf-8")).hexdigest()
hash_path = os.path.join(
hash_dir, os.path.basename(child.fix_spec_path)
)
child.fix_spec_path = hash_path
self.attachments[hash_path] = real_path

def _get_comparison_attrs(self):
return super(TestReport, self)._get_comparison_attrs() + [
"tags_index",
Expand Down Expand Up @@ -251,7 +245,6 @@ def __init__(
category=ReportCategories.TESTGROUP,
tags=None,
part=None,
fix_spec_path=None,
env_status=None,
strict_order=False,
**kwargs,
Expand All @@ -272,8 +265,6 @@ def __init__(
self.part = part # i.e. (m, n), while 0 <= m < n and n > 1
self.part_report_lookup = {}

self.fix_spec_path = fix_spec_path

if self.entries:
self.propagate_tag_indices()

Expand Down
28 changes: 21 additions & 7 deletions testplan/report/testing/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import math

from boltons.iterutils import is_scalar, remap
from marshmallow import Schema, fields, post_load
from marshmallow import Schema, fields, post_load, post_dump
from marshmallow.utils import EXCLUDE

from testplan.common.report.base import ReportCategories
from testplan.common.report.schemas import (
BaseReportGroupSchema,
ReportLinkSchema,
Expand Down Expand Up @@ -88,7 +89,7 @@ class TestCaseReportSchema(ReportSchema):
source_class = TestCaseReport

entries = fields.List(EntriesField())
category = fields.String(dump_only=True)
category = fields.String()
counter = fields.Dict(dump_only=True)
tags = TagField()

Expand Down Expand Up @@ -116,9 +117,8 @@ class TestGroupReportSchema(BaseReportGroupSchema):
source_class = TestGroupReport

part = fields.List(fields.Integer, allow_none=True)
fix_spec_path = fields.String(allow_none=True)
env_status = fields.String(allow_none=True)
strict_order = fields.Bool()
strict_order = fields.Bool(allow_none=True)
category = fields.String()
tags = TagField()

Expand All @@ -129,7 +129,10 @@ class TestGroupReportSchema(BaseReportGroupSchema):
},
many=True,
)
host = fields.String(allow_none=True)

# # abolished
# fix_spec_path = fields.String(allow_none=True, load_only=True)
# host = fields.String(allow_none=True, load_only=True)

@post_load
def make_report(self, data, **kwargs):
Expand All @@ -140,6 +143,15 @@ def make_report(self, data, **kwargs):
rep.propagate_tag_indices()
return rep

@post_dump
def strip_none_by_category(self, data, **kwargs):
if not ReportCategories.is_test_level(data["category"]):
del data["part"]
del data["env_status"]
if data["category"] != ReportCategories.TESTSUITE:
del data["strict_order"]
return data


class TestReportSchema(BaseReportGroupSchema):
"""Schema for test report root, ``testing.TestReport``."""
Expand All @@ -153,7 +165,7 @@ class Meta:
meta = fields.Dict()
label = fields.String(allow_none=True)
tags_index = TagField(dump_only=True)
information = fields.List(fields.List(fields.String()))
information = fields.List(fields.Tuple([fields.String(), fields.String()]))
resource_meta_path = fields.String(dump_only=True, allow_none=True)
counter = fields.Dict(dump_only=True)

Expand Down Expand Up @@ -194,7 +206,6 @@ class Meta:
category = fields.String()
timer = TimerField(required=True)
part = fields.List(fields.Integer, allow_none=True)
fix_spec_path = fields.String(allow_none=True)

status_override = fields.Function(
lambda x: x.status_override.to_json_compatible(), allow_none=True
Expand All @@ -214,6 +225,9 @@ class Meta:
strict_order = fields.Bool()
children = fields.List(fields.Nested(ReportLinkSchema))

# # abolished
# fix_spec_path = fields.String(allow_none=True, load_only=True)

@post_load
def make_testgroup_report(self, data, **kwargs):
children = data.pop("children", [])
Expand Down
7 changes: 0 additions & 7 deletions testplan/testing/multitest/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,6 @@ def get_options(cls):
config.ConfigOption("multi_part_uid", default=None): Or(
None, lambda x: callable(x)
),
config.ConfigOption("fix_spec_path", default=None): Or(
None, And(str, os.path.exists)
),
config.ConfigOption("testcase_report_target", default=True): bool,
}

Expand Down Expand Up @@ -265,8 +262,6 @@ class MultiTest(testing_base.Test):
if `part` attribute is defined, otherwise use default implementation.
:type multi_part_uid: ``callable``
:type result: :py:class:`~testplan.testing.multitest.result.result.Result`
:param fix_spec_path: Path of fix specification file.
:type fix_spec_path: ``NoneType`` or ``str``.
:param testcase_report_target: Whether to mark testcases as assertions for filepath
and line number information
:type testcase_report_target: ``bool``
Expand Down Expand Up @@ -304,7 +299,6 @@ def __init__(
stdout_style=None,
tags=None,
result=result.Result,
fix_spec_path=None,
testcase_report_target=True,
**options,
):
Expand Down Expand Up @@ -733,7 +727,6 @@ def _new_test_report(self):
category=ReportCategories.MULTITEST,
tags=self.cfg.tags,
part=self.cfg.part,
fix_spec_path=self.cfg.fix_spec_path,
env_status=entity.ResourceStatus.STOPPED,
)

Expand Down
Loading