Viewing files at commit 63ed254f5df1Commit detailsView latest

Browse files
tests/test_schema_and_oracle.py 104 lines · 4.3 KB · python
1
"""Tests for the agent row schema and the workflow oracles."""
2
3
from __future__ import annotations
4
5
import sys
6
import tempfile
7
from pathlib import Path
8
9
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
10
11
import unittest
12
from pathlib import Path
13
14
import agent_metrics_schema
15
import agent_workflow
16
import workflow_ab
17
18
19
class SchemaTest(unittest.TestCase):
20
    def test_example_row_validates(self) -> None:
21
        row = agent_metrics_schema.example_row()
22
        self.assertEqual(agent_metrics_schema.validate_row(row), [])
23
24
    def test_missing_group_rejected(self) -> None:
25
        row = agent_metrics_schema.example_row()
26
        del row["turn_metrics"]
27
        errors = agent_metrics_schema.validate_row(row)
28
        self.assertTrue(any("turn_metrics" in error for error in errors))
29
30
    def test_unknown_property_rejected(self) -> None:
31
        row = agent_metrics_schema.example_row()
32
        row["token_metrics"]["surprise"] = 1
33
        errors = agent_metrics_schema.validate_row(row)
34
        self.assertTrue(any("surprise" in error for error in errors))
35
36
    def test_nulls_are_legal_for_unmeasured_signals(self) -> None:
37
        # ADR-0002: unmeasured must be expressible as null, not forced to zero.
38
        row = agent_metrics_schema.example_row()
39
        row["turn_metrics"]["assistant_turns_total"] = None
40
        row["bytes_metrics"]["bytes_hydrated"] = None
41
        row["wall_clock_metrics"]["tool_wait_ms"] = None
42
        self.assertEqual(agent_metrics_schema.validate_row(row), [])
43
44
45
class OracleShapeTest(unittest.TestCase):
46
    def workflow(self, name: str) -> workflow_ab.Workflow:
47
        return workflow_ab.WORKFLOWS[name]
48
49
    def test_bugfix_requires_the_one_file(self) -> None:
50
        wf = self.workflow("bugfix_test_loop")
51
        self.assertTrue(agent_workflow.expected_change_ok(wf, ["app/pricing.py"]))
52
        self.assertTrue(
53
            agent_workflow.expected_change_ok(
54
                wf,
55
                [
56
                    "app/pricing.py",
57
                    "app/__pycache__/pricing.cpython-313.pyc",
58
                    "tests/__pycache__/test_pricing.cpython-313.pyc",
59
                ],
60
            )
61
        )
62
        self.assertFalse(agent_workflow.expected_change_ok(wf, []))
63
        self.assertFalse(agent_workflow.expected_change_ok(wf, ["app/pricing.py", "README.md"]))
64
65
    def test_workspace_manifest_ignores_python_bytecode_caches(self) -> None:
66
        with tempfile.TemporaryDirectory() as tmp:
67
            root = Path(tmp)
68
            (root / "app" / "__pycache__").mkdir(parents=True)
69
            (root / "tests" / "__pycache__").mkdir(parents=True)
70
            (root / "app" / "pricing.py").write_text("def f(): pass\n")
71
            (root / "app" / "__pycache__" / "pricing.cpython-313.pyc").write_bytes(b"cache")
72
            (root / "tests" / "__pycache__" / "test_pricing.cpython-313.pyc").write_bytes(b"cache")
73
74
            manifest = agent_workflow.workspace_manifest(root)
75
76
        self.assertEqual(list(manifest), ["app/pricing.py"])
77
78
    def test_history_archaeology_is_read_only(self) -> None:
79
        wf = self.workflow("history_archaeology")
80
        self.assertTrue(agent_workflow.expected_change_ok(wf, []))
81
        self.assertFalse(agent_workflow.expected_change_ok(wf, ["app/pricing.py"]))
82
83
    def test_error_recovery_allows_only_readme(self) -> None:
84
        wf = self.workflow("vcs_error_recovery")
85
        self.assertTrue(agent_workflow.expected_change_ok(wf, ["README.md"]))
86
        self.assertFalse(agent_workflow.expected_change_ok(wf, ["app/pricing.py"]))
87
88
89
class WorkflowCatalogTest(unittest.TestCase):
90
    def test_snapshot_accounting_in_steps(self) -> None:
91
        # Workflow steps must follow the snapshot convention from the
92
        # command-semantics contract: two git calls, one oak call.
93
        git = workflow_ab.Subject(name="git", kind="git", label="Git", bin=Path("/usr/bin/git"))
94
        oak = workflow_ab.Subject(name="oak", kind="oak", label="Oak", bin=Path("/usr/local/bin/oak"))
95
        git_ops = [s.operation for s in workflow_ab.vcs_workflow_steps(git, "agent-default")]
96
        oak_ops = [s.operation for s in workflow_ab.vcs_workflow_steps(oak, "agent-default")]
97
        self.assertIn("vcs.snapshot.add", git_ops)
98
        self.assertIn("vcs.snapshot.commit", git_ops)
99
        self.assertIn("vcs.snapshot", oak_ops)
100
        self.assertNotIn("vcs.snapshot.add", oak_ops)
101
102
103
if __name__ == "__main__":
104
    unittest.main()