143 lines
5.8 KiB
TypeScript
143 lines
5.8 KiB
TypeScript
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
|
|
import type { ConsoleMessage, Page } from "@playwright/test";
|
|
import { expect, test } from "@perspective-dev/test";
|
|
import {
|
|
assertPlotNeverBlank,
|
|
calibratePlotBaseline,
|
|
captureFrames,
|
|
gotoBasic,
|
|
restoreChart,
|
|
waitOneFrame,
|
|
} from "./helpers";
|
|
|
|
const SCATTER = {
|
|
plugin: "X/Y Scatter",
|
|
columns: ["Quantity", "Postal Code"],
|
|
};
|
|
|
|
const TABLE_NAME = "load-viewer-csv";
|
|
const TOGGLE_COUNT = 12;
|
|
|
|
function collectLeakErrors(page: Page): string[] {
|
|
const hits: string[] = [];
|
|
page.on("console", (m: ConsoleMessage) => {
|
|
hits.push(m.text());
|
|
});
|
|
|
|
page.on("pageerror", (e: Error) => {
|
|
hits.push(String(e));
|
|
});
|
|
|
|
return hits;
|
|
}
|
|
|
|
async function toggleSecondViewer(page: Page, cfg: object): Promise<void> {
|
|
await page.evaluate(
|
|
async ({ cfg, tableName }) => {
|
|
const worker = (window as any).__TEST_WORKER__;
|
|
const table = await worker.open_table(tableName);
|
|
const v = document.createElement("perspective-viewer");
|
|
document.body.appendChild(v);
|
|
await v.load(table);
|
|
await v.restore(cfg);
|
|
},
|
|
{ cfg, tableName: TABLE_NAME },
|
|
);
|
|
|
|
await waitOneFrame(page);
|
|
await waitOneFrame(page);
|
|
await page.evaluate(() => {
|
|
document.querySelector("perspective-viewer[data-toggle]")?.remove();
|
|
});
|
|
}
|
|
|
|
async function toggleSecondViewerRacingInit(
|
|
page: Page,
|
|
cfg: object,
|
|
): Promise<void> {
|
|
await page.evaluate(
|
|
async ({ cfg, tableName }) => {
|
|
const worker = (window as any).__TEST_WORKER__;
|
|
const table = await worker.open_table(tableName);
|
|
const v = document.createElement("perspective-viewer");
|
|
document.body.appendChild(v);
|
|
await v.load(table);
|
|
v.restore(cfg).catch(() => {});
|
|
await new Promise<void>((resolve) =>
|
|
setTimeout(() => {
|
|
v.remove();
|
|
resolve();
|
|
}, 0),
|
|
);
|
|
},
|
|
{ cfg, tableName: TABLE_NAME },
|
|
);
|
|
|
|
await waitOneFrame(page);
|
|
}
|
|
|
|
test.describe("WebGL context leak", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await gotoBasic(page);
|
|
await restoreChart(page, SCATTER);
|
|
});
|
|
|
|
test("repeated full toggles never evict the first chart's context", async ({
|
|
page,
|
|
}) => {
|
|
// test.setTimeout(180_000);
|
|
const leakErrors = collectLeakErrors(page);
|
|
const baseline = await calibratePlotBaseline(page);
|
|
expect(baseline).toBeGreaterThan(0);
|
|
const threshold = Math.max(1, Math.floor(baseline * 0.5));
|
|
for (let i = 0; i < TOGGLE_COUNT; i++) {
|
|
await toggleSecondViewer(page, SCATTER);
|
|
}
|
|
|
|
// Force the first viewer to re-render through its own renderer.
|
|
// If its context had been evicted by a leak, the worker can no
|
|
// longer paint it and the plot region comes back blank.
|
|
await restoreChart(page, SCATTER);
|
|
const frames = await captureFrames(page, async () => {
|
|
await waitOneFrame(page);
|
|
await waitOneFrame(page);
|
|
});
|
|
|
|
assertPlotNeverBlank(frames, threshold);
|
|
expect(leakErrors).toEqual([]);
|
|
});
|
|
|
|
test("toggling during renderer init never leaks a context", async ({
|
|
page,
|
|
}) => {
|
|
// test.setTimeout(180_000);
|
|
const leakErrors = collectLeakErrors(page);
|
|
const baseline = await calibratePlotBaseline(page);
|
|
expect(baseline).toBeGreaterThan(0);
|
|
const threshold = Math.max(1, Math.floor(baseline * 0.5));
|
|
for (let i = 0; i < TOGGLE_COUNT; i++) {
|
|
await toggleSecondViewerRacingInit(page, SCATTER);
|
|
}
|
|
|
|
await restoreChart(page, SCATTER);
|
|
const frames = await captureFrames(page, async () => {
|
|
await waitOneFrame(page);
|
|
await waitOneFrame(page);
|
|
});
|
|
|
|
assertPlotNeverBlank(frames, threshold);
|
|
expect(leakErrors).toEqual([]);
|
|
});
|
|
});
|