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:
German Khokhlov committed 2026-11-10 14:23:29 +04:00
1 parent ba04ceaa32
commit 9c2a2d6951
11 files changed
+842 -3

No files matched your search

+9
View File
@@ -1075,6 +1075,15 @@ const SHAPE = {
},
};
/** Required capability members checked by assertGlobeEngine. */
export function engineContractMembers() {
return Object.entries(SHAPE)
.flatMap(([namespace, members]) =>
Object.keys(members).map((member) => `${namespace}.${member}`),
)
.sort();
}
const isNameList = (value) =>
Array.isArray(value) &&
value.length > 0 &&
+184
View File
@@ -0,0 +1,184 @@
import { engineContractMembers, isEngineEvent } from '../contract.js';
// Returned handles keep the names of their contract typedefs.
const HANDLE_TYPES = {
MoverSet: {
creators: ['movers.createSet'],
members: [
'set',
'setCount',
'count',
'commit',
'ready',
'failed',
'destroy',
],
},
Line: {
creators: ['lines.createLine'],
members: ['setPositions', 'setColor', 'setModelMatrix', 'show', 'destroy'],
},
LineSegments: {
creators: ['lines.createSegments'],
members: ['replace', 'commit', 'show', 'destroy'],
},
GroundSet: {
creators: ['ground.createSet'],
members: [
'id',
'setReveal',
'replace',
'commit',
'show',
'shown',
'count',
'ready',
'destroy',
],
},
GroundPulseField: {
creators: ['ground.createPulseField'],
members: ['id', 'draw', 'move', 'destroy', 'isDestroyed'],
},
ImageryLayer: {
creators: ['imagery.createLayer'],
members: [
'id',
'setSource',
'source',
'staging',
'setDetail',
'detail',
'raise',
'setAlpha',
'alpha',
'show',
'shown',
'setSplit',
'split',
'ready',
'destroy',
],
},
GlobeMesh: {
creators: ['meshes.createMesh'],
members: ['id', 'update', 'show', 'destroy'],
},
FollowHandle: {
creators: ['camera.follow', 'camera.following'],
members: ['stop', 'retarget', 'target', 'native', 'label', 'name'],
},
PointerClaim: {
creators: ['input.acquirePointer'],
members: ['release'],
},
EngineEvent: {
creators: [],
members: ['addEventListener', 'removeEventListener'],
},
};
/** Members tracked by the suite, including handles returned by the engine. */
export function contractCallMembers() {
return [
...engineContractMembers(),
...Object.entries(HANDLE_TYPES).flatMap(([type, { members }]) =>
members.map((member) => `${type}.${member}`),
),
].sort();
}
/** Log contract calls and value reads without changing the engine's receiver. */
export function withCallLog(engine) {
const called = new Set();
const members = new Set(contractCallMembers());
const namespaces = new Set(
engineContractMembers().map((name) => name.split('.')[0]),
);
const returnedTypes = new Map(
Object.entries(HANDLE_TYPES).flatMap(([type, { creators }]) =>
creators.map((name) => [name, type]),
),
);
const proxies = new WeakMap();
const originals = new WeakMap();
const unwrap = (value) => originals.get(value) ?? value;
const unwrapExclusions = (exclude) =>
Array.isArray(exclude) && exclude.some((value) => originals.has(value))
? exclude.map(unwrap)
: exclude;
const wrap = (target, prefix, followEvent = false) => {
if (target == null || typeof target !== 'object') return target;
let cached = proxies.get(target);
if (!cached) proxies.set(target, (cached = new Map()));
const key = followEvent ? 'camera.followChanged' : prefix;
if (cached.has(key)) return cached.get(key);
const methods = new Map();
const listeners = new WeakMap();
const proxy = new Proxy(target, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof property !== 'string') return value;
if (!prefix && namespaces.has(property)) return wrap(value, property);
const name = `${prefix}.${property}`;
if (typeof value !== 'function') {
if (members.has(name)) called.add(name);
return isEngineEvent(value)
? wrap(value, 'EngineEvent', name === 'camera.followChanged')
: value;
}
if (methods.get(property)?.original === value)
return methods.get(property).wrapped;
const wrapped = function (...args) {
if (members.has(name)) called.add(name);
if (
followEvent &&
(property === 'addEventListener' ||
property === 'removeEventListener') &&
typeof args[0] === 'function'
) {
const listener = args[0];
if (!listeners.has(listener))
listeners.set(listener, (handle) =>
listener(wrap(handle, 'FollowHandle')),
);
args[0] = listeners.get(listener);
}
if (name === 'input.on' && args[2]?.pointer) {
args[2] = { ...args[2], pointer: unwrap(args[2].pointer) };
}
// Surface adapters identify excluded resources by their native handles.
if (
name === 'surface.sampleHeight' ||
name === 'surface.clampToHeight'
) {
if (args.length > 1) args[1] = unwrapExclusions(args[1]);
} else if (
name === 'surface.sampleHeightDetailed' ||
name === 'surface.raycast'
) {
const index = name === 'surface.raycast' ? 0 : 1;
const options = args[index];
const exclude = unwrapExclusions(options?.exclude);
if (exclude !== options?.exclude)
args[index] = { ...options, exclude };
}
const result = Reflect.apply(value, target, args);
const type = returnedTypes.get(name);
return type ? wrap(result, type) : result;
};
methods.set(property, { original: value, wrapped });
return wrapped;
},
set(target, property, value) {
return Reflect.set(target, property, unwrap(value), target);
},
});
cached.set(key, proxy);
originals.set(proxy, target);
return proxy;
};
return { engine: wrap(engine, ''), called: () => [...called].sort() };
}
@@ -0,0 +1,14 @@
[
"FollowHandle.label",
"FollowHandle.name",
"GlobeMesh.id",
"GroundPulseField.id",
"Line.setColor",
"Line.setModelMatrix",
"Line.show",
"MoverSet.failed",
"maps.terrainRevision",
"render.maxTextureSize",
"surface.raycast",
"surface.sampleTerrainMostDetailed"
]
+219
View File
@@ -0,0 +1,219 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import test from 'node:test';
import { createEngineEvent, engineContractMembers } from '../contract.js';
import { contractCallMembers, withCallLog } from './callLog.js';
import { createContractSuite } from './contractSuite.mjs';
import { createFakeEngine } from './fakeEngine.js';
test('the contract suite does not leave new members uncalled', async (t) => {
const fake = createFakeEngine();
const { engine, called } = withCallLog(fake);
const checks = createContractSuite(engine, {
waitFrames: async (count) => fake.fake.frames(count),
pointer: {
click: async (x, y) => fake.fake.input('click', { position: { x, y } }),
move: async (x, y) =>
fake.fake.input('move', {
startPosition: { x: x - 1, y },
endPosition: { x, y },
}),
},
mapSources: {
imagery: [{ kind: 'osm' }, { kind: 'esri-imagery' }],
photoreal: { kind: 'photoreal' },
},
});
try {
for (const check of checks) await t.test(check.name, () => check.run());
const baseline = JSON.parse(
fs.readFileSync(
new URL('./contract-uncalled-baseline.json', import.meta.url),
'utf8',
),
);
const seen = new Set(called());
const uncalled = contractCallMembers().filter((name) => !seen.has(name));
assert.deepEqual(
baseline,
[...new Set(baseline)].sort(),
'Keep contract-uncalled-baseline.json sorted and remove duplicate names.',
);
assert.deepEqual(
uncalled.filter((name) => !baseline.includes(name)),
[],
'Contract members lost coverage; add behavioral checks to createContractSuite that exercise them instead of growing contract-uncalled-baseline.json.',
);
assert.deepEqual(
baseline.filter((name) => !uncalled.includes(name)),
[],
'Contract members are now exercised or no longer exist; remove these names from contract-uncalled-baseline.json to lower the baseline.',
);
} finally {
fake.destroy();
}
});
test('call logging keeps receivers, arguments, results and function identity', async () => {
const result = { height: 5 };
const promise = Promise.resolve(result);
const camera = {
flyTo(destination) {
assert.equal(this, camera);
return destination;
},
};
const maps = {
setTerrain() {
assert.equal(this, maps);
return promise;
},
};
const { engine, called } = withCallLog({ camera, maps });
const flyTo = engine.camera.flyTo;
assert.equal(flyTo, engine.camera.flyTo);
assert.deepEqual(called(), [], 'shape inspection must not count as a call');
assert.equal(flyTo(result), result);
assert.equal(engine.maps.setTerrain(), promise);
assert.equal(await promise, result);
assert.deepEqual(called(), ['camera.flyTo', 'maps.setTerrain']);
called().push('not a member');
assert.deepEqual(called(), ['camera.flyTo', 'maps.setTerrain']);
});
test('returned handles log under their contract type and retain receivers', () => {
const handle = {
set() {
assert.equal(this, handle);
return 42;
},
};
const { engine, called } = withCallLog({
movers: { createSet: () => handle },
});
const set = engine.movers.createSet();
assert.equal(set, engine.movers.createSet());
assert.equal(set.set(), 42);
assert.deepEqual(called(), ['MoverSet.set', 'movers.createSet']);
});
test('getter reads and setters use the original receiver', () => {
let revision = 7;
const maps = {
get terrainRevision() {
assert.equal(this, maps);
return revision;
},
set terrainRevision(value) {
assert.equal(this, maps);
revision = value;
},
};
const { engine, called } = withCallLog({ maps });
assert.deepEqual(called(), []);
assert.equal(engine.maps.terrainRevision, 7);
engine.maps.terrainRevision = 8;
assert.equal(engine.maps.terrainRevision, 8);
assert.deepEqual(called(), ['maps.terrainRevision']);
});
test('surface exclusions reach the engine as its original handles', () => {
const handle = { set() {} };
const point = {};
const surface = {
sampleHeight(position, exclude) {
assert.equal(position, point);
assert.equal(exclude[0], handle);
return 12;
},
sampleHeightDetailed(position, { exclude }) {
assert.equal(position, point);
assert.equal(exclude[0], handle);
return { height: 12, source: 'terrain' };
},
clampToHeight(position, exclude) {
assert.equal(exclude[0], handle);
return position;
},
raycast({ exclude }) {
assert.equal(exclude[0], handle);
return undefined;
},
};
const { engine } = withCallLog({
movers: { createSet: () => handle },
surface,
});
const set = engine.movers.createSet();
const exclude = [set];
assert.equal(engine.surface.sampleHeight(point, exclude), 12);
assert.deepEqual(engine.surface.sampleHeightDetailed(point, { exclude }), {
height: 12,
source: 'terrain',
});
assert.equal(engine.surface.clampToHeight(point, exclude), point);
assert.equal(engine.surface.raycast({ exclude }), undefined);
assert.equal(exclude[0], set, 'caller-owned exclusions must stay unchanged');
});
test('unknown members remain undefined or throw the original error', () => {
const error = new Error('unavailable');
const camera = {
get unknown() {
throw error;
},
};
const { engine, called } = withCallLog({ camera });
assert.equal(engine.missing, undefined);
assert.equal(engine.camera.missing, camera.missing);
assert.throws(() => engine.camera.missing(), TypeError);
assert.throws(
() => engine.camera.unknown,
(caught) => caught === error,
);
assert.deepEqual(called(), []);
});
test('follow events retain handle identity, duplicate listeners and removers', () => {
const followChanged = createEngineEvent();
const handle = { stop() {}, retarget() {}, target() {} };
const camera = {
followChanged,
follow() {
followChanged.raiseEvent(handle);
return handle;
},
following: () => handle,
};
const { engine } = withCallLog({ camera });
const seen = [];
const listener = (value) => seen.push(value);
const remove = engine.camera.followChanged.addEventListener(listener);
engine.camera.followChanged.addEventListener(listener);
const wrapped = engine.camera.follow();
assert.equal(engine.camera.following(), wrapped);
assert.deepEqual(seen, [wrapped]);
assert.equal(engine.camera.followChanged.removeEventListener(listener), true);
remove();
engine.camera.follow();
assert.equal(seen.length, 1);
});
test('contract inventory is sorted, unique and includes required capabilities and handles', () => {
const members = contractCallMembers();
assert.deepEqual(members, [...new Set(members)].sort());
for (const name of [
'camera.flyTo',
'maps.terrainRevision',
'FollowHandle.retarget',
'Line.setPositions',
'GroundSet.replace',
'ImageryLayer.setAlpha',
'GlobeMesh.update',
])
assert.ok(members.includes(name), name);
const names = engineContractMembers();
assert.ok(names.every((name) => members.includes(name)));
names.length = 0;
assert.ok(engineContractMembers().length > 0);
});