Skip to content

Commit

Permalink
1) change timer field from Dict[str, Interval] to Dict[str, List[Inte…
Browse files Browse the repository at this point in the history
…rval]] (#1055)

2) make timer available to all entities via ReportLink linkage to parent's report
3) remove EventRecorder
4) re-org Report class hierarchy
5) re-org ReportSchema class hierarchy
6) UI changes and tests

Co-authored-by: Pyifan <Pyifan@users.noreply.github.com>
  • Loading branch information
Pyifan and Pyifan committed Apr 19, 2024
1 parent 4b62713 commit c1e71ca
Show file tree
Hide file tree
Showing 53 changed files with 2,779 additions and 2,699 deletions.
2 changes: 2 additions & 0 deletions doc/newsfragments/2668_changed.new_timer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enhance timer field of testplan report to allow multiple records under the same key. We now merge multitest parts aggregating their runtime. Also refactored Report and ReportSchema class hierachy.

2 changes: 1 addition & 1 deletion examples/Data Science/overfitting/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def polynomial_regression(self, env, result, degrees):
description="Mean Square Error threshold on test data",
)
result.less(
timer["train_model"].elapsed,
timer.last(key="train_model").elapsed,
1,
description="How long did the model take to train?",
)
Expand Down
68 changes: 47 additions & 21 deletions testplan/common/entity/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@
from schema import Or

from testplan.common.config import Config, ConfigOption
from testplan.common.report.base import EventRecorder
from testplan.common.utils import logger
from testplan.common.utils.path import default_runpath, makedirs, makeemptydirs
from testplan.common.utils.strings import slugify, uuid4
from testplan.common.utils.thread import execute_as_thread, interruptible_join
from testplan.common.utils.timing import wait
from testplan.common.utils.timing import wait, Timer
from testplan.common.utils.validation import is_subclass


class ReportLink:
"""
A recursive linkage that will be available to all Entity object.
"""

def __init__(self, name: str):
self.name = name
self.timer = Timer()
self.children: List["ReportLink"] = []


class Environment:
"""
A collection of resources that can be started/stopped.
Expand Down Expand Up @@ -523,6 +533,7 @@ def __init__(self, **options):
self._uid = None
self._should_abort = False
self._aborted = False
self._report = None

def __str__(self):
return f"{self.__class__.__name__}[{self.uid()}]"
Expand Down Expand Up @@ -583,6 +594,28 @@ def parent(self, value):
"""
self._parent = value

@property
def report(self):
"""
A handle to access the report via recursive parent
"""
if not self._report:
self._report = ReportLink(name=str(self))
if self.parent and hasattr(self.parent, "report"):
# don't always have parent in test
# ChildLoop doesn't have report
self.parent.report.children.append(self._report)
else:
# will just have a dangling child
# but the timer won't be collected to report
self.logger.debug("dangling report")

return self._report

@property
def timer(self):
return self.report.timer

def pause(self):
"""
Pauses entity execution.
Expand Down Expand Up @@ -863,18 +896,10 @@ def __init__(self, **options):
self._environment: Environment = self.__class__.ENVIRONMENT(
parent=self
)
self._result: RunnableResult = self.__class__.RESULT()
self.result: RunnableResult = self.__class__.RESULT()
self._steps: Deque[Tuple[Callable, List, Dict]] = deque()
self._ihandler = None

@property
def result(self):
"""
Returns a
:py:class:`~testplan.common.entity.base.RunnableResult`
"""
return self._result

@property
def resources(self):
"""
Expand Down Expand Up @@ -1190,23 +1215,23 @@ def run(self):
else:
self._run_batch_steps()
except Exception as exc:
self._result.run = exc
self.result.run = exc
self.logger.error(traceback.format_exc())
else:
# TODO fix swallow exceptions in self._result.step_results.values()
self._result.run = (
# TODO fix swallow exceptions in self.result.step_results.values()
self.result.run = (
self.status == RunnableStatus.FINISHED
and self.run_result() is True
)
return self._result
return self.result

def run_result(self):
"""
Returns if a run was successful.
"""
return all(
not isinstance(val, Exception) and val is not False
for val in self._result.step_results.values()
for val in self.result.step_results.values()
)

def dry_run(self):
Expand Down Expand Up @@ -1319,9 +1344,6 @@ def __init__(self, **options):
self.STATUS.STOPPED: self._wait_stopped,
}
)
self.event_recorder: EventRecorder = EventRecorder(
name=self.uid(), event_type="Executor"
)

@property
def context(self):
Expand Down Expand Up @@ -1373,7 +1395,7 @@ def start(self):
)
return

self.event_recorder.start_time = time.time()
self.timer.start("lifespan")

self.logger.info("Starting %s", self)
self.status.change(self.STATUS.STARTING)
Expand Down Expand Up @@ -1419,7 +1441,7 @@ def stop(self):
if not self.async_start:
self.wait(self.STATUS.STOPPED)
self.logger.info("%s stopped", self)
self.event_recorder.end_time = time.time()
self.timer.end("lifespan")

def pre_start(self):
"""
Expand Down Expand Up @@ -1660,6 +1682,10 @@ def _initialize_runnable(self, **options):
runnable_class = self._cfg.runnable
return runnable_class(**options)

@property
def report(self):
return self.runnable.report

def __getattr__(self, item):
try:
return self.__getattribute__(item)
Expand Down
5 changes: 4 additions & 1 deletion testplan/common/report/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from .base import (
Status,
RuntimeStatus,
ReportCategories,
Report,
ReportGroup,
BaseReportGroup,
ExceptionLogger,
MergeError,
SkipTestcaseException,
Expand Down

0 comments on commit c1e71ca

Please sign in to comment.