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

174 lines
5.8 KiB
TypeScript

/**
* Preservation Property Tests — Task 2
*
* These tests establish the baseline behavior that MUST be preserved after the fix.
* They MUST PASS on unfixed code.
*
* Property 2: Preservation — `debug-ready` Emission and Breakpoint Test Assertions Unchanged
*
* Validates: Requirements 3.1, 3.2, 3.3, 3.4
*/
import { MI2 } from '../../src/backend/mi2/mi2';
// ---------------------------------------------------------------------------
// Test A — debug-ready emitted after 200ms
// ---------------------------------------------------------------------------
describe('Test A — debug-ready emitted after 200ms', () => {
let mi2: MI2;
beforeEach(() => {
jest.useFakeTimers();
mi2 = new MI2('gdb', []);
});
afterEach(() => {
// Clean up the timer if it's still pending
if (mi2['debugReadyTimeout']) {
clearTimeout(mi2['debugReadyTimeout']);
}
mi2.removeAllListeners();
jest.useRealTimers();
});
/**
* Validates: Requirement 3.1
* When onOutput() detects 'PlatformIO: Initialization completed' and the timeout
* fires normally, 'debug-ready' MUST be emitted after 200ms.
*/
test('emits debug-ready exactly once after 200ms when trigger line is processed', () => {
let debugReadyCount = 0;
mi2.on('debug-ready', () => {
debugReadyCount++;
});
// A valid GDB console stream record containing the trigger string
mi2.onOutput('~"PlatformIO: Initialization completed\\n"');
// Timer should be set but not yet fired
expect(debugReadyCount).toBe(0);
// Advance fake timers by 200ms — the timeout should fire
jest.advanceTimersByTime(200);
expect(debugReadyCount).toBe(1);
});
});
// ---------------------------------------------------------------------------
// Test B — debug-ready emitted immediately on generic-stopped
// ---------------------------------------------------------------------------
describe('Test B — debug-ready emitted immediately on generic-stopped', () => {
let mi2: MI2;
beforeEach(() => {
jest.useFakeTimers();
mi2 = new MI2('gdb', []);
});
afterEach(() => {
mi2.removeAllListeners();
jest.useRealTimers();
});
/**
* Validates: Requirement 3.2
* When 'generic-stopped' fires before the 200ms timeout, 'debug-ready' MUST be
* emitted immediately and the timer MUST be cancelled (no second emission).
*/
test('emits debug-ready immediately when generic-stopped fires before 200ms', () => {
let debugReadyCount = 0;
mi2.on('debug-ready', () => {
debugReadyCount++;
});
// Set up the timer by processing the trigger line
mi2.onOutput('~"PlatformIO: Initialization completed\\n"');
// Timer is pending but has not fired yet
expect(debugReadyCount).toBe(0);
// Emit generic-stopped BEFORE advancing timers — should trigger immediate emission
mi2.emit('generic-stopped', {});
// debug-ready should have been emitted immediately
expect(debugReadyCount).toBe(1);
// Advance timers by 200ms — the original timeout should have been cancelled,
// so no second emission should occur
jest.advanceTimersByTime(200);
expect(debugReadyCount).toBe(1);
});
});
// ---------------------------------------------------------------------------
// Test C — non-trigger lines produce no timer
// ---------------------------------------------------------------------------
describe('Test C — non-trigger lines produce no timer', () => {
let mi2: MI2;
beforeEach(() => {
mi2 = new MI2('gdb', []);
});
afterEach(() => {
mi2.removeAllListeners();
});
/**
* Validates: Requirement 3.1 (by contrapositive)
* Lines that do NOT contain 'PlatformIO: Initialization completed' MUST NOT
* create a debugReadyTimeout.
*/
test('empty string does not create a debugReadyTimeout', () => {
mi2.onOutput('');
expect(mi2['debugReadyTimeout']).toBeUndefined();
});
test('plain stdout line does not create a debugReadyTimeout', () => {
mi2.onOutput('~"Some other output\\n"');
expect(mi2['debugReadyTimeout']).toBeUndefined();
});
test('a different stream record does not create a debugReadyTimeout', () => {
// @ prefix = target stream record (not console)
mi2.onOutput('@"target output\\n"');
expect(mi2['debugReadyTimeout']).toBeUndefined();
});
test('a GDB result record does not create a debugReadyTimeout', () => {
// A result record (^done) — not a stream record
mi2.onOutput('^done');
expect(mi2['debugReadyTimeout']).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// Test D — removeAllListeners() zeroes listener count
// ---------------------------------------------------------------------------
describe('Test D — removeAllListeners() zeroes listener count', () => {
/**
* Validates: Requirement 3.4 (cleanup pattern)
* After calling removeAllListeners(), all listener counts MUST be 0.
*/
test('listener counts are 0 after removeAllListeners()', () => {
const mi2 = new MI2('gdb', []);
// Add some listeners manually
mi2.on('msg', () => {});
mi2.on('debug-ready', () => {});
// Confirm listeners were added
expect(mi2.listenerCount('msg')).toBeGreaterThan(0);
expect(mi2.listenerCount('debug-ready')).toBeGreaterThan(0);
// Remove all listeners
mi2.removeAllListeners();
// All listener counts must be 0
expect(mi2.listenerCount('msg')).toBe(0);
expect(mi2.listenerCount('debug-ready')).toBe(0);
});
});