test(qa): wheel floor against the floor's own ground; polite tile cache

The Navara wheel check measures clearance against engine.groundFloor's
ground (threshold 29 m: the floor keeps 30 m), so tiles streaming under a
still camera cannot flake it. The tile cache identifies itself with the
repository URL, keeps at most two misses in flight and merges identical
ones.
This commit is contained in:
German Khokhlov committed 2026-10-16 15:06:15 +04:00
1 parent da6141be4b
commit ce985768bc
2 files changed
+94 -2

No files matched your search

+50 -2
View File
@@ -16,6 +16,11 @@ export const CACHED_TILE_HOSTS = Object.freeze([
'terrain.reearth.land',
]);
const RETRIES = 4;
/** Who is asking, with a contact URL (the tile hosts' usage policies). */
export const QA_USER_AGENT =
'gods-eye-view-qa/1 (headless tests with a disk cache; https://github.com/bilawalsidhu/gods-eye-view)';
/** Fetches to the tile hosts in flight at once. */
export const MAX_MISSES_IN_FLIGHT = 2;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/**
@@ -59,7 +64,7 @@ export async function fetchTile(
for (let attempt = 0; ; attempt++) {
const response = await fetchImpl(url, {
headers: {
'User-Agent': 'gods-eye-view-qa (headless test, cached)',
'User-Agent': QA_USER_AGENT,
...(accept ? { Accept: accept } : {}),
},
});
@@ -73,6 +78,48 @@ export async function fetchTile(
}
}
/**
* Loads missing tiles politely: at most MAX_MISSES_IN_FLIGHT fetches at
* once (the rest queue), and one fetch for identical misses in flight (same
* URL and Accept). `load(url, accept)` resolves to fetchTile's result.
*/
export function createMissLoader({
fetchImpl = fetch,
wait = sleep,
maxInFlight = MAX_MISSES_IN_FLIGHT,
} = {}) {
const pending = new Map();
const queue = [];
let running = 0;
const next = () => {
while (running < maxInFlight && queue.length) {
const job = queue.shift();
running++;
fetchTile(job.url, { accept: job.accept, fetchImpl, wait })
.then(job.resolve, job.reject)
.finally(() => {
running--;
pending.delete(job.key);
next();
});
}
};
return {
load(url, accept) {
const key = `${url}\n${accept ?? ''}`;
let promise = pending.get(key);
if (!promise) {
promise = new Promise((resolve, reject) =>
queue.push({ key, url, accept, resolve, reject }),
);
pending.set(key, promise);
next();
}
return promise;
},
};
}
/**
* Serves the page's requests to CACHED_TILE_HOSTS from the cache (filling
* it on a miss). Returns the counts.
@@ -84,6 +131,7 @@ export async function installTileCache(
fs.mkdirSync(dir, { recursive: true });
const stats = { hits: 0, fetched: 0, cancelled: 0, failed: 0, errors: {} };
const client = await page.createCDPSession();
const misses = createMissLoader();
client.on('Fetch.requestPaused', async ({ requestId, request }) => {
const accept = Object.entries(request.headers ?? {}).find(
([name]) => name.toLowerCase() === 'accept',
@@ -101,7 +149,7 @@ export async function installTileCache(
};
stats.hits++;
} else {
const tile = await fetchTile(request.url, { accept });
const tile = await misses.load(request.url, accept);
// Only successes are cached; a 404 (no tile there) is passed on.
if (tile.status === 200) {
fs.writeFileSync(file, tile.body);
+44
View File
@@ -2,6 +2,7 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import {
backoffMs,
createMissLoader,
fetchTile,
tileCacheFile,
} from '../../scripts/qa/tileCache.mjs';
@@ -69,3 +70,46 @@ test('a tile fetch retries 429s, stops after four retries, keeps Accept', async
assert.equal(limited.status, 429);
assert.equal(calls, 5);
});
test('misses: at most two fetches in flight, identical ones merged, a contact UA', async () => {
let inFlight = 0;
let most = 0;
const calls = [];
const releases = [];
const loader = createMissLoader({
fetchImpl: (url, init) => {
calls.push([url, init.headers['User-Agent']]);
inFlight++;
most = Math.max(most, inFlight);
return new Promise((resolve) =>
releases.push(() => {
inFlight--;
resolve(response(200, { 'content-type': 'image/png' }));
}),
);
},
wait: async () => {},
});
const loads = [
loader.load('a', null),
loader.load('a', null),
loader.load('b', null),
loader.load('c', null),
loader.load('a', 'image/webp'),
];
for (let round = 0; round < 10 && releases.length; round++) {
await new Promise((resolve) => setTimeout(resolve, 0));
releases.splice(0).forEach((release) => release());
await new Promise((resolve) => setTimeout(resolve, 0));
}
const tiles = await Promise.all(loads);
assert.ok(tiles.every((tile) => tile.status === 200));
assert.equal(tiles[0], tiles[1], 'one fetch for two identical misses');
assert.equal(calls.length, 4, 'a, b, c and a with another Accept');
assert.ok(most <= 2, `${most} fetches in flight`);
assert.ok(
calls.every(([, agent]) =>
agent.includes('https://github.com/bilawalsidhu/gods-eye-view'),
),
);
});