Source code for pytest_experiments.experiment
import datetime as dt
import pytest
from .config import OUTCOMES_ATTR
from .common import (
PytestExperimentsError,
PytestOutcome,
PytestReportPhase,
ExperimentOutcome,
)
from .store import StorageManager, ExperimentModel
[docs]class Experiment:
def __init__(self, request: pytest.FixtureRequest) -> None:
self.context = request
self.store = StorageManager(experiments_db_uri(self.context))
self.created_at = dt.datetime.utcnow()
self.completed_at = None
self.outcome = ExperimentOutcome.not_reported
self.data = {}
[docs] def record(self, **kwargs):
"""Record data about this experiment.
As this data will be serialized as JSON, keys *must* be strings. This
is enforced by the stricter requirement that keys be valid python
identifiers.
"""
self.data.update(**kwargs)
@property
def test_fn(self) -> pytest.Function:
"""The test function."""
return self.context.node
@property
def name(self) -> str:
"""The full qualified name of the test."""
return self.test_fn.nodeid
@property
def parameters(self) -> dict:
"""The input parameters of the test.
This will contain the closure of inputs supplied to the test,
some of which we do not care about.
"""
return dict(
filter(self._ignore_funcargs_items, self.test_fn.funcargs.items())
)
def _ignore_funcargs_items(self, item) -> bool:
"""Ignores some funcarg items that we do not care about."""
_, v = item
if v is self:
return False
if isinstance(v, pytest.FixtureRequest):
return False
return True
[docs] def get_reports(self) -> dict:
"""Returns the reports dict if it exists.
The reports dict is populated by our custom hook and is *only*
available after the test has run.
"""
reports = getattr(self.context.node, OUTCOMES_ATTR, None)
if reports is None:
raise ExperimentError(
"Could not find a `reports` attribute. "
"This method may only be called after the test has run."
)
return reports
[docs] def post_process(self):
"""Inspect the context for test outcome.
The method may only be run *after* the test has completed.
"""
self.completed_at = dt.datetime.utcnow()
self.outcome = to_experiment_outcome(self.get_reports())
[docs] def to_model(self) -> ExperimentModel:
"""Render the experiment into its database model."""
return ExperimentModel(
name=self.name,
start_time=self.created_at,
end_time=self.completed_at,
outcome=self.outcome.name,
parameters=self.parameters,
data=self.data,
)
[docs] def save(self):
"""Save this experiment to the database."""
self.store.record_experiment(self.to_model())
[docs] def finish(self):
"""Post process the experiment and save to the database."""
self.post_process()
self.save()
[docs]def experiments_db_uri(request: pytest.FixtureRequest) -> str:
"""Retrieve the URI of the database used to store experiment results."""
return request.config.option.experiments_database_uri
[docs]def to_experiment_outcome(reports: dict) -> ExperimentOutcome:
"""Return an ExperimentOutcome from the pytest reports"""
if reports[PytestReportPhase.setup] is PytestOutcome.failed:
return ExperimentOutcome.error
test_outcome = reports.get(PytestReportPhase.call)
if test_outcome is None:
return ExperimentOutcome.not_reported
return ExperimentOutcome[test_outcome.name]
[docs]class ExperimentError(PytestExperimentsError):
pass