quality-harness 0.5.0__tar.gz
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.
- quality_harness-0.5.0/.gitignore +45 -0
- quality_harness-0.5.0/AGENTS.md +45 -0
- quality_harness-0.5.0/Makefile +31 -0
- quality_harness-0.5.0/PKG-INFO +21 -0
- quality_harness-0.5.0/build.sh +5 -0
- quality_harness-0.5.0/common/AGENTS.md +20 -0
- quality_harness-0.5.0/common/README.md +34 -0
- quality_harness-0.5.0/common/harness_common/__init__.py +75 -0
- quality_harness-0.5.0/common/harness_common/access/__init__.py +1 -0
- quality_harness-0.5.0/common/harness_common/access/client.py +127 -0
- quality_harness-0.5.0/common/harness_common/access/context.py +29 -0
- quality_harness-0.5.0/common/harness_common/access/datasource.py +25 -0
- quality_harness-0.5.0/common/harness_common/access/fixture.py +198 -0
- quality_harness-0.5.0/common/harness_common/execution/__init__.py +3 -0
- quality_harness-0.5.0/common/harness_common/execution/artifact.py +17 -0
- quality_harness-0.5.0/common/harness_common/execution/execution.py +25 -0
- quality_harness-0.5.0/common/harness_common/execution/experiment.py +60 -0
- quality_harness-0.5.0/common/harness_common/execution/operation.py +37 -0
- quality_harness-0.5.0/common/harness_common/execution/outcome.py +15 -0
- quality_harness-0.5.0/common/harness_common/execution/overlay.py +82 -0
- quality_harness-0.5.0/common/harness_common/execution/reducer.py +26 -0
- quality_harness-0.5.0/common/harness_common/execution/run.py +34 -0
- quality_harness-0.5.0/common/harness_common/execution/verdict.py +252 -0
- quality_harness-0.5.0/common/harness_common/identity.py +13 -0
- quality_harness-0.5.0/common/harness_common/report_kit/__init__.py +19 -0
- quality_harness-0.5.0/common/harness_common/report_kit/doc.py +134 -0
- quality_harness-0.5.0/common/harness_common/report_kit/html.py +304 -0
- quality_harness-0.5.0/common/harness_common/system/__init__.py +1 -0
- quality_harness-0.5.0/common/harness_common/system/component.py +30 -0
- quality_harness-0.5.0/common/harness_common/system/deployment.py +31 -0
- quality_harness-0.5.0/common/harness_common/system/environment.py +55 -0
- quality_harness-0.5.0/common/harness_common/system/forge.py +12 -0
- quality_harness-0.5.0/common/harness_common/system/host.py +45 -0
- quality_harness-0.5.0/common/harness_common/system/product.py +21 -0
- quality_harness-0.5.0/common/harness_common/system/repository.py +26 -0
- quality_harness-0.5.0/common/harness_common/system/resource.py +92 -0
- quality_harness-0.5.0/common/harness_common/system/service.py +32 -0
- quality_harness-0.5.0/common/harness_common/system/workload.py +95 -0
- quality_harness-0.5.0/common/harness_common/tests/__init__.py +0 -0
- quality_harness-0.5.0/common/harness_common/tests/test_client.py +155 -0
- quality_harness-0.5.0/common/harness_common/tests/test_context.py +93 -0
- quality_harness-0.5.0/common/harness_common/tests/test_datasource.py +29 -0
- quality_harness-0.5.0/common/harness_common/tests/test_environment_service.py +213 -0
- quality_harness-0.5.0/common/harness_common/tests/test_fixture.py +212 -0
- quality_harness-0.5.0/common/harness_common/tests/test_import_boundary.py +45 -0
- quality_harness-0.5.0/common/harness_common/tests/test_overlay.py +38 -0
- quality_harness-0.5.0/common/harness_common/tests/test_report_kit.py +33 -0
- quality_harness-0.5.0/common/harness_common/tests/test_resource.py +62 -0
- quality_harness-0.5.0/common/harness_common/tests/test_source_identity_conformance.py +101 -0
- quality_harness-0.5.0/common/harness_common/tests/test_verdict_evalperf.py +291 -0
- quality_harness-0.5.0/common/harness_common/tests/test_workload.py +100 -0
- quality_harness-0.5.0/common/pyproject.toml +15 -0
- quality_harness-0.5.0/e2e_harness/AGENTS.md +57 -0
- quality_harness-0.5.0/e2e_harness/__init__.py +80 -0
- quality_harness-0.5.0/e2e_harness/__main__.py +10 -0
- quality_harness-0.5.0/e2e_harness/assertion.py +144 -0
- quality_harness-0.5.0/e2e_harness/casegen/__init__.py +41 -0
- quality_harness-0.5.0/e2e_harness/casegen/compiler.py +288 -0
- quality_harness-0.5.0/e2e_harness/casegen/contract.py +344 -0
- quality_harness-0.5.0/e2e_harness/casegen/discover.py +390 -0
- quality_harness-0.5.0/e2e_harness/caserun.py +241 -0
- quality_harness-0.5.0/e2e_harness/cli.py +167 -0
- quality_harness-0.5.0/e2e_harness/command.py +71 -0
- quality_harness-0.5.0/e2e_harness/core/__init__.py +0 -0
- quality_harness-0.5.0/e2e_harness/core/config.py +274 -0
- quality_harness-0.5.0/e2e_harness/core/profile.py +31 -0
- quality_harness-0.5.0/e2e_harness/engine.py +227 -0
- quality_harness-0.5.0/e2e_harness/environment.py +99 -0
- quality_harness-0.5.0/e2e_harness/examples/chat_cases.yaml +17 -0
- quality_harness-0.5.0/e2e_harness/examples/note_cases.yaml +27 -0
- quality_harness-0.5.0/e2e_harness/judge/__init__.py +0 -0
- quality_harness-0.5.0/e2e_harness/judge/metric/__init__.py +18 -0
- quality_harness-0.5.0/e2e_harness/judge/metric/base.py +89 -0
- quality_harness-0.5.0/e2e_harness/judge/metric/outcome.py +151 -0
- quality_harness-0.5.0/e2e_harness/judge/metric/registry.py +71 -0
- quality_harness-0.5.0/e2e_harness/matrix.py +32 -0
- quality_harness-0.5.0/e2e_harness/reducer.py +41 -0
- quality_harness-0.5.0/e2e_harness/runner/__init__.py +0 -0
- quality_harness-0.5.0/e2e_harness/runner/async_base.py +24 -0
- quality_harness-0.5.0/e2e_harness/runner/async_json_runner.py +74 -0
- quality_harness-0.5.0/e2e_harness/runner/async_sse_runner.py +122 -0
- quality_harness-0.5.0/e2e_harness/runner/base.py +102 -0
- quality_harness-0.5.0/e2e_harness/runner/events.py +103 -0
- quality_harness-0.5.0/e2e_harness/runner/headers.py +30 -0
- quality_harness-0.5.0/e2e_harness/runner/json_runner.py +71 -0
- quality_harness-0.5.0/e2e_harness/runner/sse_runner.py +141 -0
- quality_harness-0.5.0/e2e_harness/temporal.py +71 -0
- quality_harness-0.5.0/e2e_harness/tests/__init__.py +0 -0
- quality_harness-0.5.0/e2e_harness/tests/test_assertion.py +75 -0
- quality_harness-0.5.0/e2e_harness/tests/test_async_runners.py +305 -0
- quality_harness-0.5.0/e2e_harness/tests/test_casegen.py +115 -0
- quality_harness-0.5.0/e2e_harness/tests/test_casegen_draft.py +98 -0
- quality_harness-0.5.0/e2e_harness/tests/test_caserun.py +140 -0
- quality_harness-0.5.0/e2e_harness/tests/test_caserun_conformance.py +79 -0
- quality_harness-0.5.0/e2e_harness/tests/test_command.py +84 -0
- quality_harness-0.5.0/e2e_harness/tests/test_contract.py +264 -0
- quality_harness-0.5.0/e2e_harness/tests/test_core.py +247 -0
- quality_harness-0.5.0/e2e_harness/tests/test_discover_scripts.py +214 -0
- quality_harness-0.5.0/e2e_harness/tests/test_e2e_cli.py +112 -0
- quality_harness-0.5.0/e2e_harness/tests/test_e2e_engine.py +105 -0
- quality_harness-0.5.0/e2e_harness/tests/test_e2e_engine_integration.py +82 -0
- quality_harness-0.5.0/e2e_harness/tests/test_e2e_sse.py +86 -0
- quality_harness-0.5.0/e2e_harness/tests/test_environment.py +161 -0
- quality_harness-0.5.0/e2e_harness/tests/test_events.py +110 -0
- quality_harness-0.5.0/e2e_harness/tests/test_module_layout.py +123 -0
- quality_harness-0.5.0/e2e_harness/tests/test_outcome_metric.py +139 -0
- quality_harness-0.5.0/e2e_harness/tests/test_runner_integration.py +162 -0
- quality_harness-0.5.0/e2e_harness/tests/test_sse_parser.py +102 -0
- quality_harness-0.5.0/e2e_harness/tests/test_sse_runner.py +213 -0
- quality_harness-0.5.0/e2e_harness/tests/test_tech_debt.py +36 -0
- quality_harness-0.5.0/e2e_harness/tests/test_temporal.py +54 -0
- quality_harness-0.5.0/eval_harness/AGENTS.md +50 -0
- quality_harness-0.5.0/eval_harness/README.md +96 -0
- quality_harness-0.5.0/eval_harness/__init__.py +53 -0
- quality_harness-0.5.0/eval_harness/cli.py +69 -0
- quality_harness-0.5.0/eval_harness/config.py +128 -0
- quality_harness-0.5.0/eval_harness/engine.py +174 -0
- quality_harness-0.5.0/eval_harness/ingest.py +107 -0
- quality_harness-0.5.0/eval_harness/materials/cases/smoke.yaml +21 -0
- quality_harness-0.5.0/eval_harness/materials/experiments/smoke.yaml +17 -0
- quality_harness-0.5.0/eval_harness/metric/__init__.py +9 -0
- quality_harness-0.5.0/eval_harness/metric/aggregate.py +67 -0
- quality_harness-0.5.0/eval_harness/metric/base.py +71 -0
- quality_harness-0.5.0/eval_harness/metric/builtins.py +98 -0
- quality_harness-0.5.0/eval_harness/metric/llm_judge.py +81 -0
- quality_harness-0.5.0/eval_harness/metric/registry.py +40 -0
- quality_harness-0.5.0/eval_harness/model/__init__.py +8 -0
- quality_harness-0.5.0/eval_harness/model/evalset.py +145 -0
- quality_harness-0.5.0/eval_harness/model/experiment.py +213 -0
- quality_harness-0.5.0/eval_harness/model/sample.py +81 -0
- quality_harness-0.5.0/eval_harness/produce/__init__.py +7 -0
- quality_harness-0.5.0/eval_harness/produce/mock.py +37 -0
- quality_harness-0.5.0/eval_harness/report/__init__.py +11 -0
- quality_harness-0.5.0/eval_harness/report/html.py +240 -0
- quality_harness-0.5.0/eval_harness/report/pivot.py +211 -0
- quality_harness-0.5.0/eval_harness/report/render.py +290 -0
- quality_harness-0.5.0/eval_harness/ruff.toml +11 -0
- quality_harness-0.5.0/eval_harness/schedule/__init__.py +7 -0
- quality_harness-0.5.0/eval_harness/schedule/ratelimit.py +84 -0
- quality_harness-0.5.0/eval_harness/schedule/reconcile.py +242 -0
- quality_harness-0.5.0/eval_harness/tests/__init__.py +0 -0
- quality_harness-0.5.0/eval_harness/tests/eval_cases.py +39 -0
- quality_harness-0.5.0/eval_harness/tests/test_aggregate.py +40 -0
- quality_harness-0.5.0/eval_harness/tests/test_config_engine.py +193 -0
- quality_harness-0.5.0/eval_harness/tests/test_importer_evalset.py +102 -0
- quality_harness-0.5.0/eval_harness/tests/test_ingest.py +56 -0
- quality_harness-0.5.0/eval_harness/tests/test_llmspec.py +97 -0
- quality_harness-0.5.0/eval_harness/tests/test_metric_extras.py +97 -0
- quality_harness-0.5.0/eval_harness/tests/test_model.py +130 -0
- quality_harness-0.5.0/eval_harness/tests/test_multicorpus.py +99 -0
- quality_harness-0.5.0/eval_harness/tests/test_reconcile.py +272 -0
- quality_harness-0.5.0/eval_harness/tests/test_report.py +149 -0
- quality_harness-0.5.0/eval_harness/tests/test_worksheet.py +175 -0
- quality_harness-0.5.0/eval_harness/verdict.py +84 -0
- quality_harness-0.5.0/eval_harness/worksheet/__init__.py +8 -0
- quality_harness-0.5.0/eval_harness/worksheet/checkpoint.py +192 -0
- quality_harness-0.5.0/eval_harness/worksheet/worksheet.py +230 -0
- quality_harness-0.5.0/perf_harness/AGENTS.md +98 -0
- quality_harness-0.5.0/perf_harness/README.md +62 -0
- quality_harness-0.5.0/perf_harness/__init__.py +88 -0
- quality_harness-0.5.0/perf_harness/analysis/__init__.py +61 -0
- quality_harness-0.5.0/perf_harness/analysis/base.py +48 -0
- quality_harness-0.5.0/perf_harness/analysis/capacity.py +137 -0
- quality_harness-0.5.0/perf_harness/analysis/latency.py +153 -0
- quality_harness-0.5.0/perf_harness/analysis/resource.py +180 -0
- quality_harness-0.5.0/perf_harness/analysis/validity.py +131 -0
- quality_harness-0.5.0/perf_harness/cli.py +123 -0
- quality_harness-0.5.0/perf_harness/comparison.py +83 -0
- quality_harness-0.5.0/perf_harness/config.py +753 -0
- quality_harness-0.5.0/perf_harness/deploy.py +116 -0
- quality_harness-0.5.0/perf_harness/docs/extensions.md +209 -0
- quality_harness-0.5.0/perf_harness/docs/load-model-redesign.md +70 -0
- quality_harness-0.5.0/perf_harness/docs/metric-model.md +334 -0
- quality_harness-0.5.0/perf_harness/docs/result-semantics.md +42 -0
- quality_harness-0.5.0/perf_harness/docs/service-metrics.md +88 -0
- quality_harness-0.5.0/perf_harness/drive/__init__.py +1 -0
- quality_harness-0.5.0/perf_harness/drive/controller.py +117 -0
- quality_harness-0.5.0/perf_harness/drive/load.py +153 -0
- quality_harness-0.5.0/perf_harness/drive/runner.py +219 -0
- quality_harness-0.5.0/perf_harness/drive/scheduler.py +243 -0
- quality_harness-0.5.0/perf_harness/engine.py +605 -0
- quality_harness-0.5.0/perf_harness/examples/example.yaml +92 -0
- quality_harness-0.5.0/perf_harness/examples/mock.yaml +37 -0
- quality_harness-0.5.0/perf_harness/examples/mock_cases.yaml +7 -0
- quality_harness-0.5.0/perf_harness/judge.py +33 -0
- quality_harness-0.5.0/perf_harness/metric/__init__.py +79 -0
- quality_harness-0.5.0/perf_harness/metric/family.py +270 -0
- quality_harness-0.5.0/perf_harness/metric/reduce.py +187 -0
- quality_harness-0.5.0/perf_harness/metric/store.py +227 -0
- quality_harness-0.5.0/perf_harness/model.py +488 -0
- quality_harness-0.5.0/perf_harness/observe/__init__.py +29 -0
- quality_harness-0.5.0/perf_harness/observe/base.py +257 -0
- quality_harness-0.5.0/perf_harness/observe/configuration.py +170 -0
- quality_harness-0.5.0/perf_harness/observe/k8s.py +478 -0
- quality_harness-0.5.0/perf_harness/observe/metric.py +172 -0
- quality_harness-0.5.0/perf_harness/observe/prometheus.py +99 -0
- quality_harness-0.5.0/perf_harness/observe/prometheus_query.py +59 -0
- quality_harness-0.5.0/perf_harness/observe/registry.py +42 -0
- quality_harness-0.5.0/perf_harness/records.py +26 -0
- quality_harness-0.5.0/perf_harness/report/__init__.py +13 -0
- quality_harness-0.5.0/perf_harness/report/columns.py +25 -0
- quality_harness-0.5.0/perf_harness/report/lifecycle.py +82 -0
- quality_harness-0.5.0/perf_harness/report/palette.py +20 -0
- quality_harness-0.5.0/perf_harness/report/render.py +1268 -0
- quality_harness-0.5.0/perf_harness/ruff.toml +11 -0
- quality_harness-0.5.0/perf_harness/runio.py +584 -0
- quality_harness-0.5.0/perf_harness/sh.py +16 -0
- quality_harness-0.5.0/perf_harness/slo.py +90 -0
- quality_harness-0.5.0/perf_harness/tests/__init__.py +0 -0
- quality_harness-0.5.0/perf_harness/tests/test_metric_windows.py +279 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_analysis.py +341 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_config.py +404 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_engine.py +566 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_environment.py +164 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_facets.py +111 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_judge.py +77 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_lifecycle.py +275 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_metrics.py +264 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_native.py +132 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_observe.py +769 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_parsers.py +148 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_phases.py +165 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_probe.py +73 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_report.py +348 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_runio.py +334 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_runs.py +105 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_slo.py +444 -0
- quality_harness-0.5.0/perf_harness/tests/test_perf_stages.py +93 -0
- quality_harness-0.5.0/perf_harness/tests/test_prometheus_query.py +200 -0
- quality_harness-0.5.0/perf_harness/verdict.py +119 -0
- quality_harness-0.5.0/pyproject.toml +79 -0
- quality_harness-0.5.0/setup.py +4 -0
- quality_harness-0.5.0/toolbox/Makefile +16 -0
- quality_harness-0.5.0/toolbox/README.md +128 -0
- quality_harness-0.5.0/toolbox/docs/lifecycle.md +207 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/AGENTS.md +50 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/__init__.py +18 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/_kube_options.py +15 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/_mysql_values.py +46 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/address.py +171 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/client.py +9 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/data_loader.py +61 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/diagnostics.py +73 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/environment.py +115 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/errors.py +66 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/host.py +44 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/http.py +42 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/__init__.py +13 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/client.py +803 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/environment_resources.py +133 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/model.py +72 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/resource_list.py +71 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/resources.py +142 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/selector.py +22 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/worker.py +223 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube/workload.py +122 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/kube_portforward.py +136 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/line_buffer.py +52 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/llm/__init__.py +9 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/llm/client.py +157 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/mysql.py +432 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/opensearch.py +334 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/pod_log.py +218 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/process.py +116 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/prometheus.py +259 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/prometheus_discovery.py +64 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/prometheus_query.py +226 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/socks.py +133 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/sse.py +84 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/sse_parser.py +101 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_address.py +265 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_data_loader.py +96 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_diagnostics.py +279 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_environment.py +79 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_environment_provider.py +52 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_host.py +33 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_http_sse.py +82 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube.py +361 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_manifests.py +196 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_portforward.py +157 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_kube_resources.py +153 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_llm_client.py +175 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_mysql.py +81 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_mysql_queries.py +282 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_opensearch.py +111 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_pod_log.py +99 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_process.py +25 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus.py +221 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus_discovery.py +98 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_prometheus_query.py +199 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_resource_list.py +222 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_socks.py +98 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_transport.py +50 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/tests/test_workload.py +262 -0
- quality_harness-0.5.0/toolbox/harness_toolbox/transport.py +184 -0
- quality_harness-0.5.0/toolbox/pyproject.toml +26 -0
- quality_harness-0.5.0/toolbox/ruff.toml +4 -0
- quality_harness-0.5.0/trace_harness/AGENTS.md +130 -0
- quality_harness-0.5.0/trace_harness/__init__.py +68 -0
- quality_harness-0.5.0/trace_harness/analyze/__init__.py +14 -0
- quality_harness-0.5.0/trace_harness/analyze/budget.py +190 -0
- quality_harness-0.5.0/trace_harness/analyze/call_breakdown.py +164 -0
- quality_harness-0.5.0/trace_harness/analyze/context.py +49 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/__init__.py +177 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/detectors.py +100 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/http.py +132 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/outliers.py +91 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/patterns.py +65 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/probes.py +41 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/registry.py +43 -0
- quality_harness-0.5.0/trace_harness/analyze/diagnose/series.py +55 -0
- quality_harness-0.5.0/trace_harness/analyze/measure.py +200 -0
- quality_harness-0.5.0/trace_harness/analyze/verdict.py +130 -0
- quality_harness-0.5.0/trace_harness/batch.py +423 -0
- quality_harness-0.5.0/trace_harness/cli.py +262 -0
- quality_harness-0.5.0/trace_harness/corpus/__init__.py +23 -0
- quality_harness-0.5.0/trace_harness/corpus/experiment.py +82 -0
- quality_harness-0.5.0/trace_harness/corpus/operators.py +214 -0
- quality_harness-0.5.0/trace_harness/corpus/report.py +108 -0
- quality_harness-0.5.0/trace_harness/corpus/store.py +97 -0
- quality_harness-0.5.0/trace_harness/corpus/tables.py +80 -0
- quality_harness-0.5.0/trace_harness/dataset.py +42 -0
- quality_harness-0.5.0/trace_harness/detectors.py +121 -0
- quality_harness-0.5.0/trace_harness/docs/batch.md +158 -0
- quality_harness-0.5.0/trace_harness/docs/kernel.md +101 -0
- quality_harness-0.5.0/trace_harness/docs/load.md +84 -0
- quality_harness-0.5.0/trace_harness/docs/single.md +73 -0
- quality_harness-0.5.0/trace_harness/harness.py +212 -0
- quality_harness-0.5.0/trace_harness/ingest/__init__.py +13 -0
- quality_harness-0.5.0/trace_harness/ingest/assemble.py +166 -0
- quality_harness-0.5.0/trace_harness/ingest/load.py +33 -0
- quality_harness-0.5.0/trace_harness/ingest/sources/__init__.py +29 -0
- quality_harness-0.5.0/trace_harness/ingest/sources/base.py +54 -0
- quality_harness-0.5.0/trace_harness/ingest/sources/jaeger_file.py +292 -0
- quality_harness-0.5.0/trace_harness/ingest/sources/opensearch.py +260 -0
- quality_harness-0.5.0/trace_harness/kinds/__init__.py +6 -0
- quality_harness-0.5.0/trace_harness/kinds/base.py +58 -0
- quality_harness-0.5.0/trace_harness/kinds/genai.py +264 -0
- quality_harness-0.5.0/trace_harness/kinds/http.py +172 -0
- quality_harness-0.5.0/trace_harness/loading/__init__.py +1 -0
- quality_harness-0.5.0/trace_harness/loading/analysis.py +172 -0
- quality_harness-0.5.0/trace_harness/loading/facts.py +30 -0
- quality_harness-0.5.0/trace_harness/loading/http.py +29 -0
- quality_harness-0.5.0/trace_harness/loading/loader.py +219 -0
- quality_harness-0.5.0/trace_harness/loading/model.py +35 -0
- quality_harness-0.5.0/trace_harness/loading/store.py +77 -0
- quality_harness-0.5.0/trace_harness/model/__init__.py +34 -0
- quality_harness-0.5.0/trace_harness/model/agent.py +339 -0
- quality_harness-0.5.0/trace_harness/model/analysis.py +142 -0
- quality_harness-0.5.0/trace_harness/model/context.py +63 -0
- quality_harness-0.5.0/trace_harness/model/intervals.py +26 -0
- quality_harness-0.5.0/trace_harness/model/ir.py +125 -0
- quality_harness-0.5.0/trace_harness/model/measurement.py +54 -0
- quality_harness-0.5.0/trace_harness/model/node.py +88 -0
- quality_harness-0.5.0/trace_harness/model/node_tree.py +72 -0
- quality_harness-0.5.0/trace_harness/model/span.py +80 -0
- quality_harness-0.5.0/trace_harness/model/spec.py +89 -0
- quality_harness-0.5.0/trace_harness/ruff.toml +12 -0
- quality_harness-0.5.0/trace_harness/runtime.py +271 -0
- quality_harness-0.5.0/trace_harness/tests/__init__.py +0 -0
- quality_harness-0.5.0/trace_harness/tests/test_agent_run.py +471 -0
- quality_harness-0.5.0/trace_harness/tests/test_batch_dependencies.py +121 -0
- quality_harness-0.5.0/trace_harness/tests/test_budget.py +103 -0
- quality_harness-0.5.0/trace_harness/tests/test_cohort.py +267 -0
- quality_harness-0.5.0/trace_harness/tests/test_conformance.py +38 -0
- quality_harness-0.5.0/trace_harness/tests/test_detector_registry.py +51 -0
- quality_harness-0.5.0/trace_harness/tests/test_explore.py +117 -0
- quality_harness-0.5.0/trace_harness/tests/test_facet_engine.py +314 -0
- quality_harness-0.5.0/trace_harness/tests/test_http_detectors.py +56 -0
- quality_harness-0.5.0/trace_harness/tests/test_http_groups.py +68 -0
- quality_harness-0.5.0/trace_harness/tests/test_latest_selection.py +95 -0
- quality_harness-0.5.0/trace_harness/tests/test_loading.py +445 -0
- quality_harness-0.5.0/trace_harness/tests/test_loading_conformance.py +81 -0
- quality_harness-0.5.0/trace_harness/tests/test_measurement.py +252 -0
- quality_harness-0.5.0/trace_harness/tests/test_node_dependencies.py +125 -0
- quality_harness-0.5.0/trace_harness/tests/test_node_value.py +86 -0
- quality_harness-0.5.0/trace_harness/tests/test_scoped_harness.py +109 -0
- quality_harness-0.5.0/trace_harness/tests/test_trace_harness.py +696 -0
- quality_harness-0.5.0/trace_harness/tests/test_transform.py +170 -0
- quality_harness-0.5.0/trace_harness/tests/test_views.py +220 -0
- quality_harness-0.5.0/trace_harness/tests/view_fixture.py +40 -0
- quality_harness-0.5.0/trace_harness/transform.py +103 -0
- quality_harness-0.5.0/trace_harness/view/__init__.py +6 -0
- quality_harness-0.5.0/trace_harness/view/agent_run.py +242 -0
- quality_harness-0.5.0/trace_harness/view/build.py +156 -0
- quality_harness-0.5.0/trace_harness/view/callstack.py +57 -0
- quality_harness-0.5.0/trace_harness/view/display.py +81 -0
- quality_harness-0.5.0/trace_harness/view/engine.py +346 -0
- quality_harness-0.5.0/trace_harness/view/explore.py +128 -0
- quality_harness-0.5.0/trace_harness/view/facet.py +151 -0
- quality_harness-0.5.0/trace_harness/view/facets/__init__.py +15 -0
- quality_harness-0.5.0/trace_harness/view/facets/agent.py +22 -0
- quality_harness-0.5.0/trace_harness/view/facets/model_call.py +28 -0
- quality_harness-0.5.0/trace_harness/view/facets/service.py +17 -0
- quality_harness-0.5.0/trace_harness/view/http.py +102 -0
- quality_harness-0.5.0/trace_harness/view/interactive.py +422 -0
- quality_harness-0.5.0/trace_harness/view/measurements.py +93 -0
- quality_harness-0.5.0/trace_harness/view/payload.py +116 -0
- quality_harness-0.5.0/trace_harness/view/registry.py +34 -0
- quality_harness-0.5.0/trace_harness/view/render.py +79 -0
- quality_harness-0.5.0/trace_harness/view/series.py +46 -0
- quality_harness-0.5.0/trace_harness/view/state.py +102 -0
- quality_harness-0.5.0/trace_harness/view/text.py +47 -0
- quality_harness-0.5.0/trace_harness/view/tool_name.py +73 -0
- quality_harness-0.5.0/trajectory_harness/AGENTS.md +96 -0
- quality_harness-0.5.0/trajectory_harness/README.md +270 -0
- quality_harness-0.5.0/trajectory_harness/__init__.py +130 -0
- quality_harness-0.5.0/trajectory_harness/_measurements.py +32 -0
- quality_harness-0.5.0/trajectory_harness/_steps.py +14 -0
- quality_harness-0.5.0/trajectory_harness/_tool_calls.py +137 -0
- quality_harness-0.5.0/trajectory_harness/build.py +167 -0
- quality_harness-0.5.0/trajectory_harness/dataset.py +127 -0
- quality_harness-0.5.0/trajectory_harness/detect.py +192 -0
- quality_harness-0.5.0/trajectory_harness/detectors/__init__.py +26 -0
- quality_harness-0.5.0/trajectory_harness/detectors/cache_retention_bloat.py +96 -0
- quality_harness-0.5.0/trajectory_harness/detectors/context_bloat.py +80 -0
- quality_harness-0.5.0/trajectory_harness/detectors/oversized_tool_observation.py +83 -0
- quality_harness-0.5.0/trajectory_harness/detectors/post_compact_refetch.py +110 -0
- quality_harness-0.5.0/trajectory_harness/detectors/repeated_tool_call.py +73 -0
- quality_harness-0.5.0/trajectory_harness/detectors/retry_loop.py +100 -0
- quality_harness-0.5.0/trajectory_harness/detectors/short_decision_churn.py +87 -0
- quality_harness-0.5.0/trajectory_harness/detectors/unchanged_tool_retry.py +82 -0
- quality_harness-0.5.0/trajectory_harness/failures.py +74 -0
- quality_harness-0.5.0/trajectory_harness/loaders/__init__.py +4 -0
- quality_harness-0.5.0/trajectory_harness/loaders/atif_json.py +37 -0
- quality_harness-0.5.0/trajectory_harness/loaders/base.py +17 -0
- quality_harness-0.5.0/trajectory_harness/loaders/otel_json.py +580 -0
- quality_harness-0.5.0/trajectory_harness/measure.py +194 -0
- quality_harness-0.5.0/trajectory_harness/measurers/__init__.py +14 -0
- quality_harness-0.5.0/trajectory_harness/measurers/_tokens.py +74 -0
- quality_harness-0.5.0/trajectory_harness/measurers/context_usage.py +207 -0
- quality_harness-0.5.0/trajectory_harness/measurers/model_usage.py +248 -0
- quality_harness-0.5.0/trajectory_harness/measurers/retry_usage.py +122 -0
- quality_harness-0.5.0/trajectory_harness/measurers/tool_usage.py +172 -0
- quality_harness-0.5.0/trajectory_harness/metrics.py +667 -0
- quality_harness-0.5.0/trajectory_harness/model.py +342 -0
- quality_harness-0.5.0/trajectory_harness/pipeline.py +82 -0
- quality_harness-0.5.0/trajectory_harness/report.py +744 -0
- quality_harness-0.5.0/trajectory_harness/report_comparison.py +187 -0
- quality_harness-0.5.0/trajectory_harness/report_detection.py +101 -0
- quality_harness-0.5.0/trajectory_harness/report_provenance.py +89 -0
- quality_harness-0.5.0/trajectory_harness/runio.py +210 -0
- quality_harness-0.5.0/trajectory_harness/runner.py +194 -0
- quality_harness-0.5.0/trajectory_harness/source.py +85 -0
- quality_harness-0.5.0/trajectory_harness/tests/__init__.py +0 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_atif_json.py +142 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_efficiency.py +329 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_failures.py +36 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_metrics_report.py +120 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_model_usage.py +215 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_otel_json.py +240 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_pipeline.py +373 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_source.py +68 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_structural_waste.py +281 -0
- quality_harness-0.5.0/trajectory_harness/tests/test_verify.py +311 -0
- quality_harness-0.5.0/trajectory_harness/verdict.py +125 -0
- quality_harness-0.5.0/trajectory_harness/verifiers/__init__.py +11 -0
- quality_harness-0.5.0/trajectory_harness/verifiers/execution_success.py +48 -0
- quality_harness-0.5.0/trajectory_harness/verifiers/measurement_threshold.py +133 -0
- quality_harness-0.5.0/trajectory_harness/verifiers/tool_success.py +59 -0
- quality_harness-0.5.0/trajectory_harness/verify.py +183 -0
- quality_harness-0.5.0/uv.lock +1373 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
*.egg
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
htmlcov/
|
|
13
|
+
.coverage
|
|
14
|
+
*.whl
|
|
15
|
+
*.tar.gz
|
|
16
|
+
|
|
17
|
+
# Go
|
|
18
|
+
/sdks/go/bin/
|
|
19
|
+
|
|
20
|
+
# TypeScript
|
|
21
|
+
node_modules/
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
|
32
|
+
|
|
33
|
+
# uv
|
|
34
|
+
sdks/python/.venv/
|
|
35
|
+
|
|
36
|
+
# DevOps plugin
|
|
37
|
+
.devops/
|
|
38
|
+
|
|
39
|
+
# Runtime artifacts (agent tier)
|
|
40
|
+
runtime/
|
|
41
|
+
runs/
|
|
42
|
+
|
|
43
|
+
# devloop
|
|
44
|
+
.devloop/
|
|
45
|
+
.worktrees/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python SDKs
|
|
2
|
+
|
|
3
|
+
## 项目定位与边界
|
|
4
|
+
|
|
5
|
+
一个 uv workspace 管理 `quality-harness`、`harness-common` 与 `harness-toolbox` 三个独立分发包。
|
|
6
|
+
领域 SDK 依赖 common/toolbox;toolbox 依赖 common,common 不反向依赖 toolbox。每个包只打包自己的命名空间。
|
|
7
|
+
|
|
8
|
+
## 代码地图与核心模块
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
python/
|
|
12
|
+
├── e2e_harness/ # 服务 API 契约测试
|
|
13
|
+
├── eval_harness/ # Agent 效果评测与对照实验
|
|
14
|
+
├── perf_harness/ # 性能、容量与资源画像
|
|
15
|
+
├── trace_harness/ # 调用链分析
|
|
16
|
+
├── trajectory_harness/ # Agent 决策与行动序列评估
|
|
17
|
+
├── common/ # 独立 harness-common:system/access/execution 契约与 report_kit
|
|
18
|
+
├── toolbox/ # 独立 harness-toolbox:协议客户端、LLM、Transport 与平台适配
|
|
19
|
+
├── pyproject.toml # 包、依赖、CLI 与测试发现
|
|
20
|
+
└── Makefile # 测试、lint、格式化、构建与版本入口
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 关键约定
|
|
24
|
+
|
|
25
|
+
- 五个领域 SDK 互不 import;公共能力集中在中立共享包。`harness_common` 统一执行事实与输出契约,各 SDK 自行拥有 runner、scheduler 和协议原语。
|
|
26
|
+
- 环境共享层只管理 EnvironmentFixture 与外层执行,Case / Trial 准备清理由各 Harness 拥有;见 [环境生命周期](../../docs/environment.md)。
|
|
27
|
+
- 测试贴近所属包,放在各包 `tests/`;默认 pytest 覆盖完整工程,发行包排除测试。
|
|
28
|
+
- 领域可选依赖通过 extras 声明,避免给其它 SDK 增加安装负担。
|
|
29
|
+
- Python 包版本使用本目录 `make bump`,同步 `pyproject.toml` 与 `uv.lock`;仓库整体版本按根目录约定独立更新。
|
|
30
|
+
|
|
31
|
+
## 开发与测试
|
|
32
|
+
|
|
33
|
+
从本目录运行:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv sync --locked
|
|
37
|
+
make fix
|
|
38
|
+
make lint
|
|
39
|
+
uv run pytest -q
|
|
40
|
+
make build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
局部验证使用 `make test TEST_FILES=<package>/tests`;toolbox 路径为 `toolbox/harness_toolbox/tests`。
|
|
44
|
+
局部格式化使用 `make fix CHECK_PATHS=toolbox/harness_toolbox`,完整门禁仍运行 `make lint`。`make fix` 会修改源码,完成后再运行 lint 和测试。
|
|
45
|
+
构建产物写入仓库根目录 `dist/`。领域 smoke 命令与 fixture 使用方式归对应 Harness。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
CHECK_PATHS ?= $(if $(filter %.py,$(LINT_FILES)),$(filter %.py,$(LINT_FILES)),common/harness_common/ toolbox/harness_toolbox/ e2e_harness/ eval_harness/ perf_harness/ trace_harness/ trajectory_harness/)
|
|
2
|
+
|
|
3
|
+
FIX_PATHS ?= $(CHECK_PATHS)
|
|
4
|
+
|
|
5
|
+
.PHONY: test lint fix build clean bump
|
|
6
|
+
|
|
7
|
+
test:
|
|
8
|
+
uv run pytest -v $(TEST_FILES)
|
|
9
|
+
|
|
10
|
+
lint:
|
|
11
|
+
uv run ruff check $(CHECK_PATHS)
|
|
12
|
+
|
|
13
|
+
fix:
|
|
14
|
+
uv run ruff check --fix $(FIX_PATHS)
|
|
15
|
+
uv run ruff format $(FIX_PATHS)
|
|
16
|
+
|
|
17
|
+
build:
|
|
18
|
+
uv build --all-packages --out-dir ../../dist
|
|
19
|
+
|
|
20
|
+
clean:
|
|
21
|
+
rm -rf dist build *.egg-info
|
|
22
|
+
|
|
23
|
+
bump:
|
|
24
|
+
@current=$$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/'); \
|
|
25
|
+
major=$$(echo $$current | cut -d. -f1); \
|
|
26
|
+
minor=$$(echo $$current | cut -d. -f2); \
|
|
27
|
+
patch=$$(echo $$current | cut -d. -f3); \
|
|
28
|
+
next="$$major.$$minor.$$((patch + 1))"; \
|
|
29
|
+
sed -i '' "s/^version = \"$$current\"/version = \"$$next\"/" pyproject.toml; \
|
|
30
|
+
uv lock; \
|
|
31
|
+
echo "bumped $$current → $$next"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: quality-harness
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Quality testing and evidence analysis SDKs for software and agents
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: atif<1.8,>=1.7
|
|
8
|
+
Requires-Dist: harness-common<0.7,>=0.6.0
|
|
9
|
+
Requires-Dist: harness-toolbox[http,llm,prometheus]<0.7,>=0.6.0
|
|
10
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
11
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
12
|
+
Requires-Dist: pytest>=8.0
|
|
13
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
14
|
+
Requires-Dist: spec-case[model]>=0.2.1
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: kubernetes-asyncio<37,>=36.1; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
18
|
+
Provides-Extra: kube
|
|
19
|
+
Requires-Dist: harness-toolbox[kube]<0.7,>=0.6.0; extra == 'kube'
|
|
20
|
+
Provides-Extra: trace-corpus
|
|
21
|
+
Requires-Dist: pyarrow<22,>=14; extra == 'trace-corpus'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Python 共享内核
|
|
2
|
+
|
|
3
|
+
`harness-common` 提供系统描述、访问契约和执行证据,不依赖 toolbox、领域 SDK 或第三方运行库。
|
|
4
|
+
具体 HTTP、LLM、数据库与平台访问属于 toolbox;领域 Harness 拥有调度和判据。
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
harness_common/
|
|
8
|
+
├── system/ # Repository/Component/Product、Service/Environment、资源、部署契约
|
|
9
|
+
├── access/ # Client/DataSource、EnvironmentContext 与环境 Fixture 生命周期
|
|
10
|
+
├── execution/ # Experiment/Execution/Operation、Outcome/Artifact、Reducer/Verdict 与 run 布局
|
|
11
|
+
├── report_kit/ # 中立报告结构和 HTML 渲染,独立于执行生命周期
|
|
12
|
+
├── identity.py # 身份协议
|
|
13
|
+
├── __init__.py # 稳定包根导出
|
|
14
|
+
└── tests/ # 跨模块契约与 conformance 测试
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`system` 描述目标,`access` 获取事实,`execution` 组织与解释证据。它们是包内职责分组,
|
|
18
|
+
不是独立分发包;跨组引用必须指向具体模块,避免依赖包根聚合导出造成循环。
|
|
19
|
+
包根保持既有符号和类型身份;深层导入迁移见 [0.6 升级说明](README.md)。
|
|
20
|
+
从 `sdks/python` 运行 `make fix`、`make lint`、`make test` 和 `make build`。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Harness Common for Python
|
|
2
|
+
|
|
3
|
+
Standard-library contracts shared by the quality SDKs. Import public models and lifecycle
|
|
4
|
+
contracts from `harness_common`; concrete protocol clients live in `harness_toolbox`.
|
|
5
|
+
|
|
6
|
+
## Upgrade to 0.6
|
|
7
|
+
|
|
8
|
+
Package-root exports such as `from harness_common import Service, ClientManager, Execution`
|
|
9
|
+
retain their identity and behavior. Internal modules are now grouped by responsibility:
|
|
10
|
+
|
|
11
|
+
| Previous module under `harness_common` | New module |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `repository`, `component`, `product`, `forge`, `service`, `environment`, `host`, `workload`, `resource`, `deployment` | `system.<module>` |
|
|
14
|
+
| `client`, `datasource`, `context`, `fixture` | `access.<module>` |
|
|
15
|
+
| `artifact`, `experiment`, `operation`, `outcome`, `overlay`, `reducer`, `run`, `verdict` | `execution.<module>` |
|
|
16
|
+
| `execution` | `execution.execution` (`from harness_common.execution import Execution` also remains valid) |
|
|
17
|
+
| `llm` | `harness_toolbox.llm` |
|
|
18
|
+
|
|
19
|
+
`identity` and `report_kit` keep their existing paths. Replace old deep imports; no flat
|
|
20
|
+
compatibility modules are shipped. For example:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from harness_common.execution.verdict import RunVerdict
|
|
24
|
+
from harness_common.execution.run import run_dir_for
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Install `harness-toolbox[llm]>=0.6,<0.7` for `LLMClient`, `LLMConfig` and `ChatResult`.
|
|
28
|
+
The old `harness-common[llm]` extra is removed. The LLM re-exports from `eval_harness`
|
|
29
|
+
and the `eval_harness.llm` compatibility package are removed; import directly from
|
|
30
|
+
`harness_toolbox.llm`. Client request, timeout and retry behavior is unchanged.
|
|
31
|
+
|
|
32
|
+
Upgrade `harness-common` and `harness-toolbox` together to 0.6; `quality-harness>=0.5.0`
|
|
33
|
+
selects this compatible pair and the LLM extra. Publish common before toolbox, then
|
|
34
|
+
quality-harness, so every dependency is available when a downstream package is installed.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""common — harness-neutral shared code and runtime identities.
|
|
2
|
+
|
|
3
|
+
The harnesses (e2e / eval / perf / trace / trajectory) depend on this package but never on each
|
|
4
|
+
other; only genuinely neutral code lives here (no harness domain concept leaks in).
|
|
5
|
+
|
|
6
|
+
The canonical case model — `Case` / `CaseSet` / `FacetSchema` and the `Face` / `FACES`
|
|
7
|
+
judgment-face enum — lives upstream in `spec_case.model` / `spec_case.facets`
|
|
8
|
+
(the spec-case package, `[model]` extra): the case format is the asset layer's contract,
|
|
9
|
+
and this repo is one of its runners. Import those names from `spec_case` directly.
|
|
10
|
+
|
|
11
|
+
`Forge` and `Repository` identify where source lives; `Product` names a business product;
|
|
12
|
+
`Component` identifies a buildable unit within a Repository. `Environment` identifies where
|
|
13
|
+
execution happens. `Service` identifies a Component's runtime presence in one Environment.
|
|
14
|
+
`Workload` locates a declared runtime carrier; a Service can reference multiple workloads.
|
|
15
|
+
`Operation` identifies a capability exposed by a Service; `HttpOperation` adds its HTTP
|
|
16
|
+
transport contract. An `Execution` groups domain-defined work such as an e2e CaseRun or
|
|
17
|
+
a perf Trial; each `OperationRun` owns one raw `Outcome`. Domain engines own scheduling
|
|
18
|
+
details. A domain `Reducer` projects the recorded Outcomes into one or more
|
|
19
|
+
`Artifact` objects without calling the tested Service again. `Deployment` records an
|
|
20
|
+
attempt applied through a `Deployer`.
|
|
21
|
+
`Experiment` names reproducible verification intent; `ExperimentRun` records one execution
|
|
22
|
+
and references its `Artifact` outputs.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from harness_common.execution.artifact import Artifact as Artifact
|
|
28
|
+
from harness_common.system.component import Component as Component
|
|
29
|
+
from harness_common.system.deployment import Deployer as Deployer
|
|
30
|
+
from harness_common.system.deployment import Deployment as Deployment
|
|
31
|
+
from harness_common.system.environment import Environment as Environment
|
|
32
|
+
from harness_common.system.environment import HostEnvironment as HostEnvironment
|
|
33
|
+
from harness_common.system.host import Host as Host
|
|
34
|
+
from harness_common.system.environment import EnvironmentFacts as EnvironmentFacts
|
|
35
|
+
from harness_common.system.environment import EnvironmentSnapshot as EnvironmentSnapshot
|
|
36
|
+
from harness_common.execution.execution import Execution as Execution
|
|
37
|
+
from harness_common.execution.experiment import Experiment as Experiment
|
|
38
|
+
from harness_common.execution.experiment import ExperimentRun as ExperimentRun
|
|
39
|
+
from harness_common.system.forge import Forge as Forge
|
|
40
|
+
from harness_common.identity import Identity as Identity
|
|
41
|
+
from harness_common.execution.operation import HttpOperation as HttpOperation
|
|
42
|
+
from harness_common.execution.operation import Operation as Operation
|
|
43
|
+
from harness_common.execution.operation import OperationRun as OperationRun
|
|
44
|
+
from harness_common.execution.outcome import Outcome as Outcome
|
|
45
|
+
from harness_common.system.product import Product as Product
|
|
46
|
+
from harness_common.system.repository import Repository as Repository
|
|
47
|
+
from harness_common.execution.reducer import Reducer as Reducer
|
|
48
|
+
from harness_common.system.service import Service as Service
|
|
49
|
+
from harness_common.system.workload import KubernetesWorkload as KubernetesWorkload
|
|
50
|
+
from harness_common.system.workload import Workload as Workload
|
|
51
|
+
from harness_common.system.workload import WorkloadInstance as WorkloadInstance
|
|
52
|
+
from harness_common.system.workload import (
|
|
53
|
+
KubernetesWorkloadInstance as KubernetesWorkloadInstance,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from harness_common.access.client import Client as Client
|
|
57
|
+
from harness_common.access.client import ClientManager as ClientManager
|
|
58
|
+
from harness_common.access.client import ClientProvider as ClientProvider
|
|
59
|
+
from harness_common.access.datasource import DataSource as DataSource
|
|
60
|
+
from harness_common.access.datasource import ServiceDataSource as ServiceDataSource
|
|
61
|
+
from harness_common.access.client import client_key as client_key
|
|
62
|
+
from harness_common.access.fixture import EnvironmentFixture as EnvironmentFixture
|
|
63
|
+
from harness_common.access.context import EnvironmentContext as EnvironmentContext
|
|
64
|
+
from harness_common.access.fixture import FixtureContext as FixtureContext
|
|
65
|
+
from harness_common.access.fixture import EnvironmentBudgets as EnvironmentBudgets
|
|
66
|
+
from harness_common.access.fixture import EnvironmentRun as EnvironmentRun
|
|
67
|
+
from harness_common.access.fixture import run_environment as run_environment
|
|
68
|
+
|
|
69
|
+
from harness_common.system.resource import Resource as Resource
|
|
70
|
+
from harness_common.system.resource import ServiceResource as ServiceResource
|
|
71
|
+
from harness_common.system.resource import ResourceInstanceRef as ResourceInstanceRef
|
|
72
|
+
from harness_common.system.resource import ResourceInstance as ResourceInstance
|
|
73
|
+
from harness_common.system.resource import ResourceMeasurement as ResourceMeasurement
|
|
74
|
+
from harness_common.system.resource import ResourceCoverage as ResourceCoverage
|
|
75
|
+
from harness_common.system.resource import ResourceSnapshot as ResourceSnapshot
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Access contracts shared across harnesses."""
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Execution-scoped, asynchronous client ownership.
|
|
2
|
+
|
|
3
|
+
ClientProvider keys describe configuration, not object identity. Consumers receive a
|
|
4
|
+
_ClientBorrower; only the root execution owns and disposes the ClientManager.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from typing import Protocol, TypeVar, cast
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Client(Protocol):
|
|
16
|
+
async def initialize(self) -> None: ...
|
|
17
|
+
async def dispose(self) -> None: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
C = TypeVar("C", bound=Client, covariant=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ClientProvider(Protocol[C]):
|
|
24
|
+
"""Keyed client construction, independent of data or environment semantics.
|
|
25
|
+
|
|
26
|
+
@spec Both environment and data clients share lifecycle ownership.
|
|
27
|
+
@rule client_key covers implementation, target, credentials and capacity policy.
|
|
28
|
+
Construction is pure; initialize owns I/O, and the root owns disposal.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def client_key(self) -> str: ...
|
|
33
|
+
def create_client(self, clients: _ClientBorrower) -> C: ...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _ClientBorrower(Protocol):
|
|
37
|
+
async def get(self, provider: ClientProvider[C]) -> C: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def client_key(protocol: str, configuration: object) -> str:
|
|
41
|
+
"""Hash JSON-compatible configuration, including credentials, without exposing it."""
|
|
42
|
+
canonical = json.dumps(
|
|
43
|
+
configuration, sort_keys=True, separators=(",", ":"), allow_nan=False
|
|
44
|
+
)
|
|
45
|
+
return protocol + ":" + hashlib.sha256(canonical.encode()).hexdigest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ClientManager:
|
|
49
|
+
"""Share initialized clients within one event loop and close them at root exit.
|
|
50
|
+
|
|
51
|
+
Initialize dependencies through the supplied borrower before publishing a
|
|
52
|
+
client as ready. Reverse readiness order then closes consumers first.
|
|
53
|
+
Join all command work before leaving the root context: borrowed clients are
|
|
54
|
+
invalid after disposal. Managers cannot be reopened or shared across loops.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self._pending: dict[str, asyncio.Task[Client]] = {}
|
|
59
|
+
self._ready: list[Client] = []
|
|
60
|
+
self._disposal: asyncio.Task[None] | None = None
|
|
61
|
+
self._cleanup_errors: list[BaseException] = []
|
|
62
|
+
self._unclean: set[str] = set()
|
|
63
|
+
|
|
64
|
+
async def get(self, provider: ClientProvider[C]) -> C:
|
|
65
|
+
if self._disposal is not None:
|
|
66
|
+
raise RuntimeError("client manager is disposed")
|
|
67
|
+
key = provider.client_key
|
|
68
|
+
task = self._pending.get(key)
|
|
69
|
+
if task is None:
|
|
70
|
+
# Store before the task can run; factories can themselves request dependencies.
|
|
71
|
+
task = asyncio.create_task(self._initialize(provider))
|
|
72
|
+
self._pending[key] = task
|
|
73
|
+
task.add_done_callback(lambda done: self._settled(key, done))
|
|
74
|
+
# A cancelled consumer must not cancel initialization needed by other consumers.
|
|
75
|
+
return cast(C, await asyncio.shield(task))
|
|
76
|
+
|
|
77
|
+
def _settled(self, key: str, task: asyncio.Task[Client]) -> None:
|
|
78
|
+
failed = task.cancelled() or task.exception() is not None
|
|
79
|
+
if failed and key not in self._unclean and self._pending.get(key) is task:
|
|
80
|
+
del self._pending[key]
|
|
81
|
+
|
|
82
|
+
async def _initialize(self, provider: ClientProvider[Client]) -> Client:
|
|
83
|
+
client = provider.create_client(self)
|
|
84
|
+
try:
|
|
85
|
+
await client.initialize()
|
|
86
|
+
self._ready.append(client)
|
|
87
|
+
return client
|
|
88
|
+
except BaseException as error:
|
|
89
|
+
try:
|
|
90
|
+
await client.dispose()
|
|
91
|
+
except BaseException as cleanup:
|
|
92
|
+
self._cleanup_errors.append(cleanup)
|
|
93
|
+
self._unclean.add(provider.client_key)
|
|
94
|
+
raise BaseExceptionGroup(
|
|
95
|
+
"client initialization and cleanup failed", [error, cleanup]
|
|
96
|
+
) from None
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
async def dispose(self) -> None:
|
|
100
|
+
"""Idempotent, concurrent-safe cleanup; cancellation of a waiter cannot stop it."""
|
|
101
|
+
if self._disposal is None:
|
|
102
|
+
self._disposal = asyncio.create_task(self._dispose())
|
|
103
|
+
await asyncio.shield(self._disposal)
|
|
104
|
+
|
|
105
|
+
async def _dispose(self) -> None:
|
|
106
|
+
pending = list(self._pending.values())
|
|
107
|
+
for task in pending:
|
|
108
|
+
if not task.done():
|
|
109
|
+
task.cancel()
|
|
110
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
111
|
+
self._pending.clear()
|
|
112
|
+
for client in reversed(self._ready):
|
|
113
|
+
try:
|
|
114
|
+
await client.dispose()
|
|
115
|
+
except BaseException as error:
|
|
116
|
+
self._cleanup_errors.append(error)
|
|
117
|
+
self._ready.clear()
|
|
118
|
+
if self._cleanup_errors:
|
|
119
|
+
raise BaseExceptionGroup("client cleanup failed", self._cleanup_errors)
|
|
120
|
+
|
|
121
|
+
async def __aenter__(self) -> ClientManager:
|
|
122
|
+
if self._disposal is not None:
|
|
123
|
+
raise RuntimeError("client manager is disposed")
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
async def __aexit__(self, *_: object) -> None:
|
|
127
|
+
await self.dispose()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Execution-scoped environment access, independent of fixture orchestration."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Generic, TypeVar
|
|
6
|
+
|
|
7
|
+
from harness_common.access.client import _ClientBorrower
|
|
8
|
+
from harness_common.system.environment import Environment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
E = TypeVar("E", bound=Environment)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class EnvironmentContext(Generic[E]):
|
|
16
|
+
"""Bind an environment, borrowed clients and a monotonic deadline in seconds.
|
|
17
|
+
|
|
18
|
+
@spec Deployment, tests and diagnosis can use this context without a fixture.
|
|
19
|
+
@rule It neither opens connections nor owns disposal or automatic cancellation.
|
|
20
|
+
Callers propagate remaining_s into operations and join work before root exit.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
environment: E
|
|
24
|
+
clients: _ClientBorrower
|
|
25
|
+
deadline: float
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def remaining_s(self) -> float:
|
|
29
|
+
return max(0.0, self.deadline - time.monotonic())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Data access associations built on the neutral client provider contract."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Generic, Protocol
|
|
5
|
+
|
|
6
|
+
from harness_common.access.client import C, ClientProvider
|
|
7
|
+
from harness_common.system.service import Service
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataSource(ClientProvider[C], Protocol[C]):
|
|
11
|
+
"""A source of data; lifecycle and reuse follow ClientProvider.
|
|
12
|
+
|
|
13
|
+
Accessible environment providers need not be described as data sources.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# Safe discovery metadata, not credentials or part of client_key.
|
|
17
|
+
description: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ServiceDataSource(Generic[C]):
|
|
22
|
+
"""Associate business ownership without changing client identity or lifetime."""
|
|
23
|
+
|
|
24
|
+
service: Service
|
|
25
|
+
source: DataSource[C]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Environment lifetime around a domain-owned execution (no Case abstraction)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Generic, Protocol, TypeVar
|
|
11
|
+
|
|
12
|
+
from harness_common.access.client import ClientManager
|
|
13
|
+
from harness_common.access.context import EnvironmentContext
|
|
14
|
+
from harness_common.system.environment import Environment, EnvironmentSnapshot
|
|
15
|
+
|
|
16
|
+
S = TypeVar("S")
|
|
17
|
+
S_contra = TypeVar("S_contra", contravariant=True)
|
|
18
|
+
R = TypeVar("R")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class FixtureContext(EnvironmentContext):
|
|
23
|
+
"""Fixture phase information extends the neutral environment context."""
|
|
24
|
+
|
|
25
|
+
phase: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EnvironmentFixture(Protocol[S_contra]):
|
|
29
|
+
"""Project-owned shared resources; retain partial handles in preallocated state.
|
|
30
|
+
|
|
31
|
+
Cleanup must tolerate incomplete preparation. Borrowed clients remain valid
|
|
32
|
+
until cleanup returns; only their manager owns final disposal.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
async def prepare(self, ctx: FixtureContext, state: S_contra) -> None: ...
|
|
36
|
+
async def cleanup(self, ctx: FixtureContext, state: S_contra) -> None: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class EnvironmentBudgets:
|
|
41
|
+
prepare_s: float
|
|
42
|
+
run_s: float
|
|
43
|
+
cleanup_s: float
|
|
44
|
+
dispose_s: float
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class EnvironmentPhase:
|
|
49
|
+
name: str
|
|
50
|
+
duration_ms: int
|
|
51
|
+
error: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class EnvironmentObservation:
|
|
56
|
+
phase: str
|
|
57
|
+
snapshot: EnvironmentSnapshot
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class EnvironmentRun(Generic[R]):
|
|
62
|
+
"""Outer health and evidence; the domain result retains its own verdicts."""
|
|
63
|
+
|
|
64
|
+
result: R | None = None
|
|
65
|
+
phases: list[EnvironmentPhase] = field(default_factory=list)
|
|
66
|
+
observations: list[EnvironmentObservation] = field(default_factory=list)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def healthy(self) -> bool:
|
|
70
|
+
return all(phase.error is None for phase in self.phases)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def run_environment(
|
|
74
|
+
environment: Environment,
|
|
75
|
+
state: S,
|
|
76
|
+
fixture: EnvironmentFixture[S],
|
|
77
|
+
run: Callable[[EnvironmentContext, S], Awaitable[R]],
|
|
78
|
+
*,
|
|
79
|
+
budgets: EnvironmentBudgets,
|
|
80
|
+
observe: Callable[[S], EnvironmentSnapshot] | None = None,
|
|
81
|
+
required: dict[str, str] | None = None,
|
|
82
|
+
) -> EnvironmentRun[R]:
|
|
83
|
+
"""Prepare → domain (including case cleanup) → environment cleanup → dispose.
|
|
84
|
+
|
|
85
|
+
Exceptions become outer phase errors without inventing a domain result.
|
|
86
|
+
Cancellation propagates after bounded teardown. Async callbacks must cooperate
|
|
87
|
+
with cancellation and finish their own child work before returning.
|
|
88
|
+
Disposal has a cooperative deadline: native cleanup is joined even on overrun.
|
|
89
|
+
``observe`` is a synchronous view of facts already collected by the project.
|
|
90
|
+
"""
|
|
91
|
+
if any(
|
|
92
|
+
value <= 0
|
|
93
|
+
for value in (
|
|
94
|
+
budgets.prepare_s,
|
|
95
|
+
budgets.run_s,
|
|
96
|
+
budgets.cleanup_s,
|
|
97
|
+
budgets.dispose_s,
|
|
98
|
+
)
|
|
99
|
+
):
|
|
100
|
+
raise ValueError("environment phases require positive budgets")
|
|
101
|
+
if required and observe is None:
|
|
102
|
+
raise ValueError("required facts need an observe callback")
|
|
103
|
+
clients = ClientManager()
|
|
104
|
+
execution: EnvironmentRun[R] = EnvironmentRun()
|
|
105
|
+
|
|
106
|
+
def capture(phase: str) -> EnvironmentSnapshot | None:
|
|
107
|
+
if observe is None:
|
|
108
|
+
return None
|
|
109
|
+
snapshot = deepcopy(observe(state))
|
|
110
|
+
execution.observations.append(EnvironmentObservation(phase, snapshot))
|
|
111
|
+
return snapshot
|
|
112
|
+
|
|
113
|
+
async def phase(
|
|
114
|
+
name: str,
|
|
115
|
+
budget: float,
|
|
116
|
+
callback: Callable[[FixtureContext], Awaitable[None]],
|
|
117
|
+
*,
|
|
118
|
+
cooperative: bool = False,
|
|
119
|
+
) -> bool:
|
|
120
|
+
started = time.monotonic()
|
|
121
|
+
error = None
|
|
122
|
+
try:
|
|
123
|
+
ctx = FixtureContext(
|
|
124
|
+
environment, clients, deadline=started + budget, phase=name
|
|
125
|
+
)
|
|
126
|
+
if cooperative:
|
|
127
|
+
await callback(ctx)
|
|
128
|
+
if ctx.remaining_s <= 0:
|
|
129
|
+
raise TimeoutError(f"{name} exceeded its time budget")
|
|
130
|
+
else:
|
|
131
|
+
async with asyncio.timeout(budget):
|
|
132
|
+
await callback(ctx)
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
error = f"{type(exc).__name__}: {exc}"
|
|
135
|
+
finally:
|
|
136
|
+
execution.phases.append(
|
|
137
|
+
EnvironmentPhase(name, int((time.monotonic() - started) * 1000), error)
|
|
138
|
+
)
|
|
139
|
+
return error is None
|
|
140
|
+
|
|
141
|
+
async def prepare(ctx: FixtureContext) -> None:
|
|
142
|
+
try:
|
|
143
|
+
await fixture.prepare(ctx, state)
|
|
144
|
+
finally:
|
|
145
|
+
# Capture partial preparation too, before any teardown can rewrite it.
|
|
146
|
+
snapshot = capture("prepare")
|
|
147
|
+
if snapshot is not None:
|
|
148
|
+
if (snapshot.name, snapshot.kind) != (environment.name, environment.kind):
|
|
149
|
+
raise ValueError("snapshot conflicts with selected environment")
|
|
150
|
+
if required and (
|
|
151
|
+
not snapshot.target.source or not snapshot.target.observed_at
|
|
152
|
+
):
|
|
153
|
+
raise ValueError("required target facts need source and observed_at")
|
|
154
|
+
for key, expected in (required or {}).items():
|
|
155
|
+
if snapshot.target.values.get(key) != expected:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"environment condition {key}: observed {snapshot.target.values.get(key)!r}, required {expected!r}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def execute(ctx: EnvironmentContext) -> None:
|
|
161
|
+
execution.result = await run(ctx, state)
|
|
162
|
+
|
|
163
|
+
async def cleanup(ctx: FixtureContext) -> None:
|
|
164
|
+
try:
|
|
165
|
+
await fixture.cleanup(ctx, state)
|
|
166
|
+
finally:
|
|
167
|
+
capture("cleanup")
|
|
168
|
+
|
|
169
|
+
async def teardown() -> None:
|
|
170
|
+
try:
|
|
171
|
+
await phase("cleanup", budgets.cleanup_s, cleanup)
|
|
172
|
+
finally:
|
|
173
|
+
# ClientManager shields native disposal. Join it before returning;
|
|
174
|
+
# report overruns instead of leaking disposal into the next execution.
|
|
175
|
+
await phase(
|
|
176
|
+
"dispose",
|
|
177
|
+
budgets.dispose_s,
|
|
178
|
+
lambda ctx: clients.dispose(),
|
|
179
|
+
cooperative=True,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
if await phase("prepare", budgets.prepare_s, prepare):
|
|
184
|
+
await phase("run", budgets.run_s, execute)
|
|
185
|
+
finally:
|
|
186
|
+
# Teardown owns a separate task so cancellation of the run cannot skip
|
|
187
|
+
# cleanup or invalidate borrowed clients while cleanup still uses them.
|
|
188
|
+
task = asyncio.create_task(teardown())
|
|
189
|
+
interrupted = False
|
|
190
|
+
while not task.done():
|
|
191
|
+
try:
|
|
192
|
+
await asyncio.shield(task)
|
|
193
|
+
except asyncio.CancelledError:
|
|
194
|
+
interrupted = True
|
|
195
|
+
task.result()
|
|
196
|
+
if interrupted:
|
|
197
|
+
raise asyncio.CancelledError
|
|
198
|
+
return execution
|