plesty init mock-test

Adds the hardware-free test gate d1 requires to an existing device module. Unlike plesty init field-test, it asks nothing.

cd plesty-pm100d
plesty init mock-test          # writes tests/test_mock_pipeline.py
plesty init mock-test --force  # regenerate over its own previous output

The field-test interview exists because every one of its questions is a safety judgement about a real instrument. Nothing in the mock pipeline can move a stage or fire an exposure, and everything it needs is either declared by the device or settled by trying it:

Source

What it gives

the device’s doc_model()

the parameters and operations, read through the same reader the field test uses

the package name

the module and the import

a construction probe

how to reach the device without hardware

The probe

The probe is the design. A device module is hardware-free in one of several ways — address="mock" selects an in-memory simulator in one reference module, use_mock_solver=True does it in the other — and which one applies is knowable by constructing the device and looking. Each candidate is verified the way the gates will verify it: connect, read is_operatable, call identity(), and call check_errors(), which must return a list — empty on a fresh device. A strategy is written into the file only once it has passed the checks the file will make, which is why a generated test that gets written is a generated test that passes those checks.

When neither applies, the last resort is the module’s own DocDevice — the hardware-free construction gate d3 already requires. A module scaffolded by plesty init device exports it as partial(Device, id=...), and because DevicePipeline constructs from a class, the file passes Device and forwards the partial’s own arguments:

PIPELINE = DevicePipeline(Device, *DocDevice.args, **DocDevice.keywords)

A DocDevice that is neither a class nor a partial of Device is refused with that reason.

Where no strategy works the command refuses and lists what it tried. It does not fall back to asking: a developer handed a question the probe could not answer would be guessing at the same thing, and a file built on a guess looks complete and tests less than it claims. Give the device a hardware-free path — address="mock" is the convention, and plesty.lib.sim.demo_device.DemoDevice is the reference implementation of it — then run the command again.

The probe covers three of the eight gates. On a fresh scaffold the file is written and gate 2 (parameter key resolution) still fails until the device declares its schema — the first of the next steps plesty init device prints.

The generated file

The generated file is short, because run_mock_pipeline() runs all eight gates and raises with everything that failed:

from plesty.lib.test.device_pipeline import DevicePipeline

from plesty.pm100d import Device

PIPELINE = DevicePipeline(Device, address="mock")


def test_mock_pipeline() -> None:
    """Run the eight mock gates and fail with everything that broke."""
    PIPELINE.run_mock_pipeline()

No schema path appears in it. The gates read what the device declares at run time, so nothing here restates a schema and nothing goes stale when the device gains a parameter.

The file is always tests/test_mock_pipeline.py, never tests/test_<module>.py — that is where a module’s hand-written unit tests already live, and one reference module keeps 487 lines of them. --force regenerates over this command’s own output and refuses anything else, checked by a marker in the file, so a regeneration cannot destroy work.