make test no longer runs the frontend's production build (refs #118)
check / check (push) Waiting to run

yarn build moves out of the stage that runs the frontend's unit tests,
now named frontend-test, into a frontend-build stage of its own. It
starts from frontend-test for its yarn packages and source, installs
the git that vite.config.js reads the commit with, and copies from the
lint and test phases as builder does, so it cannot run unless they
passed. The runtime stage copies dist/ from it. The Go compile, most of
make test's time, is unchanged and waits on the ruling asked for in
sneak/prompts#113; no build cache is added.

Model: opus-5-5
This commit was merged in pull request #137.
This commit is contained in:
2026-10-14 14:30:03 +02:00
parent 12f3dd5b07
commit 580d12a934
4 changed files with 52 additions and 28 deletions
+29 -21
View File
@@ -5,11 +5,11 @@
# #
# The lint and test phases are the gates: `make lint` (script/lint) # The lint and test phases are the gates: `make lint` (script/lint)
# builds the lint stage alone and `make test` (script/test) the test # builds the lint stage alone and `make test` (script/test) the test
# stage alone, and the builder stage depends on both, so the image # stage alone, and the two build stages, frontend-build and builder,
# cannot be built unless they pass. Each covers the frontend as well, # depend on both, so the image cannot be built unless they pass. Each
# through a copy from a node stage. Inside them each tool is invoked # phase covers the frontend as well, through a copy from a node stage.
# directly, never through make or script/, whose lint and test are # Inside them each tool is invoked directly, never through make or
# themselves docker builds. # script/, whose lint and test are themselves docker builds.
# Frontend lint stage: eslint with the rules in eslint.config.js. The # Frontend lint stage: eslint with the rules in eslint.config.js. The
# lint phase below runs it. # lint phase below runs it.
@@ -35,30 +35,26 @@ RUN golangci-lint run --config .golangci.yml ./...
# phase run it. # phase run it.
COPY --from=frontend-lint /app/yarn.lock /dev/null COPY --from=frontend-lint /app/yarn.lock /dev/null
# Frontend stage: the unit tests in test/unit/, then the production # Frontend test stage: the unit tests in test/unit/. The test phase
# build into dist/, which the runtime stage serves. The test phase below # below runs it, and the frontend-build stage starts from it. The tests
# runs it. The tests print a dot each; if any fails, they run again with # print a dot each; if any fails, they run again with every test listed,
# every test listed, and the step fails even if that run passes. # and the step fails even if that run passes. NODE_OPTIONS chooses the
# NODE_OPTIONS chooses the reporter because yarn adds its arguments # reporter because yarn adds its arguments after the test files, where
# after the test files, where node would take a reporter option for one # node would take a reporter option for one more file.
# more file.
# node:22-alpine as of 2026-02-22 # node:22-alpine as of 2026-02-22
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend-test
WORKDIR /app WORKDIR /app
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile RUN yarn install --frozen-lockfile
# vite.config.js reads the commit for the page's footer with git.
RUN apk add --no-cache git
COPY . . COPY . .
RUN NODE_OPTIONS=--test-reporter=dot timeout 90 yarn --silent run test || \ RUN NODE_OPTIONS=--test-reporter=dot timeout 90 yarn --silent run test || \
{ echo "--- Rerunning with every test listed for details ---"; \ { echo "--- Rerunning with every test listed for details ---"; \
NODE_OPTIONS=--test-reporter=spec timeout 90 yarn --silent run test; \ NODE_OPTIONS=--test-reporter=spec timeout 90 yarn --silent run test; \
exit 1; } exit 1; }
RUN yarn build
# Test phase: the backend's tests with the race detector and coverage, # Test phase: the backend's tests with the race detector and coverage,
# and the frontend's through the copy from the frontend stage at the # and the frontend's through the copy from the frontend-test stage at
# end. -race needs cgo and so a C compiler, which the Debian Go image # the end. -race needs cgo and so a C compiler, which the Debian Go image
# ships and the alpine one does not. -timeout 90s is a backstop above # ships and the alpine one does not. -timeout 90s is a backstop above
# the 60-second cap on the suite. The rerun with -v only shows details: # the 60-second cap on the suite. The rerun with -v only shows details:
# the step fails however it ends, because the first run already failed. # the step fails however it ends, because the first run already failed.
@@ -75,9 +71,21 @@ RUN --mount=type=tmpfs,target=/go/pkg/mod \
go test -timeout 90s -race -cover ./... || \ go test -timeout 90s -race -cover ./... || \
{ echo "--- Rerunning with -v for details ---"; \ { echo "--- Rerunning with -v for details ---"; \
go test -timeout 90s -race -v ./...; exit 1; } go test -timeout 90s -race -v ./...; exit 1; }
# Nothing is wanted from the frontend stage; the copy is what makes # Nothing is wanted from the frontend-test stage; the copy is what makes
# this phase run its tests. # this phase run its tests.
COPY --from=frontend /app/yarn.lock /dev/null COPY --from=frontend-test /app/yarn.lock /dev/null
# Frontend build stage: the production build into dist/, which the
# runtime stage serves, on the yarn packages and source of the
# frontend-test stage. Nothing is wanted from the two phases; the copies
# are what make BuildKit build them first, so this stage cannot run
# unless lint and test passed.
FROM frontend-test AS frontend-build
COPY --from=lint /src/go.sum /dev/null
COPY --from=test /src/go.sum /dev/null
# vite.config.js reads the commit for the page's footer with git.
RUN apk add --no-cache git
RUN yarn build
# Backend build stage. Nothing is wanted from the two phases; the copies # Backend build stage. Nothing is wanted from the two phases; the copies
# are what make BuildKit build them first, so this stage cannot run # are what make BuildKit build them first, so this stage cannot run
@@ -131,7 +139,7 @@ RUN addgroup -g 1000 -S netwatch && \
RUN rm /etc/nginx/conf.d/default.conf RUN rm /etc/nginx/conf.d/default.conf
COPY configs/nginx.conf /etc/nginx/templates/netwatch.conf.template COPY configs/nginx.conf /etc/nginx/templates/netwatch.conf.template
COPY configs/security-headers.conf /etc/nginx/security-headers.conf COPY configs/security-headers.conf /etc/nginx/security-headers.conf
COPY --from=frontend /app/dist /usr/share/nginx/html COPY --from=frontend-build /app/dist /usr/share/nginx/html
COPY --from=builder /src/netwatch-server /usr/local/bin/netwatch-server COPY --from=builder /src/netwatch-server /usr/local/bin/netwatch-server
COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh
+5 -4
View File
@@ -59,8 +59,9 @@ either one is broken. We provide:
- `script/clean` — remove what `script/build` writes - `script/clean` — remove what `script/build` writes
- `script/projectname` — print the project name (used for the Docker image tag) - `script/projectname` — print the project name (used for the Docker image tag)
- `script/test` — build the `test` phase of `Dockerfile` without the cache: the - `script/test` — build the `test` phase of `Dockerfile` without the cache: the
frontend's unit tests and production build in its `frontend` stage, and the frontend's unit tests in its `frontend-test` stage, and the backend's Go tests
backend's Go tests with the race detector and coverage with the race detector and coverage. The frontend's production build is left
to the image build, in the `frontend-build` stage
- `script/lint` — build the `lint` phase of `Dockerfile` without the cache: - `script/lint` — build the `lint` phase of `Dockerfile` without the cache:
eslint in its `frontend-lint` stage, and golangci-lint over the Go packages eslint in its `frontend-lint` stage, and golangci-lint over the Go packages
- `script/fmt` — format all files (writes): prettier over the JavaScript, CSS, - `script/fmt` — format all files (writes): prettier over the JavaScript, CSS,
@@ -78,8 +79,8 @@ either one is broken. We provide:
- `script/frontend-test` — run the unit tests in `test/unit/` on the host with - `script/frontend-test` — run the unit tests in `test/unit/` on the host with
Node's built-in test runner, through the `test` script in `package.json`, and Node's built-in test runner, through the `test` script in `package.json`, and
if any fails, run them again listing every test, and fail; then the production if any fails, run them again listing every test, and fail; then the production
build. Each test run has a 90-second timeout. `make test` runs the same in build. Each test run has a 90-second timeout. `make test` runs the same tests
Docker in Docker, without the production build
- `script/frontend-fmt` — format the JavaScript, CSS, HTML and Markdown with - `script/frontend-fmt` — format the JavaScript, CSS, HTML and Markdown with
prettier (writes) prettier (writes)
- `script/frontend-fmt-check` — check prettier formatting (read-only) - `script/frontend-fmt-check` — check prettier formatting (read-only)
+14
View File
@@ -21,6 +21,14 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
# Completed Steps # Completed Steps
- 2026-10-14: `make test` no longer runs the frontend's production build, which
the tests do not need ([#118](https://git.eeqj.de/sneak/netwatch/issues/118)).
`yarn build` moved out of the stage that runs the frontend's unit tests, now
named `frontend-test`, into a new `frontend-build` stage, which starts from it
and, like the `builder` stage, waits on the lint and test phases, so the image
still cannot be built unless they pass. The runtime stage copies `dist/` from
it, and the `git` that `vite.config.js` reads the commit with is installed
there. No build cache was added
- 2026-10-14: opening the debug log no longer raises the measured latencies - 2026-10-14: opening the debug log no longer raises the measured latencies
([#131](https://git.eeqj.de/sneak/netwatch/issues/131)). With the panel open, ([#131](https://git.eeqj.de/sneak/netwatch/issues/131)). With the panel open,
each line written drew the panel again with every line before it, up to 1000, each line written drew the panel again with every line before it, up to 1000,
@@ -503,3 +511,9 @@ Run `make frontend-viewport-test` in CI as its own step; it is not part of
- A configurable host list (an environment variable or a config file) - A configurable host list (an environment variable or a config file)
- Export of the latency history (CSV or JSON) - Export of the latency history (CSV or JSON)
- A notification when the health status changes to DEGRADED - A notification when the health status changes to DEGRADED
- `make test` within the 20-second target
([#118](https://git.eeqj.de/sneak/netwatch/issues/118)): every run compiles
the Go standard library and every dependency with the race detector from
nothing. Whether test builds may keep Go's compiled packages between runs
waits on sneak's ruling in
[prompts#113](https://git.eeqj.de/sneak/prompts/issues/113)
+4 -3
View File
@@ -2,9 +2,10 @@
# script/frontend-test: run the frontend test suite on the host: the # script/frontend-test: run the frontend test suite on the host: the
# unit tests in test/unit/, through the test script in package.json, # unit tests in test/unit/, through the test script in package.json,
# then the production build, which fails on broken code. make test runs # then the production build, which fails on broken code. make test runs
# the same in the frontend stage of Dockerfile. The tests print a dot # the same tests in the frontend-test stage of Dockerfile, and the image
# each; if any fails, they run again with every test listed, and the # build runs the production build in its frontend-build stage. The tests
# script fails even if that run passes. NODE_OPTIONS chooses the # print a dot each; if any fails, they run again with every test listed,
# and the script fails even if that run passes. NODE_OPTIONS chooses the
# reporter because yarn adds its arguments after the test files, where # reporter because yarn adds its arguments after the test files, where
# node would take a reporter option for one more file. # node would take a reporter option for one more file.
set -eu set -eu