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
testerkit/data/models.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
"""Data models for test results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from datetime import UTC, datetime, timedelta
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
from typing import Any
|
|
9
|
+
from uuid import UUID, uuid4
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from testerkit.models.test_config import Limit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _utcnow() -> datetime:
|
|
17
|
+
"""Return current UTC datetime (timezone-aware)."""
|
|
18
|
+
return datetime.now(UTC)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ensure_utc(dt: datetime) -> datetime:
|
|
22
|
+
"""Normalize a datetime to UTC-aware.
|
|
23
|
+
|
|
24
|
+
Naive inputs are assumed UTC (the server's storage convention) and
|
|
25
|
+
stamped without shifting the wall-clock value; aware inputs are
|
|
26
|
+
converted to UTC. The server only ever reasons in UTC, so every
|
|
27
|
+
datetime crossing a receive/parse boundary passes through here.
|
|
28
|
+
"""
|
|
29
|
+
if dt.tzinfo is None:
|
|
30
|
+
return dt.replace(tzinfo=UTC)
|
|
31
|
+
return dt.astimezone(UTC)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class StimulusRecord(BaseModel):
|
|
35
|
+
"""Record of a stimulus applied during test execution.
|
|
36
|
+
|
|
37
|
+
Captures the full signal path when an instrument sets an input condition:
|
|
38
|
+
- param: The parameter name (e.g., "vin", "load", "temp")
|
|
39
|
+
- value: The commanded value
|
|
40
|
+
- unit: Unit of the value
|
|
41
|
+
- instrument: Station config name (e.g., "psu_main")
|
|
42
|
+
- resource: VISA address or connection string
|
|
43
|
+
- channel: Channel on instrument (e.g., "CH1")
|
|
44
|
+
- uut_pin: UUT pin being driven
|
|
45
|
+
- fixture_connection: Named fixture connection
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
param: str
|
|
49
|
+
value: float | None = None
|
|
50
|
+
unit: str | None = None
|
|
51
|
+
instrument: str | None = None # Station config name (e.g., "psu_main")
|
|
52
|
+
resource: str | None = None # VISA address or connection string
|
|
53
|
+
channel: str | None = None # Channel on instrument (e.g., "CH1")
|
|
54
|
+
uut_pin: str | None = None # UUT pin being driven
|
|
55
|
+
fixture_connection: str | None = None # Named fixture connection
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Outcome(StrEnum):
|
|
59
|
+
"""Canonical terminal outcome of a measurement / step / run.
|
|
60
|
+
|
|
61
|
+
All values are past participles — outcomes are retrospective by
|
|
62
|
+
construction (the row reflects what happened). Each runner adapter
|
|
63
|
+
translates its native signals into these. Pytest's own
|
|
64
|
+
``passed`` / ``failed`` / ``skipped`` / ``error`` mostly map by
|
|
65
|
+
casing; bare-assert failures and ``pytest.skip()`` flow through
|
|
66
|
+
``pytest_runtest_makereport``.
|
|
67
|
+
|
|
68
|
+
Producer story:
|
|
69
|
+
|
|
70
|
+
* ``PASSED`` — measurement value met its limit. Producer:
|
|
71
|
+
``execution.verify._compute_outcome`` when ``value in limit``;
|
|
72
|
+
pytest ``passed`` propagates when no measurement-level outcome
|
|
73
|
+
contradicts.
|
|
74
|
+
* ``FAILED`` — measurement violated its limit OR the test failed an
|
|
75
|
+
assertion. Producer: ``_compute_outcome`` when ``value not in
|
|
76
|
+
limit``; ``pytest_runtest_makereport`` escalates ``step.outcome``
|
|
77
|
+
to ``FAILED`` when pytest reports ``failed`` and no measurement
|
|
78
|
+
already failed.
|
|
79
|
+
* ``ERRORED`` — exception during execution (not an assertion).
|
|
80
|
+
Producer: pytest ``error`` (setup/teardown failures); uncaught
|
|
81
|
+
non-AssertionError during call; ``_compute_outcome`` when value
|
|
82
|
+
is ``None`` (couldn't measure → can't judge).
|
|
83
|
+
* ``SKIPPED`` — explicit skip by operator/marker/condition.
|
|
84
|
+
Producer: pytest ``skipped`` (``pytest.skip()`` /
|
|
85
|
+
``@pytest.mark.skip`` / skipif); ``VectorBuilder.skip()`` on the
|
|
86
|
+
catch-all client API.
|
|
87
|
+
* ``DONE`` — recorded value, no judgment evaluated. Producer:
|
|
88
|
+
plain ``logger.measure(name, value)`` (no limit, no verify) —
|
|
89
|
+
the recorder semantic. Setup/action / characterization-mode
|
|
90
|
+
measurements that explicitly aren't being judged. Test engineers
|
|
91
|
+
reject ``PASSED`` for un-judged actions ("don't call my setup a
|
|
92
|
+
pass") — this is the answer.
|
|
93
|
+
* ``TERMINATED`` — operator stopped the run and cleanup ran (safe
|
|
94
|
+
states reached, fixtures torn down, parquet finalized). The
|
|
95
|
+
"expected, mid-flight, but graceful" stop. TestStand calls this
|
|
96
|
+
Terminated; we follow that convention so test engineers
|
|
97
|
+
crossing over from TestStand/WATS recognize the semantics.
|
|
98
|
+
Producer: ``pytest_keyboard_interrupt`` once we've confirmed
|
|
99
|
+
teardown ran; ``connect.__exit__`` on KeyboardInterrupt /
|
|
100
|
+
SystemExit; SIGTERM handler when the cleanup chain completed.
|
|
101
|
+
* ``ABORTED`` — terminated WITHOUT cleanup. Reserved for the
|
|
102
|
+
no-safe-state case: the close()-time fallback when no
|
|
103
|
+
``RunEnded`` was ever emitted, partial signal-handler exits,
|
|
104
|
+
etc. When you see Aborted in a report it means the rig may
|
|
105
|
+
not be in a known state — operator should physically check.
|
|
106
|
+
Producer: ``the materializer`` fallback;
|
|
107
|
+
``RunBuilder.abort()`` on the catch-all client.
|
|
108
|
+
|
|
109
|
+
Note on the "never ran" case: there is no ``Planned`` value.
|
|
110
|
+
A step that pytest collected but never executed is signaled by
|
|
111
|
+
``outcome is None`` at finalize time — the field-missingness IS
|
|
112
|
+
the receipt. The display layer derives "Never Ran" from
|
|
113
|
+
``outcome is None`` plus the run's finalized state.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
PASSED = "passed"
|
|
117
|
+
FAILED = "failed"
|
|
118
|
+
SKIPPED = "skipped"
|
|
119
|
+
ERRORED = "errored"
|
|
120
|
+
TERMINATED = "terminated"
|
|
121
|
+
ABORTED = "aborted"
|
|
122
|
+
DONE = "done"
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def severity(self) -> int:
|
|
126
|
+
"""Severity rank for cascade ordering. Worst wins.
|
|
127
|
+
|
|
128
|
+
``ABORTED`` (7) > ``TERMINATED`` (6) > ``ERRORED`` (5) >
|
|
129
|
+
``FAILED`` (4) > ``PASSED`` (3) > ``DONE`` (2) > ``SKIPPED``
|
|
130
|
+
(1). See :func:`escalate_outcome` for the rationale.
|
|
131
|
+
|
|
132
|
+
``None`` (no outcome stamped yet) is treated as
|
|
133
|
+
severity ``-1`` by :func:`escalate_outcome` — anything
|
|
134
|
+
wins over an unjudged row.
|
|
135
|
+
"""
|
|
136
|
+
return _OUTCOME_SEVERITY[self]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_OUTCOME_SEVERITY: dict[Outcome, int] = {
|
|
140
|
+
Outcome.ABORTED: 7,
|
|
141
|
+
Outcome.TERMINATED: 6,
|
|
142
|
+
Outcome.ERRORED: 5,
|
|
143
|
+
Outcome.FAILED: 4,
|
|
144
|
+
Outcome.PASSED: 3,
|
|
145
|
+
Outcome.DONE: 2,
|
|
146
|
+
Outcome.SKIPPED: 1,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Drift guard: every Outcome variant must have a severity rank. Catches
|
|
150
|
+
# the common bug where a new variant is added but the dict isn't updated.
|
|
151
|
+
assert set(Outcome) == set(_OUTCOME_SEVERITY), (
|
|
152
|
+
f"_OUTCOME_SEVERITY is missing ranks for: {set(Outcome) - set(_OUTCOME_SEVERITY)}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def escalate_outcome(
|
|
157
|
+
current: Outcome | None,
|
|
158
|
+
incoming: Outcome | None,
|
|
159
|
+
) -> Outcome | None:
|
|
160
|
+
"""Return the worse (higher-severity) of two outcomes.
|
|
161
|
+
|
|
162
|
+
Severity, worst first:
|
|
163
|
+
``ABORTED > TERMINATED > ERRORED > FAILED > PASSED > DONE > SKIPPED``,
|
|
164
|
+
with ``None`` ranked below everything ("never judged").
|
|
165
|
+
|
|
166
|
+
Use this everywhere outcome cascading is needed (vector → step → run)
|
|
167
|
+
to keep severity logic in one place. Reading the ladder:
|
|
168
|
+
|
|
169
|
+
* ``ABORTED`` preempts everything — when cleanup didn't run, the
|
|
170
|
+
operator needs to know the rig isn't in a known state.
|
|
171
|
+
* ``TERMINATED`` beats ``ERRORED`` — operator-initiated stop with
|
|
172
|
+
cleanup is "louder" than a test-code blow-up because the run
|
|
173
|
+
didn't complete normally.
|
|
174
|
+
* ``ERRORED`` beats ``FAILED`` — an unexpected blow-up is worse than
|
|
175
|
+
a judged-bad value.
|
|
176
|
+
* ``PASSED`` beats ``DONE`` — an actual verdict outranks a recorded-
|
|
177
|
+
but-unjudged value.
|
|
178
|
+
* Any concrete outcome beats ``None`` — the row was unjudged and
|
|
179
|
+
now has a verdict.
|
|
180
|
+
"""
|
|
181
|
+
cur_sev = current.severity if current is not None else -1
|
|
182
|
+
inc_sev = incoming.severity if incoming is not None else -1
|
|
183
|
+
return current if cur_sev >= inc_sev else incoming
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def retry_aware_rollup(steps: Iterable[TestStep]) -> Outcome | None:
|
|
187
|
+
"""Roll up step outcomes for a parent container (class step / run).
|
|
188
|
+
|
|
189
|
+
Groups steps by ``node_id`` so multiple attempts of the same test
|
|
190
|
+
(``testerkit_retry`` / ``pytest-rerunfailures``) collapse to a single
|
|
191
|
+
contribution. The LAST attempt's outcome wins per group — matching
|
|
192
|
+
pytest-rerunfailures' final-attempt-is-the-outcome semantics and
|
|
193
|
+
the STDF retest convention (retest count is metadata; the final
|
|
194
|
+
disposition is the disposition). After per-group reduction the
|
|
195
|
+
final attempts are escalated via the severity ladder.
|
|
196
|
+
|
|
197
|
+
Steps without a ``node_id`` (container steps, autouse-wrapped
|
|
198
|
+
cleanup) are not collapsed — each gets its own group keyed by
|
|
199
|
+
object identity. Steps with ``outcome is None`` are skipped (they
|
|
200
|
+
haven't recorded a verdict yet).
|
|
201
|
+
"""
|
|
202
|
+
# Modern Python dicts preserve insertion order; a single dict is
|
|
203
|
+
# enough to bucket-by-node_id AND remember which bucket came first.
|
|
204
|
+
groups: dict[Any, list[TestStep]] = {}
|
|
205
|
+
for step in steps:
|
|
206
|
+
if step.outcome is None:
|
|
207
|
+
continue
|
|
208
|
+
key: Any = step.node_id if step.node_id is not None else id(step)
|
|
209
|
+
groups.setdefault(key, []).append(step)
|
|
210
|
+
|
|
211
|
+
result: Outcome | None = None
|
|
212
|
+
for bucket in groups.values():
|
|
213
|
+
# Last attempt's outcome is THE outcome for this node_id.
|
|
214
|
+
result = escalate_outcome(result, bucket[-1].outcome)
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class Measurement(BaseModel):
|
|
219
|
+
"""A single measurement with optional limit checking."""
|
|
220
|
+
|
|
221
|
+
name: str
|
|
222
|
+
step_path: str = ""
|
|
223
|
+
value: float | None
|
|
224
|
+
unit: str | None = None
|
|
225
|
+
limit_low: float | None = None
|
|
226
|
+
limit_high: float | None = None
|
|
227
|
+
limit_nominal: float | None = None
|
|
228
|
+
outcome: Outcome | None = None
|
|
229
|
+
characteristic_id: str | None = None # Characteristic ID for structured traceability
|
|
230
|
+
spec_ref: str | None = None # Human-readable spec reference with conditions
|
|
231
|
+
limit_comparator: str | None = None # ATML comparator: EQ, NE, GE, LE, GELE, etc.
|
|
232
|
+
timestamp: datetime = Field(default_factory=_utcnow)
|
|
233
|
+
|
|
234
|
+
# Traceability (ATML: signal routing)
|
|
235
|
+
uut_pin: str | None = None # Which UUT pin was measured
|
|
236
|
+
instrument_name: str | None = None # Station config name (e.g., "dmm_main")
|
|
237
|
+
instrument_resource: str | None = None # VISA address or connection string
|
|
238
|
+
instrument_channel: str | None = None # Channel on instrument (e.g., "CH1")
|
|
239
|
+
fixture_connection: str | None = None # Fixture connection name (e.g., "VOUT")
|
|
240
|
+
|
|
241
|
+
def check_limit(self) -> Outcome:
|
|
242
|
+
"""Evaluate value against the row's limit fields; set + return outcome.
|
|
243
|
+
|
|
244
|
+
Single judgment path: rebuilds a :class:`Limit` from the
|
|
245
|
+
stamped row fields via :meth:`Limit.from_row` and delegates
|
|
246
|
+
the comparator-aware check to ``Limit.__contains__``. The
|
|
247
|
+
same path :func:`testerkit.execution.verify._compute_outcome`
|
|
248
|
+
uses, just starting from a row instead of a Limit object.
|
|
249
|
+
|
|
250
|
+
Outcomes:
|
|
251
|
+
|
|
252
|
+
* ``ERRORED`` — value is None (couldn't measure → can't judge).
|
|
253
|
+
* ``DONE`` — no limit fields stamped (recorder semantic).
|
|
254
|
+
* ``PASSED`` / ``FAILED`` — value evaluated against the
|
|
255
|
+
reconstructed limit per its comparator.
|
|
256
|
+
"""
|
|
257
|
+
if self.value is None:
|
|
258
|
+
self.outcome = Outcome.ERRORED
|
|
259
|
+
return self.outcome
|
|
260
|
+
limit = Limit.from_row(
|
|
261
|
+
low=self.limit_low,
|
|
262
|
+
high=self.limit_high,
|
|
263
|
+
nominal=self.limit_nominal,
|
|
264
|
+
unit=self.unit,
|
|
265
|
+
comparator=self.limit_comparator,
|
|
266
|
+
characteristic_id=self.characteristic_id,
|
|
267
|
+
spec_ref=self.spec_ref,
|
|
268
|
+
)
|
|
269
|
+
if limit is None:
|
|
270
|
+
self.outcome = Outcome.DONE
|
|
271
|
+
return self.outcome
|
|
272
|
+
self.outcome = Outcome.PASSED if self.value in limit else Outcome.FAILED
|
|
273
|
+
return self.outcome
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TestVector(BaseModel):
|
|
277
|
+
"""A test vector execution with its input parameters and observations.
|
|
278
|
+
|
|
279
|
+
Represents a single execution of a test with specific input values.
|
|
280
|
+
Parameters are stored once here, not duplicated on each measurement.
|
|
281
|
+
|
|
282
|
+
This is the primary unit of test execution: the framework expands
|
|
283
|
+
vectors from config (product, zip, range, nested loops) and iterates
|
|
284
|
+
over them, calling the test function for each.
|
|
285
|
+
|
|
286
|
+
Hierarchy:
|
|
287
|
+
TestRun
|
|
288
|
+
└── TestStep (one per pytest test function)
|
|
289
|
+
└── TestVector[] (one per param set, expanded from config)
|
|
290
|
+
└── Measurement[] (values captured in that vector)
|
|
291
|
+
|
|
292
|
+
Data categories:
|
|
293
|
+
- params (in_*): Configuration - commanded values, setpoints, settings
|
|
294
|
+
- observations (out_*): Measured context, environmental readings, raw data
|
|
295
|
+
- measurements: The actual test results (always scalars)
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
__test__ = False # Prevent pytest collection
|
|
299
|
+
|
|
300
|
+
id: UUID = Field(default_factory=uuid4)
|
|
301
|
+
test_step_id: UUID | None = None # FK to parent TestStep
|
|
302
|
+
index: int = 0 # 0-based index in the parameter expansion
|
|
303
|
+
params: dict[str, Any] = Field(default_factory=dict) # Input parameter values (→ in_*)
|
|
304
|
+
observations: dict[str, Any] = Field(default_factory=dict) # Observed context (→ out_*)
|
|
305
|
+
# Optional engineering unit per param / observation name → the lane unit field.
|
|
306
|
+
param_units: dict[str, str] = Field(default_factory=dict)
|
|
307
|
+
observation_units: dict[str, str] = Field(default_factory=dict)
|
|
308
|
+
observation_pins: dict[str, str] = Field(default_factory=dict)
|
|
309
|
+
stimulus: list[StimulusRecord] = Field(default_factory=list) # Stimulus signal paths
|
|
310
|
+
retry: int = 0 # Retry counter — 0 for the first execution, N for the Nth retry
|
|
311
|
+
max_retries: int = 0 # Maximum retries allowed (0 = no retries, single execution)
|
|
312
|
+
outcome: Outcome | None = None
|
|
313
|
+
measurements: list[Measurement] = Field(default_factory=list)
|
|
314
|
+
started_at: datetime = Field(default_factory=_utcnow)
|
|
315
|
+
ended_at: datetime | None = None
|
|
316
|
+
error_message: str | None = None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class TestStep(BaseModel):
|
|
320
|
+
"""A test step containing test vectors.
|
|
321
|
+
|
|
322
|
+
A step corresponds to a pytest test function. It may contain multiple
|
|
323
|
+
test vectors if the test is parametrized or uses vector expansion.
|
|
324
|
+
|
|
325
|
+
Hierarchy:
|
|
326
|
+
TestRun
|
|
327
|
+
└── TestStep (one per pytest test function)
|
|
328
|
+
└── TestVector[] (one per param set, expanded from config)
|
|
329
|
+
└── Measurement[] (values captured in that vector)
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
__test__ = False # Prevent pytest collection
|
|
333
|
+
|
|
334
|
+
id: UUID = Field(default_factory=uuid4)
|
|
335
|
+
name: str
|
|
336
|
+
step_path: str = ""
|
|
337
|
+
description: str | None = None
|
|
338
|
+
|
|
339
|
+
# Code identity (populated from pytest.Item when available)
|
|
340
|
+
node_id: str | None = None
|
|
341
|
+
file: str | None = None
|
|
342
|
+
module: str | None = None
|
|
343
|
+
class_name: str | None = None
|
|
344
|
+
function: str | None = None
|
|
345
|
+
markers: str | None = None
|
|
346
|
+
started_at: datetime = Field(default_factory=_utcnow)
|
|
347
|
+
ended_at: datetime | None = None
|
|
348
|
+
outcome: Outcome | None = None
|
|
349
|
+
# 0-based outer (item) retry — 0 for the first attempt, N for the Nth
|
|
350
|
+
# pytest-rerunfailures rerun of this step. Distinct from a vector's own
|
|
351
|
+
# inner retry; the de-fuse keys each execution by both axes.
|
|
352
|
+
retry: int = 0
|
|
353
|
+
vectors: list[TestVector] = Field(default_factory=list)
|
|
354
|
+
# Step-scope data: measurements/inputs/outputs latched when no vector is active.
|
|
355
|
+
measurements: list[Measurement] = Field(default_factory=list)
|
|
356
|
+
inputs: dict[str, Any] = Field(default_factory=dict)
|
|
357
|
+
outputs: dict[str, Any] = Field(default_factory=dict)
|
|
358
|
+
error_message: str | None = None
|
|
359
|
+
instrument_records: list[dict[str, Any]] | None = None
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def total_vectors(self) -> int:
|
|
363
|
+
"""Total number of test vectors."""
|
|
364
|
+
return len(self.vectors) if self.vectors else 1
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def passed_vectors(self) -> int:
|
|
368
|
+
"""Number of passed test vectors."""
|
|
369
|
+
return sum(1 for v in self.vectors if v.outcome == Outcome.PASSED)
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def failed_vectors(self) -> int:
|
|
373
|
+
"""Number of failed test vectors."""
|
|
374
|
+
return sum(1 for v in self.vectors if v.outcome == Outcome.FAILED)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class CollectedItem(BaseModel):
|
|
378
|
+
"""A pytest item collected for execution (before any run).
|
|
379
|
+
|
|
380
|
+
``step_index`` and ``vector_index`` are computed at collection time,
|
|
381
|
+
pre-reorder for class-level sweeps:
|
|
382
|
+
|
|
383
|
+
* ``step_index`` = position within the parent sequence (root for classless
|
|
384
|
+
methods, or within the test class). All sweep variants of the same
|
|
385
|
+
function share one ``step_index``.
|
|
386
|
+
* ``vector_index`` = 0-based position within the sweep expansion.
|
|
387
|
+
* ``vector_count_planned`` = number of items collected for this logical
|
|
388
|
+
step. Lets the manifest detect unrun vectors after the run.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
node_id: str
|
|
392
|
+
file: str | None = None
|
|
393
|
+
module: str | None = None
|
|
394
|
+
class_name: str | None = None
|
|
395
|
+
function: str | None = None
|
|
396
|
+
markers: str | None = None
|
|
397
|
+
# step_path: computed at collection time so that unrun items
|
|
398
|
+
# carry the same hierarchical identifier as executed step events.
|
|
399
|
+
step_path: str = ""
|
|
400
|
+
step_index: int = 0
|
|
401
|
+
vector_index: int = 0
|
|
402
|
+
vector_count_planned: int = 1
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class UUT(BaseModel):
|
|
406
|
+
"""Device under test identification."""
|
|
407
|
+
|
|
408
|
+
serial: str
|
|
409
|
+
part_number: str | None = None
|
|
410
|
+
revision: str | None = None
|
|
411
|
+
lot_number: str | None = None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class RunSummary(BaseModel):
|
|
415
|
+
"""Lightweight run header read from parquet index (no steps/measurements)."""
|
|
416
|
+
|
|
417
|
+
test_run_id: str
|
|
418
|
+
session_id: str | None = None
|
|
419
|
+
# Optional to tolerate the pre-RunStarted-correlation in-flight row
|
|
420
|
+
# (see ``_row_helpers.py``'s placeholder branch); persisted/correlated
|
|
421
|
+
# rows always carry 0+.
|
|
422
|
+
site_index: int | None = None
|
|
423
|
+
site_name: str | None = None
|
|
424
|
+
started_at: datetime | None = None
|
|
425
|
+
ended_at: datetime | None = None
|
|
426
|
+
uut_serial_number: str | None = None
|
|
427
|
+
uut_part_number: str | None = None
|
|
428
|
+
part_id: str | None = None
|
|
429
|
+
station_id: str | None = None
|
|
430
|
+
station_name: str | None = None
|
|
431
|
+
station_type: str | None = None
|
|
432
|
+
station_hostname: str | None = None
|
|
433
|
+
fixture_id: str | None = None
|
|
434
|
+
test_phase: str | None = None
|
|
435
|
+
operator: str | None = None
|
|
436
|
+
outcome: str | None = None
|
|
437
|
+
total_measurements: int = 0
|
|
438
|
+
total_steps: int = 0
|
|
439
|
+
project_name: str | None = None
|
|
440
|
+
file_path: str | None = None # internal: parquet file location for fast measurement lookup
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class TestRun(BaseModel):
|
|
444
|
+
"""A complete test run with steps and measurements."""
|
|
445
|
+
|
|
446
|
+
__test__ = False # Prevent pytest collection
|
|
447
|
+
|
|
448
|
+
id: UUID = Field(default_factory=uuid4)
|
|
449
|
+
session_id: UUID = Field(default_factory=uuid4) # Cross-store join key; set by logger
|
|
450
|
+
started_at: datetime = Field(default_factory=_utcnow)
|
|
451
|
+
ended_at: datetime | None = None
|
|
452
|
+
|
|
453
|
+
# Execution lane — always present, 0-based, default 0. Stamped in
|
|
454
|
+
# RunScope.__init__ from the resolved site ContextVars (see
|
|
455
|
+
# execution._state.get_current_site_index/get_current_site_name)
|
|
456
|
+
# so every identity-field reader (build_run_metadata, RunStarted)
|
|
457
|
+
# sources it uniformly from TestRun instead of reaching into the
|
|
458
|
+
# ContextVar directly.
|
|
459
|
+
site_index: int = 0
|
|
460
|
+
site_name: str | None = None
|
|
461
|
+
|
|
462
|
+
# UUT identification
|
|
463
|
+
uut: UUT
|
|
464
|
+
|
|
465
|
+
# Part traceability
|
|
466
|
+
part_id: str | None = None
|
|
467
|
+
part_name: str | None = None
|
|
468
|
+
part_revision: str | None = None
|
|
469
|
+
|
|
470
|
+
# Station traceability — ``station_id`` is None for bringup tier
|
|
471
|
+
# runs (no station YAML on disk; tests use conftest-defined
|
|
472
|
+
# instrument fixtures). ``station_hostname`` always populates from
|
|
473
|
+
# ``socket.gethostname()`` so the run is traceable to a machine
|
|
474
|
+
# even without a station id.
|
|
475
|
+
station_id: str | None = None
|
|
476
|
+
station_name: str | None = None
|
|
477
|
+
station_type: str | None = None
|
|
478
|
+
station_location: str | None = None
|
|
479
|
+
station_hostname: str | None = None
|
|
480
|
+
|
|
481
|
+
# Fixture traceability
|
|
482
|
+
fixture_id: str | None = None
|
|
483
|
+
|
|
484
|
+
# Phase / profile traceability
|
|
485
|
+
test_phase: str | None = None
|
|
486
|
+
profile: str | None = None # active --test-profile name, if any
|
|
487
|
+
# Raw CLI facet values used to select the profile; combined with git SHA
|
|
488
|
+
# this is the minimum reproducibility payload for the run.
|
|
489
|
+
profile_facets: dict[str, str] = Field(default_factory=dict)
|
|
490
|
+
# Resolved required_inputs at session start (serial_number, operator, etc.).
|
|
491
|
+
# Each project declares the keys; the values come from CLI flags, env
|
|
492
|
+
# vars, or operator prompts at the start of the run.
|
|
493
|
+
session_inputs: dict[str, str] = Field(default_factory=dict)
|
|
494
|
+
|
|
495
|
+
# Operator
|
|
496
|
+
operator_id: str | None = None # from --operator
|
|
497
|
+
operator_name: str | None = None # human-readable name
|
|
498
|
+
|
|
499
|
+
# Code traceability
|
|
500
|
+
git_commit: str | None = None
|
|
501
|
+
git_branch: str | None = None
|
|
502
|
+
git_remote: str | None = None
|
|
503
|
+
project_name: str | None = None
|
|
504
|
+
|
|
505
|
+
# Results
|
|
506
|
+
outcome: Outcome | None = None
|
|
507
|
+
steps: list[TestStep] = Field(default_factory=list)
|
|
508
|
+
|
|
509
|
+
# Collected items (full list from pytest collection, before execution)
|
|
510
|
+
collected_items: list[CollectedItem] = Field(default_factory=list)
|
|
511
|
+
|
|
512
|
+
# Custom metadata (user-defined fields)
|
|
513
|
+
custom_metadata: dict[str, Any] = Field(default_factory=dict)
|
|
514
|
+
|
|
515
|
+
# Environment snapshot (stored in Parquet file-level metadata)
|
|
516
|
+
environment_json: str | None = None
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class Waveform(BaseModel):
|
|
520
|
+
"""Time-series waveform data with metadata.
|
|
521
|
+
|
|
522
|
+
Uses compressed representation where the time axis is reconstructed
|
|
523
|
+
from ``t0 + i * dt`` instead of storing paired timestamps.
|
|
524
|
+
|
|
525
|
+
Attributes:
|
|
526
|
+
t0: Absolute UTC timestamp of the first sample. ``None`` when
|
|
527
|
+
the producer doesn't know the wall-clock time (e.g. a
|
|
528
|
+
synthesized or hardware-trigger-relative capture). For
|
|
529
|
+
scope captures where samples are relative to a trigger,
|
|
530
|
+
store the trigger offset in ``attributes`` and set ``t0``
|
|
531
|
+
to the trigger's absolute time.
|
|
532
|
+
dt: Sample interval (seconds).
|
|
533
|
+
Y: Sample values (voltage, current, etc.).
|
|
534
|
+
attributes: Metadata (unit, channel, coupling, trigger
|
|
535
|
+
offset, etc.). Renamed from ``attrs`` in build item 17 for
|
|
536
|
+
cross-schema vocabulary consistency (matches
|
|
537
|
+
FileArtifactMetadata.attributes and
|
|
538
|
+
ChannelDescriptor.attributes).
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
t0: datetime | None = None
|
|
542
|
+
dt: float
|
|
543
|
+
Y: list[float] # Sample values
|
|
544
|
+
attributes: dict[str, Any] = Field(default_factory=dict)
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def num_samples(self) -> int:
|
|
548
|
+
"""Number of samples in the waveform."""
|
|
549
|
+
return len(self.Y)
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def duration(self) -> float:
|
|
553
|
+
"""Total duration in seconds."""
|
|
554
|
+
return self.num_samples * self.dt
|
|
555
|
+
|
|
556
|
+
def time_axis(self) -> list[datetime]:
|
|
557
|
+
"""Reconstruct the absolute time axis: ``t0 + i * dt`` for each sample.
|
|
558
|
+
|
|
559
|
+
Raises ``ValueError`` if ``t0`` is None — without an anchor,
|
|
560
|
+
the absolute axis can't be reconstructed. Callers that only
|
|
561
|
+
need relative time should compute ``[i * dt for i in
|
|
562
|
+
range(num_samples)]`` directly.
|
|
563
|
+
"""
|
|
564
|
+
if self.t0 is None:
|
|
565
|
+
raise ValueError(
|
|
566
|
+
"Waveform.time_axis() requires t0 to be set. "
|
|
567
|
+
"For relative-only time, use [i * dt for i in range(num_samples)]."
|
|
568
|
+
)
|
|
569
|
+
return [self.t0 + timedelta(seconds=i * self.dt) for i in range(self.num_samples)]
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class XYData(BaseModel):
|
|
573
|
+
"""Paired x/y arrays for related-but-non-time-series data.
|
|
574
|
+
|
|
575
|
+
For data the test author thinks of as one artifact rather than two
|
|
576
|
+
parallel channels: IV curves, eye diagrams, S-parameter sweeps,
|
|
577
|
+
optical spectra. Per the §4 manifestation rules, this is "Pattern
|
|
578
|
+
B" — one discrete artifact per vector that routes to FileStore.
|
|
579
|
+
|
|
580
|
+
``observe(name, XYData(...))`` registers via the serializer
|
|
581
|
+
registry (build item 12) and lands on disk as a single ``.npz``
|
|
582
|
+
holding ``x``, ``y``, and any of the optional unit/name keys
|
|
583
|
+
that were set. The MIME convention (build item 13) is
|
|
584
|
+
``application/x-numpy-npz``.
|
|
585
|
+
|
|
586
|
+
Use parallel channels (`stream`) instead when the data is
|
|
587
|
+
continuous over time and you want it live-subscribable — see §4
|
|
588
|
+
Pattern A.
|
|
589
|
+
|
|
590
|
+
Attributes:
|
|
591
|
+
x: Independent-axis values.
|
|
592
|
+
y: Dependent-axis values. Must have the same length as ``x``.
|
|
593
|
+
x_unit: Optional unit for the x axis ("V", "Hz", "dBm").
|
|
594
|
+
y_unit: Optional unit for the y axis.
|
|
595
|
+
x_name: Optional human label for the x axis ("Bias voltage").
|
|
596
|
+
y_name: Optional human label for the y axis.
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
x: list[float]
|
|
600
|
+
y: list[float]
|
|
601
|
+
x_unit: str | None = None
|
|
602
|
+
y_unit: str | None = None
|
|
603
|
+
x_name: str | None = None
|
|
604
|
+
y_name: str | None = None
|