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

_VALID_PARAM_TYPES

Classes

DevicePipeline

Standard test pipeline for a PLESTY device API module.

Functions

_check_parameters(→ list[str])

Return every incoherence in the device's declared parameters.

_check_param_bounds(→ list[str])

Return the option and access-mode problems of one parameter.

_check_runtime_keys(→ list[str])

Return a message per runtime key claimed by more than one parameter.

_check_operations(→ list[str])

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 DataGenerator for 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_config in __init__, an @expose_to_api method — is invisible in the files, and for a grouped schema the keys are wrong, because get_config_list() answers prefix.key while 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 FuncOutput metadata, 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(), and disconnect() 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-test generates.

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._VALID_PARAM_TYPES: tuple[type, Ellipsis]
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_config validates that min_value is not above max_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]