plesty.lib.test.experiment_pipeline =================================== .. py:module:: plesty.lib.test.experiment_pipeline .. autoapi-nested-parse:: Standard experiment contract test pipeline for PLESTY experiment modules. Import ``ExperimentPipeline`` in an experiment module's test suite and call the individual gate methods from pytest test functions, or call ``run_mock_pipeline()`` to run all gates in one shot. No hardware is needed: the pipeline never calls ``run()`` — it validates the plan/checkpoint contract defined by :class:`plesty.lib.experiment.Experiment`. Typical usage in an experiment module:: # tests/test_pipeline.py from plesty.lib.test.experiment_pipeline import ExperimentPipeline PIPELINE = ExperimentPipeline(MyExperiment) def test_experiment_subclass(): PIPELINE.test_experiment_subclass() def test_plan_deterministic(): PIPELINE.test_plan_deterministic() def test_plan_ops_resolve(): PIPELINE.test_plan_ops_resolve() def test_plan_serializable(): PIPELINE.test_plan_serializable() def test_lifecycle_hooks(): PIPELINE.test_lifecycle_hooks() Gate summary ------------ * Gate 1 ``test_experiment_subclass`` — public Experiment subclass, instantiable without devices. * Gate 2 ``test_plan_deterministic`` — ``build_plan()`` twice yields an identical ``content_hash()``. * Gate 3 ``test_plan_ops_resolve`` — every ``Step.op`` resolves to a public callable. * Gate 4 ``test_plan_serializable`` — ``Plan.save``/``load`` round-trip preserves the hash. * Gate 5 ``test_lifecycle_hooks`` — ``setup``/``teardown`` overrides are async. Classes ------- .. autoapisummary:: plesty.lib.test.experiment_pipeline.ExperimentPipeline Module Contents --------------- .. py:class:: ExperimentPipeline(experiment_cls: type, *args: Any, **kwargs: Any) Standard test pipeline for a PLESTY experiment module. :param experiment_cls: The experiment class under test. Must be instantiable without a ``devices`` argument — plan construction is device-free by contract. :param \*args: Positional arguments forwarded to ``experiment_cls.__init__``. :param \*\*kwargs: Keyword arguments forwarded to ``experiment_cls.__init__``. :param Store the experiment class and its construction arguments.: .. py:attribute:: experiment_cls .. py:attribute:: args :value: () .. py:attribute:: kwargs .. py:method:: _instantiate() -> plesty.lib.experiment.Experiment Construct the experiment under test with the stored arguments. .. py:method:: test_experiment_subclass() -> None Gate 1: the class is a public, instantiable Experiment subclass. Verifies inheritance from :class:`plesty.lib.experiment.Experiment`, a public class name, and that construction succeeds without hardware or devices (``build_plan`` must therefore be implemented — an abstract subclass cannot be instantiated). .. py:method:: test_plan_deterministic() -> None Gate 2: build_plan() is deterministic and non-empty. Two consecutive calls must yield plans with the same ``content_hash()`` — resume relies on the regenerated plan matching the persisted one. Step ids therefore must not depend on run time or randomness. .. py:method:: test_plan_ops_resolve() -> None Gate 3: every Step.op names an existing public experiment method. Delegates to the framework's own pre-run validation, so the gate fails exactly when ``run()`` would refuse the plan (:class:`plesty.lib.experiment.InvalidOperationError`). .. py:method:: test_plan_serializable() -> None Gate 4: params/config are strictly JSON-serializable; save/load is hash-intact. ``Plan.save`` stringifies unknown objects (``default=str``), which silently loses type information — on resume the regenerated plan would carry the original objects and never match a reloaded one. The gate therefore first requires strict JSON serializability, then verifies the save/load round trip preserves the content hash. .. py:method:: test_lifecycle_hooks() -> None Gate 5: setup/teardown overrides are async coroutine functions. ``run()`` awaits both hooks; a synchronous override would fail at runtime, mid-measurement. Inherited defaults pass trivially. .. py:method:: run_mock_pipeline() -> None Run Gates 1–5 in sequence and raise if any gate fails. Intended for use in a single pytest test function when individual gates do not need to be reported separately.