mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
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:
1 parent
da6141be4b
commit
ce985768bc
2 files changed
+94
-2
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user