testerkit 0.4.0__py3-none-any.whl
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.
- testerkit/__init__.py +68 -0
- testerkit/_docs/_assets/operator-ui/channels/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/dashboard/runs.png +0 -0
- testerkit/_docs/_assets/operator-ui/dashboard/stations.png +0 -0
- testerkit/_docs/_assets/operator-ui/events/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/events/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/chart.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/explore/plot-controls.png +0 -0
- testerkit/_docs/_assets/operator-ui/files/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/launch/form.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/filters.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/pareto.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/ppk.png +0 -0
- testerkit/_docs/_assets/operator-ui/metrics/yield.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-header.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-measurements.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-overview.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/detail-steps.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/stats.png +0 -0
- testerkit/_docs/_assets/operator-ui/results/table.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/designer.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/docs.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/fixtures.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/instruments.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/parts.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/profiles.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/stations.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/tests.png +0 -0
- testerkit/_docs/_assets/operator-ui/tour/uuts.png +0 -0
- testerkit/_docs/concepts/configuration/capabilities.md +447 -0
- testerkit/_docs/concepts/configuration/fixtures.md +335 -0
- testerkit/_docs/concepts/configuration/index.md +14 -0
- testerkit/_docs/concepts/configuration/parts.md +276 -0
- testerkit/_docs/concepts/configuration/stations.md +214 -0
- testerkit/_docs/concepts/data/data-stores.md +199 -0
- testerkit/_docs/concepts/data/event-log.md +185 -0
- testerkit/_docs/concepts/data/event-sourcing.md +76 -0
- testerkit/_docs/concepts/data/flight-streaming.md +58 -0
- testerkit/_docs/concepts/data/index.md +17 -0
- testerkit/_docs/concepts/data/sessions.md +78 -0
- testerkit/_docs/concepts/data/three-verbs.md +260 -0
- testerkit/_docs/concepts/execution/index.md +13 -0
- testerkit/_docs/concepts/execution/outcomes.md +199 -0
- testerkit/_docs/concepts/execution/step-hierarchy.md +170 -0
- testerkit/_docs/concepts/execution/step-manifest.md +115 -0
- testerkit/_docs/concepts/index.md +40 -0
- testerkit/_docs/concepts/overview/ai-integration.md +57 -0
- testerkit/_docs/concepts/overview/architecture.md +357 -0
- testerkit/_docs/concepts/overview/index.md +14 -0
- testerkit/_docs/concepts/overview/platform-vs-framework.md +147 -0
- testerkit/_docs/concepts/overview/pytest.md +53 -0
- testerkit/_docs/concepts/overview/tiers.md +46 -0
- testerkit/_docs/how-to/catalog/datasheet-to-test.md +169 -0
- testerkit/_docs/how-to/catalog/index.md +11 -0
- testerkit/_docs/how-to/configuration/configuring-stations.md +364 -0
- testerkit/_docs/how-to/configuration/custom-drivers.md +484 -0
- testerkit/_docs/how-to/configuration/index.md +13 -0
- testerkit/_docs/how-to/configuration/mock-mode.md +342 -0
- testerkit/_docs/how-to/data/benchmarking.md +120 -0
- testerkit/_docs/how-to/data/capture-an-artifact.md +84 -0
- testerkit/_docs/how-to/data/capture-waveform.md +110 -0
- testerkit/_docs/how-to/data/choosing-a-channel-verb.md +108 -0
- testerkit/_docs/how-to/data/compare-runs.md +120 -0
- testerkit/_docs/how-to/data/export-results.md +104 -0
- testerkit/_docs/how-to/data/find-flaky-tests.md +108 -0
- testerkit/_docs/how-to/data/grafana-dashboards.md +148 -0
- testerkit/_docs/how-to/data/index.md +23 -0
- testerkit/_docs/how-to/data/mcp-debug-failures.md +125 -0
- testerkit/_docs/how-to/data/mcp-query-runs.md +120 -0
- testerkit/_docs/how-to/data/querying-channels.md +117 -0
- testerkit/_docs/how-to/data/querying-events.md +85 -0
- testerkit/_docs/how-to/data/stream-live-channel.md +134 -0
- testerkit/_docs/how-to/execution/custom-operator-ui.md +136 -0
- testerkit/_docs/how-to/execution/index.md +21 -0
- testerkit/_docs/how-to/execution/limits.md +193 -0
- testerkit/_docs/how-to/execution/managing-sessions.md +100 -0
- testerkit/_docs/how-to/execution/multi-uut-testing.md +211 -0
- testerkit/_docs/how-to/execution/operator-prompts.md +151 -0
- testerkit/_docs/how-to/execution/profiles.md +304 -0
- testerkit/_docs/how-to/execution/spec-driven-testing.md +169 -0
- testerkit/_docs/how-to/execution/test-context.md +212 -0
- testerkit/_docs/how-to/execution/traceability.md +252 -0
- testerkit/_docs/how-to/execution/vector-expansion.md +394 -0
- testerkit/_docs/how-to/execution/writing-tests.md +363 -0
- testerkit/_docs/how-to/index.md +59 -0
- testerkit/_docs/how-to/overview/index.md +11 -0
- testerkit/_docs/how-to/overview/mcp-integration.md +168 -0
- testerkit/_docs/how-to/overview/operator-ui-tour.md +234 -0
- testerkit/_docs/integration/data/grafana.md +162 -0
- testerkit/_docs/integration/data/index.md +15 -0
- testerkit/_docs/integration/data/lakehouse-import.md +226 -0
- testerkit/_docs/integration/data/logging.md +131 -0
- testerkit/_docs/integration/data/results-api.md +51 -0
- testerkit/_docs/integration/index.md +20 -0
- testerkit/_docs/integration/runtime/harness.md +281 -0
- testerkit/_docs/integration/runtime/index.md +13 -0
- testerkit/_docs/integration/runtime/instruments.md +284 -0
- testerkit/_docs/integration/runtime/pytest-existing.md +315 -0
- testerkit/_docs/reference/catalog/cookbook.md +389 -0
- testerkit/_docs/reference/catalog/index.md +11 -0
- testerkit/_docs/reference/catalog/schema.md +375 -0
- testerkit/_docs/reference/cli.md +660 -0
- testerkit/_docs/reference/configuration.md +406 -0
- testerkit/_docs/reference/data/channels-schema.md +145 -0
- testerkit/_docs/reference/data/event-types.md +554 -0
- testerkit/_docs/reference/data/events-schema.md +120 -0
- testerkit/_docs/reference/data/files-schema.md +141 -0
- testerkit/_docs/reference/data/index.md +18 -0
- testerkit/_docs/reference/data/models.md +2229 -0
- testerkit/_docs/reference/data/outputs.md +82 -0
- testerkit/_docs/reference/data/parquet-schema.md +529 -0
- testerkit/_docs/reference/data/performance-limits.md +224 -0
- testerkit/_docs/reference/data/query-api.md +247 -0
- testerkit/_docs/reference/index.md +69 -0
- testerkit/_docs/reference/operator-ui/channels/detail.md +82 -0
- testerkit/_docs/reference/operator-ui/channels/list.md +70 -0
- testerkit/_docs/reference/operator-ui/dashboard.md +87 -0
- testerkit/_docs/reference/operator-ui/designer.md +167 -0
- testerkit/_docs/reference/operator-ui/events.md +89 -0
- testerkit/_docs/reference/operator-ui/files.md +74 -0
- testerkit/_docs/reference/operator-ui/fixtures.md +100 -0
- testerkit/_docs/reference/operator-ui/instruments.md +126 -0
- testerkit/_docs/reference/operator-ui/launch.md +79 -0
- testerkit/_docs/reference/operator-ui/live.md +102 -0
- testerkit/_docs/reference/operator-ui/measurements.md +159 -0
- testerkit/_docs/reference/operator-ui/metrics.md +217 -0
- testerkit/_docs/reference/operator-ui/parts.md +98 -0
- testerkit/_docs/reference/operator-ui/profiles.md +103 -0
- testerkit/_docs/reference/operator-ui/results/detail.md +214 -0
- testerkit/_docs/reference/operator-ui/results/list.md +106 -0
- testerkit/_docs/reference/operator-ui/stations.md +98 -0
- testerkit/_docs/reference/operator-ui/tests.md +173 -0
- testerkit/_docs/reference/operator-ui/uuts.md +67 -0
- testerkit/_docs/reference/overview/index.md +11 -0
- testerkit/_docs/reference/overview/pytest-native.md +142 -0
- testerkit/_docs/reference/overview/skills.md +177 -0
- testerkit/_docs/reference/pytest/fixtures.md +342 -0
- testerkit/_docs/reference/pytest/index.md +12 -0
- testerkit/_docs/reference/pytest/markers.md +257 -0
- testerkit/_docs/reference/runtime/api.md +246 -0
- testerkit/_docs/reference/runtime/client.md +280 -0
- testerkit/_docs/reference/runtime/connect.md +140 -0
- testerkit/_docs/reference/runtime/index.md +13 -0
- testerkit/_docs/tutorial/01-first-test.md +113 -0
- testerkit/_docs/tutorial/02-mock-instruments.md +88 -0
- testerkit/_docs/tutorial/03-fixtures.md +169 -0
- testerkit/_docs/tutorial/04-limits.md +139 -0
- testerkit/_docs/tutorial/05-configuration.md +194 -0
- testerkit/_docs/tutorial/06-specifications.md +273 -0
- testerkit/_docs/tutorial/07-real-instruments.md +245 -0
- testerkit/_docs/tutorial/08-capabilities.md +223 -0
- testerkit/_docs/tutorial/09-production.md +374 -0
- testerkit/_docs/tutorial/10-live-monitoring.md +156 -0
- testerkit/_docs/tutorial/11-waveforms-and-evidence.md +169 -0
- testerkit/_docs/tutorial/12-continuous-monitoring.md +145 -0
- testerkit/_docs/tutorial/13-parallel-testing.md +233 -0
- testerkit/_docs/tutorial/index.md +61 -0
- testerkit/_docs/tutorial/quickstart.md +282 -0
- testerkit/analysis/__init__.py +9 -0
- testerkit/analysis/_common.py +24 -0
- testerkit/analysis/measurement_facets.py +524 -0
- testerkit/analysis/measurements_query.py +1458 -0
- testerkit/analysis/metrics.py +389 -0
- testerkit/analysis/runs_query.py +434 -0
- testerkit/analysis/steps_query.py +413 -0
- testerkit/api/__init__.py +6 -0
- testerkit/api/_mime.py +71 -0
- testerkit/api/app.py +1171 -0
- testerkit/api/dialogs/__init__.py +36 -0
- testerkit/api/dialogs/manager.py +617 -0
- testerkit/api/dialogs/models.py +79 -0
- testerkit/api/models.py +80 -0
- testerkit/api/responses.py +205 -0
- testerkit/api/runner.py +222 -0
- testerkit/api/schemas.py +311 -0
- testerkit/benchmark/__init__.py +34 -0
- testerkit/benchmark/concurrency.py +248 -0
- testerkit/benchmark/core.py +232 -0
- testerkit/benchmark/runner.py +628 -0
- testerkit/benchmark/scenario.py +313 -0
- testerkit/benchmark/system.py +191 -0
- testerkit/benchmark/workloads.py +507 -0
- testerkit/catalog/__init__.py +6 -0
- testerkit/catalog/generic/generic_dmm.yaml +61 -0
- testerkit/catalog/generic/generic_eload.yaml +31 -0
- testerkit/catalog/generic/generic_fgen.yaml +56 -0
- testerkit/catalog/generic/generic_oscilloscope.yaml +45 -0
- testerkit/catalog/generic/generic_psu.yaml +77 -0
- testerkit/catalog/generic/generic_smu.yaml +73 -0
- testerkit/channels.py +538 -0
- testerkit/cli/__init__.py +32 -0
- testerkit/cli/_common.py +113 -0
- testerkit/cli/_time.py +179 -0
- testerkit/cli/benchmark_cmd.py +56 -0
- testerkit/cli/catalog_cmd.py +39 -0
- testerkit/cli/daemon.py +157 -0
- testerkit/cli/data_cmd.py +1040 -0
- testerkit/cli/discover_cmd.py +94 -0
- testerkit/cli/docs_cmd.py +100 -0
- testerkit/cli/instrument.py +172 -0
- testerkit/cli/mcp_cmd.py +37 -0
- testerkit/cli/metrics.py +366 -0
- testerkit/cli/project.py +312 -0
- testerkit/cli/root.py +14 -0
- testerkit/cli/runs.py +412 -0
- testerkit/cli/schema_cmd.py +87 -0
- testerkit/cli/serve_cmd.py +50 -0
- testerkit/cli/setup_cmd.py +604 -0
- testerkit/cli/station.py +294 -0
- testerkit/cli/validate.py +112 -0
- testerkit/client.py +463 -0
- testerkit/connect.py +635 -0
- testerkit/data/__init__.py +8 -0
- testerkit/data/_accumulator_pool.py +455 -0
- testerkit/data/_atomic.py +59 -0
- testerkit/data/_collection_indices.py +67 -0
- testerkit/data/_daemon_lifecycle.py +535 -0
- testerkit/data/_duckdb_daemon.py +695 -0
- testerkit/data/_duckdb_flight_server.py +813 -0
- testerkit/data/_event_filters.py +15 -0
- testerkit/data/_event_reader.py +101 -0
- testerkit/data/_flight_errors.py +199 -0
- testerkit/data/_flight_query.py +202 -0
- testerkit/data/_flight_retry.py +148 -0
- testerkit/data/_flight_subscribe.py +67 -0
- testerkit/data/_index_epoch.py +342 -0
- testerkit/data/_ipc_writer.py +185 -0
- testerkit/data/_json_safe.py +63 -0
- testerkit/data/_process.py +18 -0
- testerkit/data/_push_relay.py +124 -0
- testerkit/data/_runs_duckdb_daemon.py +2704 -0
- testerkit/data/_session_reaper.py +110 -0
- testerkit/data/_sql_helpers.py +30 -0
- testerkit/data/_store.py +24 -0
- testerkit/data/backends/__init__.py +7 -0
- testerkit/data/backends/_event_accumulator.py +957 -0
- testerkit/data/backends/_row_helpers.py +1172 -0
- testerkit/data/backends/parquet.py +1143 -0
- testerkit/data/backends/protocol.py +35 -0
- testerkit/data/channels/__init__.py +8 -0
- testerkit/data/channels/_flight_daemon.py +75 -0
- testerkit/data/channels/client.py +242 -0
- testerkit/data/channels/flight_manager.py +88 -0
- testerkit/data/channels/index.py +664 -0
- testerkit/data/channels/models.py +418 -0
- testerkit/data/channels/server.py +162 -0
- testerkit/data/channels/store.py +1481 -0
- testerkit/data/data_dir.py +61 -0
- testerkit/data/duckdb_manager.py +79 -0
- testerkit/data/event_log.py +384 -0
- testerkit/data/event_store.py +768 -0
- testerkit/data/events.py +1025 -0
- testerkit/data/exporters/__init__.py +6 -0
- testerkit/data/exporters/_helpers.py +40 -0
- testerkit/data/exporters/csv_exporter.py +168 -0
- testerkit/data/exporters/hdf5.py +234 -0
- testerkit/data/exporters/json_exporter.py +229 -0
- testerkit/data/exporters/mdf4.py +201 -0
- testerkit/data/exporters/stdf.py +255 -0
- testerkit/data/exporters/tdms.py +292 -0
- testerkit/data/files/__init__.py +71 -0
- testerkit/data/files/_backend.py +207 -0
- testerkit/data/files/_catalog_daemon.py +84 -0
- testerkit/data/files/catalog.py +218 -0
- testerkit/data/files/catalog_manager.py +325 -0
- testerkit/data/files/models.py +60 -0
- testerkit/data/files/serializers.py +414 -0
- testerkit/data/files/store.py +463 -0
- testerkit/data/files/streaming.py +783 -0
- testerkit/data/models.py +604 -0
- testerkit/data/ref.py +152 -0
- testerkit/data/retention.py +297 -0
- testerkit/data/run_store.py +367 -0
- testerkit/data/runs_duckdb_manager.py +103 -0
- testerkit/data/schema_dispatch.py +191 -0
- testerkit/data/schema_migrate.py +76 -0
- testerkit/data/schema_versions.py +85 -0
- testerkit/data/schemas.py +288 -0
- testerkit/data/subscribers/__init__.py +48 -0
- testerkit/data/subscribers/_base.py +20 -0
- testerkit/data/subscribers/_output_file.py +25 -0
- testerkit/data/subscribers/replay.py +55 -0
- testerkit/environment.py +84 -0
- testerkit/execution/__init__.py +11 -0
- testerkit/execution/_git.py +190 -0
- testerkit/execution/_state.py +740 -0
- testerkit/execution/accessors.py +54 -0
- testerkit/execution/audit.py +51 -0
- testerkit/execution/cascade.py +85 -0
- testerkit/execution/connections.py +491 -0
- testerkit/execution/harness.py +1931 -0
- testerkit/execution/instrument_events.py +42 -0
- testerkit/execution/limits.py +154 -0
- testerkit/execution/metadata.py +102 -0
- testerkit/execution/mocks.py +63 -0
- testerkit/execution/profiles.py +616 -0
- testerkit/execution/run_scope.py +1331 -0
- testerkit/execution/session_scope.py +196 -0
- testerkit/execution/sidecar.py +280 -0
- testerkit/execution/site_runner.py +705 -0
- testerkit/execution/sites.py +183 -0
- testerkit/execution/sync.py +294 -0
- testerkit/execution/uut_provider.py +231 -0
- testerkit/execution/vectors.py +205 -0
- testerkit/execution/verify.py +396 -0
- testerkit/expand.py +75 -0
- testerkit/files.py +235 -0
- testerkit/fixtures/__init__.py +6 -0
- testerkit/fixtures/manager.py +357 -0
- testerkit/grafana/__init__.py +1 -0
- testerkit/grafana/bootstrap.py +61 -0
- testerkit/grafana/cli.py +314 -0
- testerkit/grafana/dashboards/asset_utilization.json +92 -0
- testerkit/grafana/dashboards/channel_explorer.json +77 -0
- testerkit/grafana/dashboards/event_log.json +92 -0
- testerkit/grafana/dashboards/failure_pareto.json +104 -0
- testerkit/grafana/dashboards/measurement_distribution.json +97 -0
- testerkit/grafana/dashboards/measurement_trend.json +161 -0
- testerkit/grafana/dashboards/station_comparison.json +95 -0
- testerkit/grafana/dashboards/test_duration.json +104 -0
- testerkit/grafana/dashboards/unit_traceability.json +73 -0
- testerkit/grafana/dashboards/yield_overview.json +189 -0
- testerkit/grafana/provisioning/dashboards.yaml.j2 +12 -0
- testerkit/grafana/provisioning/datasources.yaml.j2 +17 -0
- testerkit/grafana/server.py +282 -0
- testerkit/init.py +724 -0
- testerkit/instruments/__init__.py +10 -0
- testerkit/instruments/base.py +82 -0
- testerkit/instruments/discovery/__init__.py +62 -0
- testerkit/instruments/discovery/_base.py +174 -0
- testerkit/instruments/discovery/lxi.py +152 -0
- testerkit/instruments/discovery/ni.py +95 -0
- testerkit/instruments/discovery/serial.py +73 -0
- testerkit/instruments/discovery/visa.py +98 -0
- testerkit/instruments/lifecycle.py +213 -0
- testerkit/instruments/loader.py +79 -0
- testerkit/instruments/locks.py +178 -0
- testerkit/instruments/mocks.py +230 -0
- testerkit/instruments/observer.py +270 -0
- testerkit/instruments/observers/__init__.py +36 -0
- testerkit/instruments/observers/_base.py +68 -0
- testerkit/instruments/observers/daqmx.py +70 -0
- testerkit/instruments/observers/descriptor.py +69 -0
- testerkit/instruments/observers/generic.py +92 -0
- testerkit/instruments/observers/lantz.py +31 -0
- testerkit/instruments/observers/modbus.py +96 -0
- testerkit/instruments/observers/motion.py +68 -0
- testerkit/instruments/observers/ni_modular.py +73 -0
- testerkit/instruments/observers/ophyd.py +80 -0
- testerkit/instruments/observers/pymeasure.py +120 -0
- testerkit/instruments/observers/qcodes.py +66 -0
- testerkit/instruments/observers/scpi.py +88 -0
- testerkit/instruments/observers/tektronix.py +57 -0
- testerkit/instruments/observers/visa.py +103 -0
- testerkit/instruments/pool.py +459 -0
- testerkit/instruments/proxy.py +59 -0
- testerkit/instruments/route_manager.py +329 -0
- testerkit/instruments/routed_proxy.py +88 -0
- testerkit/instruments/server.py +508 -0
- testerkit/instruments/switch.py +43 -0
- testerkit/instruments/visa.py +294 -0
- testerkit/matching/__init__.py +6 -0
- testerkit/matching/service.py +969 -0
- testerkit/mcp/__init__.py +6 -0
- testerkit/mcp/server.py +681 -0
- testerkit/mcp/tools.py +1868 -0
- testerkit/models/__init__.py +18 -0
- testerkit/models/capability.py +598 -0
- testerkit/models/catalog.py +76 -0
- testerkit/models/data_options.py +153 -0
- testerkit/models/enums.py +334 -0
- testerkit/models/instrument.py +152 -0
- testerkit/models/instrument_asset.py +25 -0
- testerkit/models/part.py +271 -0
- testerkit/models/part_manifest.py +96 -0
- testerkit/models/project.py +114 -0
- testerkit/models/station.py +140 -0
- testerkit/models/test_config.py +748 -0
- testerkit/ontology/__init__.py +39 -0
- testerkit/ontology/schema.py +188 -0
- testerkit/ontology/testerkit.yaml +1420 -0
- testerkit/parts/__init__.py +8 -0
- testerkit/parts/context.py +181 -0
- testerkit/parts/folder.py +251 -0
- testerkit/parts/loader.py +27 -0
- testerkit/prompts/__init__.py +17 -0
- testerkit/prompts/core.py +133 -0
- testerkit/pytest_plugin/__init__.py +1287 -0
- testerkit/pytest_plugin/autouse.py +369 -0
- testerkit/pytest_plugin/helpers.py +354 -0
- testerkit/pytest_plugin/hooks.py +1982 -0
- testerkit/pytest_plugin/markers.py +203 -0
- testerkit/pytest_plugin/retry.py +37 -0
- testerkit/pytest_plugin/sweeps.py +117 -0
- testerkit/queries.py +78 -0
- testerkit/reports/__init__.py +6 -0
- testerkit/reports/core.py +492 -0
- testerkit/reports/datasheet.py +831 -0
- testerkit/reports/templates/datasheet.html +383 -0
- testerkit/reports/templates/default.html +153 -0
- testerkit/sbom.py +142 -0
- testerkit/schema_export.py +101 -0
- testerkit/signals.py +63 -0
- testerkit/skills/README.md +58 -0
- testerkit/skills/templates/project-instructions.md +125 -0
- testerkit/skills/testerkit-analysis/SKILL.md +87 -0
- testerkit/skills/testerkit-capture/SKILL.md +114 -0
- testerkit/skills/testerkit-data/SKILL.md +138 -0
- testerkit/skills/testerkit-datasheets/SKILL.md +100 -0
- testerkit/skills/testerkit-datasheets/agents/scaffold-writer.md +115 -0
- testerkit/skills/testerkit-datasheets/agents/section-extractor.md +98 -0
- testerkit/skills/testerkit-datasheets/agents/section-reviewer.md +153 -0
- testerkit/skills/testerkit-datasheets/agents/section-splitter.md +89 -0
- testerkit/skills/testerkit-datasheets/agents/section-writer.md +226 -0
- testerkit/skills/testerkit-datasheets/references/catalog-pipeline.md +271 -0
- testerkit/skills/testerkit-datasheets/references/process-queue.md +42 -0
- testerkit/skills/testerkit-datasheets/references/scaffold.md +156 -0
- testerkit/skills/testerkit-datasheets/references/test-pipeline.md +312 -0
- testerkit/skills/testerkit-debug/SKILL.md +127 -0
- testerkit/skills/testerkit-interactive/SKILL.md +188 -0
- testerkit/skills/testerkit-interactive/references/live-ui-patterns.md +102 -0
- testerkit/skills/testerkit-mocks/SKILL.md +128 -0
- testerkit/skills/testerkit-parts/SKILL.md +126 -0
- testerkit/skills/testerkit-profiles/SKILL.md +139 -0
- testerkit/skills/testerkit-sites/SKILL.md +140 -0
- testerkit/skills/testerkit-stations/SKILL.md +146 -0
- testerkit/skills/testerkit-tests/SKILL.md +136 -0
- testerkit/store.py +1639 -0
- testerkit/ui/__init__.py +71 -0
- testerkit/ui/_asgi.py +162 -0
- testerkit/ui/app.py +32 -0
- testerkit/ui/components/__init__.py +13 -0
- testerkit/ui/components/artifact_viewer.py +250 -0
- testerkit/ui/components/channel_values.py +159 -0
- testerkit/ui/components/event_timeline.py +222 -0
- testerkit/ui/components/execution_gantt.py +210 -0
- testerkit/ui/components/file_streams.py +147 -0
- testerkit/ui/components/instrument_activity.py +146 -0
- testerkit/ui/components/session_table.py +118 -0
- testerkit/ui/pages/__init__.py +25 -0
- testerkit/ui/pages/channels/__init__.py +4 -0
- testerkit/ui/pages/channels/detail.py +932 -0
- testerkit/ui/pages/channels/list.py +515 -0
- testerkit/ui/pages/dashboard.py +236 -0
- testerkit/ui/pages/designer/__init__.py +5 -0
- testerkit/ui/pages/designer/graph.py +635 -0
- testerkit/ui/pages/designer/matching.py +663 -0
- testerkit/ui/pages/designer/page.py +593 -0
- testerkit/ui/pages/designer/properties.py +591 -0
- testerkit/ui/pages/designer/state.py +468 -0
- testerkit/ui/pages/docs/__init__.py +6 -0
- testerkit/ui/pages/docs/index.py +95 -0
- testerkit/ui/pages/docs/page.py +698 -0
- testerkit/ui/pages/events/__init__.py +3 -0
- testerkit/ui/pages/events/list.py +263 -0
- testerkit/ui/pages/explore.py +1108 -0
- testerkit/ui/pages/files/__init__.py +4 -0
- testerkit/ui/pages/files/detail.py +426 -0
- testerkit/ui/pages/files/list.py +447 -0
- testerkit/ui/pages/fixtures/__init__.py +8 -0
- testerkit/ui/pages/fixtures/detail.py +280 -0
- testerkit/ui/pages/fixtures/edit.py +370 -0
- testerkit/ui/pages/fixtures/list.py +180 -0
- testerkit/ui/pages/fixtures/new.py +195 -0
- testerkit/ui/pages/instruments/__init__.py +4 -0
- testerkit/ui/pages/instruments/detail.py +320 -0
- testerkit/ui/pages/instruments/edit.py +482 -0
- testerkit/ui/pages/instruments/list.py +249 -0
- testerkit/ui/pages/instruments/new.py +200 -0
- testerkit/ui/pages/launch.py +261 -0
- testerkit/ui/pages/live.py +124 -0
- testerkit/ui/pages/metrics_page.py +1229 -0
- testerkit/ui/pages/parts/__init__.py +11 -0
- testerkit/ui/pages/parts/detail.py +202 -0
- testerkit/ui/pages/parts/edit.py +310 -0
- testerkit/ui/pages/parts/list.py +160 -0
- testerkit/ui/pages/parts/new.py +170 -0
- testerkit/ui/pages/parts/requirements.py +153 -0
- testerkit/ui/pages/parts/stations.py +199 -0
- testerkit/ui/pages/profiles/__init__.py +4 -0
- testerkit/ui/pages/profiles/detail.py +113 -0
- testerkit/ui/pages/profiles/list.py +86 -0
- testerkit/ui/pages/results/__init__.py +4 -0
- testerkit/ui/pages/results/detail.py +631 -0
- testerkit/ui/pages/results/list.py +230 -0
- testerkit/ui/pages/stations/__init__.py +4 -0
- testerkit/ui/pages/stations/detail.py +256 -0
- testerkit/ui/pages/stations/edit.py +273 -0
- testerkit/ui/pages/stations/list.py +180 -0
- testerkit/ui/pages/stations/new.py +311 -0
- testerkit/ui/pages/tests/__init__.py +4 -0
- testerkit/ui/pages/tests/detail.py +152 -0
- testerkit/ui/pages/tests/list.py +194 -0
- testerkit/ui/pages/uuts/__init__.py +4 -0
- testerkit/ui/pages/uuts/list.py +97 -0
- testerkit/ui/shared/__init__.py +8 -0
- testerkit/ui/shared/components.py +1826 -0
- testerkit/ui/shared/dialogs.py +105 -0
- testerkit/ui/shared/event_binding.py +194 -0
- testerkit/ui/shared/layout.py +314 -0
- testerkit/ui/shared/services.py +1719 -0
- testerkit/ui/shared/timestamps.py +21 -0
- testerkit/ui/static/branding/favicon-16.png +0 -0
- testerkit/ui/static/branding/favicon-32.png +0 -0
- testerkit/ui/static/branding/favicon-48.png +0 -0
- testerkit/ui/static/branding/testerkit-icon-256.png +0 -0
- testerkit/ui/static/branding/testerkit-mark.svg +1 -0
- testerkit/ui/static/branding/testerkit-wordmark.svg +1 -0
- testerkit/ui/static/global.css +465 -0
- testerkit/utils/__init__.py +17 -0
- testerkit/utils/enum_meta.py +800 -0
- testerkit/utils/paths.py +75 -0
- testerkit/utils/ranges.py +257 -0
- testerkit/validation.py +82 -0
- testerkit/verbs.py +142 -0
- testerkit-0.4.0.dist-info/METADATA +250 -0
- testerkit-0.4.0.dist-info/RECORD +521 -0
- testerkit-0.4.0.dist-info/WHEEL +4 -0
- testerkit-0.4.0.dist-info/entry_points.txt +5 -0
- testerkit-0.4.0.dist-info/licenses/LICENSE +191 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Writing custom instrument drivers
|
|
2
|
+
|
|
3
|
+
TesterKit doesn't ship instrument drivers — you bring your own. This page covers writing a driver, registering it so the platform finds it, and the two hardware-free paths TesterKit supports.
|
|
4
|
+
|
|
5
|
+
A TesterKit driver is just a Python class. Subclass `VisaInstrument` for SCPI-over-VISA, or `Instrument` for anything else, and add one method per thing your test reads (`measure_voltage()`, `set_voltage()`, …). The method names are whatever your test calls — that's the whole contract.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from testerkit.instruments.visa import VisaInstrument
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MyDMM(VisaInstrument):
|
|
12
|
+
def measure_voltage(self) -> float:
|
|
13
|
+
return float(self.query("MEAS:VOLT?"))
|
|
14
|
+
|
|
15
|
+
def measure_current(self) -> float:
|
|
16
|
+
return float(self.query("MEAS:CURR?"))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If your instrument speaks SCPI over VISA, [start with `VisaInstrument`](#scpi-instruments-via-visa). For serial, DAQmx, USB, or proprietary protocols, [extend `Instrument` directly](#non-visa-instruments).
|
|
20
|
+
|
|
21
|
+
**Which base class?**
|
|
22
|
+
|
|
23
|
+
| Import | Use it for |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `from testerkit.instruments.visa import VisaInstrument` | SCPI / IEEE 488.2 instruments — wraps PyVISA, adds `query()` / `write()` / `*IDN?` parsing, generates a `pyvisa-sim` config when `simulate=True` |
|
|
26
|
+
| `from testerkit.instruments.base import Instrument` | Any protocol you handle yourself — serial, DAQmx, USB, HID, proprietary RPC |
|
|
27
|
+
|
|
28
|
+
`Mock` is top-level (`from testerkit import Mock`). `Mock(MyDMM, ...)` returns a stand-in that passes `isinstance(x, MyDMM)`, no-ops `connect()`/`disconnect()`, and returns only the values you configure.
|
|
29
|
+
|
|
30
|
+
Capabilities (what the matcher pairs against parts) live in the catalog YAML you point `catalog_ref:` at, not in your driver class — see [catalog schema](../../reference/catalog/schema.md).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## SCPI instruments via VISA
|
|
35
|
+
|
|
36
|
+
For any SCPI / IEEE 488.2 instrument, subclass `VisaInstrument`. The base handles `connect()` / `disconnect()`, `write()` / `query()` / `read()`, `*IDN?` parsing, and generates a pyvisa-sim YAML when `simulate=True`.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from testerkit.instruments.visa import VisaInstrument
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MyDMM(VisaInstrument):
|
|
43
|
+
"""Custom SCPI DMM driver."""
|
|
44
|
+
|
|
45
|
+
def measure_voltage(self) -> float:
|
|
46
|
+
# Use MEAS:VOLT? (not MEAS:VOLT:DC?) — the auto-generated
|
|
47
|
+
# pyvisa-sim YAML wires `voltage` in sim_config to MEAS:VOLT?.
|
|
48
|
+
return float(self.query("MEAS:VOLT?"))
|
|
49
|
+
|
|
50
|
+
def measure_current(self) -> float:
|
|
51
|
+
return float(self.query("MEAS:CURR?"))
|
|
52
|
+
|
|
53
|
+
def configure_voltage_range(self, range_val: float | str) -> None:
|
|
54
|
+
if range_val == "AUTO":
|
|
55
|
+
self.write("VOLT:RANG:AUTO ON")
|
|
56
|
+
else:
|
|
57
|
+
self.write(f"VOLT:RANG {range_val}")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Usage on real hardware:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
dmm = MyDMM("TCPIP::192.168.1.100::INSTR")
|
|
64
|
+
dmm.connect()
|
|
65
|
+
v = dmm.measure_voltage()
|
|
66
|
+
dmm.disconnect()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
In `simulate=True` mode, the base class generates a pyvisa-sim YAML on `connect()`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
dmm = MyDMM(
|
|
73
|
+
"TCPIP::192.168.1.100::INSTR",
|
|
74
|
+
simulate=True,
|
|
75
|
+
sim_config={"voltage": 5.0, "current": 0.1},
|
|
76
|
+
)
|
|
77
|
+
dmm.connect()
|
|
78
|
+
v = dmm.measure_voltage() # 5.0
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### What `sim_config` controls
|
|
82
|
+
|
|
83
|
+
The auto-generated pyvisa-sim YAML wires `voltage` and `current` from `sim_config` into stateful properties matching `MEAS:VOLT?` and `MEAS:CURR?` queries respectively. Setters (`VOLT {value}`, `CURR {value}`) update that state. You also get:
|
|
84
|
+
|
|
85
|
+
| sim_config key | Effect |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `voltage: <float>` | Default value returned by `MEAS:VOLT?`; `VOLT <value>` updates it |
|
|
88
|
+
| `current: <float>` | Default value returned by `MEAS:CURR?`; `CURR <value>` updates it |
|
|
89
|
+
| `idn: "Vendor,Model,Serial,Firmware"` | Overrides the `*IDN?` response (default: `TesterKit,SimulatedVisa,SN001,1.0`) |
|
|
90
|
+
| `responses: {<scpi-cmd>: <response>}` | Static query-response dialogues for any other SCPI command. The response is returned as a literal string. |
|
|
91
|
+
| `noise: {<name>: <pct>}` | Adds random noise to the simulated `<name>` value. Useful if your driver implements `simulate` branches by hand. |
|
|
92
|
+
|
|
93
|
+
If your driver uses non-standard SCPI commands (e.g. `MEAS:VOLT:DC?` instead of `MEAS:VOLT?`), add them via `responses`:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
dmm = MyDMM(
|
|
97
|
+
"TCPIP::192.168.1.100::INSTR",
|
|
98
|
+
simulate=True,
|
|
99
|
+
sim_config={"responses": {"MEAS:VOLT:DC?": "5.0"}},
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The response value is the literal string returned to the query. `float("5.0")` works; `float("voltage")` does not. Don't put placeholder names like `"voltage"` in the response — put the actual value.
|
|
104
|
+
|
|
105
|
+
### Class-level `_sim_responses`
|
|
106
|
+
|
|
107
|
+
If your driver always needs a particular static SCPI dialogue (e.g. a system query), set it on the class so every instance gets it:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
class MyDMM(VisaInstrument):
|
|
111
|
+
_sim_responses = {
|
|
112
|
+
"SYST:ERR?": '0,"No error"',
|
|
113
|
+
"CAL:STAT?": "OK",
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Per-instance `sim_config["responses"]` merges over `_sim_responses` (instance wins).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Non-VISA instruments
|
|
122
|
+
|
|
123
|
+
For serial, DAQmx, USB, or any other protocol, subclass `Instrument` and own `connect()` / `disconnect()` plus simulation yourself. The base class gives you `resource`, `simulate`, `sim_config`, `_connected`, four optional identity fields (`manufacturer`, `model`, `serial`, `firmware`), and the context-manager protocol.
|
|
124
|
+
|
|
125
|
+
### Pattern: serial device
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import serial
|
|
129
|
+
|
|
130
|
+
from testerkit.instruments.base import Instrument
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class SerialDMM(Instrument):
|
|
134
|
+
"""DMM with serial (RS-232) interface."""
|
|
135
|
+
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
port: str,
|
|
139
|
+
baudrate: int = 9600,
|
|
140
|
+
simulate: bool = False,
|
|
141
|
+
sim_config: dict | None = None,
|
|
142
|
+
):
|
|
143
|
+
# Pass the port as `resource` so the base records it for traceability.
|
|
144
|
+
super().__init__(resource=port, simulate=simulate, sim_config=sim_config)
|
|
145
|
+
self.baudrate = baudrate
|
|
146
|
+
self._serial: serial.Serial | None = None
|
|
147
|
+
self._sim_voltage = float((sim_config or {}).get("voltage", 0.0))
|
|
148
|
+
|
|
149
|
+
def connect(self) -> None:
|
|
150
|
+
if self.simulate:
|
|
151
|
+
self._connected = True
|
|
152
|
+
return
|
|
153
|
+
self._serial = serial.Serial(self.resource, self.baudrate, timeout=1)
|
|
154
|
+
self._connected = True
|
|
155
|
+
|
|
156
|
+
def disconnect(self) -> None:
|
|
157
|
+
if self._serial:
|
|
158
|
+
self._serial.close()
|
|
159
|
+
self._serial = None
|
|
160
|
+
self._connected = False
|
|
161
|
+
|
|
162
|
+
def measure_voltage(self) -> float:
|
|
163
|
+
if self.simulate:
|
|
164
|
+
return self._sim_voltage
|
|
165
|
+
self._serial.write(b"MEAS:VOLT?\r\n")
|
|
166
|
+
return float(self._serial.readline().decode().strip())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Pattern: NI DAQmx
|
|
170
|
+
|
|
171
|
+
Guard the import so the driver imports on machines without DAQmx installed (`pip install nidaqmx`):
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from typing import Any
|
|
175
|
+
|
|
176
|
+
from testerkit.instruments.base import Instrument
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
import nidaqmx
|
|
180
|
+
from nidaqmx.constants import TerminalConfiguration
|
|
181
|
+
|
|
182
|
+
HAS_DAQMX = True
|
|
183
|
+
except ImportError:
|
|
184
|
+
HAS_DAQMX = False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class DaqmxAnalogInput(Instrument):
|
|
188
|
+
"""NI DAQmx analog input channel."""
|
|
189
|
+
|
|
190
|
+
def __init__(
|
|
191
|
+
self,
|
|
192
|
+
physical_channel: str, # e.g. "Dev1/ai0"
|
|
193
|
+
simulate: bool = False,
|
|
194
|
+
sim_config: dict | None = None,
|
|
195
|
+
):
|
|
196
|
+
super().__init__(resource=physical_channel, simulate=simulate, sim_config=sim_config)
|
|
197
|
+
self._task: Any = None
|
|
198
|
+
self._sim_voltage = float((sim_config or {}).get("voltage", 0.0))
|
|
199
|
+
|
|
200
|
+
def connect(self) -> None:
|
|
201
|
+
if self.simulate:
|
|
202
|
+
self._connected = True
|
|
203
|
+
return
|
|
204
|
+
if not HAS_DAQMX:
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
"nidaqmx is not installed; pass simulate=True or run under "
|
|
207
|
+
"--mock-instruments for hardware-free tests."
|
|
208
|
+
)
|
|
209
|
+
self._task = nidaqmx.Task()
|
|
210
|
+
self._task.ai_channels.add_ai_voltage_chan(
|
|
211
|
+
self.resource, terminal_config=TerminalConfiguration.RSE
|
|
212
|
+
)
|
|
213
|
+
self._connected = True
|
|
214
|
+
|
|
215
|
+
def disconnect(self) -> None:
|
|
216
|
+
if self._task:
|
|
217
|
+
self._task.close()
|
|
218
|
+
self._task = None
|
|
219
|
+
self._connected = False
|
|
220
|
+
|
|
221
|
+
def measure_voltage(self) -> float:
|
|
222
|
+
if self.simulate:
|
|
223
|
+
return self._sim_voltage
|
|
224
|
+
return float(self._task.read())
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Pattern: proprietary USB / HID
|
|
228
|
+
|
|
229
|
+
Guard the import so the driver imports on machines without pyusb installed (`pip install pyusb`):
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
import struct
|
|
233
|
+
|
|
234
|
+
from testerkit.instruments.base import Instrument
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
import usb.core
|
|
238
|
+
|
|
239
|
+
HAS_USB = True
|
|
240
|
+
except ImportError:
|
|
241
|
+
HAS_USB = False
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class USBPowerSupply(Instrument):
|
|
245
|
+
"""Power supply with a proprietary HID protocol."""
|
|
246
|
+
|
|
247
|
+
VENDOR_ID = 0x1234
|
|
248
|
+
PRODUCT_ID = 0x5678
|
|
249
|
+
|
|
250
|
+
def __init__(self, simulate: bool = False, sim_config: dict | None = None):
|
|
251
|
+
super().__init__(
|
|
252
|
+
resource=f"USB:{self.VENDOR_ID:04x}:{self.PRODUCT_ID:04x}",
|
|
253
|
+
simulate=simulate,
|
|
254
|
+
sim_config=sim_config,
|
|
255
|
+
)
|
|
256
|
+
self._device = None
|
|
257
|
+
self._sim_voltage = 0.0
|
|
258
|
+
self._sim_enabled = False
|
|
259
|
+
|
|
260
|
+
def connect(self) -> None:
|
|
261
|
+
if self.simulate:
|
|
262
|
+
self._connected = True
|
|
263
|
+
return
|
|
264
|
+
if not HAS_USB:
|
|
265
|
+
raise RuntimeError("pyusb not installed; pass simulate=True for tests.")
|
|
266
|
+
self._device = usb.core.find(idVendor=self.VENDOR_ID, idProduct=self.PRODUCT_ID)
|
|
267
|
+
if self._device is None:
|
|
268
|
+
raise RuntimeError("Device not found")
|
|
269
|
+
self._connected = True
|
|
270
|
+
|
|
271
|
+
def disconnect(self) -> None:
|
|
272
|
+
self._device = None
|
|
273
|
+
self._connected = False
|
|
274
|
+
|
|
275
|
+
def set_voltage(self, voltage: float) -> None:
|
|
276
|
+
if self.simulate:
|
|
277
|
+
self._sim_voltage = float(voltage)
|
|
278
|
+
return
|
|
279
|
+
packet = struct.pack("<BHf", 0x10, 4, voltage)
|
|
280
|
+
self._device.write(0x01, packet)
|
|
281
|
+
|
|
282
|
+
def measure_output_voltage(self) -> float:
|
|
283
|
+
if self.simulate:
|
|
284
|
+
return self._sim_voltage if self._sim_enabled else 0.0
|
|
285
|
+
self._device.write(0x01, struct.pack("<BH", 0x20, 0))
|
|
286
|
+
return float(struct.unpack("<f", bytes(self._device.read(0x81, 64))[:4])[0])
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Identity fields on non-VISA drivers
|
|
290
|
+
|
|
291
|
+
`VisaInstrument` populates `manufacturer` / `model` / `serial` / `firmware` from `*IDN?` automatically. Non-VISA drivers either leave them `None` (no identity verification) or populate them in `connect()`:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
def connect(self) -> None:
|
|
295
|
+
# ... open the connection ...
|
|
296
|
+
self.manufacturer = "MyVendor"
|
|
297
|
+
self.model = "MyDMM-1000"
|
|
298
|
+
self.serial = self._read_serial_from_device()
|
|
299
|
+
self.firmware = self._read_firmware_version()
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Running without hardware
|
|
305
|
+
|
|
306
|
+
Two paths. They behave differently and you pick based on what the test is exercising.
|
|
307
|
+
|
|
308
|
+
### Platform mock-mode — the default
|
|
309
|
+
|
|
310
|
+
In station YAML, set `mock: true` and list the method return values under `mock_config:`. The platform substitutes a stand-in for the real driver: your driver class is never instantiated, `connect()` is never called, and methods you listed return the configured values (everything else returns `None`).
|
|
311
|
+
|
|
312
|
+
```yaml
|
|
313
|
+
# stations/my_station.yaml
|
|
314
|
+
instruments:
|
|
315
|
+
dmm:
|
|
316
|
+
type: dmm
|
|
317
|
+
driver: my_pkg.MyDMM
|
|
318
|
+
resource: "TCPIP::192.168.1.100::INSTR"
|
|
319
|
+
mock: true # opt in at the instrument level
|
|
320
|
+
mock_config:
|
|
321
|
+
measure_voltage: 5.0 # dmm.measure_voltage() → 5.0 inside tests
|
|
322
|
+
measure_current: 0.1
|
|
323
|
+
query: # dict — first arg is the lookup key
|
|
324
|
+
"MEAS:VOLT?": "5.0"
|
|
325
|
+
"*IDN?": "Vendor,Model,SN1,1.0"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`mock_config:` keys are **method names on your driver**, not signal names. Values can be:
|
|
329
|
+
|
|
330
|
+
- a scalar → returned on every call regardless of args
|
|
331
|
+
- a dict → first positional argument is the lookup key (great for SCPI `query()`)
|
|
332
|
+
- a callable → invoked with the call's args, return value goes back to the test
|
|
333
|
+
|
|
334
|
+
Method names you list return the configured value. Method names you don't list are silent no-ops returning `None`, regardless of whether they exist on the real driver class — the platform path uses a generic stand-in, so the typed-method surface isn't there to validate against. A typo in `mock_config:` keys won't raise; it'll just show up as a `None` reading on the row.
|
|
335
|
+
|
|
336
|
+
`--mock-instruments` (CLI) and `TESTERKIT_MOCK_INSTRUMENTS=1` (env var) force `mock: true` for every instrument in the station. Test code is identical whether the station is real or mocked:
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
def test_voltage(dmm, verify):
|
|
340
|
+
verify("output_voltage", dmm.measure_voltage())
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The test calls `dmm.measure_voltage()` whether `dmm` is a real `MyDMM` or the stand-in returning `5.0` from `mock_config:` — pytest never sees the difference. The auto-fixture is registered from the station YAML's `instruments:` keys; see [TesterKit fixtures](../../reference/pytest/fixtures.md#per-role-auto-fixtures). One thing to note: `isinstance(dmm, MyDMM)` is `False` under platform mock-mode (the stand-in isn't a subclass of your driver class). If a test path depends on that check, use the bringup-tier `conftest.py` pattern below where `Mock(MyDMM, ...)` does preserve isinstance.
|
|
344
|
+
|
|
345
|
+
For sidecar `mocks:` overrides and resolution order — see [mock-mode.md](mock-mode.md).
|
|
346
|
+
|
|
347
|
+
### Driver-internal `simulate=True` — when you write the simulation yourself
|
|
348
|
+
|
|
349
|
+
`Instrument` takes a `simulate: bool` flag on `__init__` and stores it. **What that flag actually does is up to your driver.** The base class does nothing with it; the platform doesn't wire it. If you write `if self.simulate: ...` branches in your methods, those branches run. If you don't, `simulate=True` is silent.
|
|
350
|
+
|
|
351
|
+
The exception is `VisaInstrument`, which auto-generates a pyvisa-sim YAML on `connect()`. The generator wires exactly two SCPI properties: `voltage` (queries `MEAS:VOLT?`, setter `VOLT {value}`) and `current` (queries `MEAS:CURR?`, setter `CURR {value}`), plus `*IDN?` and whatever static dialogues you list in `sim_config["responses"]`. That covers a DMM measuring DC voltage / current. Resistance, frequency, scope waveforms, PSU output-enable state, anything else — your driver writes its own `if self.simulate:` branches or its own SCPI dialogue entries.
|
|
352
|
+
|
|
353
|
+
For non-VISA protocols, there is no framework simulation. `Instrument.__init__` stores `simulate=True`; the rest is your code. The DAQmx and serial examples above show the pattern.
|
|
354
|
+
|
|
355
|
+
Use `simulate=True` when you've put real work into the driver's own simulation logic — a pyvisa-sim setup that holds state, a state machine for a sequencer, a closed-loop model for a PSU — and the test needs to exercise that logic. Otherwise use platform mock-mode (`mock_config:`); it doesn't require any simulation code in the driver at all.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Registering your driver
|
|
360
|
+
|
|
361
|
+
### Station YAML (production path)
|
|
362
|
+
|
|
363
|
+
Point `driver:` at your driver class's dotted path. TesterKit loads that class, constructs it with the `resource:` you give, and exposes it to tests as a fixture named after the YAML key (`dmm:` → `dmm` fixture):
|
|
364
|
+
|
|
365
|
+
```yaml
|
|
366
|
+
# stations/my_station.yaml
|
|
367
|
+
id: my_station
|
|
368
|
+
name: "My Test Station"
|
|
369
|
+
|
|
370
|
+
instruments:
|
|
371
|
+
dmm: # ← becomes the `dmm` pytest fixture
|
|
372
|
+
type: dmm
|
|
373
|
+
driver: my_pkg.drivers.MyDMM # dotted path to your driver class
|
|
374
|
+
catalog_ref: my_pkg.my_dmm # ← what the matcher reads
|
|
375
|
+
resource: "TCPIP::192.168.1.100::INSTR"
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Now `def test_voltage(dmm, verify): ...` resolves `dmm` to a connected `MyDMM` instance.
|
|
379
|
+
|
|
380
|
+
For [station configuration](configuring-stations.md) details (other `driver:` examples, multi-channel routing, the `catalog_ref:` link) see the how-to. For the `catalog_ref:` target schema see [catalog schema](../../reference/catalog/schema.md).
|
|
381
|
+
|
|
382
|
+
### conftest.py (bringup tier — no station YAML yet)
|
|
383
|
+
|
|
384
|
+
Before you have a station YAML, write the fixture yourself in `conftest.py`. The TesterKit-provided `mock_instruments` fixture is `True` when `--mock-instruments` or `TESTERKIT_MOCK_INSTRUMENTS=1` is set, so the same fixture serves real and mock paths:
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
# tests/conftest.py
|
|
388
|
+
import pytest
|
|
389
|
+
|
|
390
|
+
from testerkit import Mock
|
|
391
|
+
from my_pkg.drivers import MyDMM
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@pytest.fixture(scope="session")
|
|
395
|
+
def dmm(mock_instruments) -> MyDMM:
|
|
396
|
+
if mock_instruments:
|
|
397
|
+
return Mock(MyDMM, measure_voltage=5.0, measure_current=0.1)
|
|
398
|
+
return MyDMM("TCPIP::192.168.1.100::INSTR")
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
This is the same pattern [tutorial step 2](../../tutorial/02-mock-instruments.md) introduces — `Mock(MyDMM, **values)` returns a `MockMyDMM` instance whose declared methods become no-ops returning your configured values. `isinstance(dmm, MyDMM)` still passes; `dmm.set_voltage(3.3)` is a silent no-op; `dmm.measure_voltage()` returns `5.0`.
|
|
402
|
+
|
|
403
|
+
Step up to station YAML once you have more than one bench or want capability matching. The station path supersedes the conftest fixture — the platform auto-registers a `dmm` fixture from `instruments.dmm:` in the YAML.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Testing your driver
|
|
408
|
+
|
|
409
|
+
Two scopes: testing *the driver class itself* (does its `connect()` open the port? does its `measure_voltage()` parse the response correctly?) is separate from testing *a procedure that uses the driver* (which is what `mock_config:` + the TesterKit plugin handle).
|
|
410
|
+
|
|
411
|
+
For driver-level tests, exercise whatever simulation pathway you've actually built. If your driver is `VisaInstrument`-based and you only need voltage/current/IDN, the auto-generated pyvisa-sim config covers you:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
import pytest
|
|
415
|
+
|
|
416
|
+
from my_pkg.drivers import MyDMM
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def test_measure_voltage_simulated():
|
|
420
|
+
with MyDMM(
|
|
421
|
+
"TCPIP::192.168.1.100::INSTR",
|
|
422
|
+
simulate=True,
|
|
423
|
+
sim_config={"voltage": 3.3},
|
|
424
|
+
) as dmm:
|
|
425
|
+
assert dmm.measure_voltage() == pytest.approx(3.3, abs=0.001)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
If your driver needs methods the auto-sim doesn't cover (resistance, frequency, waveform, output-enable state, anything non-voltage / non-current), add static SCPI dialogues via `sim_config["responses"]` for query-shaped methods, or write `if self.simulate:` branches inside the driver. There is no auto-simulation for non-VISA drivers — `Instrument.__init__` stores the `simulate` flag and that's it.
|
|
429
|
+
|
|
430
|
+
When in doubt for driver-level tests, use `Mock` directly — it doesn't depend on any simulation infrastructure being present:
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
from testerkit import Mock
|
|
434
|
+
|
|
435
|
+
from my_pkg.drivers import MyDMM
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def test_overrange_handling():
|
|
439
|
+
dmm = Mock(MyDMM, measure_voltage=999.0)
|
|
440
|
+
# exercise your driver-wrapper code that consumes measure_voltage()
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
To gate tests on real hardware availability, register a marker in your project's `conftest.py` and use `-m` to filter:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
# conftest.py
|
|
447
|
+
def pytest_configure(config):
|
|
448
|
+
config.addinivalue_line("markers", "hardware: requires real instruments")
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
```python
|
|
452
|
+
@pytest.mark.hardware
|
|
453
|
+
def test_measure_voltage_real():
|
|
454
|
+
with MyDMM("TCPIP::192.168.1.100::INSTR") as dmm:
|
|
455
|
+
v = dmm.measure_voltage()
|
|
456
|
+
assert isinstance(v, float) and v > 0.0
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Then:
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
pytest -m hardware # only hardware tests
|
|
463
|
+
pytest -m "not hardware" # only simulation tests (CI default)
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`hardware` is not a TesterKit-registered marker; the `testerkit_*` markers are listed in [TesterKit markers](../../reference/pytest/markers.md). You own this marker locally.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Best practices
|
|
471
|
+
|
|
472
|
+
- **Pass `resource=` to the base.** Non-VISA drivers should pass the connection identifier (port, channel, vendor:product) to `super().__init__(resource=...)` so traceability and the operator UI display something meaningful.
|
|
473
|
+
- **Guard optional dependencies.** Wrap `import nidaqmx` / `import usb.core` / etc. in a `try / except ImportError` block so the driver imports cleanly on hardware-free hosts.
|
|
474
|
+
- **Let the station YAML decide mock vs real.** Don't import any mock class in your driver or test code. `mock: true` + `mock_config:` in the station block is the canonical path; the platform substitutes a stand-in that returns your listed method values. Reach for driver-internal `simulate=True` only when you've written non-trivial simulation logic that the test should exercise.
|
|
475
|
+
- **Capabilities live in the catalog, not in code.** Your driver class is just code — TesterKit learns "this is a DMM that measures DC voltage" from the catalog YAML you point `catalog_ref:` at. Don't try to declare capabilities via Python mixins or class attributes.
|
|
476
|
+
|
|
477
|
+
## See also
|
|
478
|
+
|
|
479
|
+
- [Catalog schema](../../reference/catalog/schema.md) — what a `catalog/<vendor>/<model>.yaml` entry declares (the matcher's contract)
|
|
480
|
+
- [Catalog cookbook](../../reference/catalog/cookbook.md) — worked recipes for common datasheet shapes
|
|
481
|
+
- [Capabilities](../../concepts/configuration/capabilities.md) — capability model + matching algorithm
|
|
482
|
+
- [Configuring stations](configuring-stations.md) — the `driver:` field and the rest of the station YAML
|
|
483
|
+
- [Mock mode](mock-mode.md) — `--mock-instruments`, `mock_config:`, the three mock pipelines
|
|
484
|
+
- [TesterKit fixtures](../../reference/pytest/fixtures.md) — `instruments`, `instrument`, `pins`, `mock_instruments`, and how per-role auto-fixtures get registered
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# How-To — Configuration
|
|
2
|
+
|
|
3
|
+
Get hardware on the bench, wire it into a station, and run the same code with or without instruments physically present.
|
|
4
|
+
|
|
5
|
+
- [Configuring stations](configuring-stations.md) — station YAML, instruments, environments
|
|
6
|
+
- [Custom instrument drivers](custom-drivers.md) — bring your own driver (PyVISA / PyMeasure / vendor)
|
|
7
|
+
- [Mock mode](mock-mode.md) — `--mock-instruments`, station `mock_config`, per-test mocks
|
|
8
|
+
|
|
9
|
+
## See also
|
|
10
|
+
|
|
11
|
+
- [Concepts → Configuration](../../concepts/configuration/index.md) — the part / station / fixture / capability model behind these recipes
|
|
12
|
+
- [Reference → Configuration](../../reference/configuration.md) — YAML schemas for every entity
|
|
13
|
+
- [Tutorial → Step 7: Real Instruments](../../tutorial/07-real-instruments.md) — the canonical end-to-end walkthrough
|