Platform Architecture #
Litmus is a hardware test platform, not a test framework. Understanding this distinction is key to using Litmus effectively.
Platform vs framework #
A test framework's job ends at "run the test." A test platform's job is everything around it: store the result, version the config, route signals to instruments, expose what happened to operators and dashboards and AI agents. Litmus does the platform job and delegates execution to pytest (the primary integration) or any other Python entry point that calls into LitmusClient to submit results.
What Litmus provides #
The infrastructure pieces a hardware-test team needs whether they're running pytest, a hand-written loop, or a bridge from a non-Python runner:
- Configuration —
litmus.yaml(project),stations/*.yaml(benches),fixtures/*.yaml(UUT routing),parts/*.yaml(specs),catalog/*.yaml(instrument capabilities). All YAML, all Pydantic-validated, all editable without touching test code. - Instrument plumbing — a ready-made handle per instrument from station YAML, the
Mocksubstitution for hardware-free tests, switch-route activation through fixture connections. Drivers themselves are user-supplied (PyMeasure, PyVISA, vendor SDK). - Capability matching — does this station have what this part needs? See capabilities.
- Results storage — four stores feeding one queryable surface: the event log, the run store (parquet), the channel store for time-series, and the file store for artifacts. See data stores for the layout and tradeoffs.
- Operator surface — NiceGUI web UI, operator prompts during a test, real-time dashboards.
- AI surface — MCP server exposing tools an agent can drive: discovery, matching, run launching, results query. Platform never calls an LLM itself.
What Litmus does not provide #
- A test execution engine. Litmus delegates to pytest for new projects; non-pytest runners (LabVIEW / TestStand bridges, hand-written loops, etc.) use
LitmusClientto submit results. - Instrument drivers. Bring your own — PyMeasure, PyVISA, vendor libraries, or your own classes derived from
Instrument/VisaInstrument. See custom drivers.
Multiple Entry Points #
Because Litmus is a platform, you can access it through multiple entry points:
| Entry Point | Use Case | How It Works |
|---|---|---|
| pytest | New test development | pytest-native: context, verify, measure fixtures |
| CLI | Operations, debugging | litmus runs, litmus show |
| HTTP API | CI/CD, dashboards | POST /api/runs, GET /api/runs/{id} |
| MCP Server | AI integration | Claude Code, other AI agents |
| Operator UI | Production floor | NiceGUI web interface |
All entry points share the same:
- Configuration files
- Instrument drivers
- Result storage
- Data models
pytest integration (primary path) #
For new projects, use the pytest plugin. Station YAML declares your instruments; the plugin auto-registers a fixture per role (dmm, psu, etc.) and supplies context / verify / measure for the test body:
def test_output_voltage(context, psu, dmm, verify):
psu.set_voltage(context.get_param("vin", 5.0))
psu.enable_output()
verify("output_voltage", dmm.measure_dc_voltage())psu and dmm come from instruments: in the active station YAML — they aren't built-in fixtures. See writing-tests for the full pytest-native surface.
Catch-all (results API) #
For any test source that isn't pytest — LabVIEW shelling out, TestStand step calling Python, an ad-hoc characterization script:
from litmus import LitmusClient
client = LitmusClient()
run = client.start_run(
uut_serial="SN123",
station_id="bench_01",
test_phase="production",
)
with run.step("output_voltage") as step:
step.measure("output_voltage", 3.31, unit="V", low=3.135, high=3.465)
run.finish()See the Python client reference for the full surface (start_run, RunBuilder.step, StepBuilder.measure, VectorBuilder for parametrized steps).
AI Integration (MCP) #
Litmus exposes its platform services via MCP (Model Context Protocol):
flowchart TB
agent["AI Agent (Claude Code)"]
mcp["**MCP Server**<br/><br/>Tools (thirteen, all litmus_*):<br/>• litmus_project (CRUD on YAML)<br/>• litmus_discover (find instruments)<br/>• litmus_match (capability check)<br/>• litmus_run (execute tests)<br/>• litmus_open (browser URLs)<br/>• litmus_schema (entity JSON schema)<br/>• litmus_events (query events)<br/>• litmus_sessions (list sessions)<br/>• litmus_channels (query channels)<br/>• litmus_files (list artifacts)<br/>• litmus_metrics (yield / Pareto / Ppk)<br/>• litmus_runs (query runs view)<br/>• litmus_steps (query steps view)"]
platform["Litmus Platform Services"]
agent --> mcp --> platformImportant: Litmus does NOT call LLMs. It exposes tools for AI agents to call.
When to use what #
| Scenario | Approach |
|---|---|
| New pytest project | pytest-native tests with context / verify / measure fixtures (see tutorial step 3). |
| Existing pytest tests | Drop in Litmus fixtures + sidecar YAML incrementally — see integration/pytest-existing. |
| LabVIEW / TestStand / non-pytest runners | Use LitmusClient to write run results from any Python boundary the other runner can shell out to. |
| AI-assisted test authoring | Run the MCP server and point Claude Code / Cursor / Cline at it. |
How it fits together #
flowchart TB
subgraph UI["USER INTERFACES"]
direction LR
cli["CLI"]
api["API"]
mcp["MCP"]
nicegui["UI"]
pytest["pytest"]
end
subgraph Platform["LITMUS PLATFORM"]
direction LR
cfg["Config"]
instr["Instruments"]
match["Matching"]
results["Results"]
dialogs["Dialogs"]
parts["Parts"]
end
subgraph Storage["STORAGE LAYER"]
direction LR
events["Events<br/>(Arrow)"]
channels["Channels<br/>(Arrow)"]
files["Files<br/>(Artifacts)"]
parquet["Runs<br/>(Parquet)"]
end
UI --> Platform --> StorageLitmus is the infrastructure layer that connects your tests (top) to your data (bottom), whether you run them through pytest, the results API, or a bridge from another runner.
See also #
Related quadrants:
- How-to → Overview — how-to entry point for this category
- Reference → Overview — reference entry point for this category
- Integration — integration entry point for this category
- Tutorial — tutorial entry point for this category