pytest-native reference #

Litmus is a hardware test platform; pytest is its primary test-runner integration. The bundled pytest plugin slots into a stock pytest install with zero configuration — every pytest concept (collection, fixtures, markers, plugins, conftest.py, command-line flags) works unchanged. This page is the map of what pytest gives you natively and what the plugin layers on top. For the plugin's own surface see Litmus fixtures and Litmus markers. Other runner integrations (OpenHTF, LabVIEW / TestStand via the results API) live under Integrations.

Collection #

The bundled pytest plugin uses pytest's default collection. No custom collectors, no replacement of pytest_collect_file.

ConventionDefault
Test filestests/test_*.py or tests/*_test.py
Test classesclasses named Test* (no __init__)
Test functions / methodsfunctions named test_*
Overridestandard pytest python_files / python_classes / python_functions in pyproject.toml

What Litmus adds at collection time:

  • A pytest_collection_modifyitems hook merges per-test sidecar YAML (tests/test_<module>.yaml) into each item's marker set. This expands litmus_sweeps into one pytest case per row exactly as if you had written @pytest.mark.parametrize — pytest still owns the case multiplication.
  • Profiles (--test-profile=<name>) add pytest.mark.skip to items they exclude. The selection is visible in pytest --collect-only -q.

The sidecar is recursive: top-level keys apply to every test in the file; tests: { ClassName: { ... } } scopes per class; tests: { ClassName: { tests: { test_method: { ... } } } } scopes per method. See Sidecar configuration.

Fixtures #

pytest's fixture model is unchanged.

  • All four scopes work. function, class, module, session — choose the one that matches your resource cost.
  • Resolution by name. Take a fixture in the test signature; pytest resolves it from the nearest conftest.py upward, then from registered plugins (Litmus included).
  • Yield fixtures, finalizers, request injection all work as pytest documents them.
  • autouse=True works. Litmus's own logger fixture is autouse-session so every test sees an active logger without taking it as an argument.

You can write your own fixtures in conftest.py alongside Litmus's. A common pattern is a project-local dut factory wrapping the Litmus dut session fixture, or a per-class hardware-setup fixture that takes instruments as a dependency.

The 20 fixtures the Litmus plugin contributes are documented in Litmus fixtures.

Markers #

pytest's marker mechanism is unchanged. All of these work on Litmus tests as documented in the pytest manual:

MarkerUse
@pytest.mark.parametrizeGenerate one pytest case per row. Stack to cross-product.
@pytest.mark.skip / @pytest.mark.skipifSkip the test (with optional condition).
@pytest.mark.xfailMark as expected-to-fail; surfaces as XFAIL / XPASS.
@pytest.mark.usefixtures("a", "b")Require fixtures without taking them in the signature.
@pytest.mark.filterwarningsPer-test warning filters.
Custom markers via pytest.ini / pyproject.tomlRegister and filter with -m.

The seven @pytest.mark.litmus_* markers Litmus adds live in the same registry and stack with native ones (with one constraint — see the no-stacking rule for Litmus markers). See Litmus markers for full details.

@pytest.mark.parametrize and @pytest.mark.litmus_sweeps interoperate: both feed the same context.get_param(name) API and the same parquet in_* columns at runtime.

conftest.py #

Works as pytest documents it. Place a conftest.py in tests/ (or a subdirectory) for fixtures and hooks that apply to that scope. Common uses with Litmus:

  • Project-local fixtures that wrap or extend Litmus's (a typed dut accessor, a per-class measurement helper).
  • pytest_addoption for project-specific CLI flags.
  • pytest_collection_modifyitems for project-specific item filtering. (Litmus's hook is in the plugin and runs alongside, not instead of, yours.)
  • pytest_runtest_setup / _teardown for per-test setup beyond what fixtures express.

The Litmus plugin loads via the standard pytest entry-point mechanism — no conftest.py manipulation needed.

Command-line flags #

All pytest flags work. The ones that matter most for hardware test work:

FlagPurpose
-k "expr"Run tests whose nodeid matches the substring expression.
-m "marker"Run tests whose markers match the boolean expression.
-x / --maxfail=NStop after the first failure / Nth failure.
--lf / --ffRun last-failed / failed-first (uses pytest's cache).
--collect-only -qShow what would run without running.
-v / -qVerbose / quiet.
--tb=short / --tb=line / --tb=noTraceback style.
-p no:pluginDisable a specific plugin.
--coAlias for --collect-only.

Litmus adds the flags below. The table is generated from pytest_addoption in src/litmus/pytest_plugin/hooks.py; regenerate with uv run python scripts/generate_reference_docs.py pytest-native after editing the hook.

FlagTypeDefaultDescription
--dut-serialtext'DUT001'DUT serial number
--dut-serialstextPer-slot DUT serials: slot_1=SN1,slot_2=SN2
--slottextPhysical fixture slot for this single-process run (e.g. slot_1, slot_2). Use this when running a single DUT against a specific position in a multi-slot fixture so the run records which slot was exercised. Multi-slot orchestration uses --dut-serials instead — supplying both is an error.
--dut-part-numbertextDUT part number
--dut-revisiontextDUT revision
--dut-lot-numbertextDUT lot/batch number (mirrors LITMUS_DUT_LOT_NUMBER env var)
--stationtextStation ID or YAML path. Bare id looks up stations/<id>.yaml; a value with / or .yaml/.yml is used as an explicit path. When unset, the resolver tries hostname auto-match against stations/*.yaml hostname: fields, then falls back to ProjectConfig.default_station.
--operatortextOperator name
--data-dirtextresolved at runtimeDirectory for Parquet results (default: platform data dir)
--producttextProduct ID or YAML path. Bare id looks up products/<id>.yaml; a value with / or .yaml/.yml is used as an explicit path.
--guardbandtext'0'Default guardband percentage
--mock-instrumentsflagFalseUse mock instruments instead of real hardware. Resolution: this flag > LITMUS_MOCK_INSTRUMENTS env var > litmus.yaml mock_instruments: > false.
--no-mock-instrumentsflag (inverse)TrueUse real hardware (overrides LITMUS_MOCK_INSTRUMENTS env and litmus.yaml mock_instruments: true).
--fixturetextFixture ID or YAML path. Bare id looks up fixtures/<id>.yaml; a value with / or .yaml/.yml is used as an explicit path. When unset, the resolver tries the active profile's fixture: field, then ProjectConfig.default_fixture, then the single-file fallback in fixtures/.
--test-phasetextTest phase (development, validation, characterization, production). If not specified, auto-detects from git status.
--strict-traceabilityflagFalseFail tests whose measurements lack required traceability fields (run_id, step_name, and spec_ref/dut_pin when a spec is active).
--test-profiletextresolved at runtimeNamed profile from litmus.yaml profiles: (overrides vectors, limits, markers, and filter for the session).
--no-test-profileflagFalseSkip profile resolution. Use when profiles are declared but you want to run with bare project defaults (ad-hoc runs).

Plus dynamic flags generated from litmus.yaml: every profiles[*].facets: key becomes --<facet-key> and every required_inputs: key becomes --<required-input-key>. See how-to/profiles.md for the resolution chain (CLI flag → env var → profile binding → default_*).

Plugins that interact with Litmus #

PluginNotes
pytest-rerunfailuresPowers @pytest.mark.litmus_retry. Install it if you use the marker; the translation happens in the plugin.
pytest-xdistParallel execution. Generally not appropriate for hardware tests on a single bench (instruments aren't reentrant). Fine for mock-only suites and CI lint passes.
pytest-covCode coverage. Works unchanged on test files; collects coverage on the test code, not on instrument drivers behind hardware.
pytest-html / pytest-json-reportIndependent of Litmus's own event log + parquet output. Run them alongside if you want pytest-flavored reports too.

Discovery vs activation #

Two things to keep separate:

  • Discovery is pytest's: what tests/test_*.py files match, what items they expose. Litmus has no opinion here.
  • Activation is Litmus's: which station, product, fixture, profile is loaded for the session. Driven by the CLI flags above (or by default_station: / default_profile: in litmus.yaml).

A test that runs on a bringup tier with no station YAML and a test that runs on a factory tier with full traceability are collected identically. The activation context decides what fixtures resolve to and what limits verify finds.

See also #

Related quadrants: