mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
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.
389 lines
14 KiB
JavaScript
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);
|
|
});
|