File size: 3,864 Bytes
37e3d5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python3
"""Tests for the bounded correction-loop stop policy (§3.6).

Pure stdlib unittest. The termination guarantee (test_hard_ceiling_always_terminates
and test_loop_cannot_exceed_max_iter) is the load-bearing property of this suite.
"""

import os
import sys
import unittest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "stage4_review"))

from correction_loop import budget_exceeded, decide  # noqa: E402


def _it(fidelity, defect_tags=None, reverted=False):
    """Build one iteration record."""
    return {
        "fidelity": fidelity,
        "defectTags": list(defect_tags or []),
        "reverted": reverted,
    }


class CorrectionLoopTest(unittest.TestCase):
    def test_empty_history_continues(self):
        d = decide([])
        self.assertFalse(d["stop"])
        self.assertEqual(d["action"], "continue-iterating")
        self.assertEqual(d["reason"], "no iterations yet")

    def test_success_when_target_met_and_no_defects(self):
        d = decide([_it(0.90, [])])
        self.assertTrue(d["stop"])
        self.assertEqual(d["action"], "continue")

    def test_success_requires_no_defects(self):
        # Fidelity above target BUT open defects remain -> NOT success.
        d = decide([_it(0.95, ["visible-seam"])])
        self.assertNotEqual(d["action"], "continue")

    def test_repeated_defect_stops_refine_spec(self):
        history = [
            _it(0.60, ["seam"]),
            _it(0.72, ["seam", "gap"]),
        ]
        d = decide(history)
        self.assertTrue(d["stop"])
        self.assertEqual(d["action"], "refine-spec")
        self.assertIn("seam", d["reason"])

    def test_plateau_stops_request_input(self):
        # Second iteration improves by < min_delta and stays below target.
        history = [
            _it(0.60, []),
            _it(0.61, []),
        ]
        d = decide(history)
        self.assertTrue(d["stop"])
        self.assertEqual(d["action"], "request-input")

    def test_hard_ceiling_always_terminates(self):
        # Steadily improving (delta 0.05 > min_delta so PLATEAU cannot fire),
        # never reaching target 0.85, no shared defect tags, no reverts.
        history = [
            _it(0.50, []),
            _it(0.55, []),
            _it(0.60, []),
            _it(0.65, []),
            _it(0.70, []),
            _it(0.75, []),
        ]
        self.assertEqual(len(history), 6)  # == default max_iter
        d = decide(history)
        self.assertTrue(d["stop"])
        self.assertIn("ceiling", d["reason"].lower())

    def test_oscillation_two_reverts_stops(self):
        history = [
            _it(0.50, [], reverted=True),
            _it(0.50, [], reverted=False),
            _it(0.50, [], reverted=True),
        ]
        d = decide(history)
        self.assertTrue(d["stop"])
        self.assertEqual(d["action"], "refine-spec")
        self.assertEqual(d["reason"], "oscillating/thrashing")

    def test_loop_cannot_exceed_max_iter(self):
        # Simulate a real caller loop. Even with monotonic tiny improvements,
        # the body MUST execute at most max_iter times. A hard safety counter
        # fails the test if the loop somehow refuses to terminate.
        max_iter = 6
        history = []
        iterations = 0
        safety = 0
        while not decide(history, max_iter=max_iter)["stop"]:
            safety += 1
            if safety > 100:
                self.fail("did not terminate")
            history.append(_it(0.001 * (len(history) + 1), []))
            iterations += 1
        self.assertLessEqual(iterations, max_iter)

    def test_budget_exceeded_helper(self):
        self.assertTrue(budget_exceeded(100, 100))
        self.assertTrue(budget_exceeded(101, 100))
        self.assertFalse(budget_exceeded(99, 100))


if __name__ == "__main__":
    unittest.main(verbosity=2)