Viewing files at commit 369435870869Commit detailsView latest

Browse files
tests/test_rows.py 167 lines · 6.5 KB · python
1
"""Tests for the row contract: accessors and per-lane validation."""
2
3
from __future__ import annotations
4
5
import sys
6
from pathlib import Path
7
8
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
9
10
import unittest
11
12
from oakbench.rows import (
13
    number_or_none,
14
    output_bytes,
15
    output_truncated,
16
    token_total,
17
    tool_calls,
18
    validate_rows,
19
)
20
21
22
class TokenTotalTest(unittest.TestCase):
23
    def test_core_admitted_output_estimate_survives_source_output_cap(self):
24
        self.assertEqual(token_total({"operation": "diff", "output_truncated": True,
25
                                     "estimated_tokens_total": 12}), 12)
26
27
    def test_provider_reported_wins(self) -> None:
28
        row = {
29
            "token_metrics": {"total_tokens_reported": 1234, "transcript_chars": 999999},
30
            "estimated_tokens_total": 7,
31
        }
32
        self.assertEqual(token_total(row), 1234.0)
33
34
    def test_reported_parts_summed(self) -> None:
35
        row = {
36
            "token_metrics": {
37
                "total_tokens_reported": None,
38
                "input_tokens_reported": 100,
39
                "output_tokens_reported": 40,
40
                "reasoning_tokens_reported": None,
41
            }
42
        }
43
        self.assertEqual(token_total(row), 140.0)
44
45
    def test_flat_estimate(self) -> None:
46
        self.assertEqual(token_total({"estimated_tokens_total": 88}), 88.0)
47
48
    def test_char_fallback(self) -> None:
49
        row = {"token_metrics": {"total_tokens_reported": None, "transcript_chars": 401}}
50
        self.assertEqual(token_total(row), 101.0)
51
52
    def test_no_signal_is_none(self) -> None:
53
        self.assertIsNone(token_total({"operation": "proc.spawn"}))
54
55
    def test_non_finite_and_boolean_numbers_are_unmeasured(self) -> None:
56
        for value in (True, "nan", "inf", float("nan"), float("inf")):
57
            with self.subTest(value=value):
58
                self.assertIsNone(number_or_none(value))
59
                self.assertIsNone(token_total({"estimated_tokens_total": value}))
60
                self.assertIsNone(output_bytes({"raw_output_bytes": value}))
61
62
63
class OtherAccessorsTest(unittest.TestCase):
64
    def test_tool_calls_paths(self) -> None:
65
        self.assertEqual(tool_calls({"tool_call_count": 3}), 3.0)
66
        self.assertEqual(tool_calls({"tool_calls": {"total": 5}}), 5.0)
67
        self.assertEqual(tool_calls({"tool_call_metrics": {"tool_calls_total": 9}}), 9.0)
68
        self.assertIsNone(tool_calls({}))
69
70
    def test_output_bytes_paths(self) -> None:
71
        self.assertEqual(output_bytes({"raw_output_bytes": 10}), 10.0)
72
        self.assertEqual(output_bytes({"stdout_bytes": 4, "stderr_bytes": 6}), 10.0)
73
        self.assertEqual(output_bytes({"token_estimate": {"stdout_bytes": 7, "stderr_bytes": 3}}), 10.0)
74
        self.assertIsNone(output_bytes({}))
75
76
    def test_output_truncated(self) -> None:
77
        self.assertTrue(output_truncated({"output_truncated": True}))
78
        self.assertTrue(output_truncated({"token_estimate": {"stderr_truncated": True}}))
79
        self.assertFalse(output_truncated({}))
80
81
82
def minimal_core_row(**overrides) -> dict:
83
    row = {
84
        "bench_id": "20260610T000000Z",
85
        "profile": "smoke",
86
        "scenario": "tiny_text",
87
        "operation": "status.clean",
88
        "run": 0,
89
        "subject": "git",
90
        "subject_kind": "git",
91
        "elapsed_ms": 1.5,
92
        "returncode": 0,
93
        "command": ["git", "status", "--short"],
94
    }
95
    row.update(overrides)
96
    return row
97
98
99
class LaneValidationTest(unittest.TestCase):
100
    def test_valid_core_row(self) -> None:
101
        self.assertEqual(validate_rows([minimal_core_row()], "core"), [])
102
103
    def test_only_explicit_core_output_diagnostics_allow_null_timing(self):
104
        diagnostic = minimal_core_row(operation="diff.full.inforecall", elapsed_ms=None,
105
                                      measurement_phase="output_diagnostic")
106
        self.assertEqual(validate_rows([diagnostic], "core"), [])
107
        for changes in ({"operation": "diff.dirty"}, {"measurement_phase": "measured"},
108
                        {"operation": "arbitrary.inforecall"}, {"elapsed_ms": "unknown"}):
109
            with self.subTest(changes=changes):
110
                self.assertTrue(any("elapsed_ms" in message for message in
111
                                    validate_rows([{**diagnostic, **changes}], "core")))
112
        self.assertTrue(any("elapsed_ms" in message for message in validate_rows([diagnostic], "agent")))
113
114
    def test_missing_key_flagged(self) -> None:
115
        row = minimal_core_row()
116
        del row["elapsed_ms"]
117
        errors = validate_rows([row], "core")
118
        self.assertEqual(len(errors), 1)
119
        self.assertIn("elapsed_ms", errors[0])
120
121
    def test_type_checks(self) -> None:
122
        errors = validate_rows([minimal_core_row(elapsed_ms="fast")], "core")
123
        self.assertTrue(any("must be numeric" in error for error in errors))
124
        errors = validate_rows([minimal_core_row(returncode=True)], "core")
125
        self.assertTrue(any("must be an integer" in error for error in errors))
126
127
    def test_elapsed_ms_rejects_bool_negative_and_non_finite(self) -> None:
128
        for value in (True, -1.0, float("nan"), float("inf")):
129
            with self.subTest(value=value):
130
                errors = validate_rows([minimal_core_row(elapsed_ms=value)], "core")
131
                self.assertTrue(any("elapsed_ms" in error for error in errors))
132
133
    def test_unknown_lane(self) -> None:
134
        errors = validate_rows([], "warp")
135
        self.assertEqual(len(errors), 1)
136
        self.assertIn("unknown lane", errors[0])
137
138
    def test_contention_lane_requires_mode_and_workers(self) -> None:
139
        row = minimal_core_row()
140
        errors = validate_rows([row], "contention")
141
        joined = "\n".join(errors)
142
        self.assertIn("contention_mode", joined)
143
        self.assertIn("workers", joined)
144
145
    def test_optional_provenance_fields_are_type_checked(self) -> None:
146
        row = minimal_core_row(
147
            runner_id="abc123",
148
            runner_class="macos-apple-silicon",
149
            runner_profile={"system": "Darwin"},
150
            fixture_id="large-mixed-v1",
151
            fixture_seed="seed",
152
            binary_sha256="0" * 64,
153
            env_isolation_version="1",
154
        )
155
        self.assertEqual(validate_rows([row], "core"), [])
156
        errors = validate_rows(
157
            [minimal_core_row(runner_profile="not-object", runner_id=7, env_isolation_version=1)],
158
            "core",
159
        )
160
        joined = "\n".join(errors)
161
        self.assertIn("runner_profile", joined)
162
        self.assertIn("runner_id", joined)
163
        self.assertIn("env_isolation_version", joined)
164
165
166
if __name__ == "__main__":
167
    unittest.main()