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,113 @@
|
|
|
1
|
+
# Step 1: Run Something
|
|
2
|
+
|
|
3
|
+
**Goal:** Install TesterKit and run your first test against a mock instrument.
|
|
4
|
+
|
|
5
|
+
## What You'll Build
|
|
6
|
+
|
|
7
|
+
A tiny project with one mock instrument and a passing measurement test — no hardware, and no station or part YAML yet. This is the **bench-bringup** scaffold: the smallest thing that records a real measurement.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install testerkit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
That installs the `testerkit` CLI and the pytest plugin — your tests are ordinary pytest functions; the plugin adds the hardware-test pieces.
|
|
16
|
+
|
|
17
|
+
## Scaffold a project
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
testerkit init my_project --tier=bringup
|
|
21
|
+
cd my_project
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `bringup` tier is the smallest scaffold: mock instrument fixtures in a `conftest.py`, one smoke test, and one sidecar — no station, catalog, or part YAML. It creates:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
my_project/
|
|
28
|
+
├── testerkit.yaml # project config
|
|
29
|
+
├── pyproject.toml
|
|
30
|
+
├── tests/
|
|
31
|
+
│ ├── conftest.py # mock dmm / psu fixtures
|
|
32
|
+
│ ├── test_smoke.py # measurement tests
|
|
33
|
+
│ └── test_smoke.yaml # sidecar limits
|
|
34
|
+
└── reports/
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Run it
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pytest -v
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Expected output:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
tests/test_smoke.py::test_rail_inline PASSED
|
|
47
|
+
tests/test_smoke.py::test_rail_sidecar PASSED
|
|
48
|
+
tests/test_smoke.py::test_current_draw PASSED
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Three measurements recorded against mock instruments, each checked against a limit.
|
|
52
|
+
|
|
53
|
+
## What's in the scaffold
|
|
54
|
+
|
|
55
|
+
The `conftest.py` defines instrument fixtures with `MagicMock` standing in for a real driver:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# tests/conftest.py
|
|
59
|
+
from unittest.mock import MagicMock
|
|
60
|
+
import pytest
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def dmm() -> MagicMock:
|
|
65
|
+
"""Bench DMM. Replace MagicMock with a real driver."""
|
|
66
|
+
inst = MagicMock()
|
|
67
|
+
inst.measure_dc_voltage.return_value = 3.3
|
|
68
|
+
return inst
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`test_smoke.py` ships three measurement tests; here's the first — it takes the `dmm` fixture and records a measurement:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# tests/test_smoke.py
|
|
75
|
+
def test_rail_inline(dmm, verify) -> None:
|
|
76
|
+
verify(
|
|
77
|
+
"v_rail",
|
|
78
|
+
float(dmm.measure_dc_voltage()),
|
|
79
|
+
limit={"low": 3.2, "high": 3.4, "nominal": 3.3, "unit": "V"},
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`verify` is a fixture the TesterKit plugin provides (installed with `testerkit`): it records the measurement and checks it against the limit, failing the test if the value is out of band. You'll meet `verify` and limits properly in [Step 3](03-fixtures.md) and [Step 4](04-limits.md) — for now, you've run a test that captures a real measurement. Swap the `MagicMock` for a [PyVISA](https://pyvisa.readthedocs.io/) or [PyMeasure](https://pymeasure.readthedocs.io/) driver when you move to the bench; the test body doesn't change.
|
|
84
|
+
|
|
85
|
+
## About conftest.py
|
|
86
|
+
|
|
87
|
+
Right now the instruments come from `conftest.py` fixtures — the same pattern you'd use in any pytest project. TesterKit doesn't need its own configuration to get started.
|
|
88
|
+
|
|
89
|
+
Later steps introduce a [station YAML](../concepts/configuration/stations.md) — one file that declares the bench's instruments. When it exists, TesterKit auto-registers an instrument-role fixture for each instrument it declares (`dmm`, `psu`, …), and you delete the matching `conftest.py` fixtures. The test bodies stay the same.
|
|
90
|
+
|
|
91
|
+
## Results
|
|
92
|
+
|
|
93
|
+
Each measurement is recorded to TesterKit's **run store** — the value `verify` captured, not just a pass/fail. Viewing and querying runs comes in later steps; the point for now is that the test recorded a real measurement.
|
|
94
|
+
|
|
95
|
+
## Troubleshooting
|
|
96
|
+
|
|
97
|
+
**"pytest: command not found"** — make sure `testerkit` installed into the active environment, and if you use a virtualenv, that it's activated.
|
|
98
|
+
|
|
99
|
+
**"No tests collected"** — check the test file name starts with `test_` and each function starts with `test_`.
|
|
100
|
+
|
|
101
|
+
**"fixture 'dmm' not found"** — the fixture lives in `tests/conftest.py`, which `testerkit init --tier=bringup` creates. Later steps lift the fixture into a station YAML, where the role fixture is auto-registered.
|
|
102
|
+
|
|
103
|
+
## What You Learned
|
|
104
|
+
|
|
105
|
+
- Install TesterKit with `pip install testerkit`
|
|
106
|
+
- Scaffold the smallest project with `testerkit init --tier=bringup`
|
|
107
|
+
- Run measurement tests against mock instruments with `pytest`
|
|
108
|
+
|
|
109
|
+
## Continue
|
|
110
|
+
|
|
111
|
+
Next, run the same tests in TesterKit's mock mode and control the returned values from config.
|
|
112
|
+
|
|
113
|
+
← [Quick Start](quickstart.md) | [Step 2: Mock Instruments →](02-mock-instruments.md)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Step 2: Running Without Hardware
|
|
2
|
+
|
|
3
|
+
**Goal:** Run your tests without real instruments by wrapping your driver classes with TesterKit's `Mock` helper.
|
|
4
|
+
|
|
5
|
+
In step 1 you wrote vanilla pytest tests against `psu` and `dmm` fixtures defined in `conftest.py`. This step shows the smallest change that lets the same tests run on a laptop with no hardware attached.
|
|
6
|
+
|
|
7
|
+
## The conftest pattern
|
|
8
|
+
|
|
9
|
+
Your `conftest.py` already returns driver instances. Wrap them in `testerkit.instruments.mocks.Mock` when the `--mock-instruments` flag is set:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# tests/conftest.py
|
|
13
|
+
import pytest
|
|
14
|
+
from drivers import DMM, PSU
|
|
15
|
+
from testerkit import Mock
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(scope="session")
|
|
19
|
+
def psu(mock_instruments) -> PSU:
|
|
20
|
+
if mock_instruments:
|
|
21
|
+
return Mock(PSU, measure_voltage=5.0, measure_current=0.042)
|
|
22
|
+
return PSU(resource="TCPIP::192.168.1.101::INSTR")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="session")
|
|
26
|
+
def dmm(mock_instruments) -> DMM:
|
|
27
|
+
if mock_instruments:
|
|
28
|
+
return Mock(DMM, measure_dc_voltage=3.31)
|
|
29
|
+
return DMM(resource="TCPIP::192.168.1.102::INSTR")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`mock_instruments` is a fixture TesterKit provides — it returns `True` whenever `--mock-instruments` is on the command line or `TESTERKIT_MOCK_INSTRUMENTS=1` is set.
|
|
33
|
+
|
|
34
|
+
`Mock(DMM, measure_dc_voltage=3.31)` returns a stand-in `DMM` — every method call does nothing and returns `None` unless you give it a return value. Pass a literal for a constant reading, a `dict` to map an argument value to a reading, or a function for dynamic behavior.
|
|
35
|
+
|
|
36
|
+
This is exactly what [`examples/01-vanilla`](https://github.com/pragmatest-dev/testerkit/tree/main/examples/01-vanilla) and [`examples/02-verify`](https://github.com/pragmatest-dev/testerkit/tree/main/examples/02-verify) ship.
|
|
37
|
+
|
|
38
|
+
## Running with mocks
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pytest tests/ --mock-instruments -v
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Same test code as step 1, no hardware required.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Or via env var
|
|
48
|
+
TESTERKIT_MOCK_INSTRUMENTS=1 pytest tests/ -v
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Mock cheat sheet
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Constant return
|
|
55
|
+
dmm = Mock(DMM, measure_dc_voltage=3.31)
|
|
56
|
+
|
|
57
|
+
# Map by argument — different return per query string
|
|
58
|
+
dmm = Mock(DMM, query={"MEAS:VOLT:DC?": "3.300", "*IDN?": "Keysight,34461A,..."})
|
|
59
|
+
|
|
60
|
+
# Callable — for noise or sweeps
|
|
61
|
+
import random
|
|
62
|
+
dmm = Mock(DMM, measure_dc_voltage=lambda: 3.3 + random.gauss(0, 0.005))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Every method you don't configure does nothing and returns `None`. Reading an attribute you never configured raises an `AttributeError` instead — so a missing mock value fails loudly rather than silently returning nothing.
|
|
66
|
+
|
|
67
|
+
## Mocks vs real hardware
|
|
68
|
+
|
|
69
|
+
| You run | `mock_instruments` is | Test code |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `pytest tests/` | `False` | identical |
|
|
72
|
+
| `pytest tests/ --mock-instruments` | `True` | identical |
|
|
73
|
+
|
|
74
|
+
The point of the wrap-in-conftest pattern: **the test code is the same on a laptop and on the bench**. Tests don't know which mode they're in.
|
|
75
|
+
|
|
76
|
+
## What you learned
|
|
77
|
+
|
|
78
|
+
- `--mock-instruments` flag + the `mock_instruments` fixture
|
|
79
|
+
- `Mock(DriverClass, method=return_value, ...)` wraps any driver class
|
|
80
|
+
- The conftest fixture decides real vs mock — tests don't change
|
|
81
|
+
|
|
82
|
+
In later steps you'll move this real-vs-mock choice out of `conftest.py` and into station YAML (step 7), so one setting serves a whole bench of tests. For now, conftest is enough.
|
|
83
|
+
|
|
84
|
+
## Continue
|
|
85
|
+
|
|
86
|
+
Now let's adopt three of [TesterKit's per-test fixtures](../reference/pytest/fixtures.md) — `context`, `verify`, `measure` — to start recording measurements with limits.
|
|
87
|
+
|
|
88
|
+
← [Step 1: Run Something](01-first-test.md) | [Step 3: pytest-native tests →](03-fixtures.md)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Step 3: pytest-native tests
|
|
2
|
+
|
|
3
|
+
**Goal:** Adopt TesterKit's per-test fixtures so measurements get recorded with full [traceability](../how-to/execution/traceability.md).
|
|
4
|
+
|
|
5
|
+
In step 2, your tests called driver methods and used `assert` for pass/fail. TesterKit's `measure` and `verify` fixtures slot in alongside that, recording each measurement to the run record (see [data stores](../concepts/data/data-stores.md)) without changing how your test reads.
|
|
6
|
+
|
|
7
|
+
You don't need any new YAML for this step. Keep the `conftest.py` from step 2 — the `psu` / `dmm` fixtures still work.
|
|
8
|
+
|
|
9
|
+
## The fixtures you add
|
|
10
|
+
|
|
11
|
+
All three are available on every test run — no station, no sidecar, no sweep required. `measure` and `verify` record measurements; `context` exposes what's active for this test — the run, UUT, station, and any sweep values.
|
|
12
|
+
|
|
13
|
+
| Fixture | What it gives the test | Verbs |
|
|
14
|
+
|----------|--------------------------------------------------------|--------------------------------------------------|
|
|
15
|
+
| `measure`| Records a measurement row — no pass/fail check | `measure(name, value, limit=None, characteristic=None)` |
|
|
16
|
+
| `verify` | Records the row, resolves a limit, raises on FAIL | `verify(name, value, limit=..., characteristic=...)` (`characteristic` = a named measurable property on the part spec — covered in step 6 / [concepts/capabilities](../concepts/configuration/capabilities.md)) |
|
|
17
|
+
| `context`| What's active — the run, UUT, station, and (if parametrized) sweep values | `get_param`, `changed`, `last`, `observe`, `.part`, `.station`, `.run` |
|
|
18
|
+
|
|
19
|
+
These are the three you'll reach for most. The TesterKit plugin for pytest provides more — hardware accessors (`pins`, `instruments`, `uut`), config accessors (`part`, `station_config`), and special-purpose fixtures (`vectors`, `sync`) — see the [TesterKit fixtures reference](../reference/pytest/fixtures.md) for the full set.
|
|
20
|
+
|
|
21
|
+
## From assert to measure
|
|
22
|
+
|
|
23
|
+
Take the test from step 2:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
def test_output_voltage(psu, dmm):
|
|
27
|
+
psu.set_voltage(5.0)
|
|
28
|
+
psu.enable_output()
|
|
29
|
+
v = dmm.measure_dc_voltage()
|
|
30
|
+
assert 3.2 <= v <= 3.4
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Add `measure` and record the measurement explicitly:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
def test_output_voltage(psu, dmm, measure):
|
|
37
|
+
psu.set_voltage(5.0)
|
|
38
|
+
psu.enable_output()
|
|
39
|
+
v = dmm.measure_dc_voltage()
|
|
40
|
+
measure("output_voltage", v, limit={"low": 3.2, "high": 3.4, "unit": "V"})
|
|
41
|
+
assert 3.2 <= v <= 3.4
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Same control flow, but now there's a measurement recorded in the run record — value, units, limits, and outcome — visible to `testerkit runs`, the operator UI, and any downstream analysis.
|
|
45
|
+
|
|
46
|
+
## Skip the assert with `verify`
|
|
47
|
+
|
|
48
|
+
`verify` is `measure` + `assert` in one call. Pass / fail is decided by the limit; an out-of-range value raises `AssertionError`:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
def test_output_voltage(psu, dmm, verify):
|
|
52
|
+
psu.set_voltage(5.0)
|
|
53
|
+
psu.enable_output()
|
|
54
|
+
verify("output_voltage", dmm.measure_dc_voltage(),
|
|
55
|
+
limit={"low": 3.2, "high": 3.4, "unit": "V"})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For one-off tests, passing `limit=` inline is fine. The cleaner home for limits is the part spec or the sidecar YAML — both arrive in later steps.
|
|
59
|
+
|
|
60
|
+
## Classes group related tests
|
|
61
|
+
|
|
62
|
+
Grouping related tests in a plain pytest class is the standard way to structure a TesterKit test:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
class TestPowerUp:
|
|
66
|
+
def test_input_voltage(self, psu, verify):
|
|
67
|
+
psu.set_voltage(5.0)
|
|
68
|
+
psu.enable_output()
|
|
69
|
+
verify("input_voltage", psu.measure_voltage(),
|
|
70
|
+
limit={"low": 4.5, "high": 5.5, "unit": "V"})
|
|
71
|
+
|
|
72
|
+
def test_output_voltage(self, dmm, verify):
|
|
73
|
+
verify("output_voltage", dmm.measure_dc_voltage(),
|
|
74
|
+
limit={"low": 3.2, "high": 3.4, "unit": "V"})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Methods run in source order. Each emits its own [step](../concepts/execution/step-hierarchy.md) events; the class container's [outcome](../reference/data/models.md#enum-outcome) rolls up from the worst child outcome.
|
|
78
|
+
|
|
79
|
+
If a downstream test should skip when an upstream one fails, use `@pytest.mark.dependency(depends=["test_input_voltage"])` from the [`pytest-dependency`](https://pytest-dependency.readthedocs.io/) plugin — pytest's ecosystem, not a TesterKit addition.
|
|
80
|
+
|
|
81
|
+
## Parametrize is first-class
|
|
82
|
+
|
|
83
|
+
`@pytest.mark.parametrize` works the way it always does. Add the `context` fixture if you want those parametrize values recorded with the measurement:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import pytest
|
|
87
|
+
@pytest.mark.parametrize("vin", [4.5, 5.0, 5.5])
|
|
88
|
+
def test_output_voltage(vin, psu, dmm, verify):
|
|
89
|
+
psu.set_voltage(vin)
|
|
90
|
+
psu.enable_output()
|
|
91
|
+
verify("output_voltage", dmm.measure_dc_voltage(),
|
|
92
|
+
limit={"low": 3.2, "high": 3.4, "unit": "V"})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Each parametrized `vin` value is recorded as an **input** named `vin` on the measurement (its role is `input` — see [traceability](../how-to/execution/traceability.md)), so you can later query "how did output_voltage track vin?" — inputs are addressable by name and role — without adding extra code to the test. Sweeping from YAML instead of inline arrives in step 5.
|
|
96
|
+
|
|
97
|
+
TesterKit also adds a native sweep marker, `@pytest.mark.testerkit_sweeps`, that records the same inputs and supports range expanders (`linspace`, `arange`, `logspace`):
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import pytest
|
|
101
|
+
|
|
102
|
+
@pytest.mark.testerkit_sweeps([{"vin": [4.5, 5.0, 5.5]}])
|
|
103
|
+
def test_output_voltage(vin, psu, dmm, verify):
|
|
104
|
+
...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use `@pytest.mark.parametrize` when you want pytest's per-row `pytest.param(..., id="...")` metadata; use `@pytest.mark.testerkit_sweeps` when you want range expanders (`linspace` / `arange` / `logspace`) or want the sweep to match how you'll define it in YAML (step 5). See [`testerkit_sweeps`](../reference/pytest/markers.md#testerkit_sweeps) and the [TesterKit markers reference](../reference/pytest/markers.md) for all seven `testerkit_*` markers.
|
|
108
|
+
|
|
109
|
+
## Multiple measurements per test
|
|
110
|
+
|
|
111
|
+
Each `verify` or `measure` call records one measurement. Call them as many times as you need:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
def test_power_analysis(psu, dmm, verify):
|
|
115
|
+
verify("input_voltage", psu.measure_voltage(),
|
|
116
|
+
limit={"low": 4.5, "high": 5.5, "unit": "V"})
|
|
117
|
+
verify("input_current", psu.measure_current(),
|
|
118
|
+
limit={"high": 0.5, "unit": "A"})
|
|
119
|
+
verify("output_voltage", dmm.measure_dc_voltage(),
|
|
120
|
+
limit={"low": 3.2, "high": 3.4, "unit": "V"})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Recording many samples
|
|
124
|
+
|
|
125
|
+
`measure` records one row per name within a step. For a stream of samples under a single name — a stability capture or a scope trace — use a channel (`stream`), covered in [Step 10](10-live-monitoring.md) and [Step 11](11-waveforms-and-evidence.md).
|
|
126
|
+
|
|
127
|
+
## Running the tests
|
|
128
|
+
|
|
129
|
+
Nothing new on the command line — same `pytest` invocation from step 2:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pytest tests/ --mock-instruments -v
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If you want to see the recorded measurements, list runs from the CLI:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
testerkit runs
|
|
139
|
+
testerkit show <run_id>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## What a measurement records
|
|
143
|
+
|
|
144
|
+
Read a run back and each measurement gives you:
|
|
145
|
+
|
|
146
|
+
| Field | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `measurement_name` | name passed to `verify` / `measure` |
|
|
149
|
+
| `measurement_value` | the measured value |
|
|
150
|
+
| `measurement_unit` | unit (from `limit.unit`) |
|
|
151
|
+
| `measurement_outcome` | `passed` / `failed` / `done` / `skipped` / `errored` |
|
|
152
|
+
| `limit_low`, `limit_high`, `limit_nominal`, `limit_comparator` | the active limit |
|
|
153
|
+
| `measurement_timestamp` | when it was recorded |
|
|
154
|
+
| `vector_index` | which sweep variant (NULL for non-parametrized tests) |
|
|
155
|
+
|
|
156
|
+
Read these with `testerkit runs` / the operator UI, or the [Query API](../reference/data/query-api.md).
|
|
157
|
+
|
|
158
|
+
## What you learned
|
|
159
|
+
|
|
160
|
+
- `measure(name, value, limit={"low": ..., "high": ..., "unit": "V"})` records a measurement explicitly
|
|
161
|
+
- `verify(name, value, limit=...)` does the same plus pass/fail + raise on FAIL
|
|
162
|
+
- Pytest classes group related tests; methods run in source order
|
|
163
|
+
- Parametrize works as it always does; values are recorded as inputs (role `input`)
|
|
164
|
+
|
|
165
|
+
## Continue
|
|
166
|
+
|
|
167
|
+
So far you've been passing `limit=` inline on every `verify` call. Step 4 separates the limit shape from the test code.
|
|
168
|
+
|
|
169
|
+
← [Step 2: Mock Instruments](02-mock-instruments.md) | [Step 4: Add Limits →](04-limits.md)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Step 4: Add Limits
|
|
2
|
+
|
|
3
|
+
**Goal:** Decide pass/fail for a measurement.
|
|
4
|
+
|
|
5
|
+
In step 3 your tests called `verify(name, value, limit=...)`. The pass/fail decision happens because a **limit** is attached. This step covers the limit shape and the two ways to attach a limit from code: inline on the call, or via the `testerkit_limits` marker on the test function. Both end up as the same limit.
|
|
6
|
+
|
|
7
|
+
Step 5 moves limits out of code into a YAML file next to the test — keep that destination in mind, but don't reach for YAML yet.
|
|
8
|
+
|
|
9
|
+
## The limit shape
|
|
10
|
+
|
|
11
|
+
A limit is a plain dict — same shape whether it lives inline, on a marker, or in YAML:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
limit = {
|
|
15
|
+
"low": 3.135, # Minimum acceptable value
|
|
16
|
+
"high": 3.465, # Maximum acceptable value
|
|
17
|
+
"nominal": 3.3, # Expected value (optional)
|
|
18
|
+
"unit": "V", # Unit of measure
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Both `verify(name, value, limit=...)` and `measure(name, value, limit=...)` accept this dict directly. It's validated against the [`Limit`](../reference/data/models.md#model-limit) model. If you'd rather construct it explicitly — for IDE type-checking or a shared constant — `Limit` is re-exported from the top-level package:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from testerkit import Limit
|
|
26
|
+
|
|
27
|
+
V_RAIL = Limit(low=3.135, high=3.465, unit="V")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The dict form is what tutorials and examples use; reach for `Limit(...)` when you want the model object.
|
|
31
|
+
|
|
32
|
+
## How a measurement is checked
|
|
33
|
+
|
|
34
|
+
`measure(...)` records a [`Measurement`](../reference/data/models.md#model-measurement) row with the value, units, and limit. `verify(...)` does the same plus raises `AssertionError` on FAIL. Either way, the row carries an `Outcome`:
|
|
35
|
+
|
|
36
|
+
| Outcome | String value | Meaning |
|
|
37
|
+
|---------|--------------|---------|
|
|
38
|
+
| `Outcome.PASSED` | `"passed"` | Value within limits |
|
|
39
|
+
| `Outcome.FAILED` | `"failed"` | Value outside limits |
|
|
40
|
+
| `Outcome.SKIPPED` | `"skipped"` | Test was skipped |
|
|
41
|
+
| `Outcome.ERRORED` | `"errored"` | Test encountered an error |
|
|
42
|
+
| `Outcome.ABORTED` | `"aborted"` | Run aborted by operator |
|
|
43
|
+
| `Outcome.TERMINATED` | `"terminated"` | Run terminated (keyboard interrupt, signal) |
|
|
44
|
+
| `Outcome.DONE` | `"done"` | Recorded without a limit, or container with no measurements |
|
|
45
|
+
|
|
46
|
+
A parent step takes the **worst** outcome of its children, ranked `skipped < done < passed < failed < errored < terminated < aborted` (so a parent with one skipped child and one passing child still resolves to `passed`).
|
|
47
|
+
|
|
48
|
+
## Inline limit on the call
|
|
49
|
+
|
|
50
|
+
The simplest form — pass `limit=` directly to `verify` or `measure`:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
def test_output_voltage(dmm, verify):
|
|
54
|
+
verify(
|
|
55
|
+
"output_voltage",
|
|
56
|
+
dmm.measure_dc_voltage(),
|
|
57
|
+
limit={"low": 3.135, "high": 3.465, "unit": "V"},
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Inline limits are fine for one-off tests. They clutter the test body when limits get long or vary per test.
|
|
62
|
+
|
|
63
|
+
## Limit via marker
|
|
64
|
+
|
|
65
|
+
`testerkit_limits` pulls the limit out of the body and pins it at the top of the test:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import pytest
|
|
69
|
+
|
|
70
|
+
@pytest.mark.testerkit_limits(
|
|
71
|
+
output_voltage={"low": 3.135, "high": 3.465, "unit": "V"},
|
|
72
|
+
)
|
|
73
|
+
def test_output_voltage(dmm, verify):
|
|
74
|
+
verify("output_voltage", dmm.measure_dc_voltage())
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The marker accepts one keyword per measurement name. `verify("output_voltage", ...)` resolves the limit from the marker without an explicit `limit=`. You can apply `@pytest.mark.testerkit_limits` at function, class, or module level — class scope applies to every method on the class.
|
|
78
|
+
|
|
79
|
+
## Comparators
|
|
80
|
+
|
|
81
|
+
By default, limits use `GELE` (greater-or-equal to low, less-or-equal to high): `low <= value <= high`. Other comparators are available when the test needs a different shape:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# Upper limit only
|
|
85
|
+
limit = {"high": 1.0, "unit": "A", "comparator": "LE"} # value <= 1.0
|
|
86
|
+
|
|
87
|
+
# Lower limit only
|
|
88
|
+
limit = {"low": 0.0, "unit": "V", "comparator": "GE"} # value >= 0.0
|
|
89
|
+
|
|
90
|
+
# Must equal nominal
|
|
91
|
+
limit = {"nominal": 5.0, "unit": "V", "comparator": "EQ"}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Full list:
|
|
95
|
+
|
|
96
|
+
| Comparator | Pass condition |
|
|
97
|
+
|------------|----------------|
|
|
98
|
+
| `GELE` | `low <= value <= high` (default) |
|
|
99
|
+
| `GELT` | `low <= value < high` |
|
|
100
|
+
| `GTLE` | `low < value <= high` |
|
|
101
|
+
| `GTLT` | `low < value < high` |
|
|
102
|
+
| `EQ` | `value == nominal` |
|
|
103
|
+
| `NE` | `value != nominal` |
|
|
104
|
+
| `GE` | `value >= low` |
|
|
105
|
+
| `GT` | `value > low` |
|
|
106
|
+
| `LE` | `value <= high` |
|
|
107
|
+
| `LT` | `value < high` |
|
|
108
|
+
|
|
109
|
+
## Recording without judging
|
|
110
|
+
|
|
111
|
+
`measure` records a value without comparing it to a limit — pass no `limit=` and the row carries `Outcome.DONE`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
def test_voltage(dmm, measure):
|
|
115
|
+
measure("output_voltage", dmm.measure_dc_voltage())
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`verify` requires a limit: calling it with none (no inline `limit=`, no marker, no sidecar, no part spec) raises `MissingLimitError`. For a wide characterization sweep where you want the same `verify()` test code to record values without judging, set `verify_requires_limit: false` on a [profile](../how-to/execution/profiles.md) — `verify` then falls back to `measure` semantics for that session.
|
|
119
|
+
|
|
120
|
+
## What's missing — and what step 5 fixes
|
|
121
|
+
|
|
122
|
+
Inline limits and markers live in the test code. That means a non-developer can't change them, condition-dependent limits get awkward, and limits can't be reused across multiple test files. Step 5 introduces the **[sidecar YAML](05-configuration.md)** — a file next to the test that carries limits (and sweeps, mocks, retries, prompts) without changing the test code.
|
|
123
|
+
|
|
124
|
+
For [condition-indexed bands](../how-to/execution/limits.md#condition-indexed-bands) (different bands at different temperatures or loads) jump to [Test limits](../how-to/execution/limits.md#condition-indexed-bands) when you need it.
|
|
125
|
+
|
|
126
|
+
## What you learned
|
|
127
|
+
|
|
128
|
+
- The limit dict — `low`, `high`, `nominal`, `unit`, `comparator`
|
|
129
|
+
- Inline limits via `verify(..., limit={...})` or `measure(..., limit={...})`
|
|
130
|
+
- The `testerkit_limits` marker for attaching limits at class or function level
|
|
131
|
+
- The `Outcome` ladder and what each value means
|
|
132
|
+
- The `Comparator` enum for non-`GELE` checks
|
|
133
|
+
- Recording without judging via `measure(no limit)` or a record-only profile
|
|
134
|
+
|
|
135
|
+
## Continue
|
|
136
|
+
|
|
137
|
+
Move the limits out of code and into a YAML file next to your test.
|
|
138
|
+
|
|
139
|
+
← [Step 3: pytest-native tests](03-fixtures.md) | [Step 5: Test Configuration →](05-configuration.md)
|