Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,37 @@ import { TruthLogTreeProvider } from './treeProvider';
import type { Execution } from './types';

let outputChannel: vscode.OutputChannel | undefined;

/**
* Kept only so integration tests can read back what was logged: VS Code has no
* public API for reading an OutputChannel's contents. Bounded because this is
* the one structure here with no natural ceiling, unlike history, which
* truthlog.maxEntries caps. One line per recorded command means it otherwise
* grows for as long as the window is open.
*
* Well above what any single test inspects, and far below what would matter for
* memory. The OutputChannel itself is unaffected and still holds the full log,
* which is what a user actually reads.
*/
const MAX_LOG_LINES = 5000;
const logLines: string[] = [];

/**
* Appends to a bounded buffer, dropping the oldest lines once it is full.
* Exported for tests: driving this through log() itself would mean pushing
* thousands of lines into the shared array the other suites read by offset,
* which would shift those offsets out from under them.
*/
export function appendBounded(lines: string[], line: string, max: number): void {
lines.push(line);
if (lines.length > max) {
lines.splice(0, lines.length - max);
}
}

function log(message: string): void {
const line = `[${new Date().toISOString()}] ${message}`;
logLines.push(line);
appendBounded(logLines, line, MAX_LOG_LINES);
outputChannel?.appendLine(line);
}

Expand Down Expand Up @@ -345,4 +371,9 @@ export function activate(context: vscode.ExtensionContext): TruthLogTestApi {

export function deactivate(): void {
outputChannel = undefined;
// Module state outlives a deactivate/activate cycle in the same extension
// host. Left in place, a reactivated session starts with the previous
// session's lines already present, and getLogLines() hands tests both
// sessions mixed together.
logLines.length = 0;
}
44 changes: 44 additions & 0 deletions src/test/suite/logBuffer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as assert from 'assert';
import { appendBounded } from '../../extension';

describe('bounded log buffer', () => {
it('keeps everything below the cap', () => {
const lines: string[] = [];
for (let i = 0; i < 10; i++) {
appendBounded(lines, `line ${i}`, 100);
}
assert.strictEqual(lines.length, 10);
assert.strictEqual(lines[0], 'line 0');
});

it('stops growing at the cap and keeps the newest lines', () => {
const lines: string[] = [];
for (let i = 0; i < 250; i++) {
appendBounded(lines, `line ${i}`, 100);
}
assert.strictEqual(lines.length, 100, 'must not grow past the cap');
assert.strictEqual(lines[99], 'line 249', 'the newest line must survive');
assert.strictEqual(lines[0], 'line 150', 'the oldest lines are the ones dropped');
});

it('holds at exactly the cap', () => {
const lines: string[] = [];
for (let i = 0; i < 100; i++) {
appendBounded(lines, `line ${i}`, 100);
}
assert.strictEqual(lines.length, 100);

appendBounded(lines, 'one more', 100);
assert.strictEqual(lines.length, 100);
assert.strictEqual(lines[99], 'one more');
});

it('recovers if the buffer somehow starts over the cap', () => {
// Defensive: a smaller cap applied to an already-long buffer must trim all
// the way down in one go, not one line per call.
const lines = Array.from({ length: 500 }, (_, i) => `old ${i}`);
appendBounded(lines, 'new', 10);
assert.strictEqual(lines.length, 10);
assert.strictEqual(lines[9], 'new');
});
});
Loading