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:

  • Configurationlitmus.yaml (project), stations/*.yaml (benches), fixtures/*.yaml (DUT routing), products/*.yaml (specs), catalog/*.yaml (instrument capabilities). All YAML, all Pydantic-validated, all editable without touching test code.
  • Instrument plumbing — auto-fixtures from station YAML, the Mock substitution 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 product needs? See capabilities.
  • Results storage — three stores feeding one queryable surface: the event log, the parquet runs store, and the channel store for time-series. See three 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 LitmusClient to submit results.
  • Instrument drivers. Bring your own — PyMeasure, PyVISA, vendor libraries, or your own classes derived from Instrument / VisaInstrument (importable from litmus.instruments.base and litmus.instruments.visa respectively). See custom drivers.

Multiple Entry Points #

Because Litmus is a platform, you can access it through multiple entry points:

Entry PointUse CaseHow It Works
pytestNew test developmentpytest-native: context, verify, logger fixtures
CLIOperations, debugginglitmus runs, litmus show
HTTP APICI/CD, dashboardsPOST /api/runs, GET /api/runs/{id}
MCP ServerAI integrationClaude Code, other AI agents
Operator UIProduction floorNiceGUI 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 / logger 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.client import LitmusClient
 
client = LitmusClient()
 
run = client.start_run(
    dut_serial="SN123",
    station_id="bench_01",
    test_phase="production",
)
 
with run.step("output_voltage") as step:
    step.measure("output_voltage", 3.31, units="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 (twelve, 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_metrics (yield / Pareto / Cpk)<br/>• litmus_runs (query runs view)<br/>• litmus_steps (query steps view)"]
    platform["Litmus Platform Services"]
 
    agent --> mcp --> platform

Important: Litmus does NOT call LLMs. It exposes tools for AI agents to call.

When to use what #

ScenarioApproach
New pytest projectpytest-native tests with context / verify / logger fixtures (see tutorial step 3).
Existing pytest testsDrop in Litmus fixtures + sidecar YAML incrementally — see integration/pytest-existing.
LabVIEW / TestStand / non-pytest runnersUse LitmusClient to write run results from any Python boundary the other runner can shell out to.
AI-assisted test authoringRun the MCP server and point Claude Code / Cursor / Cline at it.

Architecture Summary #

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"]
        products["Products"]
    end
 
    subgraph Storage["STORAGE LAYER"]
        direction LR
        events["Events<br/>(Arrow)"]
        channels["Channels<br/>(Arrow)"]
        parquet["Runs<br/>(Parquet)"]
    end
 
    UI --> Platform --> Storage

Litmus is the infrastructure layer that connects your tests (top) to your data (bottom), regardless of how you choose to run them.

See also #

Related quadrants: