Files
xterm.js/addons/addon-search/benchmark/SearchAddon.benchmark.ts
T
Daniel ImmsandCursor fedf29e870 test(addon-search): add wholeWord and reverse-regex benchmarks
Add benchmark coverage for whole-word navigation and reverse regex
whole-word searches to guard optimization regressions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 19:15:29 -07:00

259 lines
9.6 KiB
TypeScript

/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { perfContext, before, RuntimeCase } from 'xterm-benchmark';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { Terminal } from 'browser/public/Terminal';
import { SearchAddon } from 'SearchAddon';
import type { ISearchOptions } from '@xterm/addon-search';
const enum Constants {
COLS = 120,
ROWS = 40,
SCROLLBACK = 8000,
NAVIGATION_ITERATIONS = 250,
INCREMENTAL_ITERATIONS = 75,
DECORATION_REFRESH_ITERATIONS = 25
}
const DECORATION_OPTIONS: ISearchOptions = {
decorations: {
activeMatchBackground: '#0000ff',
activeMatchColorOverviewRuler: '#ff0000',
matchBackground: '#ffff00',
matchBorder: '#000000',
matchOverviewRuler: '#00ffff'
}
};
class TestTerminal extends Terminal {
public writeSync(data: string): void {
(this as any)._core.writeSync(data);
}
public override select(_column: number, _row: number, _length: number): void {
// SearchAddon always selects matches; skip selection internals in headless benchmark runs.
}
public override clearSelection(): void {
// SearchAddon clears the previous selection before selecting another match.
}
public override scrollLines(_amount: number): void {
// No viewport scrolling is needed for API runtime measurement.
}
}
function buildRealWorldBuffer(): string {
const fixture = readFileSync(resolve(__dirname, '../../fixtures/issue-2444'), 'utf8')
.replace(/\r?\n/g, '\r\n');
const lines: string[] = [fixture];
for (let i = 0; i < 2500; i++) {
const minute = (i % 60).toString().padStart(2, '0');
const second = ((i * 7) % 60).toString().padStart(2, '0');
lines.push(`2026-05-25T16:${minute}:${second}Z INFO [api-${i % 8}] requestId=req-${i.toString(16).padStart(6, '0')} path=/v1/projects/${i % 120}/search?q=opencv status=200 durationMs=${10 + (i % 90)}`);
if (i % 5 === 0) {
lines.push(`2026-05-25T16:${minute}:${second}Z WARN [worker-${i % 4}] slow query detected query="SELECT * FROM runs WHERE status='pending'" elapsedMs=${500 + (i % 1200)}`);
}
if (i % 11 === 0) {
lines.push(`2026-05-25T16:${minute}:${second}Z ERROR [worker-${i % 4}] command failed: package.json parse error at column ${10 + (i % 70)}; retrying in ${2 + (i % 7)}s`);
}
if (i % 17 === 0) {
lines.push(`https://example.internal/run/${i}/details?source=terminal-search&state=error`);
}
}
return lines.join('\r\n') + '\r\n';
}
perfContext('SearchAddon API on real-world terminal content', () => {
const bufferContent = buildRealWorldBuffer();
perfContext('findNext/plain navigation', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findNext('opencv')) {
foundCount++;
}
}
if (foundCount !== Constants.NAVIGATION_ITERATIONS) {
throw new Error(`Expected ${Constants.NAVIGATION_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findNext/no match full scan', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findNext('zzzznotfoundzzzz')) {
foundCount++;
}
}
if (foundCount !== 0) {
throw new Error(`Expected 0 matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findNext/case insensitive navigation', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findNext('OPENCV', { caseSensitive: false })) {
foundCount++;
}
}
if (foundCount !== Constants.NAVIGATION_ITERATIONS) {
throw new Error(`Expected ${Constants.NAVIGATION_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findNext/regex navigation', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findNext('https://example\\.internal/run/\\d+/details\\?source=terminal-search', { regex: true })) {
foundCount++;
}
}
if (foundCount !== Constants.NAVIGATION_ITERATIONS) {
throw new Error(`Expected ${Constants.NAVIGATION_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findPrevious/incremental typing flow', () => {
let terminal: TestTerminal;
let search: SearchAddon;
const terms = ['e', 'er', 'err', 'erro', 'error'];
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.INCREMENTAL_ITERATIONS; i++) {
for (const term of terms) {
if (search.findPrevious(term, { incremental: true })) {
foundCount++;
}
}
}
return { payloadSize: Constants.INCREMENTAL_ITERATIONS * terms.length, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findNext/wholeWord dense-punctuation', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findNext('error', { wholeWord: true })) {
foundCount++;
}
}
if (foundCount !== Constants.NAVIGATION_ITERATIONS) {
throw new Error(`Expected ${Constants.NAVIGATION_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findPrevious/regex wholeWord reverse', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon();
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.NAVIGATION_ITERATIONS; i++) {
if (search.findPrevious('error', { regex: true, wholeWord: true })) {
foundCount++;
}
}
if (foundCount !== Constants.NAVIGATION_ITERATIONS) {
throw new Error(`Expected ${Constants.NAVIGATION_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.NAVIGATION_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
perfContext('findNext/decorations refresh', () => {
let terminal: TestTerminal;
let search: SearchAddon;
before(() => {
terminal = new TestTerminal({ cols: Constants.COLS, rows: Constants.ROWS, scrollback: Constants.SCROLLBACK });
search = new SearchAddon({ highlightLimit: 1500 });
search.activate(terminal);
terminal.writeSync(bufferContent);
});
new RuntimeCase('', () => {
let foundCount = 0;
for (let i = 0; i < Constants.DECORATION_REFRESH_ITERATIONS; i++) {
const term = i % 2 === 0 ? 'WARN' : 'ERROR';
if (search.findNext(term, DECORATION_OPTIONS)) {
foundCount++;
}
}
if (foundCount !== Constants.DECORATION_REFRESH_ITERATIONS) {
throw new Error(`Expected ${Constants.DECORATION_REFRESH_ITERATIONS} matches, got ${foundCount}`);
}
return { payloadSize: Constants.DECORATION_REFRESH_ITERATIONS, foundCount };
}, { fork: false }).showAverageRuntime();
});
});