mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Measure engine coupling, QA harness use and contract coverage
Counters that may only fall, each with a committed baseline and a test that fails on growth: - report-engine-coupling.mjs gains layer modules that take the viewer in attachRenderer (33), layer folders with their own "view being restored" flag (9), engine.surface calls outside src/globe (41) and layer ids named in the engine switch (3). - report-qa-harness.mjs counts QA scripts that launch Chrome themselves (98), wait with fixed sleeps (117), take the hidden loading screen as readiness (9), read Cesium-only objects (75) or define their own waitFor helpers (19). - withCallLog records every contract member the contract suite calls on the fake engine; contractCoverage.test.mjs fails when a member outside contract-uncalled-baseline.json goes uncalled, and when a listed one is called now, so the list of 12 only shrinks.
This commit is contained in:
1 parent
ba04ceaa32
commit
9c2a2d6951
11 files changed
+842
-3
No files matched your search
@@ -0,0 +1,142 @@
|
||||
#!/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.
|
||||
*
|
||||
* node scripts/report-qa-harness.mjs
|
||||
* node scripts/report-qa-harness.mjs --json
|
||||
* node scripts/report-qa-harness.mjs --write-baseline
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
||||
export const BASELINE_PATH = path.join(
|
||||
ROOT,
|
||||
'scripts',
|
||||
'qa-harness-baseline.json',
|
||||
);
|
||||
|
||||
const readsCesiumState = (text) => {
|
||||
const names = [
|
||||
'__godsEyeView',
|
||||
...[
|
||||
...text.matchAll(
|
||||
/\b(?:const|let|var)\s+(\w+)\s*=\s*(?:window\.)?__godsEyeView\s*[;,]/g,
|
||||
),
|
||||
].map((match) => match[1]),
|
||||
];
|
||||
return names.some((name) =>
|
||||
new RegExp(
|
||||
`\\b${name}\\s*\\??\\.\\s*(?:viewer|tileset|scene)\\b|\\{[^{}]*\\b(?:viewer|tileset|scene)\\b[^{}]*\\}\\s*=\\s*(?:window\\.)?${name}\\b`,
|
||||
).test(text),
|
||||
);
|
||||
};
|
||||
|
||||
export const QA_HARNESS_METRICS = Object.freeze([
|
||||
{
|
||||
id: 'ownLaunch',
|
||||
label: 'Scripts launching puppeteer themselves',
|
||||
measure: (files) =>
|
||||
files.filter(({ text }) => /\bpuppeteer\.launch\s*\(/.test(text)).length,
|
||||
},
|
||||
{
|
||||
id: 'fixedSleeps',
|
||||
label: 'Scripts with setTimeout-based pauses',
|
||||
measure: (files) =>
|
||||
files.filter(({ text }) =>
|
||||
/new\s+Promise\s*\(\s*\(?\s*(\w+)\s*\)?\s*=>\s*(?:\{\s*(?:return\s+)?)?setTimeout\s*\(\s*\1\s*[,)]/.test(
|
||||
text,
|
||||
),
|
||||
).length,
|
||||
},
|
||||
{
|
||||
id: 'loadingScreenReadiness',
|
||||
label: 'Scripts waiting for #loading-screen without waitForEngine',
|
||||
measure: (files) =>
|
||||
files.filter(
|
||||
({ text }) =>
|
||||
/\bwaitFor(?:Selector|Function)\s*\([^;]*#loading-screen\b/.test(
|
||||
text,
|
||||
) && !/\bwaitForEngine\s*\(/.test(text),
|
||||
).length,
|
||||
},
|
||||
{
|
||||
id: 'cesiumOnlyReads',
|
||||
label: 'Scripts reading __godsEyeView viewer, tileset or scene',
|
||||
measure: (files) =>
|
||||
files.filter(({ text }) => readsCesiumState(text)).length,
|
||||
},
|
||||
{
|
||||
id: 'ownWaitHelpers',
|
||||
label: 'Scripts defining their own waitFor functions',
|
||||
measure: (files) =>
|
||||
files.filter(({ text }) =>
|
||||
/\bfunction\s+waitFor\w*\s*\(|\b(?:const|let|var)\s+waitFor\w*\s*=\s*(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|\w+\s*=>)/.test(
|
||||
text,
|
||||
),
|
||||
).length,
|
||||
},
|
||||
]);
|
||||
|
||||
/** QA scripts, including the shared harness itself, but excluding tests. */
|
||||
export function readQaSources(root = ROOT) {
|
||||
return fs
|
||||
.readdirSync(path.join(root, 'scripts'))
|
||||
.filter((name) => /^qa-.*\.mjs$/.test(name) && !name.endsWith('.test.mjs'))
|
||||
.sort()
|
||||
.map((name) => ({
|
||||
file: `scripts/${name}`,
|
||||
text: fs.readFileSync(path.join(root, 'scripts', name), 'utf8'),
|
||||
}));
|
||||
}
|
||||
|
||||
export function measureQaHarness(root = ROOT) {
|
||||
const files = readQaSources(root);
|
||||
return Object.fromEntries(
|
||||
QA_HARNESS_METRICS.map(({ id, measure }) => [id, measure(files)]),
|
||||
);
|
||||
}
|
||||
|
||||
export function harnessGrowth(measured, baseline) {
|
||||
return QA_HARNESS_METRICS.filter(
|
||||
({ id }) => !(measured[id] <= (baseline[id] ?? -Infinity)),
|
||||
).map(({ id, label }) => ({
|
||||
id,
|
||||
label,
|
||||
measured: measured[id],
|
||||
baseline: baseline[id],
|
||||
}));
|
||||
}
|
||||
|
||||
export function readBaseline(file = BASELINE_PATH) {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
|
||||
function main(args) {
|
||||
const measured = measureQaHarness();
|
||||
if (args.includes('--write-baseline')) {
|
||||
const note = args.find((arg) => arg.startsWith('--note='))?.slice(7);
|
||||
fs.writeFileSync(
|
||||
BASELINE_PATH,
|
||||
`${JSON.stringify({ metrics: measured, ...(note ? { measuredAt: note } : {}) }, null, 2)}\n`,
|
||||
);
|
||||
}
|
||||
if (args.includes('--json')) {
|
||||
console.log(JSON.stringify(measured, null, 2));
|
||||
return;
|
||||
}
|
||||
const baseline = fs.existsSync(BASELINE_PATH) ? readBaseline().metrics : {};
|
||||
for (const { id, label } of QA_HARNESS_METRICS) {
|
||||
const was = baseline[id];
|
||||
const change =
|
||||
was === undefined
|
||||
? ''
|
||||
: ` (baseline ${was}, ${measured[id] - was >= 0 ? '+' : ''}${measured[id] - was})`;
|
||||
console.log(`${String(measured[id]).padStart(7)} ${label}${change}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href)
|
||||
main(process.argv.slice(2));
|
||||
Reference in new issue
Block a user