Target check timeout is 80% of the refresh interval (closes #78)
check / check (push) Successful in 1m55s
check / check (push) Successful in 1m55s
Each target check now times out after 80% of the refresh interval, 24 seconds at 30 seconds, where it was capped at 3 seconds, so slow, far targets are recorded with their real time. Rounds never overlap: a round gives up the last round's checks if they are still waiting, which happens only when a round starts early, after an interval change or when the recovery probe finds a target answering. The probe still checks every half second, giving up its previous checks, so they do not pile up. The frontend has its first unit tests, run by script/frontend-test with Node's built-in test runner on a mocked clock. index.html now links src/styles.css, which src/main.js imported, since Node cannot import CSS. Model: opus-5-5
This commit was merged in pull request #79.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// Unit tests for src/main.js, run by script/frontend-test with Node's
|
||||
// built-in test runner. Importing the module does not start the page.
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { CONFIG, measureLatency } from "../../src/main.js";
|
||||
|
||||
// measureLatency writes timeouts to the debug log, which looks for its
|
||||
// panel in the page. There is no page here.
|
||||
globalThis.document = { getElementById: () => null };
|
||||
|
||||
// Mocks the clock for test t, so that a check lasting seconds takes no real
|
||||
// time, and replaces fetch with a target that answers after answerAfter
|
||||
// milliseconds of that clock, or never when answerAfter is Infinity. Both
|
||||
// are restored when the test ends.
|
||||
function mockTarget(t, answerAfter) {
|
||||
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
||||
t.mock.method(performance, "now", () => Date.now());
|
||||
t.mock.method(
|
||||
globalThis,
|
||||
"fetch",
|
||||
(url, { signal }) =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (answerAfter !== Infinity) setTimeout(resolve, answerAfter);
|
||||
signal.addEventListener("abort", () => reject(signal.reason));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// The result of check if it has ended, otherwise "still waiting".
|
||||
function settled(check) {
|
||||
return Promise.race([
|
||||
check,
|
||||
new Promise((resolve) => setImmediate(resolve, "still waiting")),
|
||||
]);
|
||||
}
|
||||
|
||||
for (const interval of [10000, 30000]) {
|
||||
const timeout = interval * 0.8;
|
||||
// Over 3 seconds, which the timeout used to be capped at.
|
||||
const slowAnswer = timeout - 1000;
|
||||
|
||||
test(`at a ${interval}ms interval, an answer after ${slowAnswer}ms is recorded with its real time`, async (t) => {
|
||||
CONFIG.updateInterval = interval;
|
||||
mockTarget(t, slowAnswer);
|
||||
const check = measureLatency("https://target.test");
|
||||
t.mock.timers.tick(slowAnswer);
|
||||
assert.deepEqual(await settled(check), {
|
||||
latency: slowAnswer,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
test(`at a ${interval}ms interval, a target that never answers is recorded as a timeout after ${timeout}ms`, async (t) => {
|
||||
CONFIG.updateInterval = interval;
|
||||
mockTarget(t, Infinity);
|
||||
const check = measureLatency("https://target.test");
|
||||
t.mock.timers.tick(timeout - 1);
|
||||
assert.equal(await settled(check), "still waiting");
|
||||
t.mock.timers.tick(1);
|
||||
assert.deepEqual(await settled(check), {
|
||||
latency: null,
|
||||
error: "timeout",
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user