classifyMainLoopStop keeps the unknown class for the empty-round and compression exits, but each reason must name its trigger: in --format json runs the manifest reason is the only stop diagnostic that leaves the runner, so the three non-budget stops must not collapse into one string.
(t *testing.T)
| 975 | // runs the manifest reason is the only stop diagnostic that leaves the runner, |
| 976 | // so the three non-budget stops must not collapse into one string. |
| 977 | func TestClassifyMainLoopStop(t *testing.T) { |
| 978 | reasons := make(map[string]llmloop.MainLoopStop) |
| 979 | for _, tc := range []struct { |
| 980 | name string |
| 981 | stop llmloop.MainLoopStop |
| 982 | wantClass session.FailureClass |
| 983 | wantReason string |
| 984 | }{ |
| 985 | {"max_rounds", llmloop.StopMaxRounds, session.FailureBudget, "reached the maximum tool-request rounds without finishing"}, |
| 986 | {"empty_rounds", llmloop.StopEmptyRounds, session.FailureUnknown, "stopped after repeated rounds without a usable tool result"}, |
| 987 | {"compression", llmloop.StopCompression, session.FailureUnknown, "stopped because context compression exceeded its threshold"}, |
| 988 | {"none", llmloop.StopNone, session.FailureUnknown, "main task stopped before completing"}, |
| 989 | } { |
| 990 | t.Run(tc.name, func(t *testing.T) { |
| 991 | class, reason := classifyMainLoopStop(tc.stop) |
| 992 | if class != tc.wantClass { |
| 993 | t.Errorf("class = %q, want %q", class, tc.wantClass) |
| 994 | } |
| 995 | if reason != tc.wantReason { |
| 996 | t.Errorf("reason = %q, want %q", reason, tc.wantReason) |
| 997 | } |
| 998 | if prev, dup := reasons[reason]; dup { |
| 999 | t.Errorf("reason %q is shared by %v and %v; stops must stay distinguishable", reason, prev, tc.stop) |
| 1000 | } |
| 1001 | reasons[reason] = tc.stop |
| 1002 | }) |
| 1003 | } |
| 1004 | } |
nothing calls this directly
no test coverage detected