merge: maestro/tsk_N5wO3Armums2 [重试与超时策略细化(可配 + 退避)]
# Conflicts: # src/api/server.ts # test/store.test.ts
This commit is contained in:
@@ -301,3 +301,69 @@ test('合并失败补救:建最高优先级 easy 任务,且幂等不重复
|
||||
assert.notEqual(fresh.id, rem.id, '旧补救任务已结束 → 建新的');
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('patchProject:maxRetries/timeoutMs 可配 + 校验', () => {
|
||||
const s = freshStore();
|
||||
const p = s.createProject({ name: 'cfg', repoPath: '/tmp/cfg-' + Math.random() });
|
||||
// 默认值
|
||||
assert.equal(p.maxRetries, 2);
|
||||
assert.equal(p.timeoutMs, 1_800_000);
|
||||
|
||||
// 更新 maxRetries + timeoutMs
|
||||
const updated = s.patchProject(p.id, { maxRetries: 5, timeoutMs: 60_000 });
|
||||
assert.equal(updated.maxRetries, 5);
|
||||
assert.equal(updated.timeoutMs, 60_000);
|
||||
|
||||
// 校验:maxRetries 必须 >=0 的整数
|
||||
assert.throws(() => s.patchProject(p.id, { maxRetries: -1 }), /maxRetries/);
|
||||
assert.throws(() => s.patchProject(p.id, { maxRetries: 1.5 }), /maxRetries/);
|
||||
|
||||
// 校验:timeoutMs 必须 >=1000
|
||||
assert.throws(() => s.patchProject(p.id, { timeoutMs: 500 }), /timeoutMs/);
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('createProject:maxRetries/timeoutMs 自定义初值', () => {
|
||||
const s = freshStore();
|
||||
const p = s.createProject({ name: 'custom', repoPath: '/tmp/custom-' + Math.random(), maxRetries: 0, timeoutMs: 120_000 });
|
||||
assert.equal(p.maxRetries, 0);
|
||||
assert.equal(p.timeoutMs, 120_000);
|
||||
s.close();
|
||||
});
|
||||
|
||||
test('requeueTask:needs_attention → queued,重置 retryBaseline', () => {
|
||||
const s = freshStore();
|
||||
const p = s.createProject({ name: 'rq', repoPath: '/tmp/rq-' + Math.random() });
|
||||
const t = s.createTask({ projectId: p.id, title: 'flaky', complexity: 'easy' });
|
||||
|
||||
// 推进到 needs_attention(模拟 3 次失败 run)
|
||||
s.transition(t.id, 'queued');
|
||||
s.transition(t.id, 'executing');
|
||||
s.transition(t.id, 'failed');
|
||||
s.transition(t.id, 'queued');
|
||||
s.transition(t.id, 'executing');
|
||||
s.transition(t.id, 'failed');
|
||||
s.transition(t.id, 'queued');
|
||||
s.transition(t.id, 'executing');
|
||||
s.transition(t.id, 'failed');
|
||||
s.transition(t.id, 'needs_attention');
|
||||
|
||||
// 模拟 3 条 failed executor run
|
||||
const r1 = s.startRun(t.id, 'executor');
|
||||
s.finishRun(r1.id, 'failed', { error: 'boom1' });
|
||||
const r2 = s.startRun(t.id, 'executor');
|
||||
s.finishRun(r2.id, 'failed', { error: 'boom2' });
|
||||
const r3 = s.startRun(t.id, 'executor');
|
||||
s.finishRun(r3.id, 'failed', { error: 'boom3' });
|
||||
|
||||
assert.equal(s.getTask(t.id)!.retryBaseline, 0);
|
||||
|
||||
// 一键重投
|
||||
const requeued = s.requeueTask(t.id);
|
||||
assert.equal(requeued.status, 'queued');
|
||||
assert.equal(requeued.retryBaseline, 3); // 基线设为当前失败 run 数
|
||||
|
||||
// 只有 needs_attention 状态才可重投
|
||||
assert.throws(() => s.requeueTask(t.id), /needs_attention/);
|
||||
s.close();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user