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,507 @@
1
+ """Store workloads — the single definition of what we measure.
2
+
3
+ Both ``testerkit benchmark`` and the perf test suite build their cases from
4
+ this module, so user numbers and CI numbers come from identical code.
5
+
6
+ Each operation is swept across a list of ``scales`` (unit of work) and,
7
+ for writes, a list of ``writers`` (concurrency) — every combination is a
8
+ separate :class:`Workload` case, hence a separate row in the report.
9
+
10
+ Every workload measures the SHIPPED path: channel queries route through
11
+ the channels daemon (not a disk glob); file resolves route through the
12
+ catalog daemon (not a date-dir walk). A user's number matches what they
13
+ actually experience.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import Callable
19
+ from datetime import UTC, datetime
20
+ from typing import NamedTuple
21
+ from uuid import UUID, uuid4
22
+
23
+ from testerkit.benchmark.core import BenchContext, Workload
24
+
25
+
26
+ class _Releaser:
27
+ """Tiny closeable that runs ``fn`` on teardown (for daemon releases)."""
28
+
29
+ def __init__(self, fn: Callable[[], object]) -> None:
30
+ self._fn = fn
31
+
32
+ def close(self) -> None:
33
+ self._fn()
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Sample data builders (shared with the perf tests)
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ def make_measurement(session_id: UUID, i: int):
42
+ """A representative MeasurementRecorded event."""
43
+ from testerkit.data.events import MeasurementRecorded
44
+
45
+ return MeasurementRecorded(
46
+ session_id=session_id,
47
+ step_name=f"step_{i % 10}",
48
+ step_index=i % 10,
49
+ measurement_name=f"voltage_{i}",
50
+ value=3.3 + (i % 100) * 0.01,
51
+ unit="V",
52
+ outcome="passed",
53
+ limit_low=3.0,
54
+ limit_high=3.6,
55
+ )
56
+
57
+
58
+ def build_run(seed: int, *, n_steps: int = 10, n_meas: int = 5):
59
+ """A finalized TestRun with ``n_steps`` steps x ``n_meas`` measurements."""
60
+ from testerkit.data.models import (
61
+ UUT,
62
+ Measurement,
63
+ Outcome,
64
+ TestRun,
65
+ TestStep,
66
+ TestVector,
67
+ )
68
+
69
+ return TestRun(
70
+ id=uuid4(),
71
+ started_at=datetime(2026, 6, 1, 12, 0, 0, tzinfo=UTC),
72
+ ended_at=datetime(2026, 6, 1, 12, 1, 0, tzinfo=UTC),
73
+ uut=UUT(serial=f"SN-{seed:06d}"),
74
+ outcome=Outcome.PASSED,
75
+ steps=[
76
+ TestStep(
77
+ name=f"step_{s}",
78
+ outcome=Outcome.PASSED,
79
+ started_at=datetime(2026, 6, 1, 12, 0, 0, tzinfo=UTC),
80
+ ended_at=datetime(2026, 6, 1, 12, 0, 30, tzinfo=UTC),
81
+ vectors=[
82
+ TestVector(
83
+ outcome=Outcome.PASSED,
84
+ measurements=[
85
+ Measurement(name=f"m_{m}", value=3.3 + m * 0.01, outcome=Outcome.PASSED)
86
+ for m in range(n_meas)
87
+ ],
88
+ )
89
+ ],
90
+ )
91
+ for s in range(n_steps)
92
+ ],
93
+ )
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Events
98
+ # ---------------------------------------------------------------------------
99
+
100
+
101
+ def _setup_events_emit(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
102
+ def setup(ctx: BenchContext) -> Callable[[], object]:
103
+ from testerkit.data.event_store import EventStore
104
+
105
+ es: EventStore = ctx.track(EventStore(_data_dir=ctx.data_dir)) # type: ignore[assignment]
106
+
107
+ def emit_all() -> None:
108
+ # Fresh session + flush() per call so the timed work is the
109
+ # DURABLE path (written to disk AND acked by the daemon), not
110
+ # an in-memory batch fill.
111
+ sid = uuid4()
112
+ for i in range(scale):
113
+ es.emit(make_measurement(sid, i))
114
+ es.flush()
115
+
116
+ return emit_all
117
+
118
+ return setup
119
+
120
+
121
+ def _setup_events_query(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
122
+ def setup(ctx: BenchContext) -> Callable[[], object]:
123
+ from testerkit.data.event_store import EventStore
124
+
125
+ es: EventStore = ctx.track(EventStore(_data_dir=ctx.data_dir)) # type: ignore[assignment]
126
+ sid = uuid4()
127
+ for i in range(scale):
128
+ es.emit(make_measurement(sid, i))
129
+
130
+ def query_all() -> object:
131
+ return es.events(session_id=sid)
132
+
133
+ return query_all
134
+
135
+ return setup
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Runs (the materialized view of events)
140
+ # ---------------------------------------------------------------------------
141
+
142
+
143
+ def _setup_runs_save(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
144
+ def setup(ctx: BenchContext) -> Callable[[], object]:
145
+ # Mirror the real finalize path (``save_test_run`` = atomic file
146
+ # write, no synchronous daemon notify).
147
+ from testerkit.data.backends.parquet import ParquetBackend
148
+
149
+ backend = ParquetBackend(data_dir=ctx.data_dir)
150
+
151
+ def save_n() -> None:
152
+ for _ in range(scale):
153
+ backend.save_test_run(build_run(uuid4().int % 1_000_000))
154
+
155
+ return save_n
156
+
157
+ return setup
158
+
159
+
160
+ def _populate_runs(ctx: BenchContext, n: int) -> None:
161
+ from testerkit.data.backends.parquet import ParquetBackend
162
+ from testerkit.data.run_store import RunStore
163
+
164
+ backend = ParquetBackend(data_dir=ctx.data_dir)
165
+ store = RunStore(_data_dir=ctx.data_dir)
166
+ try:
167
+ for i in range(n):
168
+ store.notify_new_run(backend.save_test_run(build_run(i)))
169
+ finally:
170
+ store.close()
171
+
172
+
173
+ def _setup_runs_list(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
174
+ def setup(ctx: BenchContext) -> Callable[[], object]:
175
+ from testerkit.analysis.runs_query import RunsQuery
176
+
177
+ _populate_runs(ctx, scale)
178
+
179
+ def list_page() -> object:
180
+ with RunsQuery(_data_dir=ctx.data_dir) as q:
181
+ return q.list_recent(limit=50)
182
+
183
+ return list_page
184
+
185
+ return setup
186
+
187
+
188
+ def _setup_runs_steps(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
189
+ def setup(ctx: BenchContext) -> Callable[[], object]:
190
+ from testerkit.analysis.runs_query import RunsQuery
191
+ from testerkit.analysis.steps_query import StepsQuery
192
+
193
+ _populate_runs(ctx, scale)
194
+ with RunsQuery(_data_dir=ctx.data_dir) as q:
195
+ rows = q.list_recent(limit=1)
196
+ run_id = str(rows[0].run_id) if rows else ""
197
+
198
+ def steps() -> object:
199
+ with StepsQuery(_data_dir=ctx.data_dir) as q:
200
+ return q.list_for_run(run_id)
201
+
202
+ return steps
203
+
204
+ return setup
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Channels — write through a serve producer, query the daemon
209
+ # ---------------------------------------------------------------------------
210
+
211
+
212
+ def _setup_channels_write(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
213
+ def setup(ctx: BenchContext) -> Callable[[], object]:
214
+ from testerkit.data.channels.store import ChannelStore
215
+
216
+ store = ChannelStore(ctx.data_dir, uuid4(), flush_threshold=100, serve=True)
217
+ store.open()
218
+ ctx.track(store)
219
+
220
+ def write_all() -> None:
221
+ for i in range(scale):
222
+ store.write("sensor.temp", 25.0 + i * 0.01, unit="C")
223
+
224
+ return write_all
225
+
226
+ return setup
227
+
228
+
229
+ def _setup_channels_query(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
230
+ def setup(ctx: BenchContext) -> Callable[[], object]:
231
+ from testerkit.data.channels import flight_manager
232
+ from testerkit.data.channels.client import ChannelClient
233
+ from testerkit.data.channels.store import ChannelStore
234
+
235
+ # Unique channel per case — all channel cases share the temp dir's
236
+ # one daemon/index, so a fixed name would let this query count
237
+ # samples written by other cases (polluting the row count).
238
+ channel_id = f"bench.{uuid4().hex[:8]}"
239
+ producer = ChannelStore(ctx.data_dir, uuid4(), flush_threshold=100, serve=True)
240
+ producer.open()
241
+ ctx.track(producer)
242
+ for i in range(scale):
243
+ producer.write(channel_id, 25.0 + i * 0.01, unit="C")
244
+
245
+ channels_dir = ctx.data_dir / "channels"
246
+ location = flight_manager.acquire(channels_dir)
247
+ client = ChannelClient(location)
248
+ ctx.track(client)
249
+ ctx.track(_Releaser(lambda: flight_manager.release(channels_dir)))
250
+
251
+ def query() -> object:
252
+ return client.query(channel_id)
253
+
254
+ return query
255
+
256
+ return setup
257
+
258
+
259
+ def _setup_channels_stream(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
260
+ def setup(ctx: BenchContext) -> Callable[[], object]:
261
+ import testerkit.channels as channels_mod
262
+ from testerkit.data.channels.store import ChannelStore
263
+ from testerkit.execution._state import set_channel_store
264
+
265
+ store = ChannelStore(ctx.data_dir, uuid4(), flush_threshold=50, serve=True)
266
+ store.open()
267
+ ctx.track(store)
268
+ set_channel_store(store)
269
+ ctx.track(_Releaser(lambda: set_channel_store(None)))
270
+ values = [25.0 + (i % 100) * 0.01 for i in range(scale)]
271
+
272
+ def stream_one() -> None:
273
+ with channels_mod.stream("dmm.voltage") as sink:
274
+ for v in values:
275
+ sink.write(v)
276
+
277
+ return stream_one
278
+
279
+ return setup
280
+
281
+
282
+ # ---------------------------------------------------------------------------
283
+ # Files — object store + catalog daemon
284
+ # ---------------------------------------------------------------------------
285
+
286
+
287
+ def _acquire_catalog(ctx: BenchContext):
288
+ from testerkit.data.files.catalog_manager import acquire, release
289
+
290
+ files_dir = ctx.data_dir / "files"
291
+ acquire(files_dir)
292
+ ctx.track(_Releaser(lambda: release(files_dir)))
293
+ return files_dir
294
+
295
+
296
+ def _setup_files_write(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
297
+ def setup(ctx: BenchContext) -> Callable[[], object]:
298
+ from testerkit.data.files.store import FileStore
299
+
300
+ _acquire_catalog(ctx)
301
+ store = FileStore(_data_dir=ctx.data_dir)
302
+ sid = uuid4().hex
303
+ payload = b"x" * (100 * 1024) # 100 KB blobs
304
+
305
+ def write_n() -> None:
306
+ for _ in range(scale):
307
+ store.write(f"blob_{uuid4().hex[:8]}", payload, session_id=sid)
308
+
309
+ return write_n
310
+
311
+ return setup
312
+
313
+
314
+ def _setup_files_resolve(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
315
+ def setup(ctx: BenchContext) -> Callable[[], object]:
316
+ from testerkit.data.files.catalog_manager import resolve_uri
317
+ from testerkit.data.files.store import FileStore
318
+
319
+ files_dir = _acquire_catalog(ctx)
320
+ store = FileStore(_data_dir=ctx.data_dir)
321
+ uri = store.write("resolveme", b"x" * 256, session_id=uuid4().hex)
322
+
323
+ def resolve() -> object:
324
+ return resolve_uri(files_dir, uri)
325
+
326
+ return resolve
327
+
328
+ return setup
329
+
330
+
331
+ def _setup_files_list(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
332
+ def setup(ctx: BenchContext) -> Callable[[], object]:
333
+ from testerkit.data.files.catalog_manager import list_recent
334
+ from testerkit.data.files.store import FileStore
335
+
336
+ files_dir = _acquire_catalog(ctx)
337
+ store = FileStore(_data_dir=ctx.data_dir)
338
+ sid = uuid4().hex
339
+ for i in range(scale):
340
+ store.write(f"list_{i}_{uuid4().hex[:6]}", b"y" * 128, session_id=sid)
341
+
342
+ def listing() -> object:
343
+ return list_recent(files_dir, 50)
344
+
345
+ return listing
346
+
347
+ return setup
348
+
349
+
350
+ def _setup_files_stream_raw(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
351
+ def setup(ctx: BenchContext) -> Callable[[], object]:
352
+ from testerkit.data.files.store import FileStore
353
+
354
+ store = FileStore(_data_dir=ctx.data_dir)
355
+ sid = uuid4().hex
356
+ chunk = b"a" * (64 * 1024) # 64 KB chunks
357
+
358
+ def stream_one() -> None:
359
+ sink = store.open_stream(name=f"raw_{uuid4().hex[:8]}", format="raw", session_id=sid)
360
+ for _ in range(scale):
361
+ sink.write(chunk)
362
+ sink.close()
363
+
364
+ return stream_one
365
+
366
+ return setup
367
+
368
+
369
+ def _setup_channels_block(scale: int) -> Callable[[BenchContext], Callable[[], object]]:
370
+ """The high-rate channel path: one array (waveform block) per write —
371
+ one RPC carries ``scale`` points, vs one RPC per scalar in ``channels.write``."""
372
+
373
+ def setup(ctx: BenchContext) -> Callable[[], object]:
374
+ from testerkit.data.channels.store import ChannelStore
375
+
376
+ store = ChannelStore(ctx.data_dir, uuid4(), flush_threshold=100, serve=True)
377
+ store.open()
378
+ ctx.track(store)
379
+ block = [25.0 + (i % 100) * 0.01 for i in range(scale)]
380
+
381
+ def write_block() -> None:
382
+ store.write("scope.ch1", block, sample_interval=1e-6)
383
+
384
+ return write_block
385
+
386
+ return setup
387
+
388
+
389
+ # ---------------------------------------------------------------------------
390
+ # Case registry — sweep scales (unit) and writers (concurrency)
391
+ # ---------------------------------------------------------------------------
392
+
393
+
394
+ class _Op(NamedTuple):
395
+ store: str
396
+ unit: str
397
+ factory: Callable[[int], Callable[[BenchContext], Callable[[], object]]]
398
+ fast_scales: list[int]
399
+ full_scales: list[int]
400
+ bytes_per_unit: int | None # set for byte-heavy ops → enables bytes/s
401
+
402
+
403
+ # Write ops include a ``scale=1`` floor anchor so the per-call overhead is
404
+ # measured directly (not extrapolated). bytes_per_unit is set only where a
405
+ # record has a fixed byte size (channels samples, file blobs/chunks) — for
406
+ # those, the report shows bytes/s, which is the real throughput axis.
407
+ # Bytes per record, so every op reports a real byte rate. Events/runs are
408
+ # measured from a serialized sample; the rest are known fixed sizes.
409
+ _EVENT_BYTES = len(make_measurement(uuid4(), 0).model_dump_json().encode())
410
+ _RUN_BYTES = len(build_run(0).model_dump_json().encode())
411
+ _SCALAR_BYTES = 8 # one float sample / point
412
+ _ROW_BYTES = 256 # one summary/metadata row from a query
413
+
414
+ # Each scale is its own row; the smallest (often 1) is the single-call case
415
+ # — its rate is overhead-bound but a valid data point. Call overhead is the
416
+ # line's intercept; per-record is the slope.
417
+ _OPS: dict[str, _Op] = {
418
+ "events.emit": _Op(
419
+ "events", "events", _setup_events_emit, [1, 1000], [1, 100, 1000, 10000], _EVENT_BYTES
420
+ ),
421
+ "events.query": _Op(
422
+ "events", "events", _setup_events_query, [100, 1000], [100, 1000, 10000], _EVENT_BYTES
423
+ ),
424
+ "channels.write": _Op(
425
+ "channels",
426
+ "samples",
427
+ _setup_channels_write,
428
+ [1, 1000],
429
+ [1, 100, 1000, 10000],
430
+ _SCALAR_BYTES,
431
+ ),
432
+ "channels.block": _Op(
433
+ "channels",
434
+ "points",
435
+ _setup_channels_block,
436
+ [1000, 10000],
437
+ [1000, 10000, 100000],
438
+ _SCALAR_BYTES,
439
+ ),
440
+ "channels.query": _Op(
441
+ "channels", "samples", _setup_channels_query, [100, 1000], [100, 1000, 10000], _SCALAR_BYTES
442
+ ),
443
+ "channels.stream": _Op(
444
+ "channels",
445
+ "samples",
446
+ _setup_channels_stream,
447
+ [1, 1000],
448
+ [1, 100, 1000, 10000],
449
+ _SCALAR_BYTES,
450
+ ),
451
+ "runs.save": _Op("runs", "runs", _setup_runs_save, [1, 10], [1, 10, 100], _RUN_BYTES),
452
+ "runs.list": _Op("runs", "runs", _setup_runs_list, [10, 100], [10, 100, 1000], _ROW_BYTES),
453
+ "runs.steps": _Op("runs", "runs", _setup_runs_steps, [10, 100], [10, 100, 1000], _ROW_BYTES),
454
+ "files.write": _Op("files", "artifacts", _setup_files_write, [1, 10], [1, 10, 100], 100 * 1024),
455
+ "files.resolve": _Op("files", "files", _setup_files_resolve, [1], [1], _ROW_BYTES),
456
+ "files.list": _Op("files", "files", _setup_files_list, [10, 100], [10, 100, 1000], _ROW_BYTES),
457
+ "files.stream_raw": _Op(
458
+ "files", "chunks", _setup_files_stream_raw, [1, 64], [1, 64, 256], 64 * 1024
459
+ ),
460
+ }
461
+
462
+ # Write ops get a concurrency sweep at one representative scale (which is
463
+ # also present in the 1-writer scale sweep, so speedup has a baseline).
464
+ _CONCURRENCY: dict[str, int] = {
465
+ "events.emit": 1000,
466
+ "channels.write": 1000,
467
+ "files.write": 10,
468
+ "runs.save": 10,
469
+ }
470
+
471
+
472
+ def build_cases(tier: str = "fast") -> list[Workload]:
473
+ """All cases for ``tier``: scale sweep (1 writer) + concurrency sweep."""
474
+ full = tier == "full"
475
+ writer_counts = [2, 4] if full else [2]
476
+ cases: list[Workload] = []
477
+ for op, spec in _OPS.items():
478
+ scales = spec.full_scales if full else spec.fast_scales
479
+ for scale in scales:
480
+ cases.append(
481
+ Workload(
482
+ op,
483
+ spec.store,
484
+ spec.unit,
485
+ scale,
486
+ writers=1,
487
+ setup=spec.factory(scale),
488
+ tier=tier,
489
+ bytes_per_unit=spec.bytes_per_unit,
490
+ )
491
+ )
492
+ for op, rep_scale in _CONCURRENCY.items():
493
+ spec = _OPS[op]
494
+ for w in writer_counts:
495
+ cases.append(
496
+ Workload(
497
+ op,
498
+ spec.store,
499
+ spec.unit,
500
+ rep_scale,
501
+ writers=w,
502
+ setup=None,
503
+ tier=tier,
504
+ bytes_per_unit=spec.bytes_per_unit,
505
+ )
506
+ )
507
+ return cases
@@ -0,0 +1,6 @@
1
+ """Instrument catalog: structured capability data for real instruments.
2
+
3
+ Import directly from the submodule:
4
+
5
+ from testerkit.models.catalog import InstrumentCatalogEntry
6
+ """
@@ -0,0 +1,61 @@
1
+ id: generic_dmm
2
+ manufacturer: Generic
3
+ model: DMM
4
+ name: Generic Digital Multimeter
5
+ description: General-purpose DMM for DC/AC voltage, current, and resistance measurement
6
+ type: dmm
7
+ scaffold: true
8
+ channels:
9
+ '1':
10
+ terminals:
11
+ - hi
12
+ - lo
13
+ connector: binding_post
14
+ ground: shared
15
+ capabilities:
16
+ - function: dc_voltage
17
+ direction: input
18
+ signals:
19
+ voltage:
20
+ range:
21
+ min: 0.001
22
+ max: 1000
23
+ unit: V
24
+ - function: ac_voltage
25
+ direction: input
26
+ signals:
27
+ voltage:
28
+ range:
29
+ min: 0.001
30
+ max: 750
31
+ unit: V
32
+ attributes:
33
+ bandwidth:
34
+ range:
35
+ min: 3
36
+ max: 300000
37
+ unit: Hz
38
+ - function: dc_current
39
+ direction: input
40
+ signals:
41
+ current:
42
+ range:
43
+ min: 1.0e-07
44
+ max: 10
45
+ unit: A
46
+ - function: ac_current
47
+ direction: input
48
+ signals:
49
+ current:
50
+ range:
51
+ min: 1.0e-06
52
+ max: 10
53
+ unit: A
54
+ - function: resistance
55
+ direction: input
56
+ signals:
57
+ resistance:
58
+ range:
59
+ min: 0.01
60
+ max: 100000000
61
+ unit: ohm
@@ -0,0 +1,31 @@
1
+ id: generic_eload
2
+ manufacturer: Generic
3
+ model: Electronic Load
4
+ name: Generic DC Electronic Load
5
+ description: Programmable DC electronic load for sinking current from UUT outputs
6
+ type: eload
7
+ scaffold: true
8
+ channels:
9
+ '1':
10
+ terminals:
11
+ - hi
12
+ - lo
13
+ connector: binding_post
14
+ ground: shared
15
+ capabilities:
16
+ - function: dc_current
17
+ direction: input
18
+ signals:
19
+ current:
20
+ range:
21
+ min: 0
22
+ max: 30
23
+ unit: A
24
+ - function: dc_voltage
25
+ direction: input
26
+ signals:
27
+ voltage:
28
+ range:
29
+ min: 0
30
+ max: 150
31
+ unit: V
@@ -0,0 +1,56 @@
1
+ id: generic_fgen
2
+ manufacturer: Generic
3
+ model: Function Generator
4
+ name: Generic Function Generator
5
+ description: Arbitrary waveform and function generator for signal sourcing
6
+ type: fgen
7
+ scaffold: true
8
+ channels:
9
+ CH1:
10
+ terminals:
11
+ - signal
12
+ connector: bnc
13
+ ground: shared
14
+ CH2:
15
+ terminals:
16
+ - signal
17
+ connector: bnc
18
+ ground: shared
19
+ capabilities:
20
+ - function: waveform
21
+ direction: output
22
+ signals:
23
+ voltage:
24
+ range:
25
+ min: 0.001
26
+ max: 10
27
+ unit: Vpp
28
+ frequency:
29
+ range:
30
+ min: 0.001
31
+ max: 25000000
32
+ unit: Hz
33
+ attributes:
34
+ waveforms:
35
+ options:
36
+ - sine
37
+ - square
38
+ - triangle
39
+ - ramp
40
+ - pulse
41
+ - noise
42
+ - dc
43
+ channels:
44
+ - CH1
45
+ - CH2
46
+ - function: dc_voltage
47
+ direction: output
48
+ signals:
49
+ voltage:
50
+ range:
51
+ min: -10
52
+ max: 10
53
+ unit: V
54
+ channels:
55
+ - CH1
56
+ - CH2
@@ -0,0 +1,45 @@
1
+ id: generic_oscilloscope
2
+ manufacturer: Generic
3
+ model: Oscilloscope
4
+ name: Generic Digital Oscilloscope
5
+ description: Multi-channel oscilloscope for time-domain voltage waveform capture
6
+ type: scope
7
+ scaffold: true
8
+ channels:
9
+ CH1:
10
+ terminals:
11
+ - signal
12
+ connector: bnc
13
+ ground: shared
14
+ CH2:
15
+ terminals:
16
+ - signal
17
+ connector: bnc
18
+ ground: shared
19
+ CH3:
20
+ terminals:
21
+ - signal
22
+ connector: bnc
23
+ ground: shared
24
+ CH4:
25
+ terminals:
26
+ - signal
27
+ connector: bnc
28
+ ground: shared
29
+ capabilities:
30
+ - function: waveform
31
+ direction: input
32
+ signals:
33
+ voltage:
34
+ range:
35
+ min: 0.001
36
+ max: 400
37
+ unit: V
38
+ attributes:
39
+ bandwidth:
40
+ value: 50000000
41
+ unit: Hz
42
+ sample_rate:
43
+ value: 1000000000
44
+ unit: Sa/s
45
+ channels: CH[1:4]