mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Merge branch 'german/refactor-e13-navara-workarounds' into german/navara-refactor
This commit is contained in:
commit
0953287c3d
4 files changed
+625
-35
No files matched your search
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env node
|
||||
// Package canaries deliberately bypass adapter workarounds. A passing canary
|
||||
// means the upstream bug still exists. Fixture errors prove nothing about it.
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import puppeteer from 'puppeteer';
|
||||
import { closeBrowser } from './qa/browser.mjs';
|
||||
|
||||
const origin = new URL(process.argv[2] ?? 'http://localhost:4300').origin;
|
||||
assert.ok(['localhost', '127.0.0.1'].includes(new URL(origin).hostname));
|
||||
assert.equal(
|
||||
new URL(origin).port,
|
||||
'4300',
|
||||
'Use the dedicated keyless canary server on port 4300',
|
||||
);
|
||||
const profile = await mkdtemp(path.resolve('tmp_navara_canaries_profile_'));
|
||||
const results = { confirmed: 0, gone: 0, fixtureErrors: 0 };
|
||||
let browser;
|
||||
try {
|
||||
browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
userDataDir: profile,
|
||||
protocolTimeout: 60_000,
|
||||
args: ['--use-angle=metal', '--enable-gpu'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 800, height: 600 });
|
||||
page.setDefaultTimeout(30_000);
|
||||
page.on('pageerror', (error) => console.error(error.message));
|
||||
await page.setRequestInterception(true);
|
||||
const pendingTiles = [];
|
||||
const tile = {
|
||||
contentType: 'image/png',
|
||||
body: Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
),
|
||||
};
|
||||
let holdTiles = true;
|
||||
await page.exposeFunction('pendingTileCount', () => pendingTiles.length);
|
||||
page.on('request', (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (url.pathname === '/tmp_navara_canaries.html') {
|
||||
request.respond({
|
||||
contentType: 'text/html',
|
||||
body: '<body style="margin:0"><div id="globe" style="width:800px;height:600px"></div></body>',
|
||||
});
|
||||
} else if (url.pathname.startsWith('/tmp_navara_canaries_tile/')) {
|
||||
if (holdTiles) pendingTiles.push(request);
|
||||
else request.respond(tile);
|
||||
} else if (
|
||||
url.origin === origin ||
|
||||
['blob:', 'data:'].includes(url.protocol)
|
||||
) {
|
||||
request.continue();
|
||||
} else request.abort();
|
||||
});
|
||||
await page.goto(`${origin}/tmp_navara_canaries.html`);
|
||||
await page.evaluate(async () => {
|
||||
// Use Vite's resolved package URLs so the adapter and fixture share classes.
|
||||
const imports = await (
|
||||
await fetch('/src/globe/navara/internals.js')
|
||||
).text();
|
||||
const sdkUrl = imports.match(
|
||||
/from "([^"]*\/three\/dist\/index.js[^\"]*)"/,
|
||||
)[1];
|
||||
const optionsSource = await (
|
||||
await fetch('/src/globe/navara/viewOptions.js')
|
||||
).text();
|
||||
const threeUrl = optionsSource.match(/from "([^"]+)"/)[1];
|
||||
const [sdk, three, internals, options] = await Promise.all([
|
||||
import(sdkUrl),
|
||||
import(threeUrl),
|
||||
import('/src/globe/navara/internals.js'),
|
||||
import('/src/globe/navara/viewOptions.js'),
|
||||
]);
|
||||
const container = document.getElementById('globe');
|
||||
const canvas = document.createElement('canvas');
|
||||
container.appendChild(canvas);
|
||||
canvas.getContext('webgl2', options.NAVARA_CONTEXT_ATTRIBUTES);
|
||||
const view = new sdk.default(
|
||||
options.navaraViewOptions({ container, canvas }),
|
||||
);
|
||||
await view.init();
|
||||
internals.navaraHooks(view);
|
||||
view.lit = false;
|
||||
view.setCamera({
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
height: 18_000_000,
|
||||
heading: 0,
|
||||
pitch: -90,
|
||||
roll: 0,
|
||||
});
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
const until = async (predicate, timeout = 5000) => {
|
||||
const start = performance.now();
|
||||
while (!predicate()) {
|
||||
if (performance.now() - start > timeout)
|
||||
throw new Error('Fixture did not become ready');
|
||||
await wait(20);
|
||||
}
|
||||
};
|
||||
const frame = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
view.off('postRender', done);
|
||||
reject(new Error('No rendered frame within five seconds'));
|
||||
}, 5000);
|
||||
const done = () => {
|
||||
clearTimeout(timer);
|
||||
view.off('postRender', done);
|
||||
resolve();
|
||||
};
|
||||
view.on('postRender', done);
|
||||
view.forceUpdate();
|
||||
});
|
||||
let ctx;
|
||||
class Probe extends sdk.MeshDesc {
|
||||
createMesh() {
|
||||
ctx = this.ctx;
|
||||
return new three.Group();
|
||||
}
|
||||
}
|
||||
view.registerMesh('canaryProbe', Probe);
|
||||
const probe = view.addMesh({ canaryProbe: {} });
|
||||
await frame();
|
||||
const polygon = {
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[-8, -8],
|
||||
[8, -8],
|
||||
[8, 8],
|
||||
[-8, 8],
|
||||
[-8, -8],
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
window.canary = {
|
||||
sdk,
|
||||
three,
|
||||
internals,
|
||||
view,
|
||||
canvas,
|
||||
ctx,
|
||||
probe,
|
||||
wait,
|
||||
until,
|
||||
frame,
|
||||
polygon,
|
||||
};
|
||||
});
|
||||
|
||||
async function check(name, remove, run) {
|
||||
try {
|
||||
const evidence = await page.evaluate(run);
|
||||
assert.equal(
|
||||
typeof evidence?.present,
|
||||
'boolean',
|
||||
'Fixture returned no verdict',
|
||||
);
|
||||
if (evidence.present) {
|
||||
console.log(`PASS ${name}: bug confirmed; ${JSON.stringify(evidence)}`);
|
||||
results.confirmed++;
|
||||
} else {
|
||||
console.error(
|
||||
`BUG GONE ${name}: bug absent with fixture controls satisfied; review/delete ${remove}. ${JSON.stringify(evidence)}`,
|
||||
);
|
||||
results.gone++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`FIXTURE ERROR ${name}: ${error.message}; no conclusion about the bug; keep the workaround`,
|
||||
);
|
||||
results.fixtureErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
await check(
|
||||
'batch-ids-retained',
|
||||
'src/globe/navara/movers/batchIds.js and its pool wiring',
|
||||
async () => {
|
||||
const { sdk, three, ctx, frame } = window.canary;
|
||||
const batches = [];
|
||||
for (let round = 0; round < 3; round++) {
|
||||
const mesh = new three.InstancedMesh(
|
||||
new three.PlaneGeometry(),
|
||||
new three.MeshBasicMaterial(),
|
||||
8,
|
||||
);
|
||||
const wrapper = new sdk.PickableInstancedMeshWrapper(mesh, 8, ctx);
|
||||
ctx.registerPickableMesh('canary-batch', wrapper);
|
||||
batches.push([...wrapper.batchIds]);
|
||||
ctx.unregisterPickableMesh('canary-batch');
|
||||
mesh.geometry.dispose();
|
||||
mesh.material.dispose();
|
||||
await frame();
|
||||
}
|
||||
const ids = batches.flat();
|
||||
if (
|
||||
batches.some(
|
||||
(batch) =>
|
||||
batch.length !== 8 ||
|
||||
batch.some((id) => !Number.isInteger(id) || id <= 0) ||
|
||||
new Set(batch).size !== batch.length,
|
||||
)
|
||||
)
|
||||
throw new Error('Fixture did not allocate eight valid IDs per batch');
|
||||
return {
|
||||
present: new Set(ids).size === ids.length,
|
||||
batches,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
await check(
|
||||
'deleted-vector-events-retained',
|
||||
'src/globe/navara/featureQueue.js and dropOrphanFeatureChanges wiring',
|
||||
async () => {
|
||||
const { view, internals, polygon, frame, wait } = window.canary;
|
||||
for (let attempt = 0; attempt < 12; attempt++) {
|
||||
const source = view.addSource({ type: 'geojson', data: polygon });
|
||||
const layer = view.addLayer({
|
||||
type: 'vector',
|
||||
source,
|
||||
polygon: { color: 0xff0000 },
|
||||
});
|
||||
await wait(30 + attempt * 10);
|
||||
layer.update({ type: 'vector', source, polygon: { color: 0x00ff00 } });
|
||||
layer.delete();
|
||||
source.delete();
|
||||
await wait(100);
|
||||
}
|
||||
const orphaned = () =>
|
||||
internals
|
||||
.featureChangeQueue(view)
|
||||
.events.filter(
|
||||
(event) => !view.findLayerById(String(event.layer_id)),
|
||||
);
|
||||
const before = orphaned().length;
|
||||
await frame();
|
||||
await wait(500);
|
||||
await frame();
|
||||
const after = orphaned().length;
|
||||
internals.navaraHooks(view).dropOrphanFeatureChanges();
|
||||
if (before === 0)
|
||||
throw new Error('Fixture did not produce orphaned vector events');
|
||||
return { present: after > 0, before, after };
|
||||
},
|
||||
);
|
||||
|
||||
await check(
|
||||
'idle-before-raster-arrives',
|
||||
'maps.js SETTLE_MS/SETTLE_EVERY_MS redraw timer',
|
||||
async () => {
|
||||
const { view, internals, until } = window.canary;
|
||||
await until(() => internals.isIdle(view));
|
||||
let idle = false;
|
||||
const onIdle = () => {
|
||||
idle = true;
|
||||
};
|
||||
view.on('idle', onIdle);
|
||||
const source = view.addSource({
|
||||
type: 'raster-tile',
|
||||
url: `${location.origin}/tmp_navara_canaries_tile/{z}/{x}/{y}.png`,
|
||||
maxZoom: 1,
|
||||
});
|
||||
const layer = view.addLayer({ type: 'raster', source });
|
||||
view.forceUpdate();
|
||||
await until(() => idle);
|
||||
view.off('idle', onIdle);
|
||||
window.canary.raster = { source, layer };
|
||||
const pending = await window.pendingTileCount();
|
||||
if (pending === 0)
|
||||
throw new Error('Fixture did not hold any raster requests');
|
||||
return {
|
||||
present: idle && pending > 0,
|
||||
idle,
|
||||
pending,
|
||||
};
|
||||
},
|
||||
);
|
||||
holdTiles = false;
|
||||
for (const request of pendingTiles) await request.respond(tile);
|
||||
if (pendingTiles.length) {
|
||||
await page.waitForFunction(() =>
|
||||
performance
|
||||
.getEntriesByType('resource')
|
||||
.some(
|
||||
(entry) =>
|
||||
entry.name.includes('/tmp_navara_canaries_tile/') &&
|
||||
entry.responseEnd > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
window.canary.raster?.layer.delete();
|
||||
window.canary.raster?.source.delete();
|
||||
});
|
||||
|
||||
await page.evaluate(() => window.canary.view.dispose());
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`FIXTURE ERROR browser session: ${error.message}; no conclusion about the bugs; keep the workarounds`,
|
||||
);
|
||||
results.fixtureErrors++;
|
||||
} finally {
|
||||
if (browser) await closeBrowser(browser);
|
||||
await rm(profile, { recursive: true, force: true });
|
||||
console.log(
|
||||
`${results.confirmed} bugs confirmed, ${results.gone} bugs gone, ${results.fixtureErrors} fixture errors`,
|
||||
);
|
||||
process.exitCode = results.gone === 0 && results.fixtureErrors === 0 ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user