generateColoredAsciiFlood emitted a trailing \r\n after its final cell, so a flood whose last line lands on the bottom row scrolled the screen. In the alt-buffer repro this destroyed the pinned header row in the BUFFER, making the 255/255 signature diff a content change, not renderer corruption. The single-terminal merge path is actually healthy: with the scroll removed the same 30-chunk merge-heavy workload renders the header with zero drift. - generateColoredAsciiFlood: no linefeed after the final cell - WebglReproExperiment: now a green guard; asserts row-1 buffer text first so a content bug can never masquerade as corruption again - WebglVisual: accurate comments; erase rewritten lines so leftover prefill text doesn't look like corruption - WebglSingleTermStress: replace stale EXPECTED-RED comment (test passes)
63 lines
3.1 KiB
TypeScript
63 lines
3.1 KiB
TypeScript
/**
|
|
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*
|
|
* Single-terminal guard for the vscode#322756 "garbled glyph" class: a pinned
|
|
* alt-buffer header must render identically while the body churns enough
|
|
* distinct colored glyphs to force texture-atlas page merges.
|
|
*
|
|
* Note this scenario is GREEN on current master: the single-terminal mid-frame
|
|
* merge race was fixed upstream (merge-retry loop in WebglRenderer.renderRows +
|
|
* globally monotonic AtlasPage versions). The test asserts the buffer content
|
|
* first so a content bug (e.g. an accidental scroll from the churn stream) can
|
|
* never masquerade as renderer corruption.
|
|
*/
|
|
import test, { expect } from '@playwright/test';
|
|
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
|
import { captureRowSignatures, expectSignatureMatches, getAtlasPageCount, loadWebglStrict, setMaxAtlasPages, writeAndWaitForRender } from '../../../test/playwright/RendererTestUtils';
|
|
import { generateColoredAsciiFlood } from '../../../test/playwright/SyntheticTui';
|
|
|
|
const HEADER = ' HEADER_REF_0123456789';
|
|
|
|
let ctx: ITestContext;
|
|
test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); });
|
|
test.afterAll(async () => await ctx.page.close());
|
|
|
|
test.describe('single-terminal merge integrity (#322756 class)', () => {
|
|
test.skip(({ browserName }) => browserName !== 'chromium');
|
|
test.describe.configure({ timeout: 60000 });
|
|
|
|
test('pinned header renders identically across 30 merge-heavy body redraws', async () => {
|
|
await openTerminal(ctx, { cols: 80, rows: 24 });
|
|
await loadWebglStrict(ctx);
|
|
await setMaxAtlasPages(ctx, 4);
|
|
const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
// Alt-buffer TUI: pinned header on row 1, body churns below (like Copilot CLI).
|
|
await writeAndWaitForRender(ctx, `\x1b[?1049h\x1b[H${HEADER}`);
|
|
const ref = await captureRowSignatures(ctx, 1, cols);
|
|
let maxDiff = 0;
|
|
for (let c = 0; c < 30; c++) {
|
|
// Redraw body rows 2..24 with unique glyphs; the header row is never touched.
|
|
let frame = '\x1b[2;1H';
|
|
frame += generateColoredAsciiFlood(23 * 78, c * 23 * 78);
|
|
await writeAndWaitForRender(ctx, frame);
|
|
const now = await captureRowSignatures(ctx, 1, cols); // forces GPU sync
|
|
for (let i = 0; i < cols.length; i++) {
|
|
const d = Math.max(...ref[i].map((v, k) => Math.abs(v - now[i][k])));
|
|
maxDiff = Math.max(maxDiff, d);
|
|
}
|
|
}
|
|
console.error('[repro] pages=', await getAtlasPageCount(ctx), 'headerMaxDiff=', maxDiff);
|
|
|
|
// Guard: the header must still be in the buffer. If this fails the churn
|
|
// stream scrolled the screen and any pixel diff is meaningless.
|
|
const row1Text = await ctx.page.evaluate(() => (window as any).term.buffer.active.getLine(0)?.translateToString(true));
|
|
expect(row1Text, 'churn stream must not scroll the pinned header out of the buffer').toBe(HEADER);
|
|
|
|
const after = await captureRowSignatures(ctx, 1, cols);
|
|
for (let i = 0; i < cols.length; i++) {
|
|
expectSignatureMatches(after[i], ref[i], 14, `header col ${cols[i]} garbled`);
|
|
}
|
|
});
|
|
});
|