Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6126a7f85c |
@@ -106,10 +106,8 @@ code lives in `src/main.js` with a class-based architecture:
|
|||||||
`updateSummary()` / `updateHealthBox()` handle incremental updates
|
`updateSummary()` / `updateHealthBox()` handle incremental updates
|
||||||
- **`tick()`**: Main loop — measures all hosts in parallel, pushing each host's
|
- **`tick()`**: Main loop — measures all hosts in parallel, pushing each host's
|
||||||
sample and redrawing its row as soon as its check ends, then redraws every
|
sample and redrawing its row as soon as its check ends, then redraws every
|
||||||
row, the summary and the health box once the last check ends. The rows are
|
row, sorts, and redraws the summary and health box once the last check ends.
|
||||||
sorted then too, after the first round that is not discarded and every tenth
|
When paused, pushes blank markers (no probes, no false outage)
|
||||||
round after that. When paused, pushes blank markers (no probes, no false
|
|
||||||
outage)
|
|
||||||
- **`Reporter`**: Posts collected samples to the backend
|
- **`Reporter`**: Posts collected samples to the backend
|
||||||
|
|
||||||
### Reporting
|
### Reporting
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ latest run passes.
|
|||||||
round's last check ends, so no row reads "paused" after a pause and resume
|
round's last check ends, so no row reads "paused" after a pause and resume
|
||||||
during the round. A check that ends after the user pauses or after its round
|
during the round. A check that ends after the user pauses or after its round
|
||||||
is given up shows nothing, and the first round is still discarded as a whole
|
is given up shows nothing, and the first round is still discarded as a whole
|
||||||
- 2026-10-03: `backend/script/lint` says what went wrong with its
|
|
||||||
`.golangci.yml` check (issue #34). On a hash mismatch it says to compare the
|
|
||||||
file with the org standard: if they differ, restore the org standard; if they
|
|
||||||
are the same, the org standard changed, so update `GOLANGCI_CONFIG_SHA256` in
|
|
||||||
that script. It used to say only to restore the file, which loops once the org
|
|
||||||
standard itself has moved. A missing `.golangci.yml`, and a `sha256sum` that
|
|
||||||
is missing or prints no hash, each get their own message instead of being
|
|
||||||
reported as a mismatch; every one still fails the lint
|
|
||||||
- 2026-10-03: the Go tests run with the race detector and coverage (issue #88):
|
- 2026-10-03: the Go tests run with the race detector and coverage (issue #88):
|
||||||
`backend/script/test` runs `go test -timeout 30s -race -cover ./...` and, if
|
`backend/script/test` runs `go test -timeout 30s -race -cover ./...` and, if
|
||||||
that fails, runs it again with `-v` and fails. Go's `-timeout` bounds the
|
that fails, runs it again with `-v` and fails. Go's `-timeout` bounds the
|
||||||
|
|||||||
+2
-20
@@ -14,34 +14,16 @@ set -eu
|
|||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
# The sha256 of the org standard .golangci.yml. When that file changes in
|
|
||||||
# sneak/prompts and is copied here again, this changes with it.
|
|
||||||
GOLANGCI_CONFIG_SHA256="a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776"
|
GOLANGCI_CONFIG_SHA256="a79b63a254602a5318db5d0e9a06bc71b84bf0c1d896305229d8bfed1d1b1776"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
if [ ! -f .golangci.yml ]; then
|
|
||||||
echo "backend/.golangci.yml is missing. Copy the org standard verbatim" >&2
|
|
||||||
echo "from https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
|
actual="$(sha256sum .golangci.yml | cut -d' ' -f1)"
|
||||||
if [ -z "$actual" ]; then
|
|
||||||
echo "sha256sum is missing or printed no hash, so" >&2
|
|
||||||
echo "backend/.golangci.yml could not be checked." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
|
if [ "$actual" != "$GOLANGCI_CONFIG_SHA256" ]; then
|
||||||
echo "backend/.golangci.yml does not match GOLANGCI_CONFIG_SHA256" >&2
|
echo ".golangci.yml has drifted from the org standard." >&2
|
||||||
echo "in backend/script/lint." >&2
|
|
||||||
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
|
echo " expected $GOLANGCI_CONFIG_SHA256" >&2
|
||||||
echo " actual $actual" >&2
|
echo " actual $actual" >&2
|
||||||
echo "Compare it with the org standard," >&2
|
echo "Restore it verbatim from sneak/prompts; do not edit it." >&2
|
||||||
echo "https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml" >&2
|
|
||||||
echo "- If they differ, it was edited here: restore the org standard" >&2
|
|
||||||
echo " verbatim. Do not edit it." >&2
|
|
||||||
echo "- If they are the same, the org standard changed: set" >&2
|
|
||||||
echo " GOLANGCI_CONFIG_SHA256 in backend/script/lint to the actual hash." >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
golangci-lint run ./...
|
golangci-lint run ./...
|
||||||
|
|||||||
+1
-1
@@ -1224,7 +1224,7 @@ function stopRecoveryProbe(state) {
|
|||||||
|
|
||||||
// --- Pause / Resume ----------------------------------------------------------
|
// --- Pause / Resume ----------------------------------------------------------
|
||||||
|
|
||||||
export function greyOutUI(state) {
|
function greyOutUI(state) {
|
||||||
// Grey out all host rows
|
// Grey out all host rows
|
||||||
state.allHosts.forEach((host, i) => {
|
state.allHosts.forEach((host, i) => {
|
||||||
const latencyEl = document.querySelector(
|
const latencyEl = document.querySelector(
|
||||||
|
|||||||
+6
-54
@@ -3,21 +3,14 @@
|
|||||||
|
|
||||||
import { beforeEach, test } from "node:test";
|
import { beforeEach, test } from "node:test";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import {
|
import { AppState, CONFIG, measureLatency, tick } from "../../src/main.js";
|
||||||
AppState,
|
|
||||||
CONFIG,
|
|
||||||
greyOutUI,
|
|
||||||
measureLatency,
|
|
||||||
tick,
|
|
||||||
} from "../../src/main.js";
|
|
||||||
|
|
||||||
// There is no page here, so the tests stand in for it. The debug log looks
|
// There is no page here, so the tests stand in for it. The debug log looks
|
||||||
// for its panel by id and finds none. Each element of a host's row that
|
// for its panel by id and finds none. Each element of a host's row that
|
||||||
// tick or greyOutUI draws into is a plain object, made the first time a
|
// tick draws into is a plain object, made the first time a test looks it
|
||||||
// test looks it up and kept in elements under its selector until the next
|
// up and kept in elements under its selector until the next test starts.
|
||||||
// test starts. As on a page, writing its text replaces its markup; the
|
// Drawing a sparkline does nothing; it looks for the pixel ratio on window
|
||||||
// status dot greyOutUI looks for in it is not there. Drawing a sparkline
|
// and finds none.
|
||||||
// does nothing; it looks for the pixel ratio on window and finds none.
|
|
||||||
let elements;
|
let elements;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
elements = {};
|
elements = {};
|
||||||
@@ -38,13 +31,7 @@ globalThis.window = {};
|
|||||||
globalThis.document = {
|
globalThis.document = {
|
||||||
getElementById: () => null,
|
getElementById: () => null,
|
||||||
querySelector: (selector) =>
|
querySelector: (selector) =>
|
||||||
(elements[selector] ??= {
|
(elements[selector] ??= { getContext: () => canvasContext }),
|
||||||
getContext: () => canvasContext,
|
|
||||||
querySelector: () => null,
|
|
||||||
set textContent(text) {
|
|
||||||
this.innerHTML = text;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// What tick last wrote into the latency figure in host's row, or undefined
|
// What tick last wrote into the latency figure in host's row, or undefined
|
||||||
@@ -54,12 +41,6 @@ function latencyFigure(state, host) {
|
|||||||
return elements[`.latency-value[data-host="${index}"]`]?.innerHTML;
|
return elements[`.latency-value[data-host="${index}"]`]?.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What was last written into the status text in host's row.
|
|
||||||
function statusText(state, host) {
|
|
||||||
const index = state.allHosts.indexOf(host);
|
|
||||||
return elements[`.status-text[data-host="${index}"]`]?.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mocks the clock for test t, so that a check lasting seconds takes no real
|
// Mocks the clock for test t, so that a check lasting seconds takes no real
|
||||||
// time, and replaces fetch with targets that each answer after
|
// time, and replaces fetch with targets that each answer after
|
||||||
// answerAfter(url) milliseconds of that clock, or never when that is
|
// answerAfter(url) milliseconds of that clock, or never when that is
|
||||||
@@ -195,32 +176,3 @@ test("at a 30000ms interval, a check in the first round does not show in its row
|
|||||||
assert.equal(await settled(round), "still waiting");
|
assert.equal(await settled(round), "still waiting");
|
||||||
assert.equal(latencyFigure(state, answering), undefined);
|
assert.equal(latencyFigure(state, answering), undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("at a 30000ms interval, after the user pauses and resumes during a round, no row reads paused once its last check ends", async (t) => {
|
|
||||||
CONFIG.updateInterval = 30000;
|
|
||||||
const state = new AppState([
|
|
||||||
{ name: "Answering", url: "https://answering.test" },
|
|
||||||
]);
|
|
||||||
const answering = state.local[0];
|
|
||||||
mockTargets(t, (url) => (url.startsWith(answering.url) ? 1000 : Infinity));
|
|
||||||
state.tickCount = 2;
|
|
||||||
|
|
||||||
const round = tick(state, new AbortController().signal);
|
|
||||||
t.mock.timers.tick(1000);
|
|
||||||
assert.equal(await settled(round), "still waiting");
|
|
||||||
// The user pauses, which greys out every row, and resumes, which leaves
|
|
||||||
// the rows as they are, as togglePause does. The answering target's
|
|
||||||
// check has already ended, so only the redraw of every row at the end
|
|
||||||
// of the round can take "paused" out of its row.
|
|
||||||
state.paused = true;
|
|
||||||
greyOutUI(state);
|
|
||||||
state.paused = false;
|
|
||||||
assert.equal(statusText(state, answering), "paused");
|
|
||||||
|
|
||||||
// The round ends when the last check times out.
|
|
||||||
t.mock.timers.tick(CONFIG.requestTimeout - 1000);
|
|
||||||
assert.notEqual(await settled(round), "still waiting");
|
|
||||||
for (const host of state.allHosts) {
|
|
||||||
assert.notEqual(statusText(state, host), "paused", host.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user