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.
Files changed (521) hide show
  1. testerkit/__init__.py +68 -0
  2. testerkit/_docs/_assets/operator-ui/channels/table.png +0 -0
  3. testerkit/_docs/_assets/operator-ui/dashboard/runs.png +0 -0
  4. testerkit/_docs/_assets/operator-ui/dashboard/stations.png +0 -0
  5. testerkit/_docs/_assets/operator-ui/events/filters.png +0 -0
  6. testerkit/_docs/_assets/operator-ui/events/table.png +0 -0
  7. testerkit/_docs/_assets/operator-ui/explore/chart.png +0 -0
  8. testerkit/_docs/_assets/operator-ui/explore/filters.png +0 -0
  9. testerkit/_docs/_assets/operator-ui/explore/plot-controls.png +0 -0
  10. testerkit/_docs/_assets/operator-ui/files/table.png +0 -0
  11. testerkit/_docs/_assets/operator-ui/launch/form.png +0 -0
  12. testerkit/_docs/_assets/operator-ui/metrics/filters.png +0 -0
  13. testerkit/_docs/_assets/operator-ui/metrics/pareto.png +0 -0
  14. testerkit/_docs/_assets/operator-ui/metrics/ppk.png +0 -0
  15. testerkit/_docs/_assets/operator-ui/metrics/yield.png +0 -0
  16. testerkit/_docs/_assets/operator-ui/results/detail-header.png +0 -0
  17. testerkit/_docs/_assets/operator-ui/results/detail-measurements.png +0 -0
  18. testerkit/_docs/_assets/operator-ui/results/detail-overview.png +0 -0
  19. testerkit/_docs/_assets/operator-ui/results/detail-steps.png +0 -0
  20. testerkit/_docs/_assets/operator-ui/results/stats.png +0 -0
  21. testerkit/_docs/_assets/operator-ui/results/table.png +0 -0
  22. testerkit/_docs/_assets/operator-ui/tour/designer.png +0 -0
  23. testerkit/_docs/_assets/operator-ui/tour/docs.png +0 -0
  24. testerkit/_docs/_assets/operator-ui/tour/fixtures.png +0 -0
  25. testerkit/_docs/_assets/operator-ui/tour/instruments.png +0 -0
  26. testerkit/_docs/_assets/operator-ui/tour/parts.png +0 -0
  27. testerkit/_docs/_assets/operator-ui/tour/profiles.png +0 -0
  28. testerkit/_docs/_assets/operator-ui/tour/stations.png +0 -0
  29. testerkit/_docs/_assets/operator-ui/tour/tests.png +0 -0
  30. testerkit/_docs/_assets/operator-ui/tour/uuts.png +0 -0
  31. testerkit/_docs/concepts/configuration/capabilities.md +447 -0
  32. testerkit/_docs/concepts/configuration/fixtures.md +335 -0
  33. testerkit/_docs/concepts/configuration/index.md +14 -0
  34. testerkit/_docs/concepts/configuration/parts.md +276 -0
  35. testerkit/_docs/concepts/configuration/stations.md +214 -0
  36. testerkit/_docs/concepts/data/data-stores.md +199 -0
  37. testerkit/_docs/concepts/data/event-log.md +185 -0
  38. testerkit/_docs/concepts/data/event-sourcing.md +76 -0
  39. testerkit/_docs/concepts/data/flight-streaming.md +58 -0
  40. testerkit/_docs/concepts/data/index.md +17 -0
  41. testerkit/_docs/concepts/data/sessions.md +78 -0
  42. testerkit/_docs/concepts/data/three-verbs.md +260 -0
  43. testerkit/_docs/concepts/execution/index.md +13 -0
  44. testerkit/_docs/concepts/execution/outcomes.md +199 -0
  45. testerkit/_docs/concepts/execution/step-hierarchy.md +170 -0
  46. testerkit/_docs/concepts/execution/step-manifest.md +115 -0
  47. testerkit/_docs/concepts/index.md +40 -0
  48. testerkit/_docs/concepts/overview/ai-integration.md +57 -0
  49. testerkit/_docs/concepts/overview/architecture.md +357 -0
  50. testerkit/_docs/concepts/overview/index.md +14 -0
  51. testerkit/_docs/concepts/overview/platform-vs-framework.md +147 -0
  52. testerkit/_docs/concepts/overview/pytest.md +53 -0
  53. testerkit/_docs/concepts/overview/tiers.md +46 -0
  54. testerkit/_docs/how-to/catalog/datasheet-to-test.md +169 -0
  55. testerkit/_docs/how-to/catalog/index.md +11 -0
  56. testerkit/_docs/how-to/configuration/configuring-stations.md +364 -0
  57. testerkit/_docs/how-to/configuration/custom-drivers.md +484 -0
  58. testerkit/_docs/how-to/configuration/index.md +13 -0
  59. testerkit/_docs/how-to/configuration/mock-mode.md +342 -0
  60. testerkit/_docs/how-to/data/benchmarking.md +120 -0
  61. testerkit/_docs/how-to/data/capture-an-artifact.md +84 -0
  62. testerkit/_docs/how-to/data/capture-waveform.md +110 -0
  63. testerkit/_docs/how-to/data/choosing-a-channel-verb.md +108 -0
  64. testerkit/_docs/how-to/data/compare-runs.md +120 -0
  65. testerkit/_docs/how-to/data/export-results.md +104 -0
  66. testerkit/_docs/how-to/data/find-flaky-tests.md +108 -0
  67. testerkit/_docs/how-to/data/grafana-dashboards.md +148 -0
  68. testerkit/_docs/how-to/data/index.md +23 -0
  69. testerkit/_docs/how-to/data/mcp-debug-failures.md +125 -0
  70. testerkit/_docs/how-to/data/mcp-query-runs.md +120 -0
  71. testerkit/_docs/how-to/data/querying-channels.md +117 -0
  72. testerkit/_docs/how-to/data/querying-events.md +85 -0
  73. testerkit/_docs/how-to/data/stream-live-channel.md +134 -0
  74. testerkit/_docs/how-to/execution/custom-operator-ui.md +136 -0
  75. testerkit/_docs/how-to/execution/index.md +21 -0
  76. testerkit/_docs/how-to/execution/limits.md +193 -0
  77. testerkit/_docs/how-to/execution/managing-sessions.md +100 -0
  78. testerkit/_docs/how-to/execution/multi-uut-testing.md +211 -0
  79. testerkit/_docs/how-to/execution/operator-prompts.md +151 -0
  80. testerkit/_docs/how-to/execution/profiles.md +304 -0
  81. testerkit/_docs/how-to/execution/spec-driven-testing.md +169 -0
  82. testerkit/_docs/how-to/execution/test-context.md +212 -0
  83. testerkit/_docs/how-to/execution/traceability.md +252 -0
  84. testerkit/_docs/how-to/execution/vector-expansion.md +394 -0
  85. testerkit/_docs/how-to/execution/writing-tests.md +363 -0
  86. testerkit/_docs/how-to/index.md +59 -0
  87. testerkit/_docs/how-to/overview/index.md +11 -0
  88. testerkit/_docs/how-to/overview/mcp-integration.md +168 -0
  89. testerkit/_docs/how-to/overview/operator-ui-tour.md +234 -0
  90. testerkit/_docs/integration/data/grafana.md +162 -0
  91. testerkit/_docs/integration/data/index.md +15 -0
  92. testerkit/_docs/integration/data/lakehouse-import.md +226 -0
  93. testerkit/_docs/integration/data/logging.md +131 -0
  94. testerkit/_docs/integration/data/results-api.md +51 -0
  95. testerkit/_docs/integration/index.md +20 -0
  96. testerkit/_docs/integration/runtime/harness.md +281 -0
  97. testerkit/_docs/integration/runtime/index.md +13 -0
  98. testerkit/_docs/integration/runtime/instruments.md +284 -0
  99. testerkit/_docs/integration/runtime/pytest-existing.md +315 -0
  100. testerkit/_docs/reference/catalog/cookbook.md +389 -0
  101. testerkit/_docs/reference/catalog/index.md +11 -0
  102. testerkit/_docs/reference/catalog/schema.md +375 -0
  103. testerkit/_docs/reference/cli.md +660 -0
  104. testerkit/_docs/reference/configuration.md +406 -0
  105. testerkit/_docs/reference/data/channels-schema.md +145 -0
  106. testerkit/_docs/reference/data/event-types.md +554 -0
  107. testerkit/_docs/reference/data/events-schema.md +120 -0
  108. testerkit/_docs/reference/data/files-schema.md +141 -0
  109. testerkit/_docs/reference/data/index.md +18 -0
  110. testerkit/_docs/reference/data/models.md +2229 -0
  111. testerkit/_docs/reference/data/outputs.md +82 -0
  112. testerkit/_docs/reference/data/parquet-schema.md +529 -0
  113. testerkit/_docs/reference/data/performance-limits.md +224 -0
  114. testerkit/_docs/reference/data/query-api.md +247 -0
  115. testerkit/_docs/reference/index.md +69 -0
  116. testerkit/_docs/reference/operator-ui/channels/detail.md +82 -0
  117. testerkit/_docs/reference/operator-ui/channels/list.md +70 -0
  118. testerkit/_docs/reference/operator-ui/dashboard.md +87 -0
  119. testerkit/_docs/reference/operator-ui/designer.md +167 -0
  120. testerkit/_docs/reference/operator-ui/events.md +89 -0
  121. testerkit/_docs/reference/operator-ui/files.md +74 -0
  122. testerkit/_docs/reference/operator-ui/fixtures.md +100 -0
  123. testerkit/_docs/reference/operator-ui/instruments.md +126 -0
  124. testerkit/_docs/reference/operator-ui/launch.md +79 -0
  125. testerkit/_docs/reference/operator-ui/live.md +102 -0
  126. testerkit/_docs/reference/operator-ui/measurements.md +159 -0
  127. testerkit/_docs/reference/operator-ui/metrics.md +217 -0
  128. testerkit/_docs/reference/operator-ui/parts.md +98 -0
  129. testerkit/_docs/reference/operator-ui/profiles.md +103 -0
  130. testerkit/_docs/reference/operator-ui/results/detail.md +214 -0
  131. testerkit/_docs/reference/operator-ui/results/list.md +106 -0
  132. testerkit/_docs/reference/operator-ui/stations.md +98 -0
  133. testerkit/_docs/reference/operator-ui/tests.md +173 -0
  134. testerkit/_docs/reference/operator-ui/uuts.md +67 -0
  135. testerkit/_docs/reference/overview/index.md +11 -0
  136. testerkit/_docs/reference/overview/pytest-native.md +142 -0
  137. testerkit/_docs/reference/overview/skills.md +177 -0
  138. testerkit/_docs/reference/pytest/fixtures.md +342 -0
  139. testerkit/_docs/reference/pytest/index.md +12 -0
  140. testerkit/_docs/reference/pytest/markers.md +257 -0
  141. testerkit/_docs/reference/runtime/api.md +246 -0
  142. testerkit/_docs/reference/runtime/client.md +280 -0
  143. testerkit/_docs/reference/runtime/connect.md +140 -0
  144. testerkit/_docs/reference/runtime/index.md +13 -0
  145. testerkit/_docs/tutorial/01-first-test.md +113 -0
  146. testerkit/_docs/tutorial/02-mock-instruments.md +88 -0
  147. testerkit/_docs/tutorial/03-fixtures.md +169 -0
  148. testerkit/_docs/tutorial/04-limits.md +139 -0
  149. testerkit/_docs/tutorial/05-configuration.md +194 -0
  150. testerkit/_docs/tutorial/06-specifications.md +273 -0
  151. testerkit/_docs/tutorial/07-real-instruments.md +245 -0
  152. testerkit/_docs/tutorial/08-capabilities.md +223 -0
  153. testerkit/_docs/tutorial/09-production.md +374 -0
  154. testerkit/_docs/tutorial/10-live-monitoring.md +156 -0
  155. testerkit/_docs/tutorial/11-waveforms-and-evidence.md +169 -0
  156. testerkit/_docs/tutorial/12-continuous-monitoring.md +145 -0
  157. testerkit/_docs/tutorial/13-parallel-testing.md +233 -0
  158. testerkit/_docs/tutorial/index.md +61 -0
  159. testerkit/_docs/tutorial/quickstart.md +282 -0
  160. testerkit/analysis/__init__.py +9 -0
  161. testerkit/analysis/_common.py +24 -0
  162. testerkit/analysis/measurement_facets.py +524 -0
  163. testerkit/analysis/measurements_query.py +1458 -0
  164. testerkit/analysis/metrics.py +389 -0
  165. testerkit/analysis/runs_query.py +434 -0
  166. testerkit/analysis/steps_query.py +413 -0
  167. testerkit/api/__init__.py +6 -0
  168. testerkit/api/_mime.py +71 -0
  169. testerkit/api/app.py +1171 -0
  170. testerkit/api/dialogs/__init__.py +36 -0
  171. testerkit/api/dialogs/manager.py +617 -0
  172. testerkit/api/dialogs/models.py +79 -0
  173. testerkit/api/models.py +80 -0
  174. testerkit/api/responses.py +205 -0
  175. testerkit/api/runner.py +222 -0
  176. testerkit/api/schemas.py +311 -0
  177. testerkit/benchmark/__init__.py +34 -0
  178. testerkit/benchmark/concurrency.py +248 -0
  179. testerkit/benchmark/core.py +232 -0
  180. testerkit/benchmark/runner.py +628 -0
  181. testerkit/benchmark/scenario.py +313 -0
  182. testerkit/benchmark/system.py +191 -0
  183. testerkit/benchmark/workloads.py +507 -0
  184. testerkit/catalog/__init__.py +6 -0
  185. testerkit/catalog/generic/generic_dmm.yaml +61 -0
  186. testerkit/catalog/generic/generic_eload.yaml +31 -0
  187. testerkit/catalog/generic/generic_fgen.yaml +56 -0
  188. testerkit/catalog/generic/generic_oscilloscope.yaml +45 -0
  189. testerkit/catalog/generic/generic_psu.yaml +77 -0
  190. testerkit/catalog/generic/generic_smu.yaml +73 -0
  191. testerkit/channels.py +538 -0
  192. testerkit/cli/__init__.py +32 -0
  193. testerkit/cli/_common.py +113 -0
  194. testerkit/cli/_time.py +179 -0
  195. testerkit/cli/benchmark_cmd.py +56 -0
  196. testerkit/cli/catalog_cmd.py +39 -0
  197. testerkit/cli/daemon.py +157 -0
  198. testerkit/cli/data_cmd.py +1040 -0
  199. testerkit/cli/discover_cmd.py +94 -0
  200. testerkit/cli/docs_cmd.py +100 -0
  201. testerkit/cli/instrument.py +172 -0
  202. testerkit/cli/mcp_cmd.py +37 -0
  203. testerkit/cli/metrics.py +366 -0
  204. testerkit/cli/project.py +312 -0
  205. testerkit/cli/root.py +14 -0
  206. testerkit/cli/runs.py +412 -0
  207. testerkit/cli/schema_cmd.py +87 -0
  208. testerkit/cli/serve_cmd.py +50 -0
  209. testerkit/cli/setup_cmd.py +604 -0
  210. testerkit/cli/station.py +294 -0
  211. testerkit/cli/validate.py +112 -0
  212. testerkit/client.py +463 -0
  213. testerkit/connect.py +635 -0
  214. testerkit/data/__init__.py +8 -0
  215. testerkit/data/_accumulator_pool.py +455 -0
  216. testerkit/data/_atomic.py +59 -0
  217. testerkit/data/_collection_indices.py +67 -0
  218. testerkit/data/_daemon_lifecycle.py +535 -0
  219. testerkit/data/_duckdb_daemon.py +695 -0
  220. testerkit/data/_duckdb_flight_server.py +813 -0
  221. testerkit/data/_event_filters.py +15 -0
  222. testerkit/data/_event_reader.py +101 -0
  223. testerkit/data/_flight_errors.py +199 -0
  224. testerkit/data/_flight_query.py +202 -0
  225. testerkit/data/_flight_retry.py +148 -0
  226. testerkit/data/_flight_subscribe.py +67 -0
  227. testerkit/data/_index_epoch.py +342 -0
  228. testerkit/data/_ipc_writer.py +185 -0
  229. testerkit/data/_json_safe.py +63 -0
  230. testerkit/data/_process.py +18 -0
  231. testerkit/data/_push_relay.py +124 -0
  232. testerkit/data/_runs_duckdb_daemon.py +2704 -0
  233. testerkit/data/_session_reaper.py +110 -0
  234. testerkit/data/_sql_helpers.py +30 -0
  235. testerkit/data/_store.py +24 -0
  236. testerkit/data/backends/__init__.py +7 -0
  237. testerkit/data/backends/_event_accumulator.py +957 -0
  238. testerkit/data/backends/_row_helpers.py +1172 -0
  239. testerkit/data/backends/parquet.py +1143 -0
  240. testerkit/data/backends/protocol.py +35 -0
  241. testerkit/data/channels/__init__.py +8 -0
  242. testerkit/data/channels/_flight_daemon.py +75 -0
  243. testerkit/data/channels/client.py +242 -0
  244. testerkit/data/channels/flight_manager.py +88 -0
  245. testerkit/data/channels/index.py +664 -0
  246. testerkit/data/channels/models.py +418 -0
  247. testerkit/data/channels/server.py +162 -0
  248. testerkit/data/channels/store.py +1481 -0
  249. testerkit/data/data_dir.py +61 -0
  250. testerkit/data/duckdb_manager.py +79 -0
  251. testerkit/data/event_log.py +384 -0
  252. testerkit/data/event_store.py +768 -0
  253. testerkit/data/events.py +1025 -0
  254. testerkit/data/exporters/__init__.py +6 -0
  255. testerkit/data/exporters/_helpers.py +40 -0
  256. testerkit/data/exporters/csv_exporter.py +168 -0
  257. testerkit/data/exporters/hdf5.py +234 -0
  258. testerkit/data/exporters/json_exporter.py +229 -0
  259. testerkit/data/exporters/mdf4.py +201 -0
  260. testerkit/data/exporters/stdf.py +255 -0
  261. testerkit/data/exporters/tdms.py +292 -0
  262. testerkit/data/files/__init__.py +71 -0
  263. testerkit/data/files/_backend.py +207 -0
  264. testerkit/data/files/_catalog_daemon.py +84 -0
  265. testerkit/data/files/catalog.py +218 -0
  266. testerkit/data/files/catalog_manager.py +325 -0
  267. testerkit/data/files/models.py +60 -0
  268. testerkit/data/files/serializers.py +414 -0
  269. testerkit/data/files/store.py +463 -0
  270. testerkit/data/files/streaming.py +783 -0
  271. testerkit/data/models.py +604 -0
  272. testerkit/data/ref.py +152 -0
  273. testerkit/data/retention.py +297 -0
  274. testerkit/data/run_store.py +367 -0
  275. testerkit/data/runs_duckdb_manager.py +103 -0
  276. testerkit/data/schema_dispatch.py +191 -0
  277. testerkit/data/schema_migrate.py +76 -0
  278. testerkit/data/schema_versions.py +85 -0
  279. testerkit/data/schemas.py +288 -0
  280. testerkit/data/subscribers/__init__.py +48 -0
  281. testerkit/data/subscribers/_base.py +20 -0
  282. testerkit/data/subscribers/_output_file.py +25 -0
  283. testerkit/data/subscribers/replay.py +55 -0
  284. testerkit/environment.py +84 -0
  285. testerkit/execution/__init__.py +11 -0
  286. testerkit/execution/_git.py +190 -0
  287. testerkit/execution/_state.py +740 -0
  288. testerkit/execution/accessors.py +54 -0
  289. testerkit/execution/audit.py +51 -0
  290. testerkit/execution/cascade.py +85 -0
  291. testerkit/execution/connections.py +491 -0
  292. testerkit/execution/harness.py +1931 -0
  293. testerkit/execution/instrument_events.py +42 -0
  294. testerkit/execution/limits.py +154 -0
  295. testerkit/execution/metadata.py +102 -0
  296. testerkit/execution/mocks.py +63 -0
  297. testerkit/execution/profiles.py +616 -0
  298. testerkit/execution/run_scope.py +1331 -0
  299. testerkit/execution/session_scope.py +196 -0
  300. testerkit/execution/sidecar.py +280 -0
  301. testerkit/execution/site_runner.py +705 -0
  302. testerkit/execution/sites.py +183 -0
  303. testerkit/execution/sync.py +294 -0
  304. testerkit/execution/uut_provider.py +231 -0
  305. testerkit/execution/vectors.py +205 -0
  306. testerkit/execution/verify.py +396 -0
  307. testerkit/expand.py +75 -0
  308. testerkit/files.py +235 -0
  309. testerkit/fixtures/__init__.py +6 -0
  310. testerkit/fixtures/manager.py +357 -0
  311. testerkit/grafana/__init__.py +1 -0
  312. testerkit/grafana/bootstrap.py +61 -0
  313. testerkit/grafana/cli.py +314 -0
  314. testerkit/grafana/dashboards/asset_utilization.json +92 -0
  315. testerkit/grafana/dashboards/channel_explorer.json +77 -0
  316. testerkit/grafana/dashboards/event_log.json +92 -0
  317. testerkit/grafana/dashboards/failure_pareto.json +104 -0
  318. testerkit/grafana/dashboards/measurement_distribution.json +97 -0
  319. testerkit/grafana/dashboards/measurement_trend.json +161 -0
  320. testerkit/grafana/dashboards/station_comparison.json +95 -0
  321. testerkit/grafana/dashboards/test_duration.json +104 -0
  322. testerkit/grafana/dashboards/unit_traceability.json +73 -0
  323. testerkit/grafana/dashboards/yield_overview.json +189 -0
  324. testerkit/grafana/provisioning/dashboards.yaml.j2 +12 -0
  325. testerkit/grafana/provisioning/datasources.yaml.j2 +17 -0
  326. testerkit/grafana/server.py +282 -0
  327. testerkit/init.py +724 -0
  328. testerkit/instruments/__init__.py +10 -0
  329. testerkit/instruments/base.py +82 -0
  330. testerkit/instruments/discovery/__init__.py +62 -0
  331. testerkit/instruments/discovery/_base.py +174 -0
  332. testerkit/instruments/discovery/lxi.py +152 -0
  333. testerkit/instruments/discovery/ni.py +95 -0
  334. testerkit/instruments/discovery/serial.py +73 -0
  335. testerkit/instruments/discovery/visa.py +98 -0
  336. testerkit/instruments/lifecycle.py +213 -0
  337. testerkit/instruments/loader.py +79 -0
  338. testerkit/instruments/locks.py +178 -0
  339. testerkit/instruments/mocks.py +230 -0
  340. testerkit/instruments/observer.py +270 -0
  341. testerkit/instruments/observers/__init__.py +36 -0
  342. testerkit/instruments/observers/_base.py +68 -0
  343. testerkit/instruments/observers/daqmx.py +70 -0
  344. testerkit/instruments/observers/descriptor.py +69 -0
  345. testerkit/instruments/observers/generic.py +92 -0
  346. testerkit/instruments/observers/lantz.py +31 -0
  347. testerkit/instruments/observers/modbus.py +96 -0
  348. testerkit/instruments/observers/motion.py +68 -0
  349. testerkit/instruments/observers/ni_modular.py +73 -0
  350. testerkit/instruments/observers/ophyd.py +80 -0
  351. testerkit/instruments/observers/pymeasure.py +120 -0
  352. testerkit/instruments/observers/qcodes.py +66 -0
  353. testerkit/instruments/observers/scpi.py +88 -0
  354. testerkit/instruments/observers/tektronix.py +57 -0
  355. testerkit/instruments/observers/visa.py +103 -0
  356. testerkit/instruments/pool.py +459 -0
  357. testerkit/instruments/proxy.py +59 -0
  358. testerkit/instruments/route_manager.py +329 -0
  359. testerkit/instruments/routed_proxy.py +88 -0
  360. testerkit/instruments/server.py +508 -0
  361. testerkit/instruments/switch.py +43 -0
  362. testerkit/instruments/visa.py +294 -0
  363. testerkit/matching/__init__.py +6 -0
  364. testerkit/matching/service.py +969 -0
  365. testerkit/mcp/__init__.py +6 -0
  366. testerkit/mcp/server.py +681 -0
  367. testerkit/mcp/tools.py +1868 -0
  368. testerkit/models/__init__.py +18 -0
  369. testerkit/models/capability.py +598 -0
  370. testerkit/models/catalog.py +76 -0
  371. testerkit/models/data_options.py +153 -0
  372. testerkit/models/enums.py +334 -0
  373. testerkit/models/instrument.py +152 -0
  374. testerkit/models/instrument_asset.py +25 -0
  375. testerkit/models/part.py +271 -0
  376. testerkit/models/part_manifest.py +96 -0
  377. testerkit/models/project.py +114 -0
  378. testerkit/models/station.py +140 -0
  379. testerkit/models/test_config.py +748 -0
  380. testerkit/ontology/__init__.py +39 -0
  381. testerkit/ontology/schema.py +188 -0
  382. testerkit/ontology/testerkit.yaml +1420 -0
  383. testerkit/parts/__init__.py +8 -0
  384. testerkit/parts/context.py +181 -0
  385. testerkit/parts/folder.py +251 -0
  386. testerkit/parts/loader.py +27 -0
  387. testerkit/prompts/__init__.py +17 -0
  388. testerkit/prompts/core.py +133 -0
  389. testerkit/pytest_plugin/__init__.py +1287 -0
  390. testerkit/pytest_plugin/autouse.py +369 -0
  391. testerkit/pytest_plugin/helpers.py +354 -0
  392. testerkit/pytest_plugin/hooks.py +1982 -0
  393. testerkit/pytest_plugin/markers.py +203 -0
  394. testerkit/pytest_plugin/retry.py +37 -0
  395. testerkit/pytest_plugin/sweeps.py +117 -0
  396. testerkit/queries.py +78 -0
  397. testerkit/reports/__init__.py +6 -0
  398. testerkit/reports/core.py +492 -0
  399. testerkit/reports/datasheet.py +831 -0
  400. testerkit/reports/templates/datasheet.html +383 -0
  401. testerkit/reports/templates/default.html +153 -0
  402. testerkit/sbom.py +142 -0
  403. testerkit/schema_export.py +101 -0
  404. testerkit/signals.py +63 -0
  405. testerkit/skills/README.md +58 -0
  406. testerkit/skills/templates/project-instructions.md +125 -0
  407. testerkit/skills/testerkit-analysis/SKILL.md +87 -0
  408. testerkit/skills/testerkit-capture/SKILL.md +114 -0
  409. testerkit/skills/testerkit-data/SKILL.md +138 -0
  410. testerkit/skills/testerkit-datasheets/SKILL.md +100 -0
  411. testerkit/skills/testerkit-datasheets/agents/scaffold-writer.md +115 -0
  412. testerkit/skills/testerkit-datasheets/agents/section-extractor.md +98 -0
  413. testerkit/skills/testerkit-datasheets/agents/section-reviewer.md +153 -0
  414. testerkit/skills/testerkit-datasheets/agents/section-splitter.md +89 -0
  415. testerkit/skills/testerkit-datasheets/agents/section-writer.md +226 -0
  416. testerkit/skills/testerkit-datasheets/references/catalog-pipeline.md +271 -0
  417. testerkit/skills/testerkit-datasheets/references/process-queue.md +42 -0
  418. testerkit/skills/testerkit-datasheets/references/scaffold.md +156 -0
  419. testerkit/skills/testerkit-datasheets/references/test-pipeline.md +312 -0
  420. testerkit/skills/testerkit-debug/SKILL.md +127 -0
  421. testerkit/skills/testerkit-interactive/SKILL.md +188 -0
  422. testerkit/skills/testerkit-interactive/references/live-ui-patterns.md +102 -0
  423. testerkit/skills/testerkit-mocks/SKILL.md +128 -0
  424. testerkit/skills/testerkit-parts/SKILL.md +126 -0
  425. testerkit/skills/testerkit-profiles/SKILL.md +139 -0
  426. testerkit/skills/testerkit-sites/SKILL.md +140 -0
  427. testerkit/skills/testerkit-stations/SKILL.md +146 -0
  428. testerkit/skills/testerkit-tests/SKILL.md +136 -0
  429. testerkit/store.py +1639 -0
  430. testerkit/ui/__init__.py +71 -0
  431. testerkit/ui/_asgi.py +162 -0
  432. testerkit/ui/app.py +32 -0
  433. testerkit/ui/components/__init__.py +13 -0
  434. testerkit/ui/components/artifact_viewer.py +250 -0
  435. testerkit/ui/components/channel_values.py +159 -0
  436. testerkit/ui/components/event_timeline.py +222 -0
  437. testerkit/ui/components/execution_gantt.py +210 -0
  438. testerkit/ui/components/file_streams.py +147 -0
  439. testerkit/ui/components/instrument_activity.py +146 -0
  440. testerkit/ui/components/session_table.py +118 -0
  441. testerkit/ui/pages/__init__.py +25 -0
  442. testerkit/ui/pages/channels/__init__.py +4 -0
  443. testerkit/ui/pages/channels/detail.py +932 -0
  444. testerkit/ui/pages/channels/list.py +515 -0
  445. testerkit/ui/pages/dashboard.py +236 -0
  446. testerkit/ui/pages/designer/__init__.py +5 -0
  447. testerkit/ui/pages/designer/graph.py +635 -0
  448. testerkit/ui/pages/designer/matching.py +663 -0
  449. testerkit/ui/pages/designer/page.py +593 -0
  450. testerkit/ui/pages/designer/properties.py +591 -0
  451. testerkit/ui/pages/designer/state.py +468 -0
  452. testerkit/ui/pages/docs/__init__.py +6 -0
  453. testerkit/ui/pages/docs/index.py +95 -0
  454. testerkit/ui/pages/docs/page.py +698 -0
  455. testerkit/ui/pages/events/__init__.py +3 -0
  456. testerkit/ui/pages/events/list.py +263 -0
  457. testerkit/ui/pages/explore.py +1108 -0
  458. testerkit/ui/pages/files/__init__.py +4 -0
  459. testerkit/ui/pages/files/detail.py +426 -0
  460. testerkit/ui/pages/files/list.py +447 -0
  461. testerkit/ui/pages/fixtures/__init__.py +8 -0
  462. testerkit/ui/pages/fixtures/detail.py +280 -0
  463. testerkit/ui/pages/fixtures/edit.py +370 -0
  464. testerkit/ui/pages/fixtures/list.py +180 -0
  465. testerkit/ui/pages/fixtures/new.py +195 -0
  466. testerkit/ui/pages/instruments/__init__.py +4 -0
  467. testerkit/ui/pages/instruments/detail.py +320 -0
  468. testerkit/ui/pages/instruments/edit.py +482 -0
  469. testerkit/ui/pages/instruments/list.py +249 -0
  470. testerkit/ui/pages/instruments/new.py +200 -0
  471. testerkit/ui/pages/launch.py +261 -0
  472. testerkit/ui/pages/live.py +124 -0
  473. testerkit/ui/pages/metrics_page.py +1229 -0
  474. testerkit/ui/pages/parts/__init__.py +11 -0
  475. testerkit/ui/pages/parts/detail.py +202 -0
  476. testerkit/ui/pages/parts/edit.py +310 -0
  477. testerkit/ui/pages/parts/list.py +160 -0
  478. testerkit/ui/pages/parts/new.py +170 -0
  479. testerkit/ui/pages/parts/requirements.py +153 -0
  480. testerkit/ui/pages/parts/stations.py +199 -0
  481. testerkit/ui/pages/profiles/__init__.py +4 -0
  482. testerkit/ui/pages/profiles/detail.py +113 -0
  483. testerkit/ui/pages/profiles/list.py +86 -0
  484. testerkit/ui/pages/results/__init__.py +4 -0
  485. testerkit/ui/pages/results/detail.py +631 -0
  486. testerkit/ui/pages/results/list.py +230 -0
  487. testerkit/ui/pages/stations/__init__.py +4 -0
  488. testerkit/ui/pages/stations/detail.py +256 -0
  489. testerkit/ui/pages/stations/edit.py +273 -0
  490. testerkit/ui/pages/stations/list.py +180 -0
  491. testerkit/ui/pages/stations/new.py +311 -0
  492. testerkit/ui/pages/tests/__init__.py +4 -0
  493. testerkit/ui/pages/tests/detail.py +152 -0
  494. testerkit/ui/pages/tests/list.py +194 -0
  495. testerkit/ui/pages/uuts/__init__.py +4 -0
  496. testerkit/ui/pages/uuts/list.py +97 -0
  497. testerkit/ui/shared/__init__.py +8 -0
  498. testerkit/ui/shared/components.py +1826 -0
  499. testerkit/ui/shared/dialogs.py +105 -0
  500. testerkit/ui/shared/event_binding.py +194 -0
  501. testerkit/ui/shared/layout.py +314 -0
  502. testerkit/ui/shared/services.py +1719 -0
  503. testerkit/ui/shared/timestamps.py +21 -0
  504. testerkit/ui/static/branding/favicon-16.png +0 -0
  505. testerkit/ui/static/branding/favicon-32.png +0 -0
  506. testerkit/ui/static/branding/favicon-48.png +0 -0
  507. testerkit/ui/static/branding/testerkit-icon-256.png +0 -0
  508. testerkit/ui/static/branding/testerkit-mark.svg +1 -0
  509. testerkit/ui/static/branding/testerkit-wordmark.svg +1 -0
  510. testerkit/ui/static/global.css +465 -0
  511. testerkit/utils/__init__.py +17 -0
  512. testerkit/utils/enum_meta.py +800 -0
  513. testerkit/utils/paths.py +75 -0
  514. testerkit/utils/ranges.py +257 -0
  515. testerkit/validation.py +82 -0
  516. testerkit/verbs.py +142 -0
  517. testerkit-0.4.0.dist-info/METADATA +250 -0
  518. testerkit-0.4.0.dist-info/RECORD +521 -0
  519. testerkit-0.4.0.dist-info/WHEEL +4 -0
  520. testerkit-0.4.0.dist-info/entry_points.txt +5 -0
  521. testerkit-0.4.0.dist-info/licenses/LICENSE +191 -0
@@ -0,0 +1,342 @@
1
+ # TesterKit fixtures
2
+
3
+ The bundled pytest plugin registers a set of public fixtures. Take any of them in a test's signature; pytest resolves and injects them by name. Names beginning with `_` are internal and may change without notice.
4
+
5
+ This page is the comprehensive reference. For a guided introduction see the [tutorial](../../tutorial/); for the seven `@pytest.mark.testerkit_*` markers see [TesterKit markers](markers.md).
6
+
7
+ ## At a glance
8
+
9
+ Grouped by what you reach for the fixture **for**:
10
+
11
+ | Group | What you'd reach for it for | Fixtures |
12
+ |---|---|---|
13
+ | Recording measurements | Write a measurement row, resolve a limit, raise on FAIL, prompt the operator | `verify`, `measure`, `limits`, `prompt` |
14
+ | Recording outputs & streams | Record a read-back value, or append samples to a channel | `observe`, `stream` |
15
+ | Talking to instruments | Get a driver instance, route a signal, hit a UUT pin | `instruments`, `instrument`, `instrument_records`, `uut`, `pins`, `routes`, `fixture_manager` |
16
+ | Reading per-test state | Active sweep params, observations, the connection currently being iterated | `context`, `connections` |
17
+ | Reading loaded configuration | The typed YAML / CLI that shaped this run | `part`, `station_config`, `fixture_config`, `run_context`, `mock_instruments` |
18
+ | Flow control | Drive the test body's iteration / synchronization | `vectors`, `sync` |
19
+
20
+ Plus **one role-named fixture per instrument the station YAML declares** (e.g. `dmm`, `psu`, `scope`). See [Per-role auto-fixtures](#per-role-auto-fixtures).
21
+
22
+ Every fixture above is available in every test — pytest will resolve any of them by name. The "what you'd reach for it for" column is intent, not availability. Several have meaningful "no project state" defaults (`part` returns `None`, `instruments` returns `{}`, `connections` returns `None`, etc.) so taking one in a vanilla project is safe.
23
+
24
+ ---
25
+
26
+ ## Recording measurements
27
+
28
+ The verbs you write into test bodies. Most tests need `verify` and nothing else from this group.
29
+
30
+ ### `verify` — function
31
+
32
+ Callable: `verify(name, value, limit=None, characteristic=None)`. Records the measurement row (value, units, limits, traceability), resolves a limit from the active chain (sidecar / inline marker / part spec), stamps `measurement_outcome`, and **raises `AssertionError`** when the value is out of range.
33
+
34
+ `limit=` accepts either a `Limit` model or a dict literal — `verify` coerces dicts via `Limit.model_validate(...)`.
35
+
36
+ ```python
37
+ def test_rail(dmm, verify):
38
+ verify("output_voltage", dmm.measure_dc_voltage()) # limit resolves from sidecar/marker
39
+
40
+ def test_rail_inline(dmm, verify):
41
+ verify("vout", dmm.measure_dc_voltage(),
42
+ limit={"low": 3.2, "high": 3.4, "unit": "V"}) # inline dict literal
43
+ ```
44
+
45
+ Same record-side effect as `measure`; the only difference is `verify` raises on FAIL. Use `verify` when a fail should stop the line. With no resolvable limit, `verify` raises `MissingLimitError` — unless the active profile sets `verify_requires_limit: false`, in which case it falls back to `measure` semantics (record-only, `Outcome.DONE`).
46
+
47
+ ### `measure` — function
48
+
49
+ Callable: `measure(name, value, limit=None, characteristic=None)`. The record-only sibling of `verify` — records a measurement row with `outcome = DONE` and **never raises**, even when no limit resolves. Use it when a value should be captured but not pass/fail judged (characterization, diagnostics, sweeps you plot post-hoc).
50
+
51
+ ```python
52
+ def test_voltage(dmm, measure):
53
+ v = dmm.measure_dc_voltage()
54
+ measure("output_voltage", v, limit={"low": 3.2, "high": 3.4, "unit": "V"})
55
+ ```
56
+
57
+ `limit=` accepts either a `Limit` model or a dict literal. Same recording path as `verify`, just no FAIL-side effect — use it when a failing measurement shouldn't abort the test.
58
+
59
+ ### `limits` — function
60
+
61
+ Read-only `name → Limit` mapping for the current test, resolved from the same chain as `verify`. Use for ad-hoc pythonic assertions:
62
+
63
+ ```python
64
+ def test_inline_check(dmm, limits):
65
+ v = dmm.measure_dc_voltage()
66
+ assert v in limits["output_voltage"]
67
+ ```
68
+
69
+ `limits[name]` raises `KeyError` when no limit is configured — there is no silent default.
70
+
71
+ ### `prompt` — function
72
+
73
+ Returns a callable that resolves operator prompts declared via `@pytest.mark.testerkit_prompts`:
74
+
75
+ ```python
76
+ @pytest.mark.testerkit_prompts(
77
+ inspect={"message": "Verify LED is GREEN", "prompt_type": "confirm"},
78
+ )
79
+ def test_visual(prompt, verify):
80
+ prompt("inspect") # blocks until operator responds
81
+ verify("led_state", read_led_color())
82
+ ```
83
+
84
+ See [`testerkit_prompts`](markers.md#testerkit_prompts) for the marker shape.
85
+
86
+ ---
87
+
88
+ ## Recording outputs & streams
89
+
90
+ The verbs for read-back values and continuous samples. Like `verify`/`measure`, these are callable fixtures — take the name in the test signature, then call it. Each wraps the matching `context` method.
91
+
92
+ ### `observe` — function
93
+
94
+ Callable: `observe(name, value, *, namespace=None, unit=None)`. Records a read-back value (the response side — a measured output, not a pass/fail judgment) onto the active vector. The value's shape decides where it lands: scalars stay inline; arrays / `Waveform` go to the ChannelStore; blobs go to the FileStore — with a `channel://` / `file://` reference stamped on the vector.
95
+
96
+ ```python
97
+ def test_rail(dmm, observe, verify):
98
+ observe("v_rail", dmm.measure_dc_voltage()) # output, recorded not judged
99
+ observe("scope_cap", scope.capture()) # Waveform → ChannelStore
100
+ ```
101
+
102
+ See [the three verbs](../../concepts/data/three-verbs.md) for how a value routes to the right store by shape.
103
+
104
+ ### `stream` — function
105
+
106
+ Callable: `stream(name, sample, *, namespace=None, unit=None) -> str`. Appends one sample to a named channel timeline (continuous capture). Returns the `channel://` reference. Use it for a live sensor feed or free-running acquisition, where the channel — not the individual call — is the unit you query later.
107
+
108
+ ```python
109
+ def test_soak(dmm, stream):
110
+ for _ in range(n):
111
+ stream("supply_rail", dmm.measure_dc_voltage(), unit="V")
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Talking to instruments
117
+
118
+ These fixtures need a station YAML to produce useful results. Without one they return empty dicts / `None`.
119
+
120
+ ### `instruments` — session
121
+
122
+ Yields `dict[role_name, driver_instance]`. Connects every instrument declared in the station YAML at session start, disconnects at session end. Auto-mocks when `--mock-instruments` is on. Identity and calibration are checked against config for real hardware.
123
+
124
+ ```python
125
+ def test_voltage(instruments):
126
+ dmm = instruments["dmm"]
127
+ assert dmm.measure_dc_voltage() > 3.0
128
+ ```
129
+
130
+ In most tests you take **role names directly** as fixtures (`def test_x(dmm, psu)`) — see [Per-role auto-fixtures](#per-role-auto-fixtures) — and never need `instruments` itself.
131
+
132
+ ### `instrument` — function
133
+
134
+ Returns an `InstrumentAccessor` for role-keyed access with grouping:
135
+
136
+ ```python
137
+ def test_one(instrument):
138
+ dmm = instrument("dmm")
139
+
140
+ def test_all(instrument):
141
+ dmms = instrument.by_type("pymeasure.instruments.keithley.Keithley2000")
142
+ ```
143
+
144
+ ### `instrument_records` — session
145
+
146
+ Returns `dict[role_name, InstrumentRecord]` — the resolved instrument metadata (driver class, resource string, calibration cert, mocked flag) before connection. Useful for tests that need identity or calibration info without taking the live driver.
147
+
148
+ ### `uut` — session
149
+
150
+ Yields the connected UUT driver (resolved from `Part.driver` + `FixtureConfig.uut_resource`), or `None` when the part has no driver. Mocked when `--mock-instruments` is on.
151
+
152
+ ```python
153
+ def test_firmware(uut):
154
+ assert uut.get_version().startswith("2.")
155
+ ```
156
+
157
+ ### `pins` — session
158
+
159
+ Returns a `PinAccessor` for UUT-centric pin access. Looks up the instrument that the fixture YAML maps to each UUT pin, transparently activates the route if any switch is in the path.
160
+
161
+ ```python
162
+ def test_output(pins):
163
+ pins["VIN"].set_voltage(5.0)
164
+ pins["VIN"].enable_output()
165
+ assert pins["VOUT"].measure_voltage() > 3.0
166
+ ```
167
+
168
+ Raises `pytest.UsageError` if no fixture config or instruments are loaded.
169
+
170
+ ### `routes` — function
171
+
172
+ Yields a `RouteManager` for explicit switch routing, or `None` when no routes exist:
173
+
174
+ ```python
175
+ def test_vout(dmm, routes):
176
+ with routes.for_pin("VOUT"):
177
+ v = dmm.measure_voltage()
178
+ ```
179
+
180
+ `routes.deactivate_all()` runs automatically at test teardown.
181
+
182
+ ### `fixture_manager` — session
183
+
184
+ Returns the `FixtureManager` directly, for the rare test that needs advanced lookup (e.g. net-name → connection) beyond what `pins` exposes:
185
+
186
+ ```python
187
+ def test_lookup(fixture_manager):
188
+ conn = fixture_manager.get_connection_for_net("VOUT_3V3")
189
+ inst = fixture_manager.get_instrument_for_connection(conn.name)
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Reading per-test state
195
+
196
+ The active vector's params, observations, and the connection currently being iterated.
197
+
198
+ ### `context` — function
199
+
200
+ Returns a `Context` exposing the run / UUT / station / vector state for the active test. Resolves on every test, with empty defaults when there's nothing to expose.
201
+
202
+ | Method | Returns | Purpose |
203
+ |---|---|---|
204
+ | `context.get_param(name, default=None)` | `Any` | Read a sweep / parametrize value. |
205
+ | `context.params` | `dict` | All active params for this row. |
206
+ | `context.changed(key)` | `bool` | True if `key` differs from prior iteration. |
207
+ | `context.last(key, default=None)` | `Any` | Prior iteration's value for `key`. |
208
+ | `context.observe(key, value)` | `None` | Record a free-form observation. |
209
+ | `context.observations` | `dict` | All recorded observations. |
210
+ | `context.part` | `Part \| None` | Active part definition (= `part` fixture). |
211
+ | `context.station` | `StationConfig \| None` | Active station config (= `station_config` fixture). |
212
+ | `context.run` | `TestRun \| None` | The current `TestRun`. |
213
+ | `context.limits` | `LimitsView` | Read-only view of the active limits. |
214
+ | `context.characteristics` | `tuple[str, ...]` | Active characteristic IDs from `testerkit_characteristics`. |
215
+
216
+ ```python
217
+ def test_rail(context, psu, dmm, verify):
218
+ psu.set_voltage(context.get_param("vin", 5.0))
219
+ verify("vout", dmm.measure_dc_voltage())
220
+ ```
221
+
222
+ ### `connections` — function
223
+
224
+ Returns the `ConnectionIterator` resolved from `testerkit_characteristics` / `testerkit_connections` markers, or `None` when no markers are declared.
225
+
226
+ ```python
227
+ def test_per_pin(connections, dmm):
228
+ for conn in connections:
229
+ v = dmm.measure_voltage()
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Reading loaded configuration
235
+
236
+ Typed accessors over the YAML / CLI that shaped this run. Each one resolves to its model OR `None` (or an empty dict / bool) — taking one in a vanilla project is safe.
237
+
238
+ ### `part` — session
239
+
240
+ Returns the active `Part` definition loaded from `parts/*.yaml` (identity, pins, characteristics), or `None` if no `parts/` directory or no match. For derived limits use the `limits` fixture or `context.get_limit(name)`.
241
+
242
+ Resolution chain (first match wins):
243
+ 1. `--part <id-or-path>` — `<id>` looks up `parts/<id>.yaml`; `<path>` is used directly.
244
+ 2. `--uut-part-number <pn>` — content match against `part.part_number:` across `parts/*.yaml`.
245
+ 3. Single-file fallback when `parts/` holds exactly one part file.
246
+ 4. `None`.
247
+
248
+ ```python
249
+ def test_spec(part, context, dmm, verify):
250
+ if part:
251
+ assert part.part_number == "DEMO-BUCK-3V3"
252
+ verify("output_voltage", dmm.measure_dc_voltage()) # limit resolves from the part spec
253
+ ```
254
+
255
+ ### `station_config` — session
256
+
257
+ Returns the `StationConfig` resolved from `--station` / `stations/*.yaml`, or `None`. Also publishes the value to the active-station ContextVar so `context.station` works without taking the fixture.
258
+
259
+ ### `fixture_config` — session
260
+
261
+ Returns the `FixtureConfig` resolved from `--fixture` / `fixtures/*.yaml`, or `None`. In worker mode (multi-site), extracts just this site's `connections` and `uut_resource`.
262
+
263
+ ### `run_context` — session
264
+
265
+ Returns the `RunContext` for the active run. Use it to attach run-level metadata that persists across tests:
266
+
267
+ ```python
268
+ def test_setup(run_context):
269
+ run_context.set("operator_badge", "EMP-12345")
270
+ run_context.set("fixture_serial", "FIX-001")
271
+ ```
272
+
273
+ For per-test or per-vector state, use `context` instead.
274
+
275
+ ### `mock_instruments` — session
276
+
277
+ Returns `bool`. True when `--mock-instruments` was passed or `TESTERKIT_MOCK_INSTRUMENTS=1` is set. The same flag drives the `instruments` fixture's behavior; tests rarely take it directly except for diagnostic branches.
278
+
279
+ ---
280
+
281
+ ## Flow control
282
+
283
+ Two fixtures that drive the test body's iteration shape, not just expose data. `vectors` collapses pytest's per-row case multiplication into one in-body loop; `sync` blocks the body until peer workers reach the same named point.
284
+
285
+ ### `vectors` — function
286
+
287
+ Taking `vectors` in the test signature switches collection to **self-loop mode**: the function-level vector sources (`@pytest.mark.parametrize`, function-level `testerkit_sweeps`, sidecar `sweeps:`, profile overrides) are consolidated into one matrix at collection time, and the test runs as a single pytest case. (Class- or module-level `testerkit_sweeps` still fan out as separate pytest cases — one per outer condition — each running the consolidated inner matrix.) The test body iterates the matrix itself:
288
+
289
+ ```python
290
+ def test_sweep(vectors, psu, dmm, measure):
291
+ for v in vectors:
292
+ psu.set_voltage(v["vin"])
293
+ measure("vout", dmm.measure_dc_voltage())
294
+ ```
295
+
296
+ Each `for` iteration pushes the row's params + index into active state so `measure`, `verify`, and `context` see the same row-scoped context they would in normal (one-case-per-row) mode. The fixture fails the test at teardown if the matrix is non-empty but the body iterated zero times.
297
+
298
+ Choose self-loop mode when an outer setup (thermal soak, supply ramp) shouldn't repeat per row; choose normal parametrize mode when you want pytest to report one case per row.
299
+
300
+ ### `sync` — session
301
+
302
+ Yields a `SyncPoint` for multi-UUT coordination when running in worker mode (`_TESTERKIT_SITE_INDEX` is set), or `None` in single-site mode. `sync.wait(name, timeout=...)` blocks until every site reaches the same name:
303
+
304
+ ```python
305
+ def test_measure_hot(dmm, sync):
306
+ if sync:
307
+ sync.wait("thermal_soak", timeout=300)
308
+ v = dmm.measure_voltage()
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Per-role auto-fixtures
314
+
315
+ When the plugin finds a station YAML at `pytest_configure`, it dynamically registers one session-scoped fixture per `instruments:` role. A station YAML like
316
+
317
+ ```yaml
318
+ instruments:
319
+ dmm: keithley_dmm_001
320
+ psu: keysight_psu_002
321
+ scope: tek_dpo_003
322
+ ```
323
+
324
+ exposes `dmm`, `psu`, and `scope` as fixtures, each returning the connected driver for that role:
325
+
326
+ ```python
327
+ def test_rail(dmm, psu, verify):
328
+ psu.set_voltage(5.0)
329
+ verify("vout", dmm.measure_dc_voltage())
330
+ ```
331
+
332
+ These names are not hard-coded — they come from your station YAML at session start.
333
+
334
+ ---
335
+
336
+ ## See also
337
+
338
+ - [TesterKit markers](markers.md) — the seven `@pytest.mark.testerkit_*` decorators and their sidecar equivalents
339
+ - [pytest-native reference](../overview/pytest-native.md) — how the bundled plugin uses pytest's own collection / fixtures / markers
340
+ - [Models](../data/models.md) — `Limit`, `MeasurementLimitConfig`, `PartContext`, `StationConfig`, `FixtureConfig` field shapes
341
+ - [Test vectors & sweeps](../../how-to/execution/vector-expansion.md) — `testerkit_sweeps`, `parametrize`, and the `vectors` self-loop fixture
342
+ - [Spec-driven testing](../../how-to/execution/spec-driven-testing.md) — `testerkit_characteristics` + `connections` workflow
@@ -0,0 +1,12 @@
1
+ # Reference — pytest plugin
2
+
3
+ The fixtures and markers the bundled pytest plugin contributes on top of stock pytest. For pytest-native mechanics (collection, parametrize, conftest, CLI flags), see [pytest-native](../overview/pytest-native.md).
4
+
5
+ - [Fixtures](fixtures.md) — all the fixtures the plugin contributes, with signatures, scopes, and per-fixture examples
6
+ - [Markers](markers.md) — the seven `@pytest.mark.testerkit_*` decorators and their 1:1 sidecar equivalents
7
+
8
+ ## See also
9
+
10
+ - [Concepts → pytest](../../concepts/overview/pytest.md) — why the platform rides on pytest as the primary runner
11
+ - [How-to → Writing tests](../../how-to/execution/writing-tests.md) — task recipes for authoring tests that use these fixtures + markers
12
+ - [Reference → Configuration](../configuration.md) — sidecar YAML schemas (the YAML form of every marker)
@@ -0,0 +1,257 @@
1
+ # TesterKit markers
2
+
3
+ The TesterKit pytest plugin registers **seven markers**. Each maps 1:1 to a field on `TestEntry` (the recursive node type in the sidecar YAML), so anything you can write inline as a marker, you can write in the sidecar — and vice versa.
4
+
5
+ Pytest's own markers (`@pytest.mark.parametrize`, `@pytest.mark.skip`, `@pytest.mark.flaky` from `pytest-rerunfailures`, etc.) work unchanged. TesterKit's markers slot in alongside them.
6
+
7
+ ## No-stacking rule
8
+
9
+ At most one inline `@pytest.mark.testerkit_X` decorator of each kind per test. Multi-entry payloads (a list of dicts for sweeps/mocks, multiple kwargs for limits/prompts) consolidate onto one marker. `@pytest.mark.parametrize` is the explicit exception — pytest's native stacking convention stays. Stacking a TesterKit marker raises `StackedMarkersError` at collection.
10
+
11
+ ```python
12
+ # OK
13
+ @pytest.mark.testerkit_sweeps([{"temperature": [-40, 25, 85], "load": [0.1, 0.5]}])
14
+ def test_x(...): ...
15
+
16
+ # Not OK — raises StackedMarkersError
17
+ @pytest.mark.testerkit_sweeps([{"temperature": [-40, 25, 85]}])
18
+ @pytest.mark.testerkit_sweeps([{"load": [0.1, 0.5]}])
19
+ def test_x(...): ...
20
+ ```
21
+
22
+ ---
23
+
24
+ ## `testerkit_limits`
25
+
26
+ Pin a `Limit` per measurement name. Both `verify(name, value)` and `measure(name, value)` record the measurement row and resolve the limit against this marker (or the sidecar's `limits:` block, or the active part spec, in resolution order); the only difference is `verify` raises `AssertionError` on FAIL where `measure` doesn't.
27
+
28
+ **Signature:** `@pytest.mark.testerkit_limits(**by_name)` — one keyword per measurement name; each value is a dict matching `MeasurementLimitConfig`.
29
+
30
+ ```python
31
+ @pytest.mark.testerkit_limits(
32
+ output_voltage={"low": 3.135, "high": 3.465, "unit": "V"},
33
+ output_current={"high": 0.5, "unit": "A"},
34
+ )
35
+ def test_power_rail(dmm, psu, verify):
36
+ verify("output_voltage", dmm.measure_dc_voltage())
37
+ verify("output_current", psu.measure_current())
38
+ ```
39
+
40
+ **Sidecar equivalent:**
41
+
42
+ ```yaml
43
+ limits:
44
+ output_voltage: {low: 3.135, high: 3.465, unit: V}
45
+ output_current: {high: 0.5, unit: A}
46
+ ```
47
+
48
+ **Full shape:** `low`, `high`, `nominal`, `unit`, `comparator`, plus the alternative `characteristic:` / `tolerance_pct:` / `tolerance_abs:` derivation, and the `bands:` list for condition-indexed limits. See [`MeasurementLimitConfig`](../data/models.md) for the full schema and [Test limits](../../how-to/execution/limits.md#condition-indexed-bands) for the band semantics.
49
+
50
+ ---
51
+
52
+ ## `testerkit_sweeps`
53
+
54
+ TesterKit-native parametrize. Each entry in the list is one **axis-group dict** — single-key dicts run as one independent loop; multi-key dicts inside one entry zip together; stacked entries cross-product (top entry = outermost / slowest loop).
55
+
56
+ **Signature (inline):** `@pytest.mark.testerkit_sweeps([entries])` — one positional list of axis-group dicts. Single-axis is `[{"name": [values]}]`; cross-product is multiple entries; zipped paired values are multiple keys in one entry.
57
+
58
+ ```python
59
+ # Single axis
60
+ @pytest.mark.testerkit_sweeps([{"vin": [3.3, 5.0, 12.0]}])
61
+ def test_rail(vin, psu, dmm, verify): ...
62
+
63
+ # Cross-product — outer first, inner last
64
+ @pytest.mark.testerkit_sweeps([
65
+ {"temperature": [-40, 25, 85]}, # outer
66
+ {"vin": [3.3, 5.0, 12.0]}, # inner
67
+ ])
68
+ def test_rail_temp_sweep(temperature, vin, psu, dmm, verify): ...
69
+
70
+ # Zipped axis — same-length lists, paired
71
+ @pytest.mark.testerkit_sweeps([{"load_a": [0.1, 0.4], "load_b": [10, 20]}])
72
+ def test_zipped(load_a, load_b, ...): ...
73
+ ```
74
+
75
+ **Sidecar equivalent:**
76
+
77
+ ```yaml
78
+ sweeps:
79
+ - {temperature: [-40, 25, 85]}
80
+ - {vin: [3.3, 5.0, 12.0]}
81
+ ```
82
+
83
+ **`testerkit_sweeps` vs `@pytest.mark.parametrize`:** both work; both feed the same `inputs` lane on the vector row (query with `FieldRef.input(name)`). Use `testerkit_sweeps` when you want range expanders (`linspace`, `arange`, `logspace`, etc.) or sidecar parity; use `@pytest.mark.parametrize` when you want pytest's `pytest.param(..., id="...")` / `marks=[...]` per-row metadata. See [Test vectors & sweeps](../../how-to/execution/vector-expansion.md) for full semantics including range expanders and the `vectors` self-loop fixture.
84
+
85
+ ---
86
+
87
+ ## `testerkit_mocks`
88
+
89
+ Install one or more mocks at test entry, unwound at teardown. Each entry has a `target:` and arbitrary `unittest.mock.patch.object` kwargs (`return_value`, `side_effect`, `wraps`, `spec`, etc.).
90
+
91
+ **Signature:** `@pytest.mark.testerkit_mocks([entries])` where each entry is a `MockEntry` dict.
92
+
93
+ ```python
94
+ @pytest.mark.testerkit_mocks([
95
+ {"target": "dmm.measure_dc_voltage", "return_value": 3.31},
96
+ {"target": "psu.measure_current", "side_effect": [0.1, 0.2, 0.3]},
97
+ ])
98
+ def test_mocked(dmm, psu, verify): ...
99
+ ```
100
+
101
+ **Sidecar equivalent:**
102
+
103
+ ```yaml
104
+ mocks:
105
+ - {target: dmm.measure_dc_voltage, return_value: 3.31}
106
+ - {target: psu.measure_current, side_effect: [0.1, 0.2, 0.3]}
107
+ ```
108
+
109
+ The target is `"<fixture>.<attr>"` — the pytest fixture name plus the attribute on the resolved fixture value. See [Mock mode](../../how-to/configuration/mock-mode.md) for the full priority resolution (per-test mocks > file-level mocks > station `mock_config` > zero).
110
+
111
+ ---
112
+
113
+ ## `testerkit_characteristics`
114
+
115
+ Iterate the test body over a subset of the part spec's `characteristics`. Combined with `testerkit_connections` to select which signal-path connections to bind. Used by [spec-driven testing](../../how-to/execution/spec-driven-testing.md).
116
+
117
+ **Signature:** `@pytest.mark.testerkit_characteristics([ids])` — list of characteristic IDs.
118
+
119
+ ```python
120
+ @pytest.mark.testerkit_characteristics(["output_voltage", "output_current"])
121
+ def test_rail(context, verify):
122
+ for char_id in context.characteristics:
123
+ verify(char_id, ...)
124
+ ```
125
+
126
+ **Sidecar equivalent:**
127
+
128
+ ```yaml
129
+ characteristics: [output_voltage, output_current]
130
+ ```
131
+
132
+ ---
133
+
134
+ ## `testerkit_connections`
135
+
136
+ Select which fixture connections the test iterates over. Pairs with `testerkit_characteristics`. Two payload shapes:
137
+
138
+ - **List of names** — bind by fixture-connection name: `@pytest.mark.testerkit_connections(["VOUT", "VIN"])`
139
+ - **Dict mapping instrument → channels** — bind by instrument and channel selector: `@pytest.mark.testerkit_connections(dmm=["CH1", "CH2"])`
140
+
141
+ **Sidecar equivalents:**
142
+
143
+ ```yaml
144
+ connections: [VOUT, VIN] # list form
145
+ # OR
146
+ connections: {dmm: [CH1, CH2]} # dict form
147
+ ```
148
+
149
+ The pytest plugin's `connections` fixture exposes the resolved `FixtureConnection` iterator for the test body.
150
+
151
+ ---
152
+
153
+ ## `testerkit_retry`
154
+
155
+ Per-test retry policy. Translates to `pytest-rerunfailures`' `flaky` under the hood.
156
+
157
+ **Signature:** `@pytest.mark.testerkit_retry(max_retries=, delay=, on=)`
158
+
159
+ ```python
160
+ @pytest.mark.testerkit_retry(max_retries=2, delay=0.5, on=["AssertionError"])
161
+ def test_flaky_settling(dmm, verify): ...
162
+ ```
163
+
164
+ | Field | Type | Default | Meaning |
165
+ |---|---|---|---|
166
+ | `max_retries` | `int >= 0` | `0` | 0 = single execution, 2 = up to 2 retries beyond original (3 total) |
167
+ | `delay` | `float >= 0` | `0.0` | Seconds between attempts |
168
+ | `on` | `list[str] \| None` | `None` (any) | Exception class names that trigger retry |
169
+
170
+ **Sidecar equivalent:**
171
+
172
+ ```yaml
173
+ retry: {max_retries: 2, delay: 0.5, on: [AssertionError]}
174
+ ```
175
+
176
+ Each retry produces measurement rows with the same `vector_index` and an incremented `vector_retry`. See [Parquet schema → Retries](../data/parquet-schema.md#retries).
177
+
178
+ ---
179
+
180
+ ## `testerkit_prompts`
181
+
182
+ Declare operator prompts the test can invoke via the `prompt` fixture. Keyword per prompt name.
183
+
184
+ **Signature:** `@pytest.mark.testerkit_prompts(**by_name)` — each value matches `PromptConfig`.
185
+
186
+ ```python
187
+ @pytest.mark.testerkit_prompts(
188
+ inspect={"message": "Verify LED is GREEN", "prompt_type": "confirm"},
189
+ )
190
+ def test_visual(prompt, verify):
191
+ prompt("inspect") # blocks until operator responds
192
+ verify("led_state", read_led_color())
193
+ ```
194
+
195
+ | Field | Type | Default | Meaning |
196
+ |---|---|---|---|
197
+ | `message` | `str` | (required) | Prompt text shown to the operator |
198
+ | `prompt_type` | `"confirm" \| "choice" \| "input"` | `"confirm"` | What the operator UI shows |
199
+ | `choices` | `list[str] \| None` | `None` | For `"choice"` type |
200
+ | `timeout_seconds` | `int \| None` | `None` | Stop waiting after N seconds (returns a timed-out response) |
201
+
202
+ **Sidecar equivalent:**
203
+
204
+ ```yaml
205
+ prompts:
206
+ inspect: {message: "Verify LED is GREEN", prompt_type: confirm}
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Composing `testerkit_characteristics` + `testerkit_connections`
212
+
213
+ These two markers are the two halves of selecting which connections a test iterates over `ctx.connections` (the `connections` fixture): `testerkit_characteristics` says *which characteristic* on the part, and `testerkit_connections` says *which fixture connections* to bind. They're independent, so every combination of present / absent / by-name / by-channel / fixture-loaded / fixture-absent has a defined behavior. Find the row that matches your test.
214
+
215
+ `testerkit_connections` takes one of two shapes (not both at once):
216
+
217
+ - **`[name, ...]`** — bind by fixture-connection name. Requires a fixture YAML.
218
+ - **`{instrument: [channels], ...}`** — bind by instrument → channel selector. Works without a fixture (synthesizes stubs) for early bringup.
219
+
220
+ | Case | `testerkit_characteristics` | `testerkit_connections` | Fixture loaded? | Result |
221
+ |------|---------------|----------------------|-----------------|--------|
222
+ | 1 | — | — | any | No markers → `ctx.connections` is `None`; the test runs once with no connection context. |
223
+ | 2 | `[X]` | — | yes | Iterate the fixture connections whose `uut_pin` (or `net`) matches a pin in `X`'s resolved pins. Order: characteristic order, then each characteristic's pin order (deduplicated). |
224
+ | 3 | `[X]` | — | no | Empty iterator — no fixture means no connections to bind. The test iterates `ctx.connections` zero times. |
225
+ | 4 | — | `[a, b]` (names) | yes | Iterate the named connections in the order listed. Unknown name → `pytest.UsageError`. |
226
+ | 5 | — | `[a, b]` (names) | no | `pytest.UsageError` — connection names are only meaningful against a fixture YAML. |
227
+ | 6 | — | `{inst: [ch]}` (channels) | yes | Match each `(instrument, channel)` against fixture connections, in listed order. No match → `pytest.UsageError`. `'all'` selects every connection on that instrument. |
228
+ | 7 | — | `{inst: [ch]}` (channels) | no | Synthesize connection stubs (`name="{inst}_ch{ch}"`, no `uut_pin`) for early bringup. `'all'` → `pytest.UsageError` (nothing to enumerate). |
229
+ | 8 | `[X]` | `[a, b]` (names) | yes | Resolve the names (case 4), then require every selected connection's `uut_pin` to fall in the union of the characteristics' pin sets. Out-of-set → `pytest.UsageError`. Listed order wins. |
230
+ | 9 | `[X]` | `[a, b]` (names) | no | `pytest.UsageError` (case 5 — a fixture is required for connection names). |
231
+ | 10 | `[X]` | `{inst: [ch]}` (channels) | yes | Resolve the channels (case 6), then require every match's `uut_pin` to fall in the union pin set. Out-of-set → `pytest.UsageError`. Listed order wins. |
232
+ | 11 | `[X]` | `{inst: [ch]}` (channels) | no | `pytest.UsageError` — fixtureless channel stubs have no `uut_pin` to cross-check against the characteristics. Drop `testerkit_characteristics` for pure bringup, or load a fixture. |
233
+
234
+ Invariants across the matrix:
235
+
236
+ - **No part loaded, or an unknown characteristic ID** → `pytest.UsageError`.
237
+ - **Iteration order** — when `testerkit_connections` is present it sets the order (user-listed); with characteristics alone, the order follows characteristic order then each characteristic's pin order.
238
+ - **Declared but un-iterated** — if connections resolve to a non-empty set and the test body never iterates `ctx.connections`, the test fails with `AssertionError`. An empty resolved set (case 3) is not an error; the body just runs zero rounds.
239
+
240
+ ## Where markers live
241
+
242
+ Same vocabulary, three delivery channels:
243
+
244
+ | Channel | Example |
245
+ |---|---|
246
+ | Inline decorator | `@pytest.mark.testerkit_limits(output_voltage={"low": 3.135, "high": 3.465, "unit": "V"})` |
247
+ | Sidecar YAML (`tests/test_<module>.yaml`) | `limits: { output_voltage: { low: 3.135, high: 3.465, unit: V } }` |
248
+ | Profile YAML (`profiles/*.yaml`) | Same shape; applies session-wide via `--test-profile=<name>` |
249
+
250
+ Resolution order (least → most specific): inline marker (class then method) → sidecar file-level → sidecar class/method → profile chain. Sidecar overrides inline because sidecar markers are applied after inline decorators and the resolver is last-wins. CLI flags (`-k`, `-m`, `--mock-instruments`, etc.) compose with this chain rather than overriding it wholesale. See [Test configuration](../configuration.md#sidecar-yaml) for the full merge semantics.
251
+
252
+ ## See also
253
+
254
+ - [pytest-native reference](../overview/pytest-native.md) — how TesterKit tests use pytest's own collection / fixtures / markers
255
+ - [TesterKit fixtures reference](fixtures.md) — all the fixtures the plugin exposes
256
+ - [Models](../data/models.md) — `MeasurementLimitConfig`, `MockEntry`, `SweepEntry`, `RetryConfig`, `PromptConfig` field shapes
257
+ - [Test vectors & sweeps](../../how-to/execution/vector-expansion.md) — `testerkit_sweeps` semantics + `vectors` self-loop fixture