Viewing files at commit 2caecb1e6549Commit detailsView latest

Browse files
tests/test_command_semantics.py 117 lines · 5.6 KB · python
1
"""Tests for the command-semantics contract (the fairness guarantee)."""
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
import json
12
13
from oakbench.command_semantics import CommandSemantics, semantics
14
15
ROOT = Path(__file__).resolve().parents[1]
16
17
18
class ContractTest(unittest.TestCase):
19
    def setUp(self) -> None:
20
        self.sem = semantics()
21
22
    def test_version_is_recorded_shape(self) -> None:
23
        self.assertRegex(self.sem.version, r"^\d{4}-\d{2}-\d{2}\.\d+$")
24
25
    def test_snapshot_call_accounting(self) -> None:
26
        # The suite-wide convention: git pays stage+commit as two tool calls,
27
        # oak snapshots in one. Agents pay every recorded call.
28
        git_commands = self.sem.snapshot_commands("git", "git", "agent task")
29
        oak_commands = self.sem.snapshot_commands("oak", "oak", "agent task")
30
        self.assertEqual(len(git_commands), 2)
31
        self.assertEqual(len(oak_commands), 1)
32
        self.assertEqual(git_commands[0], ["git", "add", "."])
33
        self.assertEqual(git_commands[1], ["git", "commit", "-m", "agent task"])
34
        self.assertEqual(oak_commands[0], ["oak", "commit", "--no-verify"])
35
36
    def test_message_substitution(self) -> None:
37
        self.assertEqual(self.sem.commit_args("git", "initial"), ["commit", "-m", "initial"])
38
        # Oak commits carry no message; the contract must not leak the placeholder.
39
        self.assertNotIn("{message}", " ".join(self.sem.commit_args("oak", "anything")))
40
41
    def test_workflow_tracks_keep_oak_snapshots_local_only(self) -> None:
42
        self.assertEqual(self.sem.status_args("git", "agent-default"), ["status", "--short"])
43
        self.assertEqual(self.sem.status_args("git", "core-equivalent"), ["status", "--porcelain=v1"])
44
        self.assertEqual(self.sem.diff_args("git", "agent-default"), ["diff", "--stat"])
45
        self.assertEqual(self.sem.diff_args("git", "core-equivalent"), ["diff", "--no-ext-diff"])
46
        # Workflow snapshots are local checkpoints. Remote workflow setup and
47
        # publish phases must add explicit `oak push` or `oak commit --push`.
48
        self.assertEqual(self.sem.commit_args("oak", "agent task"), ["commit", "--no-verify"])
49
        for track in ("agent-default", "core-equivalent"):
50
            self.assertEqual(self.sem.status_args("oak", track), ["status"])
51
        # Agents type plain `oak diff` (summary); the core-equivalent full diff
52
        # is `oak diff --print` (unified hunks + binary notices, oak >= 0.96).
53
        self.assertEqual(self.sem.diff_args("oak", "agent-default"), ["diff"])
54
        self.assertEqual(self.sem.diff_args("oak", "core-equivalent"), ["diff", "--print"])
55
56
    def test_published_operation_contracts_use_agent_native_oak_commands(self) -> None:
57
        data = json.loads((ROOT / "config" / "command_semantics.json").read_text())
58
        operations = data["operations"]
59
60
        self.assertEqual(operations["status.short"]["oak"], ["oak", "status", "--porcelain"])
61
        self.assertTrue(operations["status.short"]["semantic_contract_match"])
62
        self.assertEqual(operations["diff.name_only"]["oak"], ["oak", "diff", "--name-only"])
63
        self.assertTrue(operations["diff.name_only"]["semantic_contract_match"])
64
        self.assertEqual(
65
            operations["commit.quiet"]["oak"],
66
            ["oak", "commit", "--json", "--quiet", "--no-verify"],
67
        )
68
        self.assertTrue(operations["commit.quiet"]["semantic_contract_match"])
69
70
    def test_displays_match_args(self) -> None:
71
        self.assertEqual(self.sem.status_display("git", "agent-default"), "git status --short")
72
        self.assertEqual(self.sem.diff_display("oak", "agent-default"), "oak diff")
73
74
    def test_oak_agent_native_json_probes_are_configured(self) -> None:
75
        self.assertEqual(self.sem.agent_native_json_probes("git", "agent-task"), {})
76
        probes = self.sem.agent_native_json_probes("oak", "agent-task")
77
        self.assertEqual(
78
            probes["oak.agent.state.json"]["args"],
79
            ["agent", "state", "--json"],
80
        )
81
        self.assertEqual(
82
            probes["oak.agent.state.compact.json"]["args"],
83
            ["agent", "state", "--json", "--compact"],
84
        )
85
        self.assertNotIn("changes", probes["oak.agent.state.compact.json"]["required_fields"])
86
        self.assertIn("finish_eligible", probes["oak.agent.state.compact.json"]["required_fields"])
87
        self.assertIn("recommended_action", probes["oak.agent.state.compact.json"]["required_fields"])
88
        self.assertIn("recommended_action", probes["oak.agent.state.json"]["required_fields"])
89
        self.assertEqual(probes["oak.agent.state.json"]["minimum_oak_version"], "0.98.0")
90
        self.assertEqual(probes["oak.agent.state.compact.json"]["minimum_oak_version"], "0.98.0")
91
        self.assertEqual(
92
            probes["oak.status.json"]["args"],
93
            ["status", "--json"],
94
        )
95
        self.assertEqual(
96
            probes["oak.branch.review.json"]["args"],
97
            ["branch", "review", "--json", "agent-task"],
98
        )
99
        self.assertEqual(
100
            probes["oak.branch.diff.json"]["args"],
101
            ["branch", "diff", "--json", "agent-task"],
102
        )
103
        for spec in probes.values():
104
            self.assertIn("--json", spec["args"])
105
            self.assertIn("schema_version", spec["required_fields"])
106
107
    def test_unknown_kind_rejected(self) -> None:
108
        with self.assertRaises(ValueError):
109
            self.sem.init_args("svn")
110
111
    def test_loads_fresh_from_config(self) -> None:
112
        fresh = CommandSemantics()
113
        self.assertEqual(fresh.version, self.sem.version)
114
115
116
if __name__ == "__main__":
117
    unittest.main()