Files
gods-eye-view/src/data/trafficTiming.test.mjs
T
Sameh Khamis e40bff37d2 test(traffic): keep the timing test's Vite cache out of the package tree
The traffic timing test starts a Vite server rooted at the package, which
wrote its dependency cache to node_modules/.vite and kept writing after the
server closed. A host running Core's suite then finds files inside Core and
its check refuses them. The server now uses a temporary cache directory,
removed afterwards, and skips dependency discovery, which the SSR-loaded
modules never need.

The HUD place-lookup changelog note credits its PR, #1459.
2026-11-01 22:47:05 -08:00

389 lines
14 KiB
JavaScript

import assert from 'node:assert/strict';
import { readLayerSource } from '../testSupport/readLayerSource.mjs';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { getTrafficTimingDiagnostics } from './traffic.js';
const SOURCE = readLayerSource(new URL('./traffic.js', import.meta.url), 'utf8').replace(/parts\.(?:model|timing)\./g, '');
function functionBody(name) {
const declaration = `function ${name}(`;
const start = SOURCE.indexOf(declaration);
assert.notEqual(start, -1, `${declaration} must exist`);
const signatureStart = SOURCE.indexOf('(', start);
let signatureDepth = 0;
let signatureEnd = -1;
for (let i = signatureStart; i < SOURCE.length; i++) {
if (SOURCE[i] === '(') signatureDepth++;
if (SOURCE[i] === ')' && --signatureDepth === 0) {
signatureEnd = i;
break;
}
}
assert.notEqual(signatureEnd, -1, `${name} signature must terminate`);
const open = SOURCE.indexOf('{', signatureEnd);
let depth = 0;
let quote = null;
let escaped = false;
let lineComment = false;
let blockComment = false;
for (let i = open; i < SOURCE.length; i++) {
const char = SOURCE[i];
const next = SOURCE[i + 1];
if (lineComment) {
if (char === '\n') lineComment = false;
continue;
}
if (blockComment) {
if (char === '*' && next === '/') {
blockComment = false;
i++;
}
continue;
}
if (quote) {
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === '/' && next === '/') {
lineComment = true;
i++;
continue;
}
if (char === '/' && next === '*') {
blockComment = true;
i++;
continue;
}
if (char === '\'' || char === '"' || char === '`') {
quote = char;
continue;
}
if (char === '{') depth++;
if (char === '}' && --depth === 0) return SOURCE.slice(open + 1, i);
}
throw new Error(`unterminated ${name} body`);
}
function canonicalSemanticBody(body) {
return body
.replace(/\/\* TRACE_ONLY_BEGIN \*\/[\s\S]*?\/\* TRACE_ONLY_END \*\//g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/[^\n]*/g, '')
.replace(/\s+/g, '')
.replace(
'if(!overpassData||!overpassData.elements){return[];}',
'if(!overpassData||!overpassData.elements)return[];',
);
}
function traceOnlyBlocks(body) {
return [...body.matchAll(
/\/\* TRACE_ONLY_BEGIN \*\/([\s\S]*?)\/\* TRACE_ONLY_END \*\//g,
)].map((match) => match[1]);
}
function eventChannel() {
const listeners = new Set();
return {
addEventListener(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
removeEventListener(listener) {
listeners.delete(listener);
},
raise() {
for (const listener of [...listeners]) listener();
},
listenerCount() {
return listeners.size;
},
};
}
test('traffic timing stays inert when the DEV flag is off under bare Node', () => {
assert.deepEqual(getTrafficTimingDiagnostics(), {
enabled: false,
marksInstalled: 0,
traceObjectsCreated: 0,
uncorrelatedTracesDropped: 0,
});
assert.equal(
performance.getEntriesByType('mark').filter((entry) => entry.name.startsWith('traffic:')).length,
0,
);
});
test('traffic timing pairs real ordering to the scheduling change and guards re-arms', async () => {
const { createServer } = await import('vite');
const originalWindow = globalThis.window;
const originalDocument = globalThis.document;
const originalFetch = globalThis.fetch;
const originalSetTimeout = globalThis.setTimeout;
const originalClearTimeout = globalThis.clearTimeout;
const originalSetInterval = globalThis.setInterval;
const originalClearInterval = globalThis.clearInterval;
const originalLog = console.log;
const originalWarn = console.warn;
const timeouts = new Map();
let timerId = 0;
let server;
const cacheDir = mkdtempSync(path.join(tmpdir(), 'gev-traffic-timing-vite-'));
let trafficLayer;
let viewer;
const pendingDebounce = () => {
const matches = [...timeouts].filter(([, timer]) => timer.delay === 320);
assert.equal(matches.length, 1, 'exactly one traffic debounce must be pending');
return matches[0];
};
const runDebouncedLoad = async () => {
const match = pendingDebounce();
const [id, timer] = match;
timeouts.delete(id);
await timer.callback();
};
try {
globalThis.window = {
location: { search: '?trafficDebug=1' },
addEventListener() {},
};
server = await createServer({
root: fileURLToPath(new URL('../..', import.meta.url)),
// Its own optimizer cache, not node_modules/.vite in the package tree:
// a host running Core's suite refuses files inside Core.
cacheDir,
// The test loads modules over SSR; the browser dependency optimizer would
// keep writing its cache after the server closes.
optimizeDeps: { noDiscovery: true, include: [] },
configFile: false,
appType: 'custom',
logLevel: 'silent',
server: { middlewareMode: true },
plugins: [{
name: 'traffic-timing-test-hooks',
transform(code, id) {
if (!id.endsWith('/src/layers/traffic/index.js')) return null;
return code.replace('return Object.assign(', 'return Object.assign({ __trafficTimingTestHooks: { currentAnchor: () => state._trafficTimingCurrentAnchor } },');
},
}],
});
const traffic = await server.ssrLoadModule('/src/data/traffic.js');
trafficLayer = traffic.default;
globalThis.document = { documentElement: { dataset: {} } };
globalThis.fetch = async (url) => ({
ok: true,
status: 200,
headers: { get: () => null },
json: async () => (String(url).includes('/api/tomtom/status')
? { hasKey: false }
: { elements: [] }),
});
globalThis.setTimeout = (callback, delay) => {
const id = ++timerId;
timeouts.set(id, { callback, delay });
return id;
};
globalThis.clearTimeout = (id) => timeouts.delete(id);
globalThis.setInterval = () => ++timerId;
globalThis.clearInterval = () => {};
console.log = () => {};
console.warn = () => {};
const preRender = eventChannel();
const postRender = eventChannel();
const moveEnd = eventChannel();
const changed = eventChannel();
let longitude = -97.74;
const camera = {
moveEnd,
changed,
percentageChanged: 0.5,
positionCartographic: { latitude: Math.PI / 6, longitude: 0, height: 5000 },
computeViewRectangle() {
const west = longitude * Math.PI / 180;
return {
south: 30 * Math.PI / 180,
west,
north: 30.02 * Math.PI / 180,
east: west + 0.02 * Math.PI / 180,
};
},
};
const setLongitude = (next) => {
longitude = next;
camera.positionCartographic.longitude = next * Math.PI / 180;
};
setLongitude(longitude);
viewer = {
camera,
scene: {
canvas: { clientWidth: 0, clientHeight: 0, width: 0, height: 0 },
preRender,
postRender,
primitives: { add: (primitive) => primitive, remove: () => true },
},
};
performance.clearMarks();
performance.clearMeasures();
trafficLayer.init(viewer);
performance.mark('traffic:stale:mark');
performance.measure('traffic:stale:measure', {
start: 'traffic:stale:mark',
end: 'traffic:stale:mark',
});
trafficLayer.enable(viewer);
assert.equal(traffic.getTrafficTimingDiagnostics().marksInstalled, 1);
assert.equal(moveEnd.listenerCount(), 2, 'one timing listener plus the production arrival check');
assert.equal(performance.getEntriesByName('traffic:stale:mark').length, 0);
assert.equal(performance.getEntriesByName('traffic:stale:measure').length, 0);
// Real Cesium ordering: changed arms the 320 ms debounce, its load fires,
// and moveEnd arrives only after cameraEventWaitTime (~500 ms).
setLongitude(-97.72);
changed.raise();
const anchorA = traffic.default.__trafficTimingTestHooks.currentAnchor();
const anchorMarkA = performance.getEntriesByType('mark').find((entry) => (
entry.detail?.segment === 'last-camera-change'
&& entry.detail?.interactionId === anchorA.interactionId
));
assert.ok(anchorMarkA, 'the scheduling camera change must be marked');
assert.equal(anchorMarkA.startTime, anchorA.timestamp);
await runDebouncedLoad();
assert.deepEqual(traffic.getTrafficTimingDiagnostics(), {
enabled: true,
marksInstalled: 1,
traceObjectsCreated: 1,
uncorrelatedTracesDropped: 0,
});
assert.equal(traffic.default.__trafficTimingTestHooks.currentAnchor(), null);
const fetchFromA = performance.getEntriesByType('measure').find((entry) => (
entry.detail?.segment === 'last-camera-change-to-fetch-start'
&& entry.detail?.interactionId === anchorA.interactionId
));
assert.ok(fetchFromA, 'the load must pair to A without waiting for moveEnd');
assert.equal(fetchFromA.startTime, anchorA.timestamp);
assert.equal(fetchFromA.detail.cameraChangeTimestamp, anchorA.timestamp);
moveEnd.raise();
const diagnosticMoveEnd = performance.getEntriesByType('mark').find((entry) => (
entry.detail?.segment === 'camera-move-end'
));
assert.ok(diagnosticMoveEnd, 'the late moveEnd diagnostic mark must still be emitted');
assert.equal(diagnosticMoveEnd.detail.diagnosticOnly, true);
assert.equal(diagnosticMoveEnd.detail.fetchWaitsForMoveEnd, false);
assert.ok(
diagnosticMoveEnd.startTime >= fetchFromA.startTime + fetchFromA.duration,
'the real-ordering pin requires fetch-start to precede late moveEnd',
);
// Re-arm B with C before B fires. clearTimeout cancels B in normal event-
// loop ordering; invoking the saved callback models an already-queued race
// and must count one drop without consuming C's current anchor.
setLongitude(-97.70);
changed.raise();
const anchorB = traffic.default.__trafficTimingTestHooks.currentAnchor();
const [timerBId, timerB] = pendingDebounce();
setLongitude(-97.68);
changed.raise();
const anchorC = traffic.default.__trafficTimingTestHooks.currentAnchor();
assert.notEqual(anchorC.interactionId, anchorB.interactionId);
assert.equal(timeouts.has(timerBId), false, 'the ordinary debounce path must cancel B');
await timerB.callback();
assert.equal(
traffic.default.__trafficTimingTestHooks.currentAnchor().interactionId,
anchorC.interactionId,
'a stale callback must not consume the newer C anchor',
);
await runDebouncedLoad();
assert.deepEqual(traffic.getTrafficTimingDiagnostics(), {
enabled: true,
marksInstalled: 1,
traceObjectsCreated: 2,
uncorrelatedTracesDropped: 1,
});
assert.ok(performance.getEntriesByType('measure').some((entry) => (
entry.detail?.interactionId === anchorC.interactionId
)));
assert.ok(performance.getEntriesByType('measure').every((entry) => (
entry.detail?.interactionId !== anchorB.interactionId
)), 'the canceled/stale B load must never emit a correlated trace');
} finally {
trafficLayer?.disable(viewer);
await server?.close();
globalThis.window = originalWindow;
globalThis.document = originalDocument;
globalThis.fetch = originalFetch;
globalThis.setTimeout = originalSetTimeout;
globalThis.clearTimeout = originalClearTimeout;
globalThis.setInterval = originalSetInterval;
globalThis.clearInterval = originalClearInterval;
console.log = originalLog;
console.warn = originalWarn;
performance.clearMarks();
performance.clearMeasures();
rmSync(cacheDir, { recursive: true, force: true, maxRetries: 3 });
}
});
test('the timed parser stays source-equivalent to the production parser', () => {
assert.equal(
canonicalSemanticBody(functionBody('parseRoadsTimed')),
canonicalSemanticBody(functionBody('parseRoads')),
'parseRoadsTimed may add only TRACE_ONLY blocks; production operations and order must match',
);
const traceBlocks = traceOnlyBlocks(functionBody('parseRoadsTimed'));
assert.ok(traceBlocks.length > 0, 'parseRoadsTimed must retain explicit trace-only blocks');
const traceSource = traceBlocks.join('\n');
// This is deliberately a reasonable lexical tripwire, not an AST proof: it
// recognizes direct assignments and common mutator calls, but cannot prove
// safety through aliases or computed properties. Source equivalence above
// remains the independent operation-order backstop.
const assignedIdentifiers = [...traceSource.matchAll(
/\b([A-Za-z_$][\w$]*)\s*(?:\+\+|--|\+=|-=|\*=|\/=|%=|=(?!=|>))/g,
)].map((match) => match[1]);
const mutatedIdentifiers = [...traceSource.matchAll(
/\b([A-Za-z_$][\w$]*)\.(?:add|delete|clear|set|push|pop|shift|unshift|splice|sort|reverse|copyWithin|fill)\s*\(/g,
)].map((match) => match[1]);
const unsafeWrites = [...assignedIdentifiers, ...mutatedIdentifiers]
.filter((identifier) => identifier !== 'trace' && !identifier.startsWith('_trafficTiming'));
assert.deepEqual(
unsafeWrites,
[],
'TRACE_ONLY blocks may write only trace or _trafficTiming* instrumentation state',
);
assert.doesNotMatch(
traceSource,
/\b(?:roads|coords|waypoints|segmentDist)\s*(?:\.(?:add|delete|clear|set|push|pop|shift|unshift|splice|sort|reverse|copyWithin|fill)\s*\(|\+\+|--|\+=|-=|\*=|\/=|%=|=(?!=|>))/,
'TRACE_ONLY blocks must not mutate parser production collections',
);
assert.doesNotMatch(SOURCE, /function fetchRoadsTimed\s*\(/);
assert.doesNotMatch(SOURCE, /function renderRoadsForAltitudeTimed\s*\(/);
const fetchBody = canonicalSemanticBody(functionBody('readRoads'));
assert.equal(fetchBody.includes('response.text()'), false);
assert.equal(fetchBody.includes('JSON.parse('), false);
const okCheck = fetchBody.indexOf('if(!response.ok)');
const responseJson = fetchBody.indexOf('response.json()');
assert.notEqual(okCheck, -1);
assert.notEqual(responseJson, -1);
assert.ok(okCheck < responseJson);
});