Files
2026-05-01 22:03:05 +02:00

87 lines
3.6 KiB
TypeScript

/**
* Bug Condition Exploration Tests — Task 1
*
* These tests surface the two leaks that cause Jest to force-exit its worker:
*
* Cause A — Unref'd timer: MI2.onOutput() creates a setTimeout without .unref(),
* keeping the Node.js event loop alive after tests finish.
*
* Cause B — Dangling listeners: MI2 instances created in tests without afterEach
* cleanup leave EventEmitter listeners registered on live objects.
*
* EXPECTED OUTCOME ON UNFIXED CODE:
* - Cause A test FAILS → hasRef() returns true (timer keeps event loop alive)
* - Cause B test PASSES → MI2 constructor adds no listeners itself
* (the leak pattern is about not having afterEach, not about constructor listeners)
*
* DO NOT modify mi2.ts or fix these tests when they fail.
* Failure of Cause A confirms the bug exists.
*
* Validates: Requirements 1.1, 1.2, 1.3
*/
import { MI2 } from '../../src/backend/mi2/mi2';
// ---------------------------------------------------------------------------
// Cause A — Timer leak
// ---------------------------------------------------------------------------
describe('Cause A — Timer leak: debugReadyTimeout should be unref\'d', () => {
let mi2: MI2;
afterEach(() => {
// Clean up the timer so it doesn't fire during other tests
if (mi2['debugReadyTimeout']) {
clearTimeout(mi2['debugReadyTimeout']);
}
mi2.removeAllListeners();
});
test(
'hasRef() should be false after onOutput() triggers the PlatformIO init branch',
() => {
mi2 = new MI2('gdb', []);
// A valid GDB console stream record: ~"<content>\n"
// The ~ prefix makes parseMI produce isStream: true with type 'console'
const triggerLine = '~"PlatformIO: Initialization completed\\n"';
mi2.onOutput(triggerLine);
// The timer must have been created
expect(mi2['debugReadyTimeout']).toBeDefined();
// On UNFIXED code this assertion FAILS because .unref() is never called.
// hasRef() returns true → the timer keeps the event loop alive.
// On FIXED code this assertion PASSES because .unref() is called.
expect(mi2['debugReadyTimeout'].hasRef()).toBe(false);
}
);
});
// ---------------------------------------------------------------------------
// Cause B — Listener leak
// ---------------------------------------------------------------------------
describe('Cause B — Listener leak: MI2 instance should have zero listeners after construction', () => {
/**
* NOTE: The MI2 constructor does NOT add any listeners itself.
* The leak pattern described in the bug report is about tests that create MI2
* instances without an afterEach cleanup block — those instances accumulate
* listeners added during the test body and are never cleaned up.
*
* This test verifies the baseline: a freshly constructed MI2 instance has
* zero 'msg' listeners. If this passes, it documents that Cause B is about
* the *pattern* of missing afterEach, not about listeners added in the constructor.
*/
test(
'a freshly constructed MI2 instance has zero msg listeners',
() => {
const mi2 = new MI2('gdb', []);
// No afterEach cleanup is intentionally omitted here to mirror the
// pattern in breakpoint-error-handling.test.ts.
// The constructor itself adds no listeners, so this should be 0.
expect(mi2.listenerCount('msg')).toBe(0);
}
);
});