litmus-test 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- litmus_test-0.1.0/.gitignore +29 -0
- litmus_test-0.1.0/CHANGELOG.md +39 -0
- litmus_test-0.1.0/CONTRIBUTING.md +619 -0
- litmus_test-0.1.0/LICENSE +191 -0
- litmus_test-0.1.0/PKG-INFO +307 -0
- litmus_test-0.1.0/README.md +234 -0
- litmus_test-0.1.0/SECURITY.md +27 -0
- litmus_test-0.1.0/docs/architecture-erd.md +382 -0
- litmus_test-0.1.0/docs/audits/0.1.0-alignment.md +88 -0
- litmus_test-0.1.0/docs/audits/config.md +61 -0
- litmus_test-0.1.0/docs/audits/execution.md +102 -0
- litmus_test-0.1.0/docs/audits/public-api.md +127 -0
- litmus_test-0.1.0/docs/audits/store.md +39 -0
- litmus_test-0.1.0/docs/capability-examples.md +377 -0
- litmus_test-0.1.0/docs/capability-schema.md +260 -0
- litmus_test-0.1.0/docs/comparison.md +147 -0
- litmus_test-0.1.0/docs/concepts/architecture.md +396 -0
- litmus_test-0.1.0/docs/concepts/capabilities.md +359 -0
- litmus_test-0.1.0/docs/concepts/capability-model.md +319 -0
- litmus_test-0.1.0/docs/concepts/event-log.md +174 -0
- litmus_test-0.1.0/docs/concepts/fixtures.md +338 -0
- litmus_test-0.1.0/docs/concepts/flight-streaming.md +76 -0
- litmus_test-0.1.0/docs/concepts/outcomes.md +185 -0
- litmus_test-0.1.0/docs/concepts/overview.md +70 -0
- litmus_test-0.1.0/docs/concepts/platform-architecture.md +247 -0
- litmus_test-0.1.0/docs/concepts/products.md +271 -0
- litmus_test-0.1.0/docs/concepts/results-storage.md +101 -0
- litmus_test-0.1.0/docs/concepts/sessions.md +69 -0
- litmus_test-0.1.0/docs/concepts/stations.md +228 -0
- litmus_test-0.1.0/docs/concepts/step-hierarchy.md +153 -0
- litmus_test-0.1.0/docs/concepts/step-manifest.md +119 -0
- litmus_test-0.1.0/docs/concepts/three-stores.md +83 -0
- litmus_test-0.1.0/docs/concepts/why-event-sourcing.md +70 -0
- litmus_test-0.1.0/docs/concepts/why-pytest.md +51 -0
- litmus_test-0.1.0/docs/concepts.md +388 -0
- litmus_test-0.1.0/docs/connect.md +88 -0
- litmus_test-0.1.0/docs/explorations/api-stability-and-versioning.md +348 -0
- litmus_test-0.1.0/docs/explorations/data-architecture.md +290 -0
- litmus_test-0.1.0/docs/explorations/data-schemas.md +515 -0
- litmus_test-0.1.0/docs/explorations/ideal-data-architecture.md +359 -0
- litmus_test-0.1.0/docs/explorations/parquet-merge-plan-audit.md +224 -0
- litmus_test-0.1.0/docs/explorations/per-execution-step-records.md +241 -0
- litmus_test-0.1.0/docs/explorations/sequences-as-files.md +493 -0
- litmus_test-0.1.0/docs/guides/adding-instruments.md +466 -0
- litmus_test-0.1.0/docs/guides/configuring-stations.md +401 -0
- litmus_test-0.1.0/docs/guides/context-architecture.md +124 -0
- litmus_test-0.1.0/docs/guides/grafana-dashboards.md +137 -0
- litmus_test-0.1.0/docs/guides/limits.md +183 -0
- litmus_test-0.1.0/docs/guides/managing-sessions.md +89 -0
- litmus_test-0.1.0/docs/guides/mcp-integration.md +501 -0
- litmus_test-0.1.0/docs/guides/mock-mode.md +296 -0
- litmus_test-0.1.0/docs/guides/multi-dut-testing.md +156 -0
- litmus_test-0.1.0/docs/guides/profiles.md +306 -0
- litmus_test-0.1.0/docs/guides/querying-channels.md +95 -0
- litmus_test-0.1.0/docs/guides/querying-events.md +83 -0
- litmus_test-0.1.0/docs/guides/spec-driven-testing.md +130 -0
- litmus_test-0.1.0/docs/guides/traceability.md +349 -0
- litmus_test-0.1.0/docs/guides/vector-expansion.md +378 -0
- litmus_test-0.1.0/docs/guides/writing-sequences.md +401 -0
- litmus_test-0.1.0/docs/guides/writing-tests.md +389 -0
- litmus_test-0.1.0/docs/highlight-reel.md +794 -0
- litmus_test-0.1.0/docs/index.md +109 -0
- litmus_test-0.1.0/docs/instruments/custom-drivers.md +458 -0
- litmus_test-0.1.0/docs/integration/harness.md +365 -0
- litmus_test-0.1.0/docs/integration/instruments.md +279 -0
- litmus_test-0.1.0/docs/integration/lakehouse-import.md +170 -0
- litmus_test-0.1.0/docs/integration/logging.md +349 -0
- litmus_test-0.1.0/docs/integration/openhtf-adapter.md +405 -0
- litmus_test-0.1.0/docs/integration/overview.md +160 -0
- litmus_test-0.1.0/docs/integration/pytest-existing.md +337 -0
- litmus_test-0.1.0/docs/integration/results-api.md +306 -0
- litmus_test-0.1.0/docs/outputs.md +73 -0
- litmus_test-0.1.0/docs/overview.md +247 -0
- litmus_test-0.1.0/docs/quickstart.md +256 -0
- litmus_test-0.1.0/docs/reference/api.md +394 -0
- litmus_test-0.1.0/docs/reference/cli.md +647 -0
- litmus_test-0.1.0/docs/reference/client.md +262 -0
- litmus_test-0.1.0/docs/reference/configuration.md +450 -0
- litmus_test-0.1.0/docs/reference/connect-api.md +119 -0
- litmus_test-0.1.0/docs/reference/event-types.md +329 -0
- litmus_test-0.1.0/docs/reference/models.md +877 -0
- litmus_test-0.1.0/docs/reference/parquet-schema.md +636 -0
- litmus_test-0.1.0/docs/reference/pytest-native.md +161 -0
- litmus_test-0.1.0/docs/reference/pytest-plugin.md +436 -0
- litmus_test-0.1.0/docs/tutorial/01-first-test.md +156 -0
- litmus_test-0.1.0/docs/tutorial/02-mock-instruments.md +167 -0
- litmus_test-0.1.0/docs/tutorial/03-fixtures.md +193 -0
- litmus_test-0.1.0/docs/tutorial/04-limits.md +346 -0
- litmus_test-0.1.0/docs/tutorial/05-configuration.md +189 -0
- litmus_test-0.1.0/docs/tutorial/06-specifications.md +302 -0
- litmus_test-0.1.0/docs/tutorial/07-real-instruments.md +214 -0
- litmus_test-0.1.0/docs/tutorial/08-capabilities.md +237 -0
- litmus_test-0.1.0/docs/tutorial/09-production.md +345 -0
- litmus_test-0.1.0/docs/tutorial/10-live-monitoring.md +108 -0
- litmus_test-0.1.0/docs/tutorial/from-mocks-to-hardware.md +141 -0
- litmus_test-0.1.0/docs/tutorial/index.md +54 -0
- litmus_test-0.1.0/examples/01-vanilla/README.md +99 -0
- litmus_test-0.1.0/examples/01-vanilla/conftest.py +35 -0
- litmus_test-0.1.0/examples/01-vanilla/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/01-vanilla/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/01-vanilla/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/01-vanilla/litmus.yaml +3 -0
- litmus_test-0.1.0/examples/01-vanilla/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/01-vanilla/pytest.ini +12 -0
- litmus_test-0.1.0/examples/01-vanilla/tests/test_rail.py +33 -0
- litmus_test-0.1.0/examples/02-verify/README.md +100 -0
- litmus_test-0.1.0/examples/02-verify/conftest.py +34 -0
- litmus_test-0.1.0/examples/02-verify/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/02-verify/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/02-verify/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/02-verify/litmus.yaml +3 -0
- litmus_test-0.1.0/examples/02-verify/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/02-verify/pytest.ini +7 -0
- litmus_test-0.1.0/examples/02-verify/tests/test_rail.py +62 -0
- litmus_test-0.1.0/examples/03-inline-limits/README.md +37 -0
- litmus_test-0.1.0/examples/03-inline-limits/conftest.py +24 -0
- litmus_test-0.1.0/examples/03-inline-limits/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/03-inline-limits/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/03-inline-limits/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/03-inline-limits/litmus.yaml +3 -0
- litmus_test-0.1.0/examples/03-inline-limits/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/03-inline-limits/pytest.ini +2 -0
- litmus_test-0.1.0/examples/03-inline-limits/tests/test_rail.py +42 -0
- litmus_test-0.1.0/examples/03-inline-limits/tests/test_sequence.py +78 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/README.md +73 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/conftest.py +24 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/litmus.yaml +3 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/pytest.ini +2 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/tests/test_rail.py +53 -0
- litmus_test-0.1.0/examples/04-sidecar-markers/tests/test_rail.yaml +25 -0
- litmus_test-0.1.0/examples/05-product-spec/README.md +52 -0
- litmus_test-0.1.0/examples/05-product-spec/conftest.py +24 -0
- litmus_test-0.1.0/examples/05-product-spec/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/05-product-spec/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/05-product-spec/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/05-product-spec/litmus.yaml +3 -0
- litmus_test-0.1.0/examples/05-product-spec/products/buck_3v3.yaml +45 -0
- litmus_test-0.1.0/examples/05-product-spec/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/05-product-spec/tests/test_rail.py +52 -0
- litmus_test-0.1.0/examples/05-product-spec/tests/test_rail.yaml +26 -0
- litmus_test-0.1.0/examples/06-station-catalog/README.md +135 -0
- litmus_test-0.1.0/examples/06-station-catalog/catalog/generic_dmm.yaml +60 -0
- litmus_test-0.1.0/examples/06-station-catalog/catalog/generic_psu.yaml +76 -0
- litmus_test-0.1.0/examples/06-station-catalog/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/06-station-catalog/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/06-station-catalog/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/06-station-catalog/fixtures/buck_3v3_bench.yaml +19 -0
- litmus_test-0.1.0/examples/06-station-catalog/litmus.yaml +5 -0
- litmus_test-0.1.0/examples/06-station-catalog/products/buck_3v3.yaml +45 -0
- litmus_test-0.1.0/examples/06-station-catalog/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/06-station-catalog/stations/bench_01.yaml +23 -0
- litmus_test-0.1.0/examples/06-station-catalog/tests/test_rail.py +114 -0
- litmus_test-0.1.0/examples/06-station-catalog/tests/test_rail.yaml +41 -0
- litmus_test-0.1.0/examples/07-profiles/README.md +77 -0
- litmus_test-0.1.0/examples/07-profiles/catalog/generic_dmm.yaml +60 -0
- litmus_test-0.1.0/examples/07-profiles/catalog/generic_psu.yaml +76 -0
- litmus_test-0.1.0/examples/07-profiles/drivers/__init__.py +29 -0
- litmus_test-0.1.0/examples/07-profiles/drivers/dmm.py +128 -0
- litmus_test-0.1.0/examples/07-profiles/drivers/psu.py +122 -0
- litmus_test-0.1.0/examples/07-profiles/fixtures/buck_3v3_bench.yaml +20 -0
- litmus_test-0.1.0/examples/07-profiles/litmus.yaml +11 -0
- litmus_test-0.1.0/examples/07-profiles/products/buck_3v3.yaml +45 -0
- litmus_test-0.1.0/examples/07-profiles/profiles/characterization.yaml +8 -0
- litmus_test-0.1.0/examples/07-profiles/profiles/development.yaml +12 -0
- litmus_test-0.1.0/examples/07-profiles/profiles/production.yaml +13 -0
- litmus_test-0.1.0/examples/07-profiles/profiles/rail_family.yaml +9 -0
- litmus_test-0.1.0/examples/07-profiles/pyproject.toml +16 -0
- litmus_test-0.1.0/examples/07-profiles/stations/bench_01.yaml +23 -0
- litmus_test-0.1.0/examples/07-profiles/stations/types/bench.yaml +13 -0
- litmus_test-0.1.0/examples/07-profiles/tests/test_rail.py +32 -0
- litmus_test-0.1.0/examples/07-profiles/tests/test_rail.yaml +16 -0
- litmus_test-0.1.0/examples/README.md +60 -0
- litmus_test-0.1.0/examples/interactive_station.py +385 -0
- litmus_test-0.1.0/examples/scripts/demo_duckdb.py +116 -0
- litmus_test-0.1.0/examples/scripts/demo_queries.sql +77 -0
- litmus_test-0.1.0/examples/scripts/query_results.py +264 -0
- litmus_test-0.1.0/examples/scripts/seed_artifact_demo.py +183 -0
- litmus_test-0.1.0/examples/static/station.css +94 -0
- litmus_test-0.1.0/pyproject.toml +187 -0
- litmus_test-0.1.0/scripts/build-skill-zip.py +182 -0
- litmus_test-0.1.0/scripts/fmt_yaml.py +57 -0
- litmus_test-0.1.0/src/litmus/__init__.py +25 -0
- litmus_test-0.1.0/src/litmus/analysis/__init__.py +9 -0
- litmus_test-0.1.0/src/litmus/analysis/_common.py +21 -0
- litmus_test-0.1.0/src/litmus/analysis/measurement_facets.py +272 -0
- litmus_test-0.1.0/src/litmus/analysis/measurements_query.py +847 -0
- litmus_test-0.1.0/src/litmus/analysis/metrics.py +389 -0
- litmus_test-0.1.0/src/litmus/analysis/runs_query.py +407 -0
- litmus_test-0.1.0/src/litmus/analysis/steps_query.py +345 -0
- litmus_test-0.1.0/src/litmus/api/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/api/_mime.py +71 -0
- litmus_test-0.1.0/src/litmus/api/app.py +943 -0
- litmus_test-0.1.0/src/litmus/api/dialogs/__init__.py +36 -0
- litmus_test-0.1.0/src/litmus/api/dialogs/manager.py +617 -0
- litmus_test-0.1.0/src/litmus/api/dialogs/models.py +79 -0
- litmus_test-0.1.0/src/litmus/api/models.py +79 -0
- litmus_test-0.1.0/src/litmus/api/responses.py +205 -0
- litmus_test-0.1.0/src/litmus/api/runner.py +220 -0
- litmus_test-0.1.0/src/litmus/api/schemas.py +347 -0
- litmus_test-0.1.0/src/litmus/catalog/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_dmm.yaml +61 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_eload.yaml +31 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_fgen.yaml +56 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_oscilloscope.yaml +45 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_psu.yaml +77 -0
- litmus_test-0.1.0/src/litmus/catalog/generic/generic_smu.yaml +73 -0
- litmus_test-0.1.0/src/litmus/cli.py +2589 -0
- litmus_test-0.1.0/src/litmus/client.py +438 -0
- litmus_test-0.1.0/src/litmus/connect.py +507 -0
- litmus_test-0.1.0/src/litmus/environment.py +84 -0
- litmus_test-0.1.0/src/litmus/execution/__init__.py +11 -0
- litmus_test-0.1.0/src/litmus/execution/_git.py +190 -0
- litmus_test-0.1.0/src/litmus/execution/_state.py +530 -0
- litmus_test-0.1.0/src/litmus/execution/accessors.py +54 -0
- litmus_test-0.1.0/src/litmus/execution/audit.py +51 -0
- litmus_test-0.1.0/src/litmus/execution/cascade.py +85 -0
- litmus_test-0.1.0/src/litmus/execution/connections.py +489 -0
- litmus_test-0.1.0/src/litmus/execution/dut_provider.py +201 -0
- litmus_test-0.1.0/src/litmus/execution/harness.py +1279 -0
- litmus_test-0.1.0/src/litmus/execution/instrument_events.py +42 -0
- litmus_test-0.1.0/src/litmus/execution/limits.py +154 -0
- litmus_test-0.1.0/src/litmus/execution/logger.py +1159 -0
- litmus_test-0.1.0/src/litmus/execution/metadata.py +102 -0
- litmus_test-0.1.0/src/litmus/execution/mocks.py +63 -0
- litmus_test-0.1.0/src/litmus/execution/profiles.py +612 -0
- litmus_test-0.1.0/src/litmus/execution/sidecar.py +280 -0
- litmus_test-0.1.0/src/litmus/execution/slot_runner.py +715 -0
- litmus_test-0.1.0/src/litmus/execution/slots.py +135 -0
- litmus_test-0.1.0/src/litmus/execution/sync.py +292 -0
- litmus_test-0.1.0/src/litmus/execution/vectors.py +205 -0
- litmus_test-0.1.0/src/litmus/execution/verify.py +218 -0
- litmus_test-0.1.0/src/litmus/expand.py +75 -0
- litmus_test-0.1.0/src/litmus/fixtures/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/fixtures/manager.py +357 -0
- litmus_test-0.1.0/src/litmus/grafana/__init__.py +1 -0
- litmus_test-0.1.0/src/litmus/grafana/bootstrap.py +61 -0
- litmus_test-0.1.0/src/litmus/grafana/cli.py +314 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/asset_utilization.json +92 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/channel_explorer.json +77 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/event_log.json +92 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/failure_pareto.json +104 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/measurement_distribution.json +97 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/measurement_trend.json +161 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/station_comparison.json +95 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/test_duration.json +104 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/unit_traceability.json +73 -0
- litmus_test-0.1.0/src/litmus/grafana/dashboards/yield_overview.json +189 -0
- litmus_test-0.1.0/src/litmus/grafana/provisioning/dashboards.yaml.j2 +12 -0
- litmus_test-0.1.0/src/litmus/grafana/provisioning/datasources.yaml.j2 +17 -0
- litmus_test-0.1.0/src/litmus/grafana/server.py +254 -0
- litmus_test-0.1.0/src/litmus/init.py +670 -0
- litmus_test-0.1.0/src/litmus/instruments/__init__.py +10 -0
- litmus_test-0.1.0/src/litmus/instruments/base.py +82 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/__init__.py +62 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/_base.py +174 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/lxi.py +152 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/ni.py +95 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/serial.py +73 -0
- litmus_test-0.1.0/src/litmus/instruments/discovery/visa.py +98 -0
- litmus_test-0.1.0/src/litmus/instruments/lifecycle.py +213 -0
- litmus_test-0.1.0/src/litmus/instruments/loader.py +79 -0
- litmus_test-0.1.0/src/litmus/instruments/locks.py +116 -0
- litmus_test-0.1.0/src/litmus/instruments/mocks.py +220 -0
- litmus_test-0.1.0/src/litmus/instruments/observer.py +184 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/__init__.py +36 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/_base.py +68 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/daqmx.py +70 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/descriptor.py +69 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/generic.py +92 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/lantz.py +31 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/modbus.py +96 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/motion.py +68 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/ni_modular.py +73 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/ophyd.py +80 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/pymeasure.py +120 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/qcodes.py +66 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/scpi.py +88 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/tektronix.py +57 -0
- litmus_test-0.1.0/src/litmus/instruments/observers/visa.py +103 -0
- litmus_test-0.1.0/src/litmus/instruments/pool.py +295 -0
- litmus_test-0.1.0/src/litmus/instruments/proxy.py +59 -0
- litmus_test-0.1.0/src/litmus/instruments/route_manager.py +329 -0
- litmus_test-0.1.0/src/litmus/instruments/routed_proxy.py +88 -0
- litmus_test-0.1.0/src/litmus/instruments/server.py +373 -0
- litmus_test-0.1.0/src/litmus/instruments/switch.py +43 -0
- litmus_test-0.1.0/src/litmus/instruments/visa.py +294 -0
- litmus_test-0.1.0/src/litmus/matching/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/matching/service.py +969 -0
- litmus_test-0.1.0/src/litmus/mcp/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/mcp/server.py +649 -0
- litmus_test-0.1.0/src/litmus/mcp/tools.py +1650 -0
- litmus_test-0.1.0/src/litmus/models/__init__.py +18 -0
- litmus_test-0.1.0/src/litmus/models/capability.py +597 -0
- litmus_test-0.1.0/src/litmus/models/catalog.py +76 -0
- litmus_test-0.1.0/src/litmus/models/enums.py +334 -0
- litmus_test-0.1.0/src/litmus/models/instrument.py +149 -0
- litmus_test-0.1.0/src/litmus/models/instrument_asset.py +25 -0
- litmus_test-0.1.0/src/litmus/models/product.py +271 -0
- litmus_test-0.1.0/src/litmus/models/product_manifest.py +96 -0
- litmus_test-0.1.0/src/litmus/models/project.py +96 -0
- litmus_test-0.1.0/src/litmus/models/station.py +140 -0
- litmus_test-0.1.0/src/litmus/models/test_config.py +717 -0
- litmus_test-0.1.0/src/litmus/products/__init__.py +8 -0
- litmus_test-0.1.0/src/litmus/products/context.py +183 -0
- litmus_test-0.1.0/src/litmus/products/folder.py +251 -0
- litmus_test-0.1.0/src/litmus/products/loader.py +27 -0
- litmus_test-0.1.0/src/litmus/prompts/__init__.py +17 -0
- litmus_test-0.1.0/src/litmus/prompts/core.py +133 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/__init__.py +1210 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/autouse.py +360 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/helpers.py +348 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/hooks.py +1670 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/markers.py +203 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/retry.py +37 -0
- litmus_test-0.1.0/src/litmus/pytest_plugin/sweeps.py +117 -0
- litmus_test-0.1.0/src/litmus/reports/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/reports/core.py +421 -0
- litmus_test-0.1.0/src/litmus/reports/datasheet.py +831 -0
- litmus_test-0.1.0/src/litmus/reports/templates/datasheet.html +383 -0
- litmus_test-0.1.0/src/litmus/reports/templates/default.html +152 -0
- litmus_test-0.1.0/src/litmus/sbom.py +142 -0
- litmus_test-0.1.0/src/litmus/schema_export.py +101 -0
- litmus_test-0.1.0/src/litmus/signals.py +63 -0
- litmus_test-0.1.0/src/litmus/skills/SKILL.md +83 -0
- litmus_test-0.1.0/src/litmus/skills/agents/scaffold-writer.md +114 -0
- litmus_test-0.1.0/src/litmus/skills/agents/section-extractor.md +97 -0
- litmus_test-0.1.0/src/litmus/skills/agents/section-reviewer.md +152 -0
- litmus_test-0.1.0/src/litmus/skills/agents/section-splitter.md +88 -0
- litmus_test-0.1.0/src/litmus/skills/agents/section-writer.md +225 -0
- litmus_test-0.1.0/src/litmus/skills/catalog-scaffold.md +156 -0
- litmus_test-0.1.0/src/litmus/skills/commands/claude-code/catalog-from-datasheet.md +15 -0
- litmus_test-0.1.0/src/litmus/skills/commands/claude-code/process-catalog.md +42 -0
- litmus_test-0.1.0/src/litmus/skills/commands/copilot/catalog-from-datasheet.prompt.md +15 -0
- litmus_test-0.1.0/src/litmus/skills/commands/copilot/process-catalog.prompt.md +23 -0
- litmus_test-0.1.0/src/litmus/skills/refs/profiles.md +279 -0
- litmus_test-0.1.0/src/litmus/skills/templates/project-instructions.md +97 -0
- litmus_test-0.1.0/src/litmus/skills/workflow/datasheet-to-catalog.md +271 -0
- litmus_test-0.1.0/src/litmus/skills/workflow/datasheet-to-test.md +309 -0
- litmus_test-0.1.0/src/litmus/store.py +1603 -0
- litmus_test-0.1.0/src/litmus/ui/__init__.py +1 -0
- litmus_test-0.1.0/src/litmus/ui/_asgi.py +197 -0
- litmus_test-0.1.0/src/litmus/ui/app.py +17 -0
- litmus_test-0.1.0/src/litmus/ui/components/__init__.py +13 -0
- litmus_test-0.1.0/src/litmus/ui/components/artifact_viewer.py +248 -0
- litmus_test-0.1.0/src/litmus/ui/components/channel_values.py +88 -0
- litmus_test-0.1.0/src/litmus/ui/components/event_timeline.py +190 -0
- litmus_test-0.1.0/src/litmus/ui/components/execution_gantt.py +199 -0
- litmus_test-0.1.0/src/litmus/ui/components/instrument_activity.py +146 -0
- litmus_test-0.1.0/src/litmus/ui/components/session_table.py +118 -0
- litmus_test-0.1.0/src/litmus/ui/pages/__init__.py +22 -0
- litmus_test-0.1.0/src/litmus/ui/pages/channels/__init__.py +4 -0
- litmus_test-0.1.0/src/litmus/ui/pages/channels/detail.py +613 -0
- litmus_test-0.1.0/src/litmus/ui/pages/channels/list.py +283 -0
- litmus_test-0.1.0/src/litmus/ui/pages/dashboard.py +175 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/__init__.py +5 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/graph.py +635 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/matching.py +663 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/page.py +586 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/properties.py +591 -0
- litmus_test-0.1.0/src/litmus/ui/pages/designer/state.py +468 -0
- litmus_test-0.1.0/src/litmus/ui/pages/docs/__init__.py +6 -0
- litmus_test-0.1.0/src/litmus/ui/pages/docs/index.py +101 -0
- litmus_test-0.1.0/src/litmus/ui/pages/docs/page.py +302 -0
- litmus_test-0.1.0/src/litmus/ui/pages/events/__init__.py +3 -0
- litmus_test-0.1.0/src/litmus/ui/pages/events/list.py +253 -0
- litmus_test-0.1.0/src/litmus/ui/pages/explore.py +854 -0
- litmus_test-0.1.0/src/litmus/ui/pages/fixtures/__init__.py +8 -0
- litmus_test-0.1.0/src/litmus/ui/pages/fixtures/detail.py +280 -0
- litmus_test-0.1.0/src/litmus/ui/pages/fixtures/edit.py +370 -0
- litmus_test-0.1.0/src/litmus/ui/pages/fixtures/list.py +104 -0
- litmus_test-0.1.0/src/litmus/ui/pages/fixtures/new.py +195 -0
- litmus_test-0.1.0/src/litmus/ui/pages/instruments/__init__.py +4 -0
- litmus_test-0.1.0/src/litmus/ui/pages/instruments/detail.py +320 -0
- litmus_test-0.1.0/src/litmus/ui/pages/instruments/edit.py +482 -0
- litmus_test-0.1.0/src/litmus/ui/pages/instruments/list.py +155 -0
- litmus_test-0.1.0/src/litmus/ui/pages/instruments/new.py +200 -0
- litmus_test-0.1.0/src/litmus/ui/pages/launch.py +178 -0
- litmus_test-0.1.0/src/litmus/ui/pages/live.py +102 -0
- litmus_test-0.1.0/src/litmus/ui/pages/metrics_page.py +1220 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/__init__.py +11 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/detail.py +204 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/edit.py +312 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/list.py +82 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/new.py +170 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/requirements.py +155 -0
- litmus_test-0.1.0/src/litmus/ui/pages/products/stations.py +203 -0
- litmus_test-0.1.0/src/litmus/ui/pages/results/__init__.py +4 -0
- litmus_test-0.1.0/src/litmus/ui/pages/results/detail.py +600 -0
- litmus_test-0.1.0/src/litmus/ui/pages/results/list.py +184 -0
- litmus_test-0.1.0/src/litmus/ui/pages/stations/__init__.py +4 -0
- litmus_test-0.1.0/src/litmus/ui/pages/stations/detail.py +252 -0
- litmus_test-0.1.0/src/litmus/ui/pages/stations/edit.py +273 -0
- litmus_test-0.1.0/src/litmus/ui/pages/stations/list.py +88 -0
- litmus_test-0.1.0/src/litmus/ui/pages/stations/new.py +311 -0
- litmus_test-0.1.0/src/litmus/ui/pages/tests/__init__.py +4 -0
- litmus_test-0.1.0/src/litmus/ui/pages/tests/list.py +37 -0
- litmus_test-0.1.0/src/litmus/ui/shared/__init__.py +8 -0
- litmus_test-0.1.0/src/litmus/ui/shared/components.py +1167 -0
- litmus_test-0.1.0/src/litmus/ui/shared/dialogs.py +105 -0
- litmus_test-0.1.0/src/litmus/ui/shared/event_binding.py +147 -0
- litmus_test-0.1.0/src/litmus/ui/shared/layout.py +200 -0
- litmus_test-0.1.0/src/litmus/ui/shared/services.py +910 -0
- litmus_test-0.1.0/src/litmus/ui/shared/timestamps.py +16 -0
- litmus_test-0.1.0/src/litmus/ui/static/global.css +381 -0
- litmus_test-0.1.0/src/litmus/utils/__init__.py +17 -0
- litmus_test-0.1.0/src/litmus/utils/enum_meta.py +800 -0
- litmus_test-0.1.0/src/litmus/utils/paths.py +75 -0
- litmus_test-0.1.0/src/litmus/utils/ranges.py +257 -0
- litmus_test-0.1.0/src/litmus/validation.py +82 -0
- litmus_test-0.1.0/tests/__init__.py +0 -0
- litmus_test-0.1.0/tests/conftest.py +65 -0
- litmus_test-0.1.0/tests/fixtures/specs/base_board.yaml +28 -0
- litmus_test-0.1.0/tests/fixtures/specs/base_board_with_driver.yaml +28 -0
- litmus_test-0.1.0/tests/fixtures/specs/circular_a.yaml +13 -0
- litmus_test-0.1.0/tests/fixtures/specs/circular_b.yaml +13 -0
- litmus_test-0.1.0/tests/fixtures/specs/power_board_v1.yaml +116 -0
- litmus_test-0.1.0/tests/fixtures/specs/variant_board.yaml +14 -0
- litmus_test-0.1.0/tests/fixtures/specs/variant_driver_inherit.yaml +3 -0
- litmus_test-0.1.0/tests/fixtures/specs/variant_driver_override.yaml +4 -0
- litmus_test-0.1.0/tests/fixtures/specs/variant_inherit_all.yaml +3 -0
- litmus_test-0.1.0/tests/test_api/test_mime_sniff.py +46 -0
- litmus_test-0.1.0/tests/test_api/test_ref_endpoint.py +210 -0
- litmus_test-0.1.0/tests/test_api/test_run_detail.py +152 -0
- litmus_test-0.1.0/tests/test_api/test_steps_endpoint.py +124 -0
- litmus_test-0.1.0/tests/test_catalog/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_catalog/test_loader.py +439 -0
- litmus_test-0.1.0/tests/test_catalog/test_spec_bands.py +86 -0
- litmus_test-0.1.0/tests/test_config/__init__.py +1 -0
- litmus_test-0.1.0/tests/test_config/test_expand_helpers.py +255 -0
- litmus_test-0.1.0/tests/test_config/test_expander_wiring.py +67 -0
- litmus_test-0.1.0/tests/test_config/test_markers.py +202 -0
- litmus_test-0.1.0/tests/test_config/test_models.py +153 -0
- litmus_test-0.1.0/tests/test_config/test_station_compliance.py +101 -0
- litmus_test-0.1.0/tests/test_config/test_switch_route.py +81 -0
- litmus_test-0.1.0/tests/test_connect.py +238 -0
- litmus_test-0.1.0/tests/test_conventions.py +125 -0
- litmus_test-0.1.0/tests/test_data/__init__.py +1 -0
- litmus_test-0.1.0/tests/test_data/conftest.py +354 -0
- litmus_test-0.1.0/tests/test_data/test_atomic.py +67 -0
- litmus_test-0.1.0/tests/test_data/test_channel_server.py +266 -0
- litmus_test-0.1.0/tests/test_data/test_channel_store.py +370 -0
- litmus_test-0.1.0/tests/test_data/test_event_log.py +153 -0
- litmus_test-0.1.0/tests/test_data/test_event_reader.py +93 -0
- litmus_test-0.1.0/tests/test_data/test_event_store.py +137 -0
- litmus_test-0.1.0/tests/test_data/test_events.py +186 -0
- litmus_test-0.1.0/tests/test_data/test_export_atml.py +234 -0
- litmus_test-0.1.0/tests/test_data/test_export_hdf5.py +198 -0
- litmus_test-0.1.0/tests/test_data/test_export_mdf4.py +159 -0
- litmus_test-0.1.0/tests/test_data/test_export_stdf.py +208 -0
- litmus_test-0.1.0/tests/test_data/test_export_tdms.py +144 -0
- litmus_test-0.1.0/tests/test_data/test_instrument_arrays.py +266 -0
- litmus_test-0.1.0/tests/test_data/test_instrument_events.py +62 -0
- litmus_test-0.1.0/tests/test_data/test_materialize.py +181 -0
- litmus_test-0.1.0/tests/test_data/test_materializer.py +360 -0
- litmus_test-0.1.0/tests/test_data/test_measurement_writer.py +60 -0
- litmus_test-0.1.0/tests/test_data/test_models.py +213 -0
- litmus_test-0.1.0/tests/test_data/test_perf.py +205 -0
- litmus_test-0.1.0/tests/test_data/test_perf_daemon.py +242 -0
- litmus_test-0.1.0/tests/test_data/test_ref.py +98 -0
- litmus_test-0.1.0/tests/test_data/test_replay.py +155 -0
- litmus_test-0.1.0/tests/test_data/test_retention.py +119 -0
- litmus_test-0.1.0/tests/test_data/test_run_store.py +262 -0
- litmus_test-0.1.0/tests/test_data/test_runs_daemon_concurrency.py +178 -0
- litmus_test-0.1.0/tests/test_data/test_runs_duckdb_daemon_subscriber.py +337 -0
- litmus_test-0.1.0/tests/test_data/test_write_schema.py +193 -0
- litmus_test-0.1.0/tests/test_datasheet.py +309 -0
- litmus_test-0.1.0/tests/test_dialog_demo.py +92 -0
- litmus_test-0.1.0/tests/test_dialogs/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_dialogs/test_dialog_events.py +144 -0
- litmus_test-0.1.0/tests/test_discovery/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_discovery/test_models.py +32 -0
- litmus_test-0.1.0/tests/test_discovery/test_scanner.py +76 -0
- litmus_test-0.1.0/tests/test_dut_options.py +138 -0
- litmus_test-0.1.0/tests/test_e2e/test_examples.py +64 -0
- litmus_test-0.1.0/tests/test_e2e/test_spec_to_results.py +461 -0
- litmus_test-0.1.0/tests/test_e2e/test_workflows.py +265 -0
- litmus_test-0.1.0/tests/test_environment.py +60 -0
- litmus_test-0.1.0/tests/test_execution/__init__.py +1 -0
- litmus_test-0.1.0/tests/test_execution/test_class_keying.py +86 -0
- litmus_test-0.1.0/tests/test_execution/test_class_step_containers.py +643 -0
- litmus_test-0.1.0/tests/test_execution/test_connections_resolution.py +711 -0
- litmus_test-0.1.0/tests/test_execution/test_context_session_attrs.py +141 -0
- litmus_test-0.1.0/tests/test_execution/test_dut_provider.py +191 -0
- litmus_test-0.1.0/tests/test_execution/test_harness.py +853 -0
- litmus_test-0.1.0/tests/test_execution/test_harness_record.py +22 -0
- litmus_test-0.1.0/tests/test_execution/test_limits.py +317 -0
- litmus_test-0.1.0/tests/test_execution/test_logger.py +345 -0
- litmus_test-0.1.0/tests/test_execution/test_logger_record.py +65 -0
- litmus_test-0.1.0/tests/test_execution/test_markers_sidecar.py +393 -0
- litmus_test-0.1.0/tests/test_execution/test_multi_dut_e2e.py +179 -0
- litmus_test-0.1.0/tests/test_execution/test_outcome_cascade.py +177 -0
- litmus_test-0.1.0/tests/test_execution/test_phase_and_mocks.py +293 -0
- litmus_test-0.1.0/tests/test_execution/test_phase_wiring.py +205 -0
- litmus_test-0.1.0/tests/test_execution/test_product_resolution.py +112 -0
- litmus_test-0.1.0/tests/test_execution/test_profile_facets.py +303 -0
- litmus_test-0.1.0/tests/test_execution/test_prompt_fixture.py +284 -0
- litmus_test-0.1.0/tests/test_execution/test_pytest_native_plugin.py +670 -0
- litmus_test-0.1.0/tests/test_execution/test_required_inputs.py +187 -0
- litmus_test-0.1.0/tests/test_execution/test_slot_flag.py +193 -0
- litmus_test-0.1.0/tests/test_execution/test_slot_runner.py +185 -0
- litmus_test-0.1.0/tests/test_execution/test_slot_termination.py +169 -0
- litmus_test-0.1.0/tests/test_execution/test_slots.py +284 -0
- litmus_test-0.1.0/tests/test_execution/test_sync.py +237 -0
- litmus_test-0.1.0/tests/test_execution/test_vectors.py +257 -0
- litmus_test-0.1.0/tests/test_execution/test_vectors_fixture.py +285 -0
- litmus_test-0.1.0/tests/test_execution/test_verify_cascade.py +215 -0
- litmus_test-0.1.0/tests/test_execution/test_when_limits.py +334 -0
- litmus_test-0.1.0/tests/test_exporters.py +588 -0
- litmus_test-0.1.0/tests/test_fixtures/test_manager.py +208 -0
- litmus_test-0.1.0/tests/test_init.py +201 -0
- litmus_test-0.1.0/tests/test_instruments/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_instruments/test_base.py +106 -0
- litmus_test-0.1.0/tests/test_instruments/test_discovery.py +186 -0
- litmus_test-0.1.0/tests/test_instruments/test_lifecycle.py +111 -0
- litmus_test-0.1.0/tests/test_instruments/test_loader.py +286 -0
- litmus_test-0.1.0/tests/test_instruments/test_locks.py +89 -0
- litmus_test-0.1.0/tests/test_instruments/test_lxi.py +97 -0
- litmus_test-0.1.0/tests/test_instruments/test_mocks.py +317 -0
- litmus_test-0.1.0/tests/test_instruments/test_models.py +217 -0
- litmus_test-0.1.0/tests/test_instruments/test_observer.py +109 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/conftest.py +44 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_daqmx.py +57 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_generic.py +98 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_lantz.py +71 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_modbus.py +60 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_motion.py +76 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_ni_modular.py +58 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_ophyd.py +80 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_pymeasure.py +226 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_qcodes.py +71 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_registry.py +128 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_scpi.py +50 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_tektronix.py +58 -0
- litmus_test-0.1.0/tests/test_instruments/test_observers/test_visa.py +98 -0
- litmus_test-0.1.0/tests/test_instruments/test_pool.py +118 -0
- litmus_test-0.1.0/tests/test_instruments/test_proxy.py +247 -0
- litmus_test-0.1.0/tests/test_instruments/test_route_manager.py +307 -0
- litmus_test-0.1.0/tests/test_instruments/test_routed_proxy.py +108 -0
- litmus_test-0.1.0/tests/test_instruments/test_server.py +515 -0
- litmus_test-0.1.0/tests/test_matching/__init__.py +1 -0
- litmus_test-0.1.0/tests/test_matching/test_channel_matching.py +642 -0
- litmus_test-0.1.0/tests/test_matching/test_conditions.py +525 -0
- litmus_test-0.1.0/tests/test_matching/test_recommend.py +396 -0
- litmus_test-0.1.0/tests/test_matching/test_service.py +531 -0
- litmus_test-0.1.0/tests/test_measurements_query/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_measurements_query/test_measurement_facets.py +165 -0
- litmus_test-0.1.0/tests/test_measurements_query/test_measurements_query_sql.py +491 -0
- litmus_test-0.1.0/tests/test_plugin_fixtures.py +333 -0
- litmus_test-0.1.0/tests/test_products/test_loader.py +284 -0
- litmus_test-0.1.0/tests/test_products/test_models.py +175 -0
- litmus_test-0.1.0/tests/test_products/test_product_specband.py +89 -0
- litmus_test-0.1.0/tests/test_reports.py +278 -0
- litmus_test-0.1.0/tests/test_runs_query/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_runs_query/test_runs_query.py +463 -0
- litmus_test-0.1.0/tests/test_sbom.py +151 -0
- litmus_test-0.1.0/tests/test_schemas.py +32 -0
- litmus_test-0.1.0/tests/test_signals.py +40 -0
- litmus_test-0.1.0/tests/test_steps_query/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_steps_query/test_steps_query.py +273 -0
- litmus_test-0.1.0/tests/test_ui/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_ui/test_subscribe_with_refresh.py +150 -0
- litmus_test-0.1.0/tests/test_utils/__init__.py +1 -0
- litmus_test-0.1.0/tests/test_utils/test_enum_meta.py +140 -0
- litmus_test-0.1.0/tests/test_utils/test_ranges.py +240 -0
- litmus_test-0.1.0/tests/test_yield/__init__.py +0 -0
- litmus_test-0.1.0/tests/test_yield/test_cli.py +179 -0
- litmus_test-0.1.0/tests/test_yield/test_metrics.py +250 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
/data/
|
|
7
|
+
/examples/*/data/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
.claude/settings.local.json
|
|
12
|
+
.claude/scheduled_tasks.lock
|
|
13
|
+
.tmp/
|
|
14
|
+
examples/uv.lock
|
|
15
|
+
examples/*/uv.lock
|
|
16
|
+
dist/
|
|
17
|
+
.playwright-mcp/
|
|
18
|
+
report_*.html
|
|
19
|
+
*.mp4
|
|
20
|
+
*.pdf
|
|
21
|
+
|
|
22
|
+
# Generated skill refs (built from docs/ and models.py)
|
|
23
|
+
litmus/skills/refs/
|
|
24
|
+
|
|
25
|
+
# JSON schemas — regenerated via `litmus schema export` / `litmus init`
|
|
26
|
+
# / `litmus schema refresh`. The source of truth is the live Pydantic
|
|
27
|
+
# models in src/litmus/models/. Don't commit snapshots.
|
|
28
|
+
/schemas/
|
|
29
|
+
.benchmarks/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Litmus are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
Pre-1.0 note: the public API is unstable. Breaking changes are possible in any
|
|
9
|
+
0.x release and will be called out in this changelog.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-04-15
|
|
14
|
+
|
|
15
|
+
Initial public release on PyPI as `litmus-test`.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `@litmus_test` decorator for pytest-native hardware tests with vector
|
|
20
|
+
expansion, limit checking, measurement recording, retries, and mock injection
|
|
21
|
+
- Station / fixture / product / sequence YAML configuration, loaded through a
|
|
22
|
+
single store layer with Pydantic validation
|
|
23
|
+
- Instrument fixtures resolved from station config (no `conftest.py`
|
|
24
|
+
boilerplate required)
|
|
25
|
+
- `--mock-instruments` mode for hardware-free development
|
|
26
|
+
- Parquet result storage with per-step instrument traceability
|
|
27
|
+
(serial, cal due date, firmware)
|
|
28
|
+
- DuckDB-backed analytics layer over the Parquet silver/gold layout
|
|
29
|
+
- Operator UI (`litmus serve`) built on NiceGUI
|
|
30
|
+
- FastAPI HTTP API and MCP server, with parity between the two
|
|
31
|
+
- Capability matching (`litmus_match`) against an instrument catalog
|
|
32
|
+
- CLI: `litmus init`, `discover`, `station init`, `new-test`, `serve`, `runs`,
|
|
33
|
+
`show`, `instrument list`, `mcp serve`, `setup`
|
|
34
|
+
- Optional extras for output formats (`stdf`, `hdf5`, `tdms`, `mdf4`),
|
|
35
|
+
transports (`s3`, `gcs`, `azure`, `sftp`), and integrations (`pymeasure`,
|
|
36
|
+
`ni`, `lxi`, `grafana`, `pdf`, `sbom`)
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/pragmatest-dev/litmus/compare/v0.1.0...HEAD
|
|
39
|
+
[0.1.0]: https://github.com/pragmatest-dev/litmus/releases/tag/v0.1.0
|
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
# Contributing to Litmus
|
|
2
|
+
|
|
3
|
+
This guide is for developers who want to contribute to Litmus itself. It provides a deep-dive into the architecture, key abstractions, and how the major systems interact.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Architecture Overview](#architecture-overview)
|
|
8
|
+
2. [Core Abstractions](#core-abstractions)
|
|
9
|
+
3. [Data Flow](#data-flow)
|
|
10
|
+
4. [Module Guide](#module-guide)
|
|
11
|
+
5. [Extension Points](#extension-points)
|
|
12
|
+
6. [Development Workflow](#development-workflow)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Architecture Overview
|
|
17
|
+
|
|
18
|
+
Litmus is a **hardware test platform** organized into distinct subsystems:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ Test Execution │
|
|
23
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
|
24
|
+
│ │ pytest │───▶│ plugin │───▶│ fixtures (context, │ │
|
|
25
|
+
│ │ │ │ (hooks) │ │ verify, logger, spec) │ │
|
|
26
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
|
27
|
+
│ │ │ │ │
|
|
28
|
+
│ ▼ ▼ ▼ │
|
|
29
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
|
30
|
+
│ │ Instruments│◀───│ Config │───▶│ Data Models │ │
|
|
31
|
+
│ │ (drivers) │ │ (YAML + │ │ (Measurement, │ │
|
|
32
|
+
│ │ │ │ Pydantic) │ │ TestRun, etc.) │ │
|
|
33
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
|
34
|
+
│ │ │
|
|
35
|
+
│ ▼ │
|
|
36
|
+
│ ┌─────────────────────────┐ │
|
|
37
|
+
│ │ Storage Backend │ │
|
|
38
|
+
│ │ (Parquet files) │ │
|
|
39
|
+
│ └─────────────────────────┘ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
41
|
+
|
|
42
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
43
|
+
│ AI Integration │
|
|
44
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
|
45
|
+
│ │ MCP Server │ │ HTTP API │ │ Skills (prompts) │ │
|
|
46
|
+
│ │ (tools) │ │ (FastAPI) │ │ │ │
|
|
47
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
|
48
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
49
|
+
|
|
50
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
51
|
+
│ Operator UI │
|
|
52
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
|
|
53
|
+
│ │ NiceGUI │ │ Dashboard │ │ Results Viewer │ │
|
|
54
|
+
│ │ (pages) │ │ + Launch │ │ │ │
|
|
55
|
+
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
|
|
56
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Key Design Principles
|
|
60
|
+
|
|
61
|
+
1. **Two Abstraction Levels**: Users get the simple pytest-native fixtures (`context`, `verify`, `logger`, `spec`). Test architects can construct a `TestHarness` directly for full control (non-pytest runners, custom flow).
|
|
62
|
+
|
|
63
|
+
2. **Configuration-Driven**: Test behavior (vectors, limits, retries) lives in YAML, not code. This enables non-developers to modify tests.
|
|
64
|
+
|
|
65
|
+
3. **Hierarchical Context**: Data flows through Run → Step → Vector scopes with inheritance.
|
|
66
|
+
|
|
67
|
+
4. **AI-Ready, Not AI-Dependent**: We expose MCP tools and HTTP APIs for external agents, but the platform never calls LLMs itself.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Core Abstractions
|
|
72
|
+
|
|
73
|
+
### The Context Hierarchy
|
|
74
|
+
|
|
75
|
+
The `Context` class (`litmus/execution/harness.py`) is the user-facing API for test functions. It provides scoped inheritance:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Run Context (session-wide metadata)
|
|
79
|
+
│
|
|
80
|
+
└── Step Context (per-test function)
|
|
81
|
+
│
|
|
82
|
+
└── Vector Context (per-parameter-set)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Key methods:**
|
|
86
|
+
```python
|
|
87
|
+
class Context:
|
|
88
|
+
# Configure inputs (become in_* columns in Parquet)
|
|
89
|
+
def configure(key: str, value: Any) -> None
|
|
90
|
+
def set_in(key: str, value: Any) -> None
|
|
91
|
+
def get_param(key: str, default=None) -> Any
|
|
92
|
+
|
|
93
|
+
# Record observations (become out_* columns)
|
|
94
|
+
def observe(key: str, value: Any) -> None
|
|
95
|
+
def set_out(key: str, value: Any) -> None
|
|
96
|
+
def get_observation(key: str, default=None) -> Any
|
|
97
|
+
|
|
98
|
+
# Change detection for optimized loops
|
|
99
|
+
def changed(key: str) -> bool
|
|
100
|
+
|
|
101
|
+
# Access resolved limits
|
|
102
|
+
def get_limit(name: str) -> Limit | None
|
|
103
|
+
|
|
104
|
+
# Properties for bulk access
|
|
105
|
+
@property inputs -> dict[str, Any] # Merged with parent chain
|
|
106
|
+
@property outputs -> dict[str, Any] # Merged with parent chain
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Parent chain lookup**: When you call `context.get_param("temperature")`, it searches the current context first, then walks up the parent chain (vector → step → run) until it finds a value.
|
|
110
|
+
|
|
111
|
+
### Vector vs Context
|
|
112
|
+
|
|
113
|
+
Test vectors are defined in config and drive test looping. They are a subset of the context a test receives.
|
|
114
|
+
|
|
115
|
+
- **Vector** (`litmus/execution/vectors.py`): A dict subclass representing one parameter set from config. The harness expands and iterates over these internally.
|
|
116
|
+
- **Context** (`litmus/execution/harness.py`): The user-facing API that test functions receive. Contains vector params plus inherited run/step data, observations, and access to limits.
|
|
117
|
+
|
|
118
|
+
When using the pytest-native `context` fixture, you get a fully-populated `Context` for the active vector. When using `TestHarness` directly, you iterate `Vector` objects and must use `run_vector()` to construct each vector-level context (which auto-populates vector params into it).
|
|
119
|
+
|
|
120
|
+
### TestHarness
|
|
121
|
+
|
|
122
|
+
The `TestHarness` class (`litmus/execution/harness.py`) is the core orchestration engine. It:
|
|
123
|
+
|
|
124
|
+
1. **Expands vectors** from config (product, zip, nested, range)
|
|
125
|
+
2. **Manages iteration** with `changed()` tracking across vectors
|
|
126
|
+
3. **Handles retries** at the vector level
|
|
127
|
+
4. **Resolves limits** from config, spec references, or callables
|
|
128
|
+
5. **Records measurements** with automatic limit checking
|
|
129
|
+
6. **Manages mock configuration** per-vector
|
|
130
|
+
|
|
131
|
+
**Key methods:**
|
|
132
|
+
```python
|
|
133
|
+
class TestHarness:
|
|
134
|
+
# Properties
|
|
135
|
+
@property vectors -> list[Vector] # Expanded vectors
|
|
136
|
+
@property context -> Context # Current active context
|
|
137
|
+
@property current_vector -> Vector|None # During iteration
|
|
138
|
+
|
|
139
|
+
# Vector execution
|
|
140
|
+
@contextmanager
|
|
141
|
+
def run_vector(vector: Vector) -> Iterator[TestVector]
|
|
142
|
+
|
|
143
|
+
def run_with_retry(vector: Vector, test_fn: Callable) -> TestVector
|
|
144
|
+
def run_all(test_fn: Callable, step_name: str) -> TestStep
|
|
145
|
+
|
|
146
|
+
# Measurement
|
|
147
|
+
def measure(name: str, value: float, limit: Limit = None) -> Measurement
|
|
148
|
+
|
|
149
|
+
# Limit resolution (internal, but accessible via context.get_limit)
|
|
150
|
+
def _resolve_limit(name: str) -> Limit | None
|
|
151
|
+
|
|
152
|
+
# Prompts
|
|
153
|
+
def prompt(message: str, prompt_type: str = "confirm") -> Any
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### pytest-native Fixtures
|
|
157
|
+
|
|
158
|
+
The pytest plugin (`litmus/pytest_plugin.py`) exposes the user-facing API
|
|
159
|
+
as a set of fixtures. A test function's signature declares which it wants:
|
|
160
|
+
|
|
161
|
+
- `context` — current-vector `Context` (params, observations, change tracking)
|
|
162
|
+
- `verify` — `verify(name, value)` shortcut that resolves a limit from sidecar
|
|
163
|
+
YAML / product spec and records a checked measurement
|
|
164
|
+
- `logger` — `TestRunLogger` for `measure(name, value, limit=...)` and
|
|
165
|
+
`record(...)` when you need full control
|
|
166
|
+
- `spec` — `SpecContext` bound to the session's product, for
|
|
167
|
+
`spec.check(char_name, value)` against product characteristics
|
|
168
|
+
|
|
169
|
+
Vector expansion is driven by `@pytest.mark.parametrize` and/or sidecar
|
|
170
|
+
`vectors:` blocks in `test_<name>.yaml`. Limit resolution chain is:
|
|
171
|
+
explicit `limit=` → sidecar `limits:` entry (flat or condition-indexed
|
|
172
|
+
via `when:`) → active product spec → unchecked.
|
|
173
|
+
|
|
174
|
+
### Data Models
|
|
175
|
+
|
|
176
|
+
The result hierarchy (`litmus/data/models.py`):
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
TestRun
|
|
180
|
+
├── id, started_at, ended_at
|
|
181
|
+
├── dut: DUT (serial, part_number, revision)
|
|
182
|
+
├── station_id, operator_id, etc.
|
|
183
|
+
├── outcome: Outcome (PASS/FAIL/ERROR/SKIP)
|
|
184
|
+
└── steps: list[TestStep]
|
|
185
|
+
├── name, description
|
|
186
|
+
├── outcome
|
|
187
|
+
└── vectors: list[TestVector]
|
|
188
|
+
├── index, params (in_*)
|
|
189
|
+
├── observations (out_*)
|
|
190
|
+
├── outcome
|
|
191
|
+
└── measurements: list[Measurement]
|
|
192
|
+
├── name, value, units
|
|
193
|
+
├── low_limit, high_limit, nominal
|
|
194
|
+
├── outcome
|
|
195
|
+
└── spec_ref (traceability)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Key model: `Measurement`**
|
|
199
|
+
```python
|
|
200
|
+
class Measurement:
|
|
201
|
+
name: str
|
|
202
|
+
value: float | None
|
|
203
|
+
units: str | None
|
|
204
|
+
low_limit: float | None
|
|
205
|
+
high_limit: float | None
|
|
206
|
+
nominal: float | None
|
|
207
|
+
outcome: Outcome | None
|
|
208
|
+
spec_ref: str | None # Human-readable spec reference
|
|
209
|
+
dut_pin: str | None # DUT pin measured
|
|
210
|
+
instrument_channel: str | None # Instrument channel used
|
|
211
|
+
|
|
212
|
+
def check_limit() -> Outcome # Evaluates value against limits
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Limit Resolution
|
|
216
|
+
|
|
217
|
+
Limits can come from multiple sources. Resolution order in `TestHarness._resolve_limit()`:
|
|
218
|
+
|
|
219
|
+
1. **Direct Limit object** in config
|
|
220
|
+
2. **MeasurementLimitConfig** with direct values (low/high/nominal)
|
|
221
|
+
3. **Spec reference** → resolves via `SpecContext`
|
|
222
|
+
4. **Callable** → Python function or inline code evaluated with context
|
|
223
|
+
5. **SpecContext lookup** → characteristic name matches measurement name
|
|
224
|
+
|
|
225
|
+
**Callable limits** enable dynamic limits based on current vector:
|
|
226
|
+
```yaml
|
|
227
|
+
limits:
|
|
228
|
+
output_voltage:
|
|
229
|
+
callable: "Limit(low=ctx.get_param('vin') * 0.65, high=ctx.get_param('vin') * 0.68, units='V')"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### SpecContext (Spec-Driven Testing)
|
|
233
|
+
|
|
234
|
+
`SpecContext` (`litmus/products/context.py`) bridges product specifications and test execution:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
class SpecContext:
|
|
238
|
+
product: Product # Loaded product spec
|
|
239
|
+
fixture: FixtureConfig|None # Fixture routing
|
|
240
|
+
default_guardband_pct: float # Default tightening
|
|
241
|
+
|
|
242
|
+
def get_limit(char_id: str, guardband_pct=None, **conditions) -> Limit
|
|
243
|
+
def get_characteristic(char_id: str) -> Characteristic
|
|
244
|
+
def get_pin_info(char_id: str) -> dict # For traceability
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Limit derivation** handles:
|
|
248
|
+
- Condition matching (temperature, load, etc.)
|
|
249
|
+
- Guardband application (tightens limits by %)
|
|
250
|
+
- Spec reference generation for traceability
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Data Flow
|
|
255
|
+
|
|
256
|
+
### Test Execution Flow
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
1. pytest starts
|
|
260
|
+
└── pytest_configure() registers markers
|
|
261
|
+
|
|
262
|
+
2. Session starts
|
|
263
|
+
├── logger fixture creates TestRunLogger
|
|
264
|
+
├── instruments fixture connects to hardware (or mocks)
|
|
265
|
+
└── spec_context fixture loads product spec
|
|
266
|
+
|
|
267
|
+
3. Each parametrize case / vector
|
|
268
|
+
├── pytest_runtest_call opens a logger step
|
|
269
|
+
├── context / verify / logger / spec fixtures resolve
|
|
270
|
+
├── Active vector params pushed into ContextVars
|
|
271
|
+
├── Test body runs — verify(name, value) calls:
|
|
272
|
+
│ ├── Resolve limit (sidecar → product spec → unchecked)
|
|
273
|
+
│ ├── logger.measure() creates Measurement
|
|
274
|
+
│ ├── measurement.check_limit()
|
|
275
|
+
│ └── Append to current TestVector
|
|
276
|
+
│
|
|
277
|
+
└── pytest_runtest_call closes the step; outcome rolls up
|
|
278
|
+
|
|
279
|
+
4. Session ends
|
|
280
|
+
├── logger.finalize() completes TestRun
|
|
281
|
+
└── ParquetBackend.save_test_run() writes results
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Measurement Recording Flow
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
Test function returns value(s)
|
|
288
|
+
│
|
|
289
|
+
▼
|
|
290
|
+
TestHarness._record_result()
|
|
291
|
+
│
|
|
292
|
+
├── dict → multiple measurements
|
|
293
|
+
├── tuple → named measurement
|
|
294
|
+
└── value → measurement with step name
|
|
295
|
+
│
|
|
296
|
+
▼
|
|
297
|
+
TestHarness.measure()
|
|
298
|
+
│
|
|
299
|
+
├── Resolve limit (_resolve_limit)
|
|
300
|
+
├── Create Measurement object
|
|
301
|
+
├── measurement.check_limit()
|
|
302
|
+
└── Append to current TestVector
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Context Inheritance Flow
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
Context created for vector
|
|
309
|
+
│
|
|
310
|
+
├── Parent = step context (or run context)
|
|
311
|
+
├── Prev = previous vector context (for changed())
|
|
312
|
+
└── Harness = TestHarness reference (for get_limit())
|
|
313
|
+
│
|
|
314
|
+
▼
|
|
315
|
+
Vector params → context._inputs
|
|
316
|
+
│
|
|
317
|
+
▼
|
|
318
|
+
context.get_param("key") checks:
|
|
319
|
+
1. This context._inputs
|
|
320
|
+
2. Parent context._inputs (recursive)
|
|
321
|
+
3. Return default
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Module Guide
|
|
327
|
+
|
|
328
|
+
### litmus/execution/
|
|
329
|
+
|
|
330
|
+
The core test execution engine.
|
|
331
|
+
|
|
332
|
+
| File | Purpose |
|
|
333
|
+
|------|---------|
|
|
334
|
+
| `plugin.py` | pytest plugin - fixtures, hooks, CLI options |
|
|
335
|
+
| `harness.py` | TestHarness + Context classes |
|
|
336
|
+
| `vectors.py` | Vector expansion (product, zip, nested, range) |
|
|
337
|
+
| `decorators.py` | @measure decorator + current-logger ContextVar helpers |
|
|
338
|
+
| `logger.py` | TestRunLogger for accumulating results |
|
|
339
|
+
| `runner.py` | Async subprocess runner for UI |
|
|
340
|
+
|
|
341
|
+
**Entry point**: `plugin.py` registers with pytest and provides fixtures that create harnesses and loggers.
|
|
342
|
+
|
|
343
|
+
### litmus/config/
|
|
344
|
+
|
|
345
|
+
Configuration models and loading.
|
|
346
|
+
|
|
347
|
+
| File | Purpose |
|
|
348
|
+
|------|---------|
|
|
349
|
+
| `models.py` | Pydantic models: Limit, Specification, RetryConfig, etc. |
|
|
350
|
+
| `loader.py` | YAML loading and test config resolution |
|
|
351
|
+
|
|
352
|
+
**Key models**:
|
|
353
|
+
- `Limit` - Test limit with units and spec reference
|
|
354
|
+
- `MeasurementLimitConfig` - Flexible limit configuration (direct, ref, callable)
|
|
355
|
+
- `RetryConfig` - Retry behavior settings
|
|
356
|
+
- `VectorConfig` - Vector expansion configuration
|
|
357
|
+
|
|
358
|
+
### litmus/data/
|
|
359
|
+
|
|
360
|
+
Data models and storage backends.
|
|
361
|
+
|
|
362
|
+
| File | Purpose |
|
|
363
|
+
|------|---------|
|
|
364
|
+
| `models.py` | TestRun, TestStep, TestVector, Measurement, Outcome |
|
|
365
|
+
| `backends/parquet.py` | Parquet file storage |
|
|
366
|
+
|
|
367
|
+
**Parquet schema**: Results are flattened to rows per measurement with `in_*` and `out_*` columns for context.
|
|
368
|
+
|
|
369
|
+
### litmus/instruments/
|
|
370
|
+
|
|
371
|
+
Instrument drivers and mocks.
|
|
372
|
+
|
|
373
|
+
| File | Purpose |
|
|
374
|
+
|------|---------|
|
|
375
|
+
| `base.py` | Abstract Instrument base class |
|
|
376
|
+
| `visa.py` | VisaInstrument for SCPI instruments |
|
|
377
|
+
| `dmm.py`, `psu.py`, `eload.py`, `scope.py` | Concrete drivers |
|
|
378
|
+
| `mocks.py` | Generic Mock factory |
|
|
379
|
+
|
|
380
|
+
**Mock system**: `Mock(DMM, measure_voltage=3.3)` creates a mock that inherits from DMM, passes isinstance checks, and returns configured values.
|
|
381
|
+
|
|
382
|
+
### litmus/products/
|
|
383
|
+
|
|
384
|
+
Product specification system.
|
|
385
|
+
|
|
386
|
+
| File | Purpose |
|
|
387
|
+
|------|---------|
|
|
388
|
+
| `models.py` | Product, Characteristic, Pin, TestRequirement |
|
|
389
|
+
| `context.py` | SpecContext for spec-driven testing |
|
|
390
|
+
| `loader.py` | YAML loading for product specs |
|
|
391
|
+
| `limits.py` | derive_limit() function |
|
|
392
|
+
|
|
393
|
+
### litmus/mcp/
|
|
394
|
+
|
|
395
|
+
MCP server for AI integration.
|
|
396
|
+
|
|
397
|
+
| File | Purpose |
|
|
398
|
+
|------|---------|
|
|
399
|
+
| `server.py` | FastMCP server definition |
|
|
400
|
+
| `tools.py` | Tool implementations (litmus, discover, match, run, open) |
|
|
401
|
+
|
|
402
|
+
### litmus/ui/
|
|
403
|
+
|
|
404
|
+
NiceGUI operator interface.
|
|
405
|
+
|
|
406
|
+
| Directory | Purpose |
|
|
407
|
+
|-----------|---------|
|
|
408
|
+
| `pages/` | Dashboard, launch, results, live views |
|
|
409
|
+
| `shared/` | Layout, components, dialogs |
|
|
410
|
+
| `static/` | CSS assets |
|
|
411
|
+
|
|
412
|
+
### litmus/api/
|
|
413
|
+
|
|
414
|
+
HTTP API endpoints.
|
|
415
|
+
|
|
416
|
+
| File | Purpose |
|
|
417
|
+
|------|---------|
|
|
418
|
+
| `app.py` | FastAPI + NiceGUI app factory |
|
|
419
|
+
| `models.py` | API request/response models |
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Extension Points
|
|
424
|
+
|
|
425
|
+
### Adding a New Instrument Driver
|
|
426
|
+
|
|
427
|
+
1. Create `litmus/instruments/new_instrument.py`:
|
|
428
|
+
```python
|
|
429
|
+
from litmus.instruments.visa import VisaInstrument
|
|
430
|
+
|
|
431
|
+
class NewInstrument(VisaInstrument):
|
|
432
|
+
def measure_something(self) -> float:
|
|
433
|
+
return float(self.query("MEAS:SOMETHING?"))
|
|
434
|
+
|
|
435
|
+
def set_something(self, value: float) -> None:
|
|
436
|
+
self.write(f"SOMETHING {value}")
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
2. Register SCPI mapping for mocks in `mocks.py`:
|
|
440
|
+
```python
|
|
441
|
+
_register_scpi_mapping(
|
|
442
|
+
NewInstrument,
|
|
443
|
+
{
|
|
444
|
+
"measure_something": ["MEAS:SOMETHING?"],
|
|
445
|
+
"something": ["MEAS:SOMETHING?"], # Alias
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
3. Add to driver lookup in `plugin.py`:
|
|
451
|
+
```python
|
|
452
|
+
def _get_driver_class(instrument_type: str):
|
|
453
|
+
from litmus.instruments import NewInstrument
|
|
454
|
+
drivers = {
|
|
455
|
+
# ...
|
|
456
|
+
"new_instrument": NewInstrument,
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Adding a New Vector Expansion Mode
|
|
461
|
+
|
|
462
|
+
1. Add expansion function in `vectors.py`:
|
|
463
|
+
```python
|
|
464
|
+
def expand_custom(config: dict) -> list[Vector]:
|
|
465
|
+
# Your expansion logic
|
|
466
|
+
result = []
|
|
467
|
+
for i, params in enumerate(your_expansion):
|
|
468
|
+
v = Vector(params)
|
|
469
|
+
v["_index"] = i
|
|
470
|
+
if i > 0:
|
|
471
|
+
v["_prev"] = result[i - 1]
|
|
472
|
+
result.append(v)
|
|
473
|
+
return result
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
2. Register in `expand_vectors()`:
|
|
477
|
+
```python
|
|
478
|
+
if expand_mode == "custom":
|
|
479
|
+
return expand_custom(config)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Adding a New Storage Backend
|
|
483
|
+
|
|
484
|
+
1. Create `litmus/data/backends/new_backend.py`:
|
|
485
|
+
```python
|
|
486
|
+
class NewBackend:
|
|
487
|
+
def save_test_run(self, test_run: TestRun) -> str:
|
|
488
|
+
# Save and return ID/path
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
def load_test_run(self, run_id: str) -> TestRun:
|
|
492
|
+
pass
|
|
493
|
+
|
|
494
|
+
def list_runs(self, limit: int = 100) -> list[dict]:
|
|
495
|
+
pass
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
2. Use your backend in plugin.py or configure it via settings.
|
|
499
|
+
|
|
500
|
+
### Adding MCP Tools
|
|
501
|
+
|
|
502
|
+
1. Add tool implementation in `mcp/tools.py`:
|
|
503
|
+
```python
|
|
504
|
+
def new_tool_impl(arg1: str, arg2: int) -> dict[str, Any]:
|
|
505
|
+
# Implementation
|
|
506
|
+
return {"result": "..."}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
2. Register in `mcp/server.py`:
|
|
510
|
+
```python
|
|
511
|
+
@mcp.tool(name="litmus_new")
|
|
512
|
+
def new_tool(arg1: str, arg2: int) -> dict[str, Any]:
|
|
513
|
+
"""Tool description for AI agents."""
|
|
514
|
+
return new_tool_impl(arg1, arg2)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Development Workflow
|
|
520
|
+
|
|
521
|
+
### Setup
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
# Clone and install with all optional extras (pyright needs these installed
|
|
525
|
+
# to resolve imports for the exporters, transports, grafana, etc.)
|
|
526
|
+
git clone <repo>
|
|
527
|
+
cd litmus
|
|
528
|
+
uv sync --all-extras
|
|
529
|
+
|
|
530
|
+
# Install the pre-commit hooks once per clone. Hooks run ruff check,
|
|
531
|
+
# ruff format, pyright, and a handful of safety checks on every commit.
|
|
532
|
+
uv run pre-commit install
|
|
533
|
+
|
|
534
|
+
# Run tests
|
|
535
|
+
pytest
|
|
536
|
+
|
|
537
|
+
# Run with coverage
|
|
538
|
+
pytest --cov=litmus
|
|
539
|
+
|
|
540
|
+
# Lint, format, type-check (all run by the pre-commit hook too)
|
|
541
|
+
uv run ruff check .
|
|
542
|
+
uv run ruff format .
|
|
543
|
+
uv run pyright
|
|
544
|
+
|
|
545
|
+
# Run all pre-commit hooks manually across the repo
|
|
546
|
+
uv run pre-commit run --all-files
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Testing Your Changes
|
|
550
|
+
|
|
551
|
+
**Unit tests**: Add to `tests/` directory
|
|
552
|
+
```bash
|
|
553
|
+
pytest tests/test_your_feature.py -v
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Demo tests** (with mock instruments):
|
|
557
|
+
```bash
|
|
558
|
+
cd demo
|
|
559
|
+
pytest tests/ --station=demo_station_001 --mock-instruments -v
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Integration testing**:
|
|
563
|
+
```bash
|
|
564
|
+
# Start UI
|
|
565
|
+
litmus serve --reload
|
|
566
|
+
|
|
567
|
+
# Run MCP server
|
|
568
|
+
litmus mcp serve
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Code Style Guidelines
|
|
572
|
+
|
|
573
|
+
1. **Pydantic for config/data models**: All configuration and result structures use Pydantic
|
|
574
|
+
2. **Type hints everywhere**: Use type annotations, especially for public APIs
|
|
575
|
+
3. **Docstrings**: Google style, with Args/Returns/Raises sections
|
|
576
|
+
4. **No magic**: Prefer explicit over implicit. Configuration should be visible.
|
|
577
|
+
5. **YAML for config**: Human-editable configuration stays in YAML files
|
|
578
|
+
|
|
579
|
+
### Common Patterns
|
|
580
|
+
|
|
581
|
+
**Context manager for resources**:
|
|
582
|
+
```python
|
|
583
|
+
with harness.run_vector(vector) as tv:
|
|
584
|
+
# Vector execution
|
|
585
|
+
harness.measure("name", value)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**Limit resolution with fallback**:
|
|
589
|
+
```python
|
|
590
|
+
limit = context.get_limit("measurement_name")
|
|
591
|
+
if limit:
|
|
592
|
+
# Use limit
|
|
593
|
+
else:
|
|
594
|
+
# No limit configured
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Mock value configuration**:
|
|
598
|
+
```python
|
|
599
|
+
# Per-vector in config.yaml
|
|
600
|
+
vectors:
|
|
601
|
+
- vin: 5.0
|
|
602
|
+
_mocks:
|
|
603
|
+
dmm.measure_voltage: 3.3
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Debugging Tips
|
|
607
|
+
|
|
608
|
+
1. **Check vector expansion**: Print `harness.vectors` to see expanded params
|
|
609
|
+
2. **Trace limit resolution**: Add logging to `_resolve_limit()`
|
|
610
|
+
3. **Mock behavior**: Check `mock.mock_write_log` for SCPI commands sent
|
|
611
|
+
4. **Context inheritance**: Print `context.params` at each level
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Questions?
|
|
616
|
+
|
|
617
|
+
- Check existing tests in `tests/` for usage examples
|
|
618
|
+
- The `examples/` directory has complete working examples (three tiers: `01-bringup`, `02-station`, `03-profiles`)
|
|
619
|
+
- Open an issue for design questions
|