mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 01:04:32 +00:00
refactor(tests): dedupe agent lock and loop detection fixtures
This commit is contained in:
@@ -75,6 +75,48 @@ function createNoProgressPollFixture(sessionId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function createReadNoProgressFixture() {
|
||||
return {
|
||||
toolName: "read",
|
||||
params: { path: "/same.txt" },
|
||||
result: {
|
||||
content: [{ type: "text", text: "same output" }],
|
||||
details: { ok: true },
|
||||
},
|
||||
} as const;
|
||||
}
|
||||
|
||||
function createPingPongFixture() {
|
||||
return {
|
||||
state: createState(),
|
||||
readParams: { path: "/a.txt" },
|
||||
listParams: { dir: "/workspace" },
|
||||
};
|
||||
}
|
||||
|
||||
function detectLoopAfterRepeatedCalls(params: {
|
||||
toolName: string;
|
||||
toolParams: unknown;
|
||||
result: unknown;
|
||||
count: number;
|
||||
config?: ToolLoopDetectionConfig;
|
||||
}) {
|
||||
const state = createState();
|
||||
recordRepeatedSuccessfulCalls({
|
||||
state,
|
||||
toolName: params.toolName,
|
||||
toolParams: params.toolParams,
|
||||
result: params.result,
|
||||
count: params.count,
|
||||
});
|
||||
return detectToolCallLoop(
|
||||
state,
|
||||
params.toolName,
|
||||
params.toolParams,
|
||||
params.config ?? enabledLoopDetectionConfig,
|
||||
);
|
||||
}
|
||||
|
||||
function recordSuccessfulPingPongCalls(params: {
|
||||
state: SessionState;
|
||||
readParams: { path: string };
|
||||
@@ -258,18 +300,13 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("keeps generic loops warn-only below global breaker threshold", () => {
|
||||
const state = createState();
|
||||
const params = { path: "/same.txt" };
|
||||
const result = {
|
||||
content: [{ type: "text", text: "same output" }],
|
||||
details: { ok: true },
|
||||
};
|
||||
|
||||
for (let i = 0; i < CRITICAL_THRESHOLD; i += 1) {
|
||||
recordSuccessfulCall(state, "read", params, result, i);
|
||||
}
|
||||
|
||||
const loopResult = detectToolCallLoop(state, "read", params, enabledLoopDetectionConfig);
|
||||
const fixture = createReadNoProgressFixture();
|
||||
const loopResult = detectLoopAfterRepeatedCalls({
|
||||
toolName: fixture.toolName,
|
||||
toolParams: fixture.params,
|
||||
result: fixture.result,
|
||||
count: CRITICAL_THRESHOLD,
|
||||
});
|
||||
expect(loopResult.stuck).toBe(true);
|
||||
if (loopResult.stuck) {
|
||||
expect(loopResult.level).toBe("warning");
|
||||
@@ -344,17 +381,13 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("warns for known polling no-progress loops", () => {
|
||||
const state = createState();
|
||||
const { params, result } = createNoProgressPollFixture("sess-1");
|
||||
recordRepeatedSuccessfulCalls({
|
||||
state,
|
||||
const loopResult = detectLoopAfterRepeatedCalls({
|
||||
toolName: "process",
|
||||
toolParams: params,
|
||||
result,
|
||||
count: WARNING_THRESHOLD,
|
||||
});
|
||||
|
||||
const loopResult = detectToolCallLoop(state, "process", params, enabledLoopDetectionConfig);
|
||||
expect(loopResult.stuck).toBe(true);
|
||||
if (loopResult.stuck) {
|
||||
expect(loopResult.level).toBe("warning");
|
||||
@@ -364,17 +397,13 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("blocks known polling no-progress loops at critical threshold", () => {
|
||||
const state = createState();
|
||||
const { params, result } = createNoProgressPollFixture("sess-1");
|
||||
recordRepeatedSuccessfulCalls({
|
||||
state,
|
||||
const loopResult = detectLoopAfterRepeatedCalls({
|
||||
toolName: "process",
|
||||
toolParams: params,
|
||||
result,
|
||||
count: CRITICAL_THRESHOLD,
|
||||
});
|
||||
|
||||
const loopResult = detectToolCallLoop(state, "process", params, enabledLoopDetectionConfig);
|
||||
expect(loopResult.stuck).toBe(true);
|
||||
if (loopResult.stuck) {
|
||||
expect(loopResult.level).toBe("critical");
|
||||
@@ -400,18 +429,13 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("blocks any tool with global no-progress breaker at 30", () => {
|
||||
const state = createState();
|
||||
const params = { path: "/same.txt" };
|
||||
const result = {
|
||||
content: [{ type: "text", text: "same output" }],
|
||||
details: { ok: true },
|
||||
};
|
||||
|
||||
for (let i = 0; i < GLOBAL_CIRCUIT_BREAKER_THRESHOLD; i += 1) {
|
||||
recordSuccessfulCall(state, "read", params, result, i);
|
||||
}
|
||||
|
||||
const loopResult = detectToolCallLoop(state, "read", params, enabledLoopDetectionConfig);
|
||||
const fixture = createReadNoProgressFixture();
|
||||
const loopResult = detectLoopAfterRepeatedCalls({
|
||||
toolName: fixture.toolName,
|
||||
toolParams: fixture.params,
|
||||
result: fixture.result,
|
||||
count: GLOBAL_CIRCUIT_BREAKER_THRESHOLD,
|
||||
});
|
||||
expect(loopResult.stuck).toBe(true);
|
||||
if (loopResult.stuck) {
|
||||
expect(loopResult.level).toBe("critical");
|
||||
@@ -441,9 +465,7 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("blocks ping-pong alternating patterns at critical threshold", () => {
|
||||
const state = createState();
|
||||
const readParams = { path: "/a.txt" };
|
||||
const listParams = { dir: "/workspace" };
|
||||
const { state, readParams, listParams } = createPingPongFixture();
|
||||
|
||||
recordSuccessfulPingPongCalls({
|
||||
state,
|
||||
@@ -465,9 +487,7 @@ describe("tool-loop-detection", () => {
|
||||
});
|
||||
|
||||
it("does not block ping-pong at critical threshold when outcomes are progressing", () => {
|
||||
const state = createState();
|
||||
const readParams = { path: "/a.txt" };
|
||||
const listParams = { dir: "/workspace" };
|
||||
const { state, readParams, listParams } = createPingPongFixture();
|
||||
|
||||
recordSuccessfulPingPongCalls({
|
||||
state,
|
||||
|
||||
Reference in New Issue
Block a user