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,484 @@
1
+ # Writing custom instrument drivers
2
+
3
+ TesterKit doesn't ship instrument drivers — you bring your own. This page covers writing a driver, registering it so the platform finds it, and the two hardware-free paths TesterKit supports.
4
+
5
+ A TesterKit driver is just a Python class. Subclass `VisaInstrument` for SCPI-over-VISA, or `Instrument` for anything else, and add one method per thing your test reads (`measure_voltage()`, `set_voltage()`, …). The method names are whatever your test calls — that's the whole contract.
6
+
7
+ ```python
8
+ from testerkit.instruments.visa import VisaInstrument
9
+
10
+
11
+ class MyDMM(VisaInstrument):
12
+ def measure_voltage(self) -> float:
13
+ return float(self.query("MEAS:VOLT?"))
14
+
15
+ def measure_current(self) -> float:
16
+ return float(self.query("MEAS:CURR?"))
17
+ ```
18
+
19
+ If your instrument speaks SCPI over VISA, [start with `VisaInstrument`](#scpi-instruments-via-visa). For serial, DAQmx, USB, or proprietary protocols, [extend `Instrument` directly](#non-visa-instruments).
20
+
21
+ **Which base class?**
22
+
23
+ | Import | Use it for |
24
+ |---|---|
25
+ | `from testerkit.instruments.visa import VisaInstrument` | SCPI / IEEE 488.2 instruments — wraps PyVISA, adds `query()` / `write()` / `*IDN?` parsing, generates a `pyvisa-sim` config when `simulate=True` |
26
+ | `from testerkit.instruments.base import Instrument` | Any protocol you handle yourself — serial, DAQmx, USB, HID, proprietary RPC |
27
+
28
+ `Mock` is top-level (`from testerkit import Mock`). `Mock(MyDMM, ...)` returns a stand-in that passes `isinstance(x, MyDMM)`, no-ops `connect()`/`disconnect()`, and returns only the values you configure.
29
+
30
+ Capabilities (what the matcher pairs against parts) live in the catalog YAML you point `catalog_ref:` at, not in your driver class — see [catalog schema](../../reference/catalog/schema.md).
31
+
32
+ ---
33
+
34
+ ## SCPI instruments via VISA
35
+
36
+ For any SCPI / IEEE 488.2 instrument, subclass `VisaInstrument`. The base handles `connect()` / `disconnect()`, `write()` / `query()` / `read()`, `*IDN?` parsing, and generates a pyvisa-sim YAML when `simulate=True`.
37
+
38
+ ```python
39
+ from testerkit.instruments.visa import VisaInstrument
40
+
41
+
42
+ class MyDMM(VisaInstrument):
43
+ """Custom SCPI DMM driver."""
44
+
45
+ def measure_voltage(self) -> float:
46
+ # Use MEAS:VOLT? (not MEAS:VOLT:DC?) — the auto-generated
47
+ # pyvisa-sim YAML wires `voltage` in sim_config to MEAS:VOLT?.
48
+ return float(self.query("MEAS:VOLT?"))
49
+
50
+ def measure_current(self) -> float:
51
+ return float(self.query("MEAS:CURR?"))
52
+
53
+ def configure_voltage_range(self, range_val: float | str) -> None:
54
+ if range_val == "AUTO":
55
+ self.write("VOLT:RANG:AUTO ON")
56
+ else:
57
+ self.write(f"VOLT:RANG {range_val}")
58
+ ```
59
+
60
+ Usage on real hardware:
61
+
62
+ ```python
63
+ dmm = MyDMM("TCPIP::192.168.1.100::INSTR")
64
+ dmm.connect()
65
+ v = dmm.measure_voltage()
66
+ dmm.disconnect()
67
+ ```
68
+
69
+ In `simulate=True` mode, the base class generates a pyvisa-sim YAML on `connect()`:
70
+
71
+ ```python
72
+ dmm = MyDMM(
73
+ "TCPIP::192.168.1.100::INSTR",
74
+ simulate=True,
75
+ sim_config={"voltage": 5.0, "current": 0.1},
76
+ )
77
+ dmm.connect()
78
+ v = dmm.measure_voltage() # 5.0
79
+ ```
80
+
81
+ ### What `sim_config` controls
82
+
83
+ The auto-generated pyvisa-sim YAML wires `voltage` and `current` from `sim_config` into stateful properties matching `MEAS:VOLT?` and `MEAS:CURR?` queries respectively. Setters (`VOLT {value}`, `CURR {value}`) update that state. You also get:
84
+
85
+ | sim_config key | Effect |
86
+ |---|---|
87
+ | `voltage: <float>` | Default value returned by `MEAS:VOLT?`; `VOLT <value>` updates it |
88
+ | `current: <float>` | Default value returned by `MEAS:CURR?`; `CURR <value>` updates it |
89
+ | `idn: "Vendor,Model,Serial,Firmware"` | Overrides the `*IDN?` response (default: `TesterKit,SimulatedVisa,SN001,1.0`) |
90
+ | `responses: {<scpi-cmd>: <response>}` | Static query-response dialogues for any other SCPI command. The response is returned as a literal string. |
91
+ | `noise: {<name>: <pct>}` | Adds random noise to the simulated `<name>` value. Useful if your driver implements `simulate` branches by hand. |
92
+
93
+ If your driver uses non-standard SCPI commands (e.g. `MEAS:VOLT:DC?` instead of `MEAS:VOLT?`), add them via `responses`:
94
+
95
+ ```python
96
+ dmm = MyDMM(
97
+ "TCPIP::192.168.1.100::INSTR",
98
+ simulate=True,
99
+ sim_config={"responses": {"MEAS:VOLT:DC?": "5.0"}},
100
+ )
101
+ ```
102
+
103
+ The response value is the literal string returned to the query. `float("5.0")` works; `float("voltage")` does not. Don't put placeholder names like `"voltage"` in the response — put the actual value.
104
+
105
+ ### Class-level `_sim_responses`
106
+
107
+ If your driver always needs a particular static SCPI dialogue (e.g. a system query), set it on the class so every instance gets it:
108
+
109
+ ```python
110
+ class MyDMM(VisaInstrument):
111
+ _sim_responses = {
112
+ "SYST:ERR?": '0,"No error"',
113
+ "CAL:STAT?": "OK",
114
+ }
115
+ ```
116
+
117
+ Per-instance `sim_config["responses"]` merges over `_sim_responses` (instance wins).
118
+
119
+ ---
120
+
121
+ ## Non-VISA instruments
122
+
123
+ For serial, DAQmx, USB, or any other protocol, subclass `Instrument` and own `connect()` / `disconnect()` plus simulation yourself. The base class gives you `resource`, `simulate`, `sim_config`, `_connected`, four optional identity fields (`manufacturer`, `model`, `serial`, `firmware`), and the context-manager protocol.
124
+
125
+ ### Pattern: serial device
126
+
127
+ ```python
128
+ import serial
129
+
130
+ from testerkit.instruments.base import Instrument
131
+
132
+
133
+ class SerialDMM(Instrument):
134
+ """DMM with serial (RS-232) interface."""
135
+
136
+ def __init__(
137
+ self,
138
+ port: str,
139
+ baudrate: int = 9600,
140
+ simulate: bool = False,
141
+ sim_config: dict | None = None,
142
+ ):
143
+ # Pass the port as `resource` so the base records it for traceability.
144
+ super().__init__(resource=port, simulate=simulate, sim_config=sim_config)
145
+ self.baudrate = baudrate
146
+ self._serial: serial.Serial | None = None
147
+ self._sim_voltage = float((sim_config or {}).get("voltage", 0.0))
148
+
149
+ def connect(self) -> None:
150
+ if self.simulate:
151
+ self._connected = True
152
+ return
153
+ self._serial = serial.Serial(self.resource, self.baudrate, timeout=1)
154
+ self._connected = True
155
+
156
+ def disconnect(self) -> None:
157
+ if self._serial:
158
+ self._serial.close()
159
+ self._serial = None
160
+ self._connected = False
161
+
162
+ def measure_voltage(self) -> float:
163
+ if self.simulate:
164
+ return self._sim_voltage
165
+ self._serial.write(b"MEAS:VOLT?\r\n")
166
+ return float(self._serial.readline().decode().strip())
167
+ ```
168
+
169
+ ### Pattern: NI DAQmx
170
+
171
+ Guard the import so the driver imports on machines without DAQmx installed (`pip install nidaqmx`):
172
+
173
+ ```python
174
+ from typing import Any
175
+
176
+ from testerkit.instruments.base import Instrument
177
+
178
+ try:
179
+ import nidaqmx
180
+ from nidaqmx.constants import TerminalConfiguration
181
+
182
+ HAS_DAQMX = True
183
+ except ImportError:
184
+ HAS_DAQMX = False
185
+
186
+
187
+ class DaqmxAnalogInput(Instrument):
188
+ """NI DAQmx analog input channel."""
189
+
190
+ def __init__(
191
+ self,
192
+ physical_channel: str, # e.g. "Dev1/ai0"
193
+ simulate: bool = False,
194
+ sim_config: dict | None = None,
195
+ ):
196
+ super().__init__(resource=physical_channel, simulate=simulate, sim_config=sim_config)
197
+ self._task: Any = None
198
+ self._sim_voltage = float((sim_config or {}).get("voltage", 0.0))
199
+
200
+ def connect(self) -> None:
201
+ if self.simulate:
202
+ self._connected = True
203
+ return
204
+ if not HAS_DAQMX:
205
+ raise RuntimeError(
206
+ "nidaqmx is not installed; pass simulate=True or run under "
207
+ "--mock-instruments for hardware-free tests."
208
+ )
209
+ self._task = nidaqmx.Task()
210
+ self._task.ai_channels.add_ai_voltage_chan(
211
+ self.resource, terminal_config=TerminalConfiguration.RSE
212
+ )
213
+ self._connected = True
214
+
215
+ def disconnect(self) -> None:
216
+ if self._task:
217
+ self._task.close()
218
+ self._task = None
219
+ self._connected = False
220
+
221
+ def measure_voltage(self) -> float:
222
+ if self.simulate:
223
+ return self._sim_voltage
224
+ return float(self._task.read())
225
+ ```
226
+
227
+ ### Pattern: proprietary USB / HID
228
+
229
+ Guard the import so the driver imports on machines without pyusb installed (`pip install pyusb`):
230
+
231
+ ```python
232
+ import struct
233
+
234
+ from testerkit.instruments.base import Instrument
235
+
236
+ try:
237
+ import usb.core
238
+
239
+ HAS_USB = True
240
+ except ImportError:
241
+ HAS_USB = False
242
+
243
+
244
+ class USBPowerSupply(Instrument):
245
+ """Power supply with a proprietary HID protocol."""
246
+
247
+ VENDOR_ID = 0x1234
248
+ PRODUCT_ID = 0x5678
249
+
250
+ def __init__(self, simulate: bool = False, sim_config: dict | None = None):
251
+ super().__init__(
252
+ resource=f"USB:{self.VENDOR_ID:04x}:{self.PRODUCT_ID:04x}",
253
+ simulate=simulate,
254
+ sim_config=sim_config,
255
+ )
256
+ self._device = None
257
+ self._sim_voltage = 0.0
258
+ self._sim_enabled = False
259
+
260
+ def connect(self) -> None:
261
+ if self.simulate:
262
+ self._connected = True
263
+ return
264
+ if not HAS_USB:
265
+ raise RuntimeError("pyusb not installed; pass simulate=True for tests.")
266
+ self._device = usb.core.find(idVendor=self.VENDOR_ID, idProduct=self.PRODUCT_ID)
267
+ if self._device is None:
268
+ raise RuntimeError("Device not found")
269
+ self._connected = True
270
+
271
+ def disconnect(self) -> None:
272
+ self._device = None
273
+ self._connected = False
274
+
275
+ def set_voltage(self, voltage: float) -> None:
276
+ if self.simulate:
277
+ self._sim_voltage = float(voltage)
278
+ return
279
+ packet = struct.pack("<BHf", 0x10, 4, voltage)
280
+ self._device.write(0x01, packet)
281
+
282
+ def measure_output_voltage(self) -> float:
283
+ if self.simulate:
284
+ return self._sim_voltage if self._sim_enabled else 0.0
285
+ self._device.write(0x01, struct.pack("<BH", 0x20, 0))
286
+ return float(struct.unpack("<f", bytes(self._device.read(0x81, 64))[:4])[0])
287
+ ```
288
+
289
+ ### Identity fields on non-VISA drivers
290
+
291
+ `VisaInstrument` populates `manufacturer` / `model` / `serial` / `firmware` from `*IDN?` automatically. Non-VISA drivers either leave them `None` (no identity verification) or populate them in `connect()`:
292
+
293
+ ```python
294
+ def connect(self) -> None:
295
+ # ... open the connection ...
296
+ self.manufacturer = "MyVendor"
297
+ self.model = "MyDMM-1000"
298
+ self.serial = self._read_serial_from_device()
299
+ self.firmware = self._read_firmware_version()
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Running without hardware
305
+
306
+ Two paths. They behave differently and you pick based on what the test is exercising.
307
+
308
+ ### Platform mock-mode — the default
309
+
310
+ In station YAML, set `mock: true` and list the method return values under `mock_config:`. The platform substitutes a stand-in for the real driver: your driver class is never instantiated, `connect()` is never called, and methods you listed return the configured values (everything else returns `None`).
311
+
312
+ ```yaml
313
+ # stations/my_station.yaml
314
+ instruments:
315
+ dmm:
316
+ type: dmm
317
+ driver: my_pkg.MyDMM
318
+ resource: "TCPIP::192.168.1.100::INSTR"
319
+ mock: true # opt in at the instrument level
320
+ mock_config:
321
+ measure_voltage: 5.0 # dmm.measure_voltage() → 5.0 inside tests
322
+ measure_current: 0.1
323
+ query: # dict — first arg is the lookup key
324
+ "MEAS:VOLT?": "5.0"
325
+ "*IDN?": "Vendor,Model,SN1,1.0"
326
+ ```
327
+
328
+ `mock_config:` keys are **method names on your driver**, not signal names. Values can be:
329
+
330
+ - a scalar → returned on every call regardless of args
331
+ - a dict → first positional argument is the lookup key (great for SCPI `query()`)
332
+ - a callable → invoked with the call's args, return value goes back to the test
333
+
334
+ Method names you list return the configured value. Method names you don't list are silent no-ops returning `None`, regardless of whether they exist on the real driver class — the platform path uses a generic stand-in, so the typed-method surface isn't there to validate against. A typo in `mock_config:` keys won't raise; it'll just show up as a `None` reading on the row.
335
+
336
+ `--mock-instruments` (CLI) and `TESTERKIT_MOCK_INSTRUMENTS=1` (env var) force `mock: true` for every instrument in the station. Test code is identical whether the station is real or mocked:
337
+
338
+ ```python
339
+ def test_voltage(dmm, verify):
340
+ verify("output_voltage", dmm.measure_voltage())
341
+ ```
342
+
343
+ The test calls `dmm.measure_voltage()` whether `dmm` is a real `MyDMM` or the stand-in returning `5.0` from `mock_config:` — pytest never sees the difference. The auto-fixture is registered from the station YAML's `instruments:` keys; see [TesterKit fixtures](../../reference/pytest/fixtures.md#per-role-auto-fixtures). One thing to note: `isinstance(dmm, MyDMM)` is `False` under platform mock-mode (the stand-in isn't a subclass of your driver class). If a test path depends on that check, use the bringup-tier `conftest.py` pattern below where `Mock(MyDMM, ...)` does preserve isinstance.
344
+
345
+ For sidecar `mocks:` overrides and resolution order — see [mock-mode.md](mock-mode.md).
346
+
347
+ ### Driver-internal `simulate=True` — when you write the simulation yourself
348
+
349
+ `Instrument` takes a `simulate: bool` flag on `__init__` and stores it. **What that flag actually does is up to your driver.** The base class does nothing with it; the platform doesn't wire it. If you write `if self.simulate: ...` branches in your methods, those branches run. If you don't, `simulate=True` is silent.
350
+
351
+ The exception is `VisaInstrument`, which auto-generates a pyvisa-sim YAML on `connect()`. The generator wires exactly two SCPI properties: `voltage` (queries `MEAS:VOLT?`, setter `VOLT {value}`) and `current` (queries `MEAS:CURR?`, setter `CURR {value}`), plus `*IDN?` and whatever static dialogues you list in `sim_config["responses"]`. That covers a DMM measuring DC voltage / current. Resistance, frequency, scope waveforms, PSU output-enable state, anything else — your driver writes its own `if self.simulate:` branches or its own SCPI dialogue entries.
352
+
353
+ For non-VISA protocols, there is no framework simulation. `Instrument.__init__` stores `simulate=True`; the rest is your code. The DAQmx and serial examples above show the pattern.
354
+
355
+ Use `simulate=True` when you've put real work into the driver's own simulation logic — a pyvisa-sim setup that holds state, a state machine for a sequencer, a closed-loop model for a PSU — and the test needs to exercise that logic. Otherwise use platform mock-mode (`mock_config:`); it doesn't require any simulation code in the driver at all.
356
+
357
+ ---
358
+
359
+ ## Registering your driver
360
+
361
+ ### Station YAML (production path)
362
+
363
+ Point `driver:` at your driver class's dotted path. TesterKit loads that class, constructs it with the `resource:` you give, and exposes it to tests as a fixture named after the YAML key (`dmm:` → `dmm` fixture):
364
+
365
+ ```yaml
366
+ # stations/my_station.yaml
367
+ id: my_station
368
+ name: "My Test Station"
369
+
370
+ instruments:
371
+ dmm: # ← becomes the `dmm` pytest fixture
372
+ type: dmm
373
+ driver: my_pkg.drivers.MyDMM # dotted path to your driver class
374
+ catalog_ref: my_pkg.my_dmm # ← what the matcher reads
375
+ resource: "TCPIP::192.168.1.100::INSTR"
376
+ ```
377
+
378
+ Now `def test_voltage(dmm, verify): ...` resolves `dmm` to a connected `MyDMM` instance.
379
+
380
+ For [station configuration](configuring-stations.md) details (other `driver:` examples, multi-channel routing, the `catalog_ref:` link) see the how-to. For the `catalog_ref:` target schema see [catalog schema](../../reference/catalog/schema.md).
381
+
382
+ ### conftest.py (bringup tier — no station YAML yet)
383
+
384
+ Before you have a station YAML, write the fixture yourself in `conftest.py`. The TesterKit-provided `mock_instruments` fixture is `True` when `--mock-instruments` or `TESTERKIT_MOCK_INSTRUMENTS=1` is set, so the same fixture serves real and mock paths:
385
+
386
+ ```python
387
+ # tests/conftest.py
388
+ import pytest
389
+
390
+ from testerkit import Mock
391
+ from my_pkg.drivers import MyDMM
392
+
393
+
394
+ @pytest.fixture(scope="session")
395
+ def dmm(mock_instruments) -> MyDMM:
396
+ if mock_instruments:
397
+ return Mock(MyDMM, measure_voltage=5.0, measure_current=0.1)
398
+ return MyDMM("TCPIP::192.168.1.100::INSTR")
399
+ ```
400
+
401
+ This is the same pattern [tutorial step 2](../../tutorial/02-mock-instruments.md) introduces — `Mock(MyDMM, **values)` returns a `MockMyDMM` instance whose declared methods become no-ops returning your configured values. `isinstance(dmm, MyDMM)` still passes; `dmm.set_voltage(3.3)` is a silent no-op; `dmm.measure_voltage()` returns `5.0`.
402
+
403
+ Step up to station YAML once you have more than one bench or want capability matching. The station path supersedes the conftest fixture — the platform auto-registers a `dmm` fixture from `instruments.dmm:` in the YAML.
404
+
405
+ ---
406
+
407
+ ## Testing your driver
408
+
409
+ Two scopes: testing *the driver class itself* (does its `connect()` open the port? does its `measure_voltage()` parse the response correctly?) is separate from testing *a procedure that uses the driver* (which is what `mock_config:` + the TesterKit plugin handle).
410
+
411
+ For driver-level tests, exercise whatever simulation pathway you've actually built. If your driver is `VisaInstrument`-based and you only need voltage/current/IDN, the auto-generated pyvisa-sim config covers you:
412
+
413
+ ```python
414
+ import pytest
415
+
416
+ from my_pkg.drivers import MyDMM
417
+
418
+
419
+ def test_measure_voltage_simulated():
420
+ with MyDMM(
421
+ "TCPIP::192.168.1.100::INSTR",
422
+ simulate=True,
423
+ sim_config={"voltage": 3.3},
424
+ ) as dmm:
425
+ assert dmm.measure_voltage() == pytest.approx(3.3, abs=0.001)
426
+ ```
427
+
428
+ If your driver needs methods the auto-sim doesn't cover (resistance, frequency, waveform, output-enable state, anything non-voltage / non-current), add static SCPI dialogues via `sim_config["responses"]` for query-shaped methods, or write `if self.simulate:` branches inside the driver. There is no auto-simulation for non-VISA drivers — `Instrument.__init__` stores the `simulate` flag and that's it.
429
+
430
+ When in doubt for driver-level tests, use `Mock` directly — it doesn't depend on any simulation infrastructure being present:
431
+
432
+ ```python
433
+ from testerkit import Mock
434
+
435
+ from my_pkg.drivers import MyDMM
436
+
437
+
438
+ def test_overrange_handling():
439
+ dmm = Mock(MyDMM, measure_voltage=999.0)
440
+ # exercise your driver-wrapper code that consumes measure_voltage()
441
+ ```
442
+
443
+ To gate tests on real hardware availability, register a marker in your project's `conftest.py` and use `-m` to filter:
444
+
445
+ ```python
446
+ # conftest.py
447
+ def pytest_configure(config):
448
+ config.addinivalue_line("markers", "hardware: requires real instruments")
449
+ ```
450
+
451
+ ```python
452
+ @pytest.mark.hardware
453
+ def test_measure_voltage_real():
454
+ with MyDMM("TCPIP::192.168.1.100::INSTR") as dmm:
455
+ v = dmm.measure_voltage()
456
+ assert isinstance(v, float) and v > 0.0
457
+ ```
458
+
459
+ Then:
460
+
461
+ ```bash
462
+ pytest -m hardware # only hardware tests
463
+ pytest -m "not hardware" # only simulation tests (CI default)
464
+ ```
465
+
466
+ `hardware` is not a TesterKit-registered marker; the `testerkit_*` markers are listed in [TesterKit markers](../../reference/pytest/markers.md). You own this marker locally.
467
+
468
+ ---
469
+
470
+ ## Best practices
471
+
472
+ - **Pass `resource=` to the base.** Non-VISA drivers should pass the connection identifier (port, channel, vendor:product) to `super().__init__(resource=...)` so traceability and the operator UI display something meaningful.
473
+ - **Guard optional dependencies.** Wrap `import nidaqmx` / `import usb.core` / etc. in a `try / except ImportError` block so the driver imports cleanly on hardware-free hosts.
474
+ - **Let the station YAML decide mock vs real.** Don't import any mock class in your driver or test code. `mock: true` + `mock_config:` in the station block is the canonical path; the platform substitutes a stand-in that returns your listed method values. Reach for driver-internal `simulate=True` only when you've written non-trivial simulation logic that the test should exercise.
475
+ - **Capabilities live in the catalog, not in code.** Your driver class is just code — TesterKit learns "this is a DMM that measures DC voltage" from the catalog YAML you point `catalog_ref:` at. Don't try to declare capabilities via Python mixins or class attributes.
476
+
477
+ ## See also
478
+
479
+ - [Catalog schema](../../reference/catalog/schema.md) — what a `catalog/<vendor>/<model>.yaml` entry declares (the matcher's contract)
480
+ - [Catalog cookbook](../../reference/catalog/cookbook.md) — worked recipes for common datasheet shapes
481
+ - [Capabilities](../../concepts/configuration/capabilities.md) — capability model + matching algorithm
482
+ - [Configuring stations](configuring-stations.md) — the `driver:` field and the rest of the station YAML
483
+ - [Mock mode](mock-mode.md) — `--mock-instruments`, `mock_config:`, the three mock pipelines
484
+ - [TesterKit fixtures](../../reference/pytest/fixtures.md) — `instruments`, `instrument`, `pins`, `mock_instruments`, and how per-role auto-fixtures get registered
@@ -0,0 +1,13 @@
1
+ # How-To — Configuration
2
+
3
+ Get hardware on the bench, wire it into a station, and run the same code with or without instruments physically present.
4
+
5
+ - [Configuring stations](configuring-stations.md) — station YAML, instruments, environments
6
+ - [Custom instrument drivers](custom-drivers.md) — bring your own driver (PyVISA / PyMeasure / vendor)
7
+ - [Mock mode](mock-mode.md) — `--mock-instruments`, station `mock_config`, per-test mocks
8
+
9
+ ## See also
10
+
11
+ - [Concepts → Configuration](../../concepts/configuration/index.md) — the part / station / fixture / capability model behind these recipes
12
+ - [Reference → Configuration](../../reference/configuration.md) — YAML schemas for every entity
13
+ - [Tutorial → Step 7: Real Instruments](../../tutorial/07-real-instruments.md) — the canonical end-to-end walkthrough