plesty.lib.test.device_pipeline
Standard device API test pipeline for PLESTY device modules.
Eight gates, no hardware. This is the contract SDK gate d1 enforces, and every gate here runs in CI on every push.
Hardware belongs to the field test. Until v0.4.0 this class also carried a
multi-client resource-contention gate and a schema-refresh gate that needed a
real instrument, numbered 9 and 10 — so the class had ten gates of which
run_mock_pipeline() ran eight, and no hub module ever called the other
two. FieldTestPipeline is where hardware
is exercised now; see Migrating from the ten-gate pipeline below.
What passing here does and does not mean: these gates verify that a module’s declarations are coherent and that its parameter and operation systems work against a mock. They say nothing about the command-format contract, driver binding, or transport pathologies — those have no mock equivalent and are what the field test is for. A module that passes all eight can still fail on the instrument, by design.
Import DevicePipeline in a device 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. plesty init
mock-test generates the whole file from the module’s own declarations.
Typical usage in a device module — the whole file:
# tests/test_mydevice.py
from plesty.lib.test.device_pipeline import DevicePipeline
from plesty.mydevice import Device
PIPELINE = DevicePipeline(Device, address="mock")
def test_mock_pipeline():
PIPELINE.run_mock_pipeline()
Gate summary
Gate 1 test_schema_integrity — the device’s declarations are coherent.
Gate 2 test_param_key_resolution — every key in get_config_list() resolves.
Gate 3 test_params_mock — all config params round-trip (mock solver).
Gate 4 test_funcs_mock — all operations return a dict (mock solver).
Gate 5 test_lifecycle — connect / is_operatable / disconnect.
Gate 6 test_identity — identity() returns a non-empty string.
Gate 7 test_check_errors — check_errors() returns [] on healthy mock.
Gate 8 test_state_coverage — dev.state keys ⊇ get_config_list().
The numbers are the docs’ running order and nothing else — three other
numbered gate sequences exist (the field test’s own eight, plesty check’s
fourteen), so a bare “gate 8” is ambiguous. Say the name.
Nothing here reads a schema file. Gate 1 validates what doc_model()
reports, because the device is the only authority: parameters registered in
Python are invisible in the JSON, and for a grouped schema the file’s keys are
not the keys get_config_list() answers. A gate reading the files checks
something the running device does not use.
Migrating from the ten-gate pipeline
test_hardware_schema_refresh (was gate 10) is superseded by
propose_schema_update(),
which proposes a reviewed change instead of silently writing a
*_refreshed.json beside the original. The underlying
plesty.lib.test.schema_params.test_real_device_refreshes_schema_defaults_and_types()
is unchanged and still importable if the old behaviour is wanted.
test_resource_allocation (was gate 9) moved out whole: call
plesty.lib.test.resource_allocation.assert_resource_manager_client_allocation()
directly. It spawns a server and three client processes, which is not
something to hide behind a method named like a mock gate — and importing it is
what pulled pytest into every consumer of this module (plesty-lib#19).
Attributes
Classes
Standard test pipeline for a PLESTY device API module. |
Functions
|
Return every incoherence in the device's declared parameters. |
|
Return the option and access-mode problems of one parameter. |
|
Return a message per runtime key claimed by more than one parameter. |
|
Return every incoherence in the device's declared operations. |
Module Contents
- class plesty.lib.test.device_pipeline.DevicePipeline(device_cls: type, *args: Any, seed: int = 123, **kwargs: Any)
Standard test pipeline for a PLESTY device API module.
- Parameters:
device_cls (type) – The device class under test.
*args (Any) – Positional arguments forwarded to
device_cls.__init__.seed (int) – RNG seed passed to
DataGeneratorfor reproducible mock values.**kwargs (Any) – Keyword arguments forwarded to
device_cls.__init__.class (Store device)
arguments (construction)
configuration. (and pipeline)
- device_cls
- args = ()
- kwargs
- seed = 123
- test_schema_integrity() None
Gate 1: what the device declares is coherent.
Read from
doc_model(), not from the schema files. The device is the only authority, and reading the JSON instead goes wrong twice, silently: anything registered in Python —register_configin__init__, an@expose_to_apimethod — is invisible in the files, and for a grouped schema the keys are wrong, becauseget_config_list()answersprefix.keywhile the file is keyed by the bare name. That is the group-name against command-prefix mismatch three modules hit independently. A gate reading the files checks something the running device does not use.Checks that every parameter declares a usable type, that its declared bounds and options agree with that type, that no two parameters collide on the runtime key, and that every operation output declares a type.
- Return type:
None
- test_param_key_resolution() None
Gate 2: every key in get_config_list() resolves via get_config(key).
Also verifies that param.name matches the bare (non-prefixed) part of each key, confirming the grouped-key invariant is upheld.
- Return type:
None
- test_params_mock(ignore_keys: list[str] | None = None) None
Gate 3: all config params round-trip through the mock solver.
Read-only params are queried; read-write params are written then queried back. Write-only params are skipped.
- Parameters:
ignore_keys (list[str] | None)
- Return type:
None
- test_funcs_mock(ignore_ops: list[str] | None = None) None
Gate 4: all registered operations return a dict with the mock solver.
A mock op solver is bound that generates schema-valid outputs from
FuncOutputmetadata, so no hardware is required.- Parameters:
ignore_ops (list[str] | None)
- Return type:
None
- test_lifecycle() None
Gate 5: context manager completes; is_operatable is True after connect.
The device must implement
init(),connect(), anddisconnect()in a way that does not require real hardware (e.g., by accepting a mock transport as a constructor argument).- Return type:
None
- test_identity() None
Gate 6: identity() returns a non-empty string.
The device must override identity() — returning a mock IDN string is acceptable in mock mode.
- Return type:
None
- test_check_errors() None
Gate 7: check_errors() returns an empty list on a healthy mock device.
- Return type:
None
- test_state_coverage() None
Gate 8: dev.state keys are a superset of get_config_list().
The mock param solver is applied so that query() calls succeed without hardware. Extra keys in state (beyond the config list) are permitted and reported as informational.
- Return type:
None
- run_mock_pipeline(ignore_keys: list[str] | None = None, ignore_ops: list[str] | None = None) None
Run all eight gates and raise with everything that failed.
Every gate runs even after one fails, so a single run reports all of it rather than one thing at a time. This is what SDK gate d1 looks for, and what
plesty init mock-testgenerates.- Parameters:
ignore_keys (list[str] | None) – Config parameter keys to skip in the parameter gate.
ignore_ops (list[str] | None) – Operation names to skip in the operation gate.
- Return type:
None
- plesty.lib.test.device_pipeline._check_parameters(groups: dict[str, list[Any]]) list[str]
Return every incoherence in the device’s declared parameters.
- Parameters:
groups (dict[str, list[Any]]) –
doc_model().parameters— parameters by configuration group.- Returns:
One message per problem found, empty when there are none.
- Return type:
list[str]
- plesty.lib.test.device_pipeline._check_param_bounds(where: str, param: Any, dtype: type) list[str]
Return the option and access-mode problems of one parameter.
Only what registration does not already reject.
register_configvalidates thatmin_valueis not abovemax_value, and that a default is within its type and options — but never that the options themselves match the declared type, and never that a parameter is not declared readable and writable-only at once. The mock generator draws from this metadata, so either produces a value the device then rejects.- Parameters:
where (str) – The parameter’s qualified name, for the message.
param (Any) – The declared parameter.
dtype (type) – Its declared type.
- Returns:
One message per problem found.
- Return type:
list[str]
- plesty.lib.test.device_pipeline._check_runtime_keys(keys: list[str]) list[str]
Return a message per runtime key claimed by more than one parameter.
- Parameters:
keys (list[str]) – What
get_config_list()answers.- Returns:
One message per collision, empty when every key is unique.
- Return type:
list[str]
- plesty.lib.test.device_pipeline._check_operations(functions: list[Any]) list[str]
Return every incoherence in the device’s declared operations.
- Parameters:
functions (list[Any]) –
doc_model().functions— the device’s own operations.- Returns:
One message per problem found.
- Return type:
list[str]