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,76 @@
|
|
|
1
|
+
# Event sourcing
|
|
2
|
+
|
|
3
|
+
The platform stores test execution data as an immutable, append-only log of events. The queryable Run / Step / Measurement views (parquet, DuckDB index) are *views built from* that log — derived from the events, rebuildable at any time, never the source of truth themselves.
|
|
4
|
+
|
|
5
|
+
The companion pages cover the **what**: [Event log](event-log.md) for the event-type taxonomy and dispatch; [Data stores](data-stores.md) for the on-disk layout of events, channels, files, and the run parquet. This page covers the **why** — what a traditional CRUD test-result schema (one where you create a run row and update it in place) forces on a platform, and what changes when you flip the model.
|
|
6
|
+
|
|
7
|
+
## Why one mutable row is awkward for a live run
|
|
8
|
+
|
|
9
|
+
A traditional test-result schema models a Run as a single row, with steps and measurements as child rows in a hierarchy. To record what happened, you have one of two options, each with a real cost:
|
|
10
|
+
|
|
11
|
+
**Option A — UPDATE-style.** Insert the run row at start (with `outcome=NULL, ended_at=NULL`), then UPDATE both fields when the run finishes. Simple, but updating a live row in place brings race conditions when something reads it mid-update, audit-trail difficulties (when did the row become `passed`?), and a category of bugs where consumers see partially-updated state.
|
|
12
|
+
|
|
13
|
+
**Option B — Post-processing stance.** Buffer everything in memory until the run ends, then INSERT the whole hierarchy in one transaction. No live visibility during the run; total data loss if the producer crashes mid-flight.
|
|
14
|
+
|
|
15
|
+
Both shapes force the schema to be the source of truth, which means one row has to represent both "the run is in progress" and "the run is finished" — and that's what forces the in-place updates.
|
|
16
|
+
|
|
17
|
+
## The inversion: events as primary, runs as projections
|
|
18
|
+
|
|
19
|
+
TesterKit emits a separate immutable event for each thing that happens: `RunStarted`, `StepStarted`, `MeasurementRecorded`, `StepEnded`, `RunEnded`. The event log is the source of truth. The Run/Step/Measurement views (parquet, DuckDB index) are derived projections — regenerable from the events at any time.
|
|
20
|
+
|
|
21
|
+
Most data systems treat the entity (Run) as primary and the audit log (events) as a secondary concern. TesterKit inverts that: events are primary, the entity is a projection. That single inversion is what sidesteps the whole problem. Nothing ever needs an UPDATE because the view isn't authoritative — it's rebuilt from the events.
|
|
22
|
+
|
|
23
|
+
## Properties that fall out
|
|
24
|
+
|
|
25
|
+
- **Live visibility during a run.** The UI subscribes to the event log and surfaces in-progress runs as events arrive. No "wait for end" behavior, no NULL outcomes in queries.
|
|
26
|
+
- **Crash recovery is automatic.** A producer that crashes mid-run leaves a `RunStarted` event with no matching `RunEnded`. The orphan-finalization path emits a synthetic `RunEnded(aborted)` and the view rebuilds normally. No partially-written run row to reconcile.
|
|
27
|
+
- **Replay is free.** Want a new analytical view three years from now — different schema, new format, additional aggregations? Replay the event log into a new projection. Traditional CRUD requires migration scripts; here it's just consumption.
|
|
28
|
+
- **Audit log = primary data path.** Some regulated industries (medical, aerospace, defense) require immutable audit trails of test runs. In CRUD systems that's a parallel write you bolt on; here it's the architecture.
|
|
29
|
+
- **Time-travel queries are natural.** "What did the system know at 14:32:17?" is just `WHERE occurred_at <= '14:32:17'` grouped by `run_id`. CRUD systems need temporal-database extensions to answer that.
|
|
30
|
+
- **Cross-system correlation comes for free.** An async temperature probe doesn't have to be a first-class participant in execution. It writes channel samples (or emits events) on its own schedule; any consumer can correlate against any time window. Producers don't have to know each other exist — that's a property of log-based architectures that integration-by-database can't get.
|
|
31
|
+
- **Composable consumers.** New analytics view, new format, new dashboard? Subscribe to (or replay) the event log and build your own view. The producer side doesn't change.
|
|
32
|
+
|
|
33
|
+
## The principled split
|
|
34
|
+
|
|
35
|
+
Not everything is event-sourced — that would be the wrong shape for some data. TesterKit splits along a clean line:
|
|
36
|
+
|
|
37
|
+
| Data shape | Pattern | Why |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Configuration (`testerkit.yaml`, `station.yaml`, parts, catalog) | CRUD via YAML, hand-edited | Operators evolve them deliberately over time |
|
|
40
|
+
| Test execution data (runs, steps, measurements, events) | Append-only events → derived projections | Immutable historical record |
|
|
41
|
+
| Channel sample data (high-rate time-series) | Append-only sample streams (event log carries metadata) | Same domain semantics, different physics — too large for the event log |
|
|
42
|
+
|
|
43
|
+
Configuration *should* be mutable: you add a station, change a limit, update a part spec. Execution data *shouldn't* be mutable: a run happened on a date with an outcome, and that doesn't change. Channel data is execution data with a size exception — sample streams at kHz–MHz rates can't fit through the event log, so they get their own append-only log with event-log metadata referencing them.
|
|
44
|
+
|
|
45
|
+
Annotations, retroactive flags, and RMA links don't break this — they're "new facts about old runs," not edits. The event-sourced answer is to emit a new event type for the new fact (e.g. a future `RunFlagged` or `MeasurementAnnotated`) and let projections incorporate it. You're not mutating the past, you're recording new observations about it.
|
|
46
|
+
|
|
47
|
+
## Materializers run in whatever process cares
|
|
48
|
+
|
|
49
|
+
In a CRUD world, "the database" is a single shared mutable structure every consumer reads and writes against. In an event-sourced world, the *events* are the shared contract — and each consumer can run *its own* materializer in *its own* process, on whatever cadence makes sense for that consumer. Different processes can care about different projections without coordinating.
|
|
50
|
+
|
|
51
|
+
That's why TesterKit's materializers take the shape they do:
|
|
52
|
+
|
|
53
|
+
- The runs daemon cares about an always-on, queryable view of recent runs, so it runs as a long-lived consumer: it collects events per in-flight run and, on `RunEnded`, writes the canonical per-run parquet. It also takes over for runs whose runner crashed. Same view, different trigger.
|
|
54
|
+
- Each exporter (CSV, JSON, HDF5, STDF, …) replays events after the fact via `testerkit export`, each in its own process at whatever cadence the operator invokes.
|
|
55
|
+
- Any future consumer — a Grafana exporter, a Snowflake pipeline, an analytics view — would run its own materializer in its own process at whatever cadence it likes (per-event, per-run, batched hourly). Sync or async, in-process or out, ephemeral or long-running — those are local choices, not architectural commitments.
|
|
56
|
+
|
|
57
|
+
There's no "the materialization service" everyone has to wait on, no central writer that becomes a bottleneck. Each consumer's timing is local to its process; from the system's perspective they're all running independently, all deriving their own views from the same shared event log. That's the property that makes log-based architectures composable without coordination.
|
|
58
|
+
|
|
59
|
+
## Trade-offs
|
|
60
|
+
|
|
61
|
+
- **Eventual consistency on projections.** Between event emission and projection materialization there's a window where queries see stale data. For test execution this window is short (per-event for live views; end-of-run for the canonical parquet) and the live event view fills the gap, but it's a real property to be aware of.
|
|
62
|
+
- **More upfront design work.** You have to choose event types deliberately. Adding a new dimension means adding an event field (additive, fine) or a new event class (also fine). But the design conversation is heavier than "add a column."
|
|
63
|
+
- **Harder mental model for newcomers.** Most developers expect to query a `runs` table directly. The "events are primary, this query hits a projection" framing requires explanation. This page is part of paying that tax.
|
|
64
|
+
|
|
65
|
+
## See also
|
|
66
|
+
|
|
67
|
+
**Same topic, other quadrants:**
|
|
68
|
+
|
|
69
|
+
- [Reference → Event types](../../reference/data/event-types.md) — every event class the runtime emits, generated from source
|
|
70
|
+
- [Reference → Parquet schema](../../reference/data/parquet-schema.md) — the materialized projection's column-level reference
|
|
71
|
+
- [How-to → Export results](../../how-to/data/export-results.md) — exporters as event-replay consumers (one materializer per format)
|
|
72
|
+
|
|
73
|
+
**Sibling concepts:**
|
|
74
|
+
|
|
75
|
+
- [Event log](event-log.md) — the *what*: event types, dispatch, durability
|
|
76
|
+
- [Data stores](data-stores.md) — events / channels / files / run projection; on-disk layout, `data_dir` resolution, schema-evolution contract
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Flight Cross-Process Model
|
|
2
|
+
|
|
3
|
+
TesterKit uses Apache Arrow Flight for cross-process data access. This enables real-time queries from any process — the operator UI, CLI tools, AI agents, or Grafana — without file locking or polling.
|
|
4
|
+
|
|
5
|
+
## Why Arrow Flight
|
|
6
|
+
|
|
7
|
+
Arrow Flight provides:
|
|
8
|
+
|
|
9
|
+
- **Zero-copy** — Arrow record batches transfer between processes without being copied or re-encoded, so live queries stay fast even with a lot of data
|
|
10
|
+
- **Cross-process** — Multiple processes query the same data through a shared background service
|
|
11
|
+
- **Language-agnostic** — Any Arrow Flight client (Python, Go, Rust, Java) can connect
|
|
12
|
+
- **SQL queryability** — [DuckDB](https://duckdb.org/) (an embedded analytical SQL engine that reads Parquet/Arrow directly) runs as the query engine behind the service
|
|
13
|
+
|
|
14
|
+
Reading the files directly from each process works, but processes can collide on file locks, and a reader can't see data still buffered in memory — the shared background service avoids both.
|
|
15
|
+
|
|
16
|
+
## How multiple processes see the same live data
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
A["pytest run"] -->|write + read| svc["Shared background service"]
|
|
21
|
+
B["testerkit serve / UI"] -->|read| svc
|
|
22
|
+
C["testerkit runs / your script"] -->|read| svc
|
|
23
|
+
svc --> disk["Persistent index on disk"]
|
|
24
|
+
svc --> live["Live in-memory overlay\n(in-progress runs)"]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Your pytest run, `testerkit serve`, `testerkit runs`, and any script you write all talk to the same shared background service. The service keeps data in two places: a persistent index on disk (a DuckDB file) and a live in-memory overlay for in-progress runs. The overlay is what lets a query see a result the instant after it's written — before the run is even complete.
|
|
28
|
+
|
|
29
|
+
The first tool that needs the data starts the shared background service automatically; everything else just connects to it. You never manage it yourself. The service shuts itself down after it has been idle for a while.
|
|
30
|
+
|
|
31
|
+
## On disk and live at the same time
|
|
32
|
+
|
|
33
|
+
The event data uses a dual-write pattern for crash safety and instant queryability:
|
|
34
|
+
|
|
35
|
+
1. **Arrow files on disk** — append-only, survive crashes. Date-partitioned, with one file per writing process.
|
|
36
|
+
2. **Live push to the shared service** — new data is sent to the service as it's written, making it available for SQL queries immediately.
|
|
37
|
+
|
|
38
|
+
If that live push fails, the data is still safe in the Arrow files on disk. On the next start, the service loads those files into the index and picks up where it left off.
|
|
39
|
+
|
|
40
|
+
Event data and channel (waveform) data each have their own shared service — separate background processes running the same mechanism — so a heavy waveform capture doesn't compete with event queries.
|
|
41
|
+
|
|
42
|
+
## Downsampling waveforms for display
|
|
43
|
+
|
|
44
|
+
A captured waveform can have millions of samples — too many to plot. The `max_points` parameter thins it to a target count using LTTB (Largest Triangle Three Buckets), a downsampling algorithm that preserves peaks and dips so the displayed shape still matches the real signal.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Query with decimation for visualization
|
|
48
|
+
table = channel_store.query(
|
|
49
|
+
"scope.ch1_waveform",
|
|
50
|
+
session_id="abc123",
|
|
51
|
+
max_points=1000, # LTTB downsample to 1000 points
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## See also
|
|
56
|
+
- [Event Log Architecture](event-log.md) — How events flow through the system
|
|
57
|
+
- [Data stores](data-stores.md) — all four data stores
|
|
58
|
+
- [Querying Channels Guide](../../how-to/data/querying-channels.md) — Practical channel queries
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Concepts — Data
|
|
2
|
+
|
|
3
|
+
Where the run data lives and how the platform stays consistent across processes. The event log is the source of truth; everything else is a derived view.
|
|
4
|
+
|
|
5
|
+
- [Event log](event-log.md) — the durable, append-only record of every test run; the source of truth
|
|
6
|
+
- [Event sourcing](event-sourcing.md) — why the platform is event-sourced rather than mutation-based; what that buys you for replay, debugging, and audit
|
|
7
|
+
- [Sessions](sessions.md) — connect-to-disconnect observation windows; how a single session can contain multiple runs (multi-UUT) or a long-running instrument session (operator UI, scripts)
|
|
8
|
+
- [Three verbs](three-verbs.md) — `observe`, `verify`, `stream`; how the platform routes a measured value to the right store by shape
|
|
9
|
+
- [Data stores](data-stores.md) — EventStore (events), ChannelStore (time-series), FileStore (artifacts), RunStore (run rows); on-disk layout, `data_dir` resolution, schema-evolution contract
|
|
10
|
+
- [Flight streaming](flight-streaming.md) — cross-process data access via Apache Arrow Flight; why the platform uses it for low-latency queries
|
|
11
|
+
|
|
12
|
+
## See also
|
|
13
|
+
|
|
14
|
+
- [Reference → Event types](../../reference/data/event-types.md) — every event class the runtime emits, generated from source
|
|
15
|
+
- [Reference → Parquet schema](../../reference/data/parquet-schema.md) — every column in the materialized run parquet
|
|
16
|
+
- [Reference → Query API](../../reference/data/query-api.md) — `RunsQuery`, `StepsQuery`, `MeasurementsQuery` — the read path the UI and HTTP API both use
|
|
17
|
+
- [How-to → Querying events](../../how-to/data/querying-events.md), [Querying channels](../../how-to/data/querying-channels.md), [Export results](../../how-to/data/export-results.md)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Sessions as Observation Windows
|
|
2
|
+
|
|
3
|
+
A session is the window from connect to disconnect — the time a process is actively using instruments and logging data.
|
|
4
|
+
|
|
5
|
+
## What is a Session?
|
|
6
|
+
|
|
7
|
+
A session begins when a process calls `connect()` and ends when the connection is released. During a session, all events share the same `session_id`, making it easy to group and query related activity.
|
|
8
|
+
|
|
9
|
+
There's no sessions table. A session is simply every event that shares one `session_id` — TesterKit groups them when you query. It "begins" and "ends" because the first and last events (`SessionStarted` / `SessionEnded`) mark the boundaries.
|
|
10
|
+
|
|
11
|
+
Sessions are broader than test runs. A single session might contain multiple test runs (e.g., retesting the same UUT), or no test runs at all (e.g., a calibration script or manual instrument exploration).
|
|
12
|
+
|
|
13
|
+
## Session Metadata
|
|
14
|
+
|
|
15
|
+
`SessionStarted` (see [event-log](event-log.md) for the full event list) records who ran it, on which station, and how (pytest, Jupyter, a script). Per-run context (UUT, part, test phase, git, environment) lives on `RunStarted`, emitted once per test run within the session.
|
|
16
|
+
|
|
17
|
+
| Category | Fields |
|
|
18
|
+
|----------|--------|
|
|
19
|
+
| **Session** | `session_type` |
|
|
20
|
+
| **Station** | `station_id`, `station_name`, `station_type`, `station_location`, `station_hostname` |
|
|
21
|
+
| **Process** | `pid`, `client` (pytest, jupyter, script name) |
|
|
22
|
+
| **Operator** | `operator_id`, `operator_name` |
|
|
23
|
+
| **Fixture / site** | `fixture_id`, `site_count`, `site_names` |
|
|
24
|
+
|
|
25
|
+
`RunStarted` (emitted once per test run within a session) carries the per-run context:
|
|
26
|
+
|
|
27
|
+
| Category | Fields |
|
|
28
|
+
|----------|--------|
|
|
29
|
+
| **UUT** | `uut_serial_number`, `uut_part_number`, `uut_revision`, `uut_lot_number` |
|
|
30
|
+
| **Part** | `part_id`, `part_name`, `part_revision` |
|
|
31
|
+
| **Site** | `site_index`, `site_name` |
|
|
32
|
+
| **Test context** | `fixture_id`, `test_phase`, `project_name` |
|
|
33
|
+
| **Git** | `git_commit`, `git_branch`, `git_remote` |
|
|
34
|
+
| **Environment** | `environment_json` (Python version, testerkit version, top-level deps, lockfile hash) |
|
|
35
|
+
| **Custom** | `custom_metadata` dict |
|
|
36
|
+
|
|
37
|
+
Config files (station, fixture, part spec) are tracked via git — the `git_commit` field on each `RunStarted` identifies the exact code and config state.
|
|
38
|
+
|
|
39
|
+
## Why Sessions Exist
|
|
40
|
+
|
|
41
|
+
Sessions solve three problems:
|
|
42
|
+
|
|
43
|
+
1. **Grouping events across runs** — Multiple test runs on the same UUT during one sitting share a session. You can query "everything that happened while bench-7 was connected" without knowing individual run IDs.
|
|
44
|
+
|
|
45
|
+
2. **Live monitoring** — The operator UI subscribes to events by `session_id` to show real-time progress. The session boundary tells the UI when to start and stop monitoring.
|
|
46
|
+
|
|
47
|
+
3. **Resource coordination** — Sessions track which instruments are in use, enabling per-resource locking. Two scripts can use different instruments on the same station simultaneously.
|
|
48
|
+
|
|
49
|
+
## The `connect()` API
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from testerkit import connect
|
|
53
|
+
|
|
54
|
+
# Using a `with` block (scripts, notebooks)
|
|
55
|
+
with connect("cell-7", mock=True) as station:
|
|
56
|
+
dmm = station.instrument("dmm")
|
|
57
|
+
v = dmm.measure_voltage()
|
|
58
|
+
# All interactions logged with this session's ID
|
|
59
|
+
|
|
60
|
+
# Explicit start/stop (UI, long-running processes)
|
|
61
|
+
station = connect("cell-7")
|
|
62
|
+
station.start()
|
|
63
|
+
dmm = station.instrument("dmm")
|
|
64
|
+
# ... work ...
|
|
65
|
+
station.stop()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`connect()` starts a session that:
|
|
69
|
+
- gets a new `session_id`
|
|
70
|
+
- emits `SessionStarted` with full context
|
|
71
|
+
- locks each instrument it uses, so two scripts can share a station
|
|
72
|
+
- emits `SessionEnded` when it closes
|
|
73
|
+
|
|
74
|
+
## See also
|
|
75
|
+
- [Event Log Architecture](event-log.md) — How events are stored and queried
|
|
76
|
+
- [Data stores](data-stores.md) — where the events behind a session are stored
|
|
77
|
+
- [connect() reference](../../reference/runtime/connect.md) — full API surface
|
|
78
|
+
- [Managing Sessions Guide](../../how-to/execution/managing-sessions.md) — Practical session management
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# The Three Test-Author Verbs
|
|
2
|
+
|
|
3
|
+
Every value a test records plays one of three roles:
|
|
4
|
+
|
|
5
|
+
| Role | What it means | Written by |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `input` | What you set — a commanded value, setpoint, or condition | `context.configure("vin", 5.0)` |
|
|
8
|
+
| `output` | What you read back — a response, environment reading, or captured artifact | `observe("v_rail", 3.3)` |
|
|
9
|
+
| `measurement` | The judged result with limits and a pass/fail outcome | `verify("v_rail", 3.31, limit=…)` / `measure(…)` |
|
|
10
|
+
|
|
11
|
+
The platform routes each call to the right store. You write the verb; the role follows automatically.
|
|
12
|
+
|
|
13
|
+
`stream` is a fourth verb — for continuous signals — and is covered below. It does not add an input/output/measurement field to the vector; it writes to the channel timeline.
|
|
14
|
+
|
|
15
|
+
## What `configure` does
|
|
16
|
+
|
|
17
|
+
`context.configure` records the stimulus you commanded before the DUT responded:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
def test_load_regulation(psu, dmm, context):
|
|
21
|
+
context.configure("psu.voltage", 5.0, unit="V") # → input role
|
|
22
|
+
context.configure("load.current", 1.0, unit="A") # → input role
|
|
23
|
+
|
|
24
|
+
reading = dmm.measure_dc_voltage()
|
|
25
|
+
verify("vout", reading, limit=Limit(low=4.75, high=5.25, unit="V"))
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `input` values travel with the measurement row. When you query across runs, you can plot `vout` (measurement) against `psu.voltage` (input) to see load regulation without doing any joins.
|
|
29
|
+
|
|
30
|
+
## What `observe` does
|
|
31
|
+
|
|
32
|
+
`observe` records a value you read from the DUT or environment — the response side:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
observe("temp", temp_probe.read(), unit="°C") # → output role
|
|
36
|
+
observe("v_rail", dmm.measure_dc_voltage()) # → output role, scalar
|
|
37
|
+
observe("scope_cap", scope.capture()) # → output role, Waveform
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`observe` accepts any shape — the value's shape determines which store receives it:
|
|
41
|
+
|
|
42
|
+
| Value shape | Routes to | How to query later |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| Scalar (`int` / `float` / `bool` / `str`) | Inline on the measurement row | `FieldRef.output("v_rail")` |
|
|
45
|
+
| Array of scalars (list / ndarray) | ChannelStore | `/channels/{id}` chart panel |
|
|
46
|
+
| `Waveform` (Y + `sample_interval`) | ChannelStore | `/channels/{id}` chart panel |
|
|
47
|
+
| `XYData` (paired x/y arrays with per-axis units) | FileStore as `.npz` | `FieldRef.output("iv_curve")` → URI |
|
|
48
|
+
| File / object (image / `bytes` / `Path` / Pydantic model) | FileStore (via a registered serializer) | `FieldRef.output("setup_photo")` → URI |
|
|
49
|
+
|
|
50
|
+
The verb you call decides that a value is an output. The value's shape decides where the bytes land. The two choices are independent.
|
|
51
|
+
|
|
52
|
+
## What `verify` and `measure` do
|
|
53
|
+
|
|
54
|
+
`verify` records a judged result — the measurement proper:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
verify("rise_time_us", rise_time(wf), limit=Limit(low=0, high=20, unit="us"))
|
|
58
|
+
# → measurement role: value, limits, outcome (PASSED / FAILED / ERRORED), unit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`measure` is the record-only sibling — it stamps `Outcome.DONE` and never raises on a missing limit:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
measure("rail_ripple", ripple(dmm.read_waveform()))
|
|
65
|
+
# → measurement role, no limit check
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Both are scalar-only. Passing a `Waveform`, array, or blob to `verify` or `measure` raises `TypeError` with a message pointing at the two-verb pattern: `observe` the evidence first, then `verify` the derived scalar.
|
|
69
|
+
|
|
70
|
+
## Querying by role
|
|
71
|
+
|
|
72
|
+
Because role is stored with every field, the query API lets you reference any field by its role and name — you don't have to guess a prefixed column name like `in_vin`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from testerkit.queries import MeasurementsQuery, FieldRef
|
|
76
|
+
|
|
77
|
+
q = MeasurementsQuery()
|
|
78
|
+
|
|
79
|
+
# Plot vout (measurement) vs vin (input) across runs
|
|
80
|
+
rows = q.parametric(
|
|
81
|
+
y=FieldRef.measurement("vout"),
|
|
82
|
+
x=FieldRef.input("vin"),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Plot temperature (output) vs date across runs
|
|
86
|
+
rows = q.parametric(
|
|
87
|
+
y=FieldRef.output("temp"),
|
|
88
|
+
x="run_started_at", # bare string = a built-in run column
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# A bare string is measurement shorthand — the most common case
|
|
92
|
+
rows = q.parametric(y="vout", x=FieldRef.input("vin"))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`FieldRef.input("vin")` / `FieldRef.output("v_rail")` / `FieldRef.measurement("vout")` are the three constructors. A bare string resolves to `FieldRef.measurement(name)` because measurements carry limits and outcomes — they're what analysis is overwhelmingly about. Inputs and outputs always require the explicit `FieldRef`.
|
|
96
|
+
|
|
97
|
+
See [Reference → Query API](../../reference/data/query-api.md) for the full `MeasurementsQuery` surface.
|
|
98
|
+
|
|
99
|
+
## Discrete vs continuous — where `stream` fits
|
|
100
|
+
|
|
101
|
+
`observe` and `stream` both write to ChannelStore for array/waveform values. The difference is **intent**, not data shape:
|
|
102
|
+
|
|
103
|
+
| | `observe` (discrete) | `stream` (continuous) |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| Unit of data | One call, one captured value | The channel itself; samples are unnamed appends |
|
|
106
|
+
| Identity | Each call has a name and a vector context | The channel has a name; samples don't |
|
|
107
|
+
| Role on the vector | `output` (for scalars / URIs) | None — stream doesn't add an input/output/measurement field |
|
|
108
|
+
| T&M shape | Triggered acquisition, scope capture, snapshot | Live sensor feed, free-run, continuous monitor |
|
|
109
|
+
|
|
110
|
+
**Rate doesn't decide. Intent does.** A 1-sample-per-5-minute temperature probe is continuous because the temperature is a signal that exists whether you sample it or not.
|
|
111
|
+
|
|
112
|
+
- **Continuous → stream** — samples accumulate on the channel timeline; the channel as a whole is the unit you query later
|
|
113
|
+
- **Discrete → observe** — each call is anchored to a specific moment in the test; queryable by `FieldRef.output(name)` for scalar outputs or URI navigation for waveforms
|
|
114
|
+
|
|
115
|
+
What breaks if you mix them up:
|
|
116
|
+
|
|
117
|
+
- `observe` in a high-rate loop → one event recorded per call (floods the event log), and the last call overwrites the previous output URI on scalar outputs
|
|
118
|
+
- `stream` for a single discrete capture → no output stamped on the vector → can't navigate from the measurement to the supporting waveform without knowing the `channel_id` + time window
|
|
119
|
+
|
|
120
|
+
To associate a streamed channel with a measurement vector, pass the sink to `observe`:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
with testerkit.channels.stream("scope.continuous") as cap:
|
|
124
|
+
for _ in range(n_samples):
|
|
125
|
+
cap.write(scope.read_trace())
|
|
126
|
+
observe("scope.snapshot", cap) # stamps cap.uri as an output on the active vector
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Two layers — test-author verbs vs store-direct
|
|
130
|
+
|
|
131
|
+
TesterKit exposes data writing at two layers:
|
|
132
|
+
|
|
133
|
+
| Layer | Use when | Discrete | Continuous |
|
|
134
|
+
|---|---|---|---|
|
|
135
|
+
| **Test-author verbs** | Inside a test (has a vector context) | `observe(name, value)`, `verify(name, value, limit=…)` | `stream(name, sample)` |
|
|
136
|
+
| **Store-direct** | Outside a test — notebooks, scripts, operator UI | `channels.write(name, sample)` | `channels.stream(name)` |
|
|
137
|
+
|
|
138
|
+
The test-author verbs are built on top of the store-direct calls. What `observe` adds is recording the value's role and storage URI on the active test vector. None of that makes sense outside a test, so the store-direct surface skips it.
|
|
139
|
+
|
|
140
|
+
`stream` does not associate with the vector automatically. Only `observe` stamps the vector. To associate a streamed channel with the active vector, pass the sink to `observe` as shown above.
|
|
141
|
+
|
|
142
|
+
## Engineering units
|
|
143
|
+
|
|
144
|
+
`configure`, `observe`, `verify`, `measure`, and `stream` all accept an optional `unit=` keyword across every call shape — top-level verb, pytest fixture, and `Context` method. The unit is stored alongside the value and is visible in query results:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
context.configure("psu.voltage", 12.0, unit="V")
|
|
148
|
+
observe("temp", 24.8, unit="°C")
|
|
149
|
+
stream("current", sample, unit="A")
|
|
150
|
+
verify("output_voltage", dmm.measure_dc_voltage(), Limit(low=4.75, high=5.25, unit="V"))
|
|
151
|
+
measure("quiescent_current", psu.measure_current(), unit="A")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For `verify`, the unit can come from `unit=` on the call, from `Limit(unit=)`, or from both when they agree. If both are provided and they differ, TesterKit raises `ValueError` immediately — it does not silently let one override the other.
|
|
155
|
+
|
|
156
|
+
For multi-axis data (IV curves, S-parameter sweeps, optical spectra), use `XYData` — it carries `x_unit` and `y_unit` as separate per-axis fields:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from testerkit.data.models import XYData
|
|
160
|
+
|
|
161
|
+
iv = XYData(x=[0.0, 0.5, 1.0, 1.5], y=[0.0, 2.1, 4.3, 6.8],
|
|
162
|
+
x_unit="V", y_unit="mA", x_name="Bias", y_name="Current")
|
|
163
|
+
observe("iv_curve", iv) # → FileStore .npz; output URI on the vector
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Capturing evidence and judging a metric together
|
|
167
|
+
|
|
168
|
+
The canonical pattern is `observe` for the raw evidence, then `verify` for the derived scalar:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
def test_psu_step_response(psu, scope, context):
|
|
172
|
+
psu.set_voltage(5.0)
|
|
173
|
+
wf = scope.capture() # block-mode acquisition
|
|
174
|
+
|
|
175
|
+
observe("scope_cap", wf) # Waveform → ChannelStore
|
|
176
|
+
# output URI stamped on this vector
|
|
177
|
+
|
|
178
|
+
verify("rise_time_us", rise_time(wf),
|
|
179
|
+
limit=Limit(low=0, high=20, unit="us")) # scalar → measurement row, judged
|
|
180
|
+
verify("overshoot_v", overshoot(wf),
|
|
181
|
+
limit=Limit(low=0, high=0.05, unit="V")) # same vector, same scope_cap URI
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Both `rise_time_us` and `overshoot_v` measurement rows carry the `scope_cap` channel URI. From any failing measurement on `/results/{run_id}` you can navigate directly to the supporting waveform.
|
|
185
|
+
|
|
186
|
+
## Streaming continuously while the test runs
|
|
187
|
+
|
|
188
|
+
For continuous capture across the test body, use the context-managed sink:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
import testerkit.channels
|
|
192
|
+
|
|
193
|
+
with testerkit.channels.stream("scope.continuous") as cap:
|
|
194
|
+
for _ in range(n_samples):
|
|
195
|
+
cap.write(scope.read_trace())
|
|
196
|
+
observe("scope.snapshot", cap) # stamps cap.uri as an output on the active vector
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For byte-stream artifacts (video / audio / vendor capture), use `testerkit.files.stream`:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import testerkit.files
|
|
203
|
+
|
|
204
|
+
with testerkit.files.stream("camera", format="mp4") as cam:
|
|
205
|
+
for frame in camera.read_frames():
|
|
206
|
+
cam.write(frame)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`files.stream` requires `format=` — the platform cannot infer `mp4` vs `wav` vs `tdms` vs `raw` from opaque bytes. Every other dispatch (scalar vs array vs blob, ChannelStore vs FileStore, inline vs URI) is automatic.
|
|
210
|
+
|
|
211
|
+
## How streams are stored — segmented per session, unified by channel
|
|
212
|
+
|
|
213
|
+
A long-running stream is not one giant file. ChannelStore files are partitioned by date and by session:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
data/channels/2026-06-03/env_temp_aaaa1111.arrow ← Session A, day 1
|
|
217
|
+
data/channels/2026-06-04/env_temp_aaaa1111.arrow ← Session A, day 2
|
|
218
|
+
data/channels/2026-06-03/env_temp_bbbb2222.arrow ← Session B, day 1
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The session boundary is the natural unit for retention, export, and crash isolation. The date partition keeps any single file from growing unbounded.
|
|
222
|
+
|
|
223
|
+
The logical channel identity is not partitioned. `channels.query("env.temp")` with no session filter spans every session that wrote to that channel. Add `session_id=…` to scope a query to one session.
|
|
224
|
+
|
|
225
|
+
## Concurrent sessions share instruments, not records
|
|
226
|
+
|
|
227
|
+
If two test sessions run concurrently and both share a scope (locked atomically by the platform), their captures are isolated on disk:
|
|
228
|
+
|
|
229
|
+
- ChannelStore: `session_id` is in the filename
|
|
230
|
+
- FileStore: `session_id` is in the directory
|
|
231
|
+
- EventStore: events from both sessions interleave on one timeline, but every event carries `session_id`
|
|
232
|
+
- RunStore: one parquet file per run, never cross-run mixing
|
|
233
|
+
|
|
234
|
+
The instrument lock orders captures in time; storage isolation is independent. The one shared thing is the channel-id namespace — `/channels/scope_cap` shows both sessions' data unless filtered. That's deliberate: cross-session views matter for trends and fixture-channel accumulation.
|
|
235
|
+
|
|
236
|
+
## Custom types
|
|
237
|
+
|
|
238
|
+
Unknown value types fall back to pickle with a `RuntimeWarning` that names the type and points at `register_serializer`. Register once and your type is auto-routed thereafter:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from testerkit.data.files import register_serializer
|
|
242
|
+
|
|
243
|
+
register_serializer(
|
|
244
|
+
MyInstrumentFrame,
|
|
245
|
+
extension=".bin",
|
|
246
|
+
mime="application/octet-stream",
|
|
247
|
+
write=lambda value, dest: dest.write_bytes(value.to_bytes()),
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Alternatively, implement `testerkit_serialize(dest: Path) -> Path` on your type — no registration call needed.
|
|
252
|
+
|
|
253
|
+
## See also
|
|
254
|
+
|
|
255
|
+
- [Data stores](data-stores.md) — where parquet, ChannelStore, and FileStore live on disk and how they relate
|
|
256
|
+
- [Tutorial 11 — Waveforms and evidence](../../tutorial/11-waveforms-and-evidence.md) — pytest test with `observe` + `verify`
|
|
257
|
+
- [Tutorial 12 — Continuous monitoring](../../tutorial/12-continuous-monitoring.md) — interactive session with `channels.stream` + live UI
|
|
258
|
+
- [Reference → Query API](../../reference/data/query-api.md) — `MeasurementsQuery`, `FieldRef`, and the role-based query surface
|
|
259
|
+
- [Reference → Parquet schema](../../reference/data/parquet-schema.md) — column conventions; `channel://` and `file://` URI formats
|
|
260
|
+
- [Reference → Channels](../../reference/operator-ui/channels/list.md) — the operator UI page where ChannelStore samples appear
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Concepts — Execution
|
|
2
|
+
|
|
3
|
+
How a test run unfolds — the step model, what each step records, and how outcomes roll up from leaves to runs.
|
|
4
|
+
|
|
5
|
+
- [Step hierarchy](step-hierarchy.md) — how test classes, methods, and parametrize vectors nest into a tree of step events
|
|
6
|
+
- [Step manifest](step-manifest.md) — what each step records (inputs, outputs, measurements, retries) and how the platform turns events into parquet rows
|
|
7
|
+
- [Outcomes](outcomes.md) — the severity ladder (`skipped` → `done` → `passed` → `failed` → `errored` → `terminated` → `aborted`) and how parent steps roll up child outcomes
|
|
8
|
+
|
|
9
|
+
## See also
|
|
10
|
+
|
|
11
|
+
- [How-to → Writing tests](../../how-to/execution/writing-tests.md) — task recipe for authoring test code that fits the step model
|
|
12
|
+
- [Reference → Pytest fixtures](../../reference/pytest/fixtures.md) — the fixtures the step model exposes (`context`, `verify`, `measure`)
|
|
13
|
+
- [Data](../data/index.md) — where step events and the materialized run go after execution
|