Build test engines from the shared fake instead of literals

Tests that made their own engine object now use createFakeEngine() with
its scripting hooks or an override of the one member under test, so they
follow the contract when it changes. 125 such literals in 74 test files
go down to two that are not globe engines on purpose; report-qa-harness
counts them, and the baseline keeps the number from growing.
This commit is contained in:
German Khokhlov committed 2026-11-10 17:07:27 +04:00
1 parent 3c304502a0
commit 225c1c25c7
76 files changed
+668 -564

No files matched your search

+2 -1
View File
@@ -4,6 +4,7 @@
"fixedSleeps": 69,
"loadingScreenReadiness": 0,
"cesiumOnlyReads": 47,
"ownWaitHelpers": 16
"ownWaitHelpers": 16,
"testEngineLiterals": 2
}
}
+80 -1
View File
@@ -2,17 +2,19 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import {
QA_HARNESS_METRICS,
engineLiteralLines,
harnessGrowth,
measureQaHarness,
readBaseline,
readQaSources,
readTestSources,
} from './report-qa-harness.mjs';
test('QA harness duplication does not grow past the baseline', () => {
assert.deepEqual(
harnessGrowth(measureQaHarness(), readBaseline().metrics),
[],
'QA harness duplication grew; use scripts/qa-strict-harness.mjs helpers for browser launch, engine readiness and camera waits instead.',
'QA duplication grew; use the shared browser harness and src/globe/testing/fakeEngine.js for test engines.',
);
});
@@ -43,6 +45,83 @@ test('QA sources include the harness and exclude tests and other reports', () =>
assert.ok(!names.includes('scripts/report-qa-harness.mjs'));
});
test('test sources include nested tests and exclude runtime files', () => {
const names = readTestSources().map(({ file }) => file);
assert.ok(names.includes('src/globe/testing/fakeEngine.test.mjs'));
assert.ok(names.includes('scripts/qaHarness.test.mjs'));
assert.ok(names.every((name) => name.endsWith('.test.mjs')));
});
test('engine literals include assignments, inline options and factory returns', () => {
for (const source of [
'const engine = {};',
'let engine; engine = { camera: {} };',
'scene.engine = { host: {} };',
"scene['engine'] = {};",
'init({ engine: {} });',
'const state = { _engine: {} };',
'const engineB = {}, trafficEngine = {};',
"function createEngine() { return { kind: 'cesium' }; }",
'const engine = () => ({ camera: {} });',
'const fakeEngine = () => { return { host: {} }; };',
'const factory = { createEngine() { return { render: {} }; } };',
'inspect({ host: { size() {} }, camera: {} });',
"const navara = { kind: 'navara', render: {} };",
])
assert.ok(engineLiteralLines(source).length > 0, source);
assert.deepEqual(
engineLiteralLines('const engine = {};\ninit({ engine: {} });'),
[1, 2],
);
});
test('engine literal counting ignores source text, options and harness observations', () => {
for (const source of [
'// const engine = {};',
"const source = 'const engine = {};';",
'const source = `init({ engine: {} });`;',
'const engine = createFakeEngine();',
'init({ engine: createFakeEngine() });',
'const options = { engineOptions: {} };',
'const engineOptions = { canvas: {}, width: 800 };',
"const headers = { host: 'localhost' };",
'const overlay = { host: { setEntries() {} } };',
'result[engine] = { camera: {} };',
'function observedEngine() { return { engine, camera }; }',
'function observedEngine() { return { snapshots: [] }; }',
'function observedEngine() { return records.map(() => ({ camera: {} })); }',
])
assert.deepEqual(engineLiteralLines(source), [], source);
});
test('engine literal metric counts test files once and rejects baseline growth', () => {
const metric = QA_HARNESS_METRICS.find(
({ id }) => id === 'testEngineLiterals',
);
const measured = metric.measure([
{
file: 'src/one.test.mjs',
text: 'const engine = {}; init({ engine: {} });',
},
{
file: 'scripts/two.test.mjs',
text: 'const engine = () => ({ host: {} });',
},
{ file: 'src/three.test.mjs', text: 'const engine = createFakeEngine();' },
{ file: 'src/runtime.js', text: 'const engine = {};' },
]);
assert.equal(measured, 2);
const baseline = Object.fromEntries(
QA_HARNESS_METRICS.map(({ id }) => [id, 0]),
);
assert.deepEqual(
harnessGrowth({ ...baseline, testEngineLiterals: measured }, baseline).map(
({ id }) => id,
),
['testEngineLiterals'],
);
});
test('metrics count scripts once and distinguish helpers from their callers', () => {
const measure = (id, ...sources) =>
QA_HARNESS_METRICS.find((metric) => metric.id === id).measure(
+106 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
/**
* Browser QA still doing work owned by scripts/qa-strict-harness.mjs.
* Each metric counts scripts, so repeated uses in one script count once.
* Browser QA and tests still duplicating the shared harness or fake engine.
* Each metric counts files, so repeated uses in one file count once.
*
* node scripts/report-qa-harness.mjs
* node scripts/report-qa-harness.mjs --json
@@ -10,6 +10,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { parsers } from 'prettier/plugins/babel.mjs';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
export const BASELINE_PATH = path.join(
@@ -18,6 +19,80 @@ export const BASELINE_PATH = path.join(
'qa-harness-baseline.json',
);
/** Engine assignments, inline options and engine factory returns, not strings or comments. */
export function engineLiteralLines(text) {
const lines = [];
const engineName = (name) =>
!/Options$/.test(name ?? '') &&
/^(?:_?engine(?:[A-Z0-9]\w*)?|\w+Engine(?:[A-Z0-9]\w*)?)$/.test(name ?? '');
function walk(node, parent, factory) {
if (!node || typeof node !== 'object') return;
if (/Function|ObjectMethod/.test(node.type))
factory = node.id?.name ?? parent?.id?.name ?? node.key?.name;
if (node.type === 'ObjectExpression') {
const name =
parent?.type === 'VariableDeclarator'
? parent.id?.name
: parent?.type === 'AssignmentExpression'
? parent.left?.computed
? parent.left.property?.value
: (parent.left?.property?.name ?? parent.left?.name)
: parent?.type === 'ObjectProperty'
? (parent.key?.name ?? parent.key?.value)
: null;
const keys = node.properties.map((property) => property.key?.name);
const host = node.properties.find(
(property) => property.key?.name === 'host',
)?.value;
const globeHost =
host?.type === 'ObjectExpression' &&
host.properties.some((property) =>
['canvas', 'container', 'size'].includes(property.key?.name),
);
const adapterKind = node.properties.some(
(property) =>
property.key?.name === 'kind' &&
['cesium', 'navara'].includes(property.value?.value),
);
const returned =
parent?.type === 'ReturnStatement' ||
parent?.type === 'ArrowFunctionExpression';
// A factory's { engine, ...observations } is a harness, not its engine.
if (
engineName(name) ||
globeHost ||
(adapterKind &&
keys.some((key) =>
['camera', 'render', 'credits', 'maps'].includes(key),
)) ||
(returned &&
engineName(factory) &&
!keys.includes('engine') &&
keys.some((key) =>
[
'kind',
'host',
'camera',
'frames',
'render',
'credits',
'maps',
].includes(key),
))
)
lines.push(node.loc.start.line);
}
for (const [key, value] of Object.entries(node)) {
if (['loc', 'comments', 'tokens'].includes(key)) continue;
if (Array.isArray(value))
for (const child of value) walk(child, node, factory);
else if (value && typeof value === 'object') walk(value, node, factory);
}
}
walk(parsers.babel.parse(text));
return lines;
}
const readsCesiumState = (text) => {
const names = [
'__godsEyeView',
@@ -78,6 +153,15 @@ export const QA_HARNESS_METRICS = Object.freeze([
),
).length,
},
{
id: 'testEngineLiterals',
label: 'Test files with their own engine literal',
measure: (files) =>
files.filter(
({ file, text }) =>
file.endsWith('.test.mjs') && engineLiteralLines(text).length,
).length,
},
]);
/** QA scripts, including the shared harness itself, but excluding tests. */
@@ -92,10 +176,29 @@ export function readQaSources(root = ROOT) {
}));
}
export function readTestSources(root = ROOT) {
return ['build', 'scripts', 'server', 'src'].flatMap((directory) => {
const base = path.join(root, directory);
if (!fs.existsSync(base)) return [];
return fs
.readdirSync(base, { recursive: true })
.sort()
.filter((name) => name.endsWith('.test.mjs'))
.map((name) => ({
file: `${directory}/${name.split(path.sep).join('/')}`,
text: fs.readFileSync(path.join(base, name), 'utf8'),
}));
});
}
export function measureQaHarness(root = ROOT) {
const files = readQaSources(root);
const tests = readTestSources(root);
return Object.fromEntries(
QA_HARNESS_METRICS.map(({ id, measure }) => [id, measure(files)]),
QA_HARNESS_METRICS.map(({ id, measure }) => [
id,
measure(id === 'testEngineLiterals' ? tests : files),
]),
);
}