mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
- UNBOUNDED-MEMORY: DEFAULT_TERMINAL_BUFFER_TRIM_BYTES from CODEMAN_TRIM_TERMINAL_TO had no relation to DEFAULT_TERMINAL_BUFFER_MAX_BYTES — setting only CODEMAN_MAX_TERMINAL_BUFFER=2097152 left the 24MB trim default in force, making BufferAccumulator.trim() (slice(-trimSize)) a no-op: unbounded growth past the cap plus a full string re-join on every append (O(n²)). Trim default is now clamped to 75% of the resolved max (the 24MB/32MB default ratio, preserved as hysteresis); regression test re-evaluates the module under the env via vi.resetModules. - OVERCLAIM: reverted DEFAULT_TERMINAL_SCROLLBACK_LINES 100k -> 50k — it has zero consumers; browser xterm scrollback is the separate hardcoded DEFAULT_SCROLLBACK (50k) in constants.js and deliberately stays 50k (mobile-memory hazard). The tmux history-limit raise (50k -> 100k) and PTY 32MB/24MB raise remain (those are wired). Module docstring now claims only what is wired; fixed the stale tmux-manager.ts comment saying the tmux limit "matches the xterm-side default in constants.js". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
134 lines
5.8 KiB
TypeScript
134 lines
5.8 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import {
|
|
resolveTerminalHistoryConfig,
|
|
DEFAULT_TERMINAL_SCROLLBACK_LINES,
|
|
DEFAULT_TMUX_HISTORY_LIMIT,
|
|
DEFAULT_TERMINAL_BUFFER_MAX_BYTES,
|
|
DEFAULT_TERMINAL_BUFFER_TRIM_BYTES,
|
|
MIN_TERMINAL_SCROLLBACK_LINES,
|
|
MAX_TERMINAL_SCROLLBACK_LINES,
|
|
MIN_TERMINAL_BUFFER_BYTES,
|
|
MAX_TERMINAL_BUFFER_BYTES,
|
|
} from '../src/config/terminal-history.js';
|
|
|
|
describe('resolveTerminalHistoryConfig', () => {
|
|
it('returns the documented defaults for empty settings', () => {
|
|
const cfg = resolveTerminalHistoryConfig({});
|
|
expect(cfg).toEqual({
|
|
terminalScrollbackLines: DEFAULT_TERMINAL_SCROLLBACK_LINES,
|
|
tmuxHistoryLimit: DEFAULT_TMUX_HISTORY_LIMIT,
|
|
terminalBufferMaxBytes: DEFAULT_TERMINAL_BUFFER_MAX_BYTES,
|
|
terminalBufferTrimBytes: DEFAULT_TERMINAL_BUFFER_TRIM_BYTES,
|
|
});
|
|
});
|
|
|
|
it('defaults raise tmux history to 100k and buffer caps to 32MB/24MB; browser scrollback stays 50k', () => {
|
|
expect(DEFAULT_TMUX_HISTORY_LIMIT).toBe(100_000);
|
|
// Matches the hardcoded browser-side DEFAULT_SCROLLBACK in src/web/public/constants.js —
|
|
// deliberately NOT raised (100k xterm lines per tab is a mobile-memory hazard).
|
|
expect(DEFAULT_TERMINAL_SCROLLBACK_LINES).toBe(50_000);
|
|
expect(DEFAULT_TERMINAL_BUFFER_MAX_BYTES).toBe(32 * 1024 * 1024);
|
|
expect(DEFAULT_TERMINAL_BUFFER_TRIM_BYTES).toBe(24 * 1024 * 1024);
|
|
});
|
|
|
|
it('clamps the env-derived trim default below an env-lowered max (CODEMAN_MAX_TERMINAL_BUFFER only)', async () => {
|
|
const originalMax = process.env.CODEMAN_MAX_TERMINAL_BUFFER;
|
|
const originalTrim = process.env.CODEMAN_TRIM_TERMINAL_TO;
|
|
vi.resetModules();
|
|
process.env.CODEMAN_MAX_TERMINAL_BUFFER = String(2 * 1024 * 1024);
|
|
delete process.env.CODEMAN_TRIM_TERMINAL_TO;
|
|
try {
|
|
const mod = await import('../src/config/terminal-history.js');
|
|
expect(mod.DEFAULT_TERMINAL_BUFFER_MAX_BYTES).toBe(2 * 1024 * 1024);
|
|
// trim >= max would make BufferAccumulator.trim() a no-op (unbounded growth + O(n²) appends).
|
|
expect(mod.DEFAULT_TERMINAL_BUFFER_TRIM_BYTES).toBeLessThan(mod.DEFAULT_TERMINAL_BUFFER_MAX_BYTES);
|
|
expect(mod.DEFAULT_TERMINAL_BUFFER_TRIM_BYTES).toBe(Math.floor(2 * 1024 * 1024 * 0.75));
|
|
} finally {
|
|
if (originalMax === undefined) delete process.env.CODEMAN_MAX_TERMINAL_BUFFER;
|
|
else process.env.CODEMAN_MAX_TERMINAL_BUFFER = originalMax;
|
|
if (originalTrim === undefined) delete process.env.CODEMAN_TRIM_TERMINAL_TO;
|
|
else process.env.CODEMAN_TRIM_TERMINAL_TO = originalTrim;
|
|
vi.resetModules();
|
|
}
|
|
});
|
|
|
|
it('passes valid in-range values through unchanged', () => {
|
|
const cfg = resolveTerminalHistoryConfig({
|
|
terminalScrollbackLines: 50_000,
|
|
tmuxHistoryLimit: 75_000,
|
|
terminalBufferMaxBytes: 16 * 1024 * 1024,
|
|
terminalBufferTrimBytes: 8 * 1024 * 1024,
|
|
});
|
|
expect(cfg).toEqual({
|
|
terminalScrollbackLines: 50_000,
|
|
tmuxHistoryLimit: 75_000,
|
|
terminalBufferMaxBytes: 16 * 1024 * 1024,
|
|
terminalBufferTrimBytes: 8 * 1024 * 1024,
|
|
});
|
|
});
|
|
|
|
it('clamps scrollback/history below MIN up to the floor', () => {
|
|
const cfg = resolveTerminalHistoryConfig({
|
|
terminalScrollbackLines: 1,
|
|
tmuxHistoryLimit: 0,
|
|
});
|
|
expect(cfg.terminalScrollbackLines).toBe(MIN_TERMINAL_SCROLLBACK_LINES);
|
|
expect(cfg.tmuxHistoryLimit).toBe(MIN_TERMINAL_SCROLLBACK_LINES);
|
|
});
|
|
|
|
it('clamps scrollback/history above MAX down to the ceiling', () => {
|
|
const cfg = resolveTerminalHistoryConfig({
|
|
terminalScrollbackLines: 999_999_999,
|
|
tmuxHistoryLimit: 999_999_999,
|
|
});
|
|
expect(cfg.terminalScrollbackLines).toBe(MAX_TERMINAL_SCROLLBACK_LINES);
|
|
expect(cfg.tmuxHistoryLimit).toBe(MAX_TERMINAL_SCROLLBACK_LINES);
|
|
});
|
|
|
|
it('clamps the buffer byte caps to their MIN/MAX bounds', () => {
|
|
const tooSmall = resolveTerminalHistoryConfig({ terminalBufferMaxBytes: 1 });
|
|
expect(tooSmall.terminalBufferMaxBytes).toBe(MIN_TERMINAL_BUFFER_BYTES);
|
|
|
|
const tooLarge = resolveTerminalHistoryConfig({ terminalBufferMaxBytes: 1024 * 1024 * 1024 });
|
|
expect(tooLarge.terminalBufferMaxBytes).toBe(MAX_TERMINAL_BUFFER_BYTES);
|
|
});
|
|
|
|
it('keeps trim <= max (trim is capped to the resolved max)', () => {
|
|
const cfg = resolveTerminalHistoryConfig({
|
|
terminalBufferMaxBytes: 4 * 1024 * 1024,
|
|
terminalBufferTrimBytes: 64 * 1024 * 1024,
|
|
});
|
|
expect(cfg.terminalBufferTrimBytes).toBeLessThanOrEqual(cfg.terminalBufferMaxBytes);
|
|
expect(cfg.terminalBufferTrimBytes).toBe(4 * 1024 * 1024);
|
|
});
|
|
|
|
it('caps the default trim to a lowered max', () => {
|
|
// Default trim (24MB) exceeds a 2MB max → trim must fall to the max.
|
|
const cfg = resolveTerminalHistoryConfig({ terminalBufferMaxBytes: 2 * 1024 * 1024 });
|
|
expect(cfg.terminalBufferTrimBytes).toBe(2 * 1024 * 1024);
|
|
});
|
|
|
|
it('truncates fractional inputs to integers', () => {
|
|
const cfg = resolveTerminalHistoryConfig({ terminalScrollbackLines: 12_345.9 });
|
|
expect(cfg.terminalScrollbackLines).toBe(12_345);
|
|
});
|
|
|
|
it('falls back to defaults for non-number / non-finite inputs', () => {
|
|
const cfg = resolveTerminalHistoryConfig({
|
|
terminalScrollbackLines: 'lots' as unknown as number,
|
|
tmuxHistoryLimit: NaN,
|
|
terminalBufferMaxBytes: null as unknown as number,
|
|
terminalBufferTrimBytes: Infinity,
|
|
});
|
|
expect(cfg.terminalScrollbackLines).toBe(DEFAULT_TERMINAL_SCROLLBACK_LINES);
|
|
expect(cfg.tmuxHistoryLimit).toBe(DEFAULT_TMUX_HISTORY_LIMIT);
|
|
expect(cfg.terminalBufferMaxBytes).toBe(DEFAULT_TERMINAL_BUFFER_MAX_BYTES);
|
|
// trim default is min(DEFAULT_TRIM, resolvedMax) — here resolvedMax is the default max.
|
|
expect(cfg.terminalBufferTrimBytes).toBe(DEFAULT_TERMINAL_BUFFER_TRIM_BYTES);
|
|
});
|
|
|
|
it('uses an empty object when called with no argument', () => {
|
|
expect(resolveTerminalHistoryConfig().tmuxHistoryLimit).toBe(DEFAULT_TMUX_HISTORY_LIMIT);
|
|
});
|
|
});
|