| 1 | import copy
|
| 2 | from pathlib import Path
|
| 3 | import sys
|
| 4 | import unittest
|
| 5 |
|
| 6 | sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
| 7 | from oakbench.campaign_report import report_campaign, render_markdown
|
| 8 |
|
| 9 |
|
| 10 | def plan(repetitions=2):
|
| 11 | return {"schema_version": 1, "campaign_id": "example", "plan_sha256": "digest", "manifest": {},
|
| 12 | "schedule": [dict(trial_id=f"{rep}-{subject}", block_id=f"block-{rep}", ordinal=rep * 2 + index,
|
| 13 | repetition=rep, workflow="wide_config_refactor", instruction_level="zero-shot",
|
| 14 | agent="codex", model="pinned-model", subject=subject)
|
| 15 | for rep in range(repetitions) for index, subject in enumerate(("git", "oak"))]}
|
| 16 |
|
| 17 |
|
| 18 | def event(trial_id, kind, **extra):
|
| 19 | return dict(event=kind, trial_id=trial_id, plan_sha256="digest", timestamp_utc="2026-09-10T00:00:00Z", **extra)
|
| 20 |
|
| 21 |
|
| 22 | def outcome(trial_id, name="pass", tokens=10):
|
| 23 | return [event(trial_id, "started"), event(trial_id, "finished", row={
|
| 24 | "outcome": name, "success": name == "pass", "wall_clock_metrics": {"agent_active_ms": 20, "total_ms": 30},
|
| 25 | "token_metrics": {"total_tokens_reported": tokens, "cost_weighted_total": 40},
|
| 26 | "turn_metrics": {"assistant_turns_total": 2}, "tool_call_metrics": {"tool_calls_total": 3},
|
| 27 | })]
|
| 28 |
|
| 29 |
|
| 30 | class CampaignReportTest(unittest.TestCase):
|
| 31 | def report(self, configuration, events):
|
| 32 | return report_campaign(configuration, events, bootstrap_samples=100, seed=7)
|
| 33 |
|
| 34 | def test_schedule_and_started_denominators_survive_interruption(self):
|
| 35 | summary = self.report(plan(), outcome("0-git") + outcome("0-oak", "fail") + [event("1-git", "started")])
|
| 36 | self.assertEqual(summary["counts"], dict(scheduled=4, started=3, finished=2, budget_truncated=0, success=1, failure=1,
|
| 37 | timeout=0, infrastructure_failure=0, interrupted=1, skipped=0, unstarted=1))
|
| 38 | subjects = {item["subject"]: item for item in summary["groups"][0]["subjects"]}
|
| 39 | self.assertEqual(subjects["git"]["verified_success_fraction_of_started"], .5)
|
| 40 | self.assertIsNone(subjects["git"]["success_wilson_interval"])
|
| 41 | self.assertIsNone(subjects["git"]["resources"]["reported_tokens"]["complete_total"])
|
| 42 | self.assertEqual(summary["groups"][0]["paired_comparisons"][0]["incomplete_pairs"], 1)
|
| 43 | self.assertFalse(summary["claim_eligible"])
|
| 44 |
|
| 45 | def test_legacy_shortened_timeout_is_not_a_complete_pair(self):
|
| 46 | configuration = plan(1)
|
| 47 | configuration["manifest"]["trial_timeout_seconds"] = 60
|
| 48 | events = outcome("0-git") + outcome("0-oak", "timeout")
|
| 49 | events[0]["timeout_seconds"] = 60
|
| 50 | events[2]["timeout_seconds"] = .15
|
| 51 | summary = self.report(configuration, events)
|
| 52 | self.assertEqual(summary["counts"]["budget_truncated"], 1)
|
| 53 | self.assertEqual(summary["counts"]["interrupted"], 1)
|
| 54 | pair = summary["groups"][0]["paired_comparisons"][0]
|
| 55 | self.assertEqual(pair["complete_pairs"], 0)
|
| 56 | self.assertIsNone(pair["success_delta_percentage_points"])
|
| 57 |
|
| 58 | def test_missing_instrument_is_unmeasured_not_a_paired_failure(self):
|
| 59 | events = outcome("0-git", "error") + outcome("0-oak")
|
| 60 | events[1]["row"].update(returncode=77, skip_reason="required oracle unavailable")
|
| 61 | summary = self.report(plan(1), events)
|
| 62 | self.assertEqual(summary["counts"]["skipped"], 1)
|
| 63 | self.assertEqual(summary["counts"]["started"], 2)
|
| 64 | pair = summary["groups"][0]["paired_comparisons"][0]
|
| 65 | self.assertEqual(pair["complete_pairs"], 0)
|
| 66 | self.assertIsNone(pair["success_delta_percentage_points"])
|
| 67 | git = next(subject for subject in summary["groups"][0]["subjects"] if subject["subject"] == "git")
|
| 68 | self.assertIsNone(git["success_wilson_interval"])
|
| 69 |
|
| 70 | def test_fixture_invalidated_trial_is_not_a_complete_paired_failure(self):
|
| 71 | events = outcome("0-git") + [event("0-oak", "started"), event("0-oak", "finished",
|
| 72 | outcome="error", measurement_invalidated=True, invalidation_reason="fixture content drift",
|
| 73 | rejected_row={"outcome": "pass", "success": True})]
|
| 74 | summary = self.report(plan(1), events)
|
| 75 | self.assertEqual(summary["counts"]["skipped"], 1)
|
| 76 | self.assertEqual(summary["counts"]["infrastructure_failure"], 0)
|
| 77 | pair = summary["groups"][0]["paired_comparisons"][0]
|
| 78 | self.assertEqual(pair["complete_pairs"], 0)
|
| 79 | self.assertIsNone(pair["success_delta_percentage_points"])
|
| 80 |
|
| 81 | def test_every_outcome_remains_visible(self):
|
| 82 | configuration = plan(4)
|
| 83 | events = (outcome("0-git") + outcome("0-oak", "fail") + outcome("1-git", "timeout") +
|
| 84 | [event("1-oak", "started"), event("1-oak", "finished", outcome="error", error="crash"),
|
| 85 | event("2-git", "started"), event("2-oak", "finished", outcome="skipped")])
|
| 86 | counts = self.report(configuration, events)["counts"]
|
| 87 | for field in ("success", "failure", "timeout", "infrastructure_failure", "interrupted", "skipped"):
|
| 88 | self.assertEqual(counts[field], 1)
|
| 89 | self.assertEqual(counts["unstarted"], 2)
|
| 90 | self.assertEqual(sum(counts[name] for name in ("success", "failure", "timeout", "infrastructure_failure", "interrupted", "skipped", "unstarted")), counts["scheduled"])
|
| 91 |
|
| 92 | def test_paired_bootstrap_preserves_block_pairs_and_is_seeded(self):
|
| 93 | events = outcome("0-git") + outcome("0-oak", "fail") + outcome("1-git", "fail") + outcome("1-oak")
|
| 94 | first = self.report(plan(), events)
|
| 95 | second = self.report(plan(), events)
|
| 96 | self.assertEqual(first, second)
|
| 97 | pair = first["groups"][0]["paired_comparisons"][0]
|
| 98 | self.assertEqual(pair["success_delta_percentage_points"], 0)
|
| 99 | self.assertEqual(pair["confidence_interval"], [-100, 100])
|
| 100 | self.assertEqual(pair["complete_pairs"], 2)
|
| 101 | self.assertFalse(pair["multiplicity_adjusted"])
|
| 102 |
|
| 103 | def test_singleton_pair_does_not_get_zero_width_interval(self):
|
| 104 | pair = self.report(plan(1), outcome("0-git") + outcome("0-oak"))["groups"][0]["paired_comparisons"][0]
|
| 105 | self.assertEqual(pair["success_delta_percentage_points"], 0)
|
| 106 | self.assertIsNone(pair["confidence_interval"])
|
| 107 |
|
| 108 | def test_missing_usage_does_not_become_zero_or_complete_total(self):
|
| 109 | summary = self.report(plan(), outcome("0-git", tokens=None) + outcome("1-git", tokens=0))
|
| 110 | values = summary["groups"][0]["subjects"][0]["resources"]["reported_tokens"]
|
| 111 | self.assertEqual(values["measured_n"], 1)
|
| 112 | self.assertEqual(values["missing_n"], 1)
|
| 113 | self.assertEqual(values["observed_sum"], 0)
|
| 114 | self.assertIsNone(values["complete_total"])
|
| 115 | self.assertIsNone(values["observed_cost_per_success"])
|
| 116 | self.assertEqual(values["success_conditioned"]["median"], 0)
|
| 117 |
|
| 118 | def test_partial_capture_counters_are_not_complete_usage(self):
|
| 119 | for changes in ({"outcome": "timeout", "success": False}, {"output_truncated": True},
|
| 120 | {"source": {"agent_execution": {"capture_complete": False}}},
|
| 121 | {"source": {"agent_execution": {"usage_complete": False}}},
|
| 122 | {"source": {"agent_execution": {"counters_coverage": "observed_lower_bounds"}}},
|
| 123 | {"source": {"agent_execution": {"output_limited": True}}}):
|
| 124 | events = outcome("0-git")
|
| 125 | events[-1]["row"].update(changes)
|
| 126 | resources = self.report(plan(1), events)["groups"][0]["subjects"][0]["resources"]
|
| 127 | for metric in ("reported_tokens", "assistant_turns", "tool_calls"):
|
| 128 | self.assertIsNone(resources[metric]["complete_total"])
|
| 129 | self.assertEqual(resources[metric]["missing_n"], 1)
|
| 130 | self.assertEqual(resources["agent_active_ms"]["complete_total"], 20)
|
| 131 |
|
| 132 | def test_capture_error_row_keeps_infrastructure_denominator_and_time(self):
|
| 133 | events = outcome("0-git")
|
| 134 | events[-1]["outcome"] = "error"
|
| 135 | events[-1]["row"].update(outcome="error", success=False, output_truncated=True)
|
| 136 | summary = self.report(plan(1), events)
|
| 137 | self.assertEqual(summary["counts"]["infrastructure_failure"], 1)
|
| 138 | resources = summary["groups"][0]["subjects"][0]["resources"]
|
| 139 | self.assertEqual(resources["agent_active_ms"]["complete_total"], 20)
|
| 140 | self.assertIsNone(resources["tool_calls"]["complete_total"])
|
| 141 |
|
| 142 | def test_failures_contribute_resource_cost_but_not_success_latency(self):
|
| 143 | events = outcome("0-git", tokens=10) + outcome("1-git", "fail", tokens=50)
|
| 144 | subject = self.report(plan(), events)["groups"][0]["subjects"][0]
|
| 145 | values = subject["resources"]["reported_tokens"]
|
| 146 | self.assertEqual(values["complete_total"], 60)
|
| 147 | self.assertEqual(values["observed_cost_per_success"], 60)
|
| 148 | self.assertEqual(values["success_conditioned"]["median"], 10)
|
| 149 | self.assertAlmostEqual(subject["success_wilson_interval"][0], .094531, places=5)
|
| 150 | self.assertAlmostEqual(subject["success_wilson_interval"][1], .905469, places=5)
|
| 151 |
|
| 152 | def test_zero_success_or_no_attempts_keeps_undefined_metrics_null(self):
|
| 153 | summary = self.report(plan(1), outcome("0-git", "fail"))
|
| 154 | git, oak = summary["groups"][0]["subjects"]
|
| 155 | self.assertIsNone(git["resources"]["reported_tokens"]["observed_cost_per_success"])
|
| 156 | self.assertIsNone(oak["verified_success_fraction_of_started"])
|
| 157 | self.assertIsNone(oak["resources"]["reported_tokens"]["observed_sum"])
|
| 158 | self.assertIn("unmeasured", render_markdown(summary))
|
| 159 |
|
| 160 | def test_instruction_and_model_conditions_never_pool(self):
|
| 161 | configuration = plan()
|
| 162 | configuration["schedule"][2]["instruction_level"] = "full-docs"
|
| 163 | configuration["schedule"][3]["model"] = "other-model"
|
| 164 | summary = self.report(configuration, [])
|
| 165 | self.assertEqual(len(summary["groups"]), 3)
|
| 166 |
|
| 167 | def test_duplicate_or_foreign_journal_evidence_rejected(self):
|
| 168 | for events in ([event("0-git", "started")] * 2,
|
| 169 | outcome("0-git") + [outcome("0-git")[1]],
|
| 170 | [event("unknown", "started")],
|
| 171 | [dict(event("0-git", "started"), plan_sha256="wrong")],
|
| 172 | [outcome("0-git")[1]],
|
| 173 | [event("0-git", "other")]):
|
| 174 | with self.subTest(events=events), self.assertRaises(ValueError):
|
| 175 | self.report(plan(), events)
|
| 176 |
|
| 177 | def test_contradictory_result_does_not_count_as_success(self):
|
| 178 | events = outcome("0-git")
|
| 179 | events[1]["row"]["outcome"] = "fail"
|
| 180 | with self.assertRaises(ValueError):
|
| 181 | self.report(plan(), events)
|
| 182 | events = outcome("0-git")
|
| 183 | events[1]["row"]["agent"] = {"model": "unplanned"}
|
| 184 | with self.assertRaises(ValueError):
|
| 185 | self.report(plan(), events)
|
| 186 |
|
| 187 | def test_mock_plan_never_becomes_claim_eligible(self):
|
| 188 | configuration = plan(1)
|
| 189 | for trial in configuration["schedule"]:
|
| 190 | trial.update(agent="mock", model=None)
|
| 191 | summary = self.report(configuration, outcome("0-git") + outcome("0-oak"))
|
| 192 | self.assertFalse(summary["claim_eligible"])
|
| 193 | self.assertIn("mock_agent_measurements_are_instrument_checks", summary["claim_ineligibility_reasons"])
|
| 194 |
|
| 195 | def test_duplicate_scheduled_subject_block_is_rejected(self):
|
| 196 | configuration = plan()
|
| 197 | duplicate = copy.deepcopy(configuration["schedule"][0])
|
| 198 | duplicate["trial_id"] = "new-id-same-block-subject"
|
| 199 | configuration["schedule"].append(duplicate)
|
| 200 | with self.assertRaises(ValueError):
|
| 201 | self.report(configuration, [])
|
| 202 |
|
| 203 | def test_runtime_environment_and_version_drift_cannot_pool_or_pair(self):
|
| 204 | events = outcome("0-git") + outcome("1-git")
|
| 205 | events[-1]["row"]["agent"] = {"environment": "local-default"}
|
| 206 | with self.assertRaisesRegex(ValueError, "incompatible campaign population"):
|
| 207 | self.report(plan(), events)
|
| 208 |
|
| 209 | def test_row_cannot_be_reassigned_to_another_trial(self):
|
| 210 | for field, value in (("task_id", "other"), ("bench_id", "other"), ("run", 99)):
|
| 211 | events = outcome("0-git")
|
| 212 | events[-1]["row"][field] = value
|
| 213 | with self.subTest(field=field), self.assertRaisesRegex(ValueError, "row trial identity"):
|
| 214 | self.report(plan(), events)
|
| 215 | events = outcome("0-git")
|
| 216 | events[-1]["outcome"] = "fail"
|
| 217 | with self.assertRaisesRegex(ValueError, "event outcome contradicts"):
|
| 218 | self.report(plan(), events)
|
| 219 |
|
| 220 | def test_efficiency_is_paired_and_only_joint_success_with_same_sources(self):
|
| 221 | events = outcome("0-git", tokens=10) + outcome("0-oak", tokens=20) + outcome("1-git", tokens=100) + outcome("1-oak", tokens=110)
|
| 222 | for entry in events:
|
| 223 | if "row" in entry:
|
| 224 | entry["row"]["token_metrics"]["token_source"] = "provider_reported"
|
| 225 | entry["row"]["wall_clock_metrics"]["measurement_source"] = "outer_cli"
|
| 226 | pair = self.report(plan(), events)["groups"][0]["paired_comparisons"][0]
|
| 227 | tokens = pair["success_conditioned_efficiency"]["reported_tokens"]
|
| 228 | self.assertEqual(tokens["candidate_minus_reference_mean_delta"], 10)
|
| 229 | self.assertEqual(tokens["confidence_interval"], [10, 10])
|
| 230 | self.assertEqual(tokens["measured_pairs"], 2)
|
| 231 | events[-1]["row"]["token_metrics"]["token_source"] = "other_convention"
|
| 232 | tokens = self.report(plan(), events)["groups"][0]["paired_comparisons"][0]["success_conditioned_efficiency"]["reported_tokens"]
|
| 233 | self.assertEqual(tokens["measured_pairs"], 1)
|
| 234 | self.assertEqual(tokens["incompatible_or_unknown_source_pairs"], 1)
|
| 235 | self.assertIsNone(tokens["confidence_interval"])
|
| 236 | events[-1]["row"].update(success=False, outcome="fail")
|
| 237 | tokens = self.report(plan(), events)["groups"][0]["paired_comparisons"][0]["success_conditioned_efficiency"]["reported_tokens"]
|
| 238 | self.assertEqual(tokens["jointly_successful_pairs"], 1)
|
| 239 | events = outcome("0-git") + outcome("0-oak")
|
| 240 | events[-1]["row"]["oracle_version"] = "different"
|
| 241 | with self.assertRaisesRegex(ValueError, "incompatible paired evidence"):
|
| 242 | self.report(plan(), events)
|
| 243 |
|
| 244 |
|
| 245 | if __name__ == "__main__":
|
| 246 | unittest.main()
|