test(bf-27e4): add test coverage for beadsCompleted vs stuck detection metric
- Add test case for worker processing 100 beads with 0 successful completions - Fix incorrect test expecting beadsCompleted to increment on bead.released - beadsCompleted only increments on bead.completed events - beadsReleased increments on bead.released with release_success - Stuck detection now uses unified beadsCompleted metric with clear messaging Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
04904ce032
commit
c047131e09
2 changed files with 53 additions and 2 deletions
|
|
@ -227,12 +227,13 @@ describe('InMemoryEventStore', () => {
|
||||||
expect(worker?.status).toBe('idle');
|
expect(worker?.status).toBe('idle');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should increment beadsCompleted on bead.released with release_success', () => {
|
it('should increment beadsReleased on bead.released with release_success', () => {
|
||||||
store.add(createEvent({ worker: 'w-test', msg: 'bead.released', reason: 'release_success', bead: 'bd-1' }));
|
store.add(createEvent({ worker: 'w-test', msg: 'bead.released', reason: 'release_success', bead: 'bd-1' }));
|
||||||
store.add(createEvent({ worker: 'w-test', msg: 'bead.released', reason: 'release_success', bead: 'bd-2' }));
|
store.add(createEvent({ worker: 'w-test', msg: 'bead.released', reason: 'release_success', bead: 'bd-2' }));
|
||||||
|
|
||||||
const worker = store.getWorker('w-test');
|
const worker = store.getWorker('w-test');
|
||||||
expect(worker?.beadsCompleted).toBe(2);
|
expect(worker?.beadsReleased).toBe(2);
|
||||||
|
expect(worker?.beadsCompleted).toBe(0); // beadsCompleted only increments on bead.completed
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear activeBead and activeFiles on bead.released with release_success', () => {
|
it('should clear activeBead and activeFiles on bead.released with release_success', () => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const makeWorker = (overrides: Partial<WorkerInfo> = {}): WorkerInfo => ({
|
||||||
id: 'w-test',
|
id: 'w-test',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
beadsCompleted: 3,
|
beadsCompleted: 3,
|
||||||
|
beadsReleased: 0,
|
||||||
firstSeen: Date.now() - 5 * 60 * 1000,
|
firstSeen: Date.now() - 5 * 60 * 1000,
|
||||||
lastActivity: Date.now(),
|
lastActivity: Date.now(),
|
||||||
activeFiles: [],
|
activeFiles: [],
|
||||||
|
|
@ -203,6 +204,55 @@ describe('Stuck Detection', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('long-running detection', () => {
|
||||||
|
it('detects worker with many beads released but zero successful completions', () => {
|
||||||
|
// Worker that has processed 100 beads (all timed out/deferred)
|
||||||
|
const worker = makeWorker({
|
||||||
|
firstSeen: Date.now() - 40 * 60 * 1000, // 40 minutes ago
|
||||||
|
lastActivity: Date.now(), // Recent activity to avoid no_progress detection
|
||||||
|
beadsCompleted: 0, // No successful completions
|
||||||
|
beadsReleased: 100, // All beads timed out/deferred
|
||||||
|
});
|
||||||
|
const events: LogEvent[] = [];
|
||||||
|
// Create only 5 events to avoid triggering "events but no completions" in no_progress
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
events.push(makeEvent({ ts: Date.now() - i * 30000, msg: 'working on task' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = isWorkerStuck(worker, events);
|
||||||
|
|
||||||
|
expect(pattern).not.toBeNull();
|
||||||
|
expect(pattern!.type).toBe('long_running');
|
||||||
|
expect(pattern!.reason).toContain('40m'); // Running for 40 minutes
|
||||||
|
expect(pattern!.reason).toContain('100 processed'); // 100 beads released
|
||||||
|
expect(pattern!.reason).toContain('0 successful completions'); // No successful completions
|
||||||
|
expect(pattern!.reason).toContain('timed out/deferred'); // Clarifies why
|
||||||
|
expect(pattern!.evidence).toContain('Beads successfully completed: 0');
|
||||||
|
expect(pattern!.evidence).toContain('Beads released (including timed-out): 100');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects worker with only 1 successful completion after long runtime', () => {
|
||||||
|
const worker = makeWorker({
|
||||||
|
firstSeen: Date.now() - 30 * 60 * 1000, // 30 minutes ago
|
||||||
|
beadsCompleted: 1, // Only 1 successful completion
|
||||||
|
beadsReleased: 50, // 50 beads released (49 timed out)
|
||||||
|
});
|
||||||
|
const events: LogEvent[] = [];
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
events.push(makeEvent({ ts: Date.now() - i * 40000 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = isWorkerStuck(worker, events);
|
||||||
|
|
||||||
|
expect(pattern).not.toBeNull();
|
||||||
|
expect(pattern!.type).toBe('long_running');
|
||||||
|
expect(pattern!.reason).toContain('30m');
|
||||||
|
expect(pattern!.reason).toContain('only 1 successful completion');
|
||||||
|
expect(pattern!.evidence).toContain('Beads successfully completed: 1');
|
||||||
|
expect(pattern!.evidence).toContain('Beads released (including timed-out): 50');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('legacy detection (non-state-transition)', () => {
|
describe('legacy detection (non-state-transition)', () => {
|
||||||
it('still detects repeated tool calls', () => {
|
it('still detects repeated tool calls', () => {
|
||||||
const worker = makeWorker();
|
const worker = makeWorker();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue