mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +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',
|
'terrain.reearth.land',
|
||||||
]);
|
]);
|
||||||
const RETRIES = 4;
|
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));
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +64,7 @@ export async function fetchTile(
|
|||||||
for (let attempt = 0; ; attempt++) {
|
for (let attempt = 0; ; attempt++) {
|
||||||
const response = await fetchImpl(url, {
|
const response = await fetchImpl(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'gods-eye-view-qa (headless test, cached)',
|
'User-Agent': QA_USER_AGENT,
|
||||||
...(accept ? { Accept: accept } : {}),
|
...(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
|
* Serves the page's requests to CACHED_TILE_HOSTS from the cache (filling
|
||||||
* it on a miss). Returns the counts.
|
* it on a miss). Returns the counts.
|
||||||
@@ -84,6 +131,7 @@ export async function installTileCache(
|
|||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
const stats = { hits: 0, fetched: 0, cancelled: 0, failed: 0, errors: {} };
|
const stats = { hits: 0, fetched: 0, cancelled: 0, failed: 0, errors: {} };
|
||||||
const client = await page.createCDPSession();
|
const client = await page.createCDPSession();
|
||||||
|
const misses = createMissLoader();
|
||||||
client.on('Fetch.requestPaused', async ({ requestId, request }) => {
|
client.on('Fetch.requestPaused', async ({ requestId, request }) => {
|
||||||
const accept = Object.entries(request.headers ?? {}).find(
|
const accept = Object.entries(request.headers ?? {}).find(
|
||||||
([name]) => name.toLowerCase() === 'accept',
|
([name]) => name.toLowerCase() === 'accept',
|
||||||
@@ -101,7 +149,7 @@ export async function installTileCache(
|
|||||||
};
|
};
|
||||||
stats.hits++;
|
stats.hits++;
|
||||||
} else {
|
} 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.
|
// Only successes are cached; a 404 (no tile there) is passed on.
|
||||||
if (tile.status === 200) {
|
if (tile.status === 200) {
|
||||||
fs.writeFileSync(file, tile.body);
|
fs.writeFileSync(file, tile.body);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import test from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import {
|
import {
|
||||||
backoffMs,
|
backoffMs,
|
||||||
|
createMissLoader,
|
||||||
fetchTile,
|
fetchTile,
|
||||||
tileCacheFile,
|
tileCacheFile,
|
||||||
} from '../../scripts/qa/tileCache.mjs';
|
} 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(limited.status, 429);
|
||||||
assert.equal(calls, 5);
|
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