mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-09 04:16:14 +00:00
expvar counters for connections, rooms, games, submissions by rejection reason, eliminations, chat, joins and bot moves, served on a separate debug address so they never sit on the public mux, plus one structured word_rejected log line per refused word carrying the normalized word and its link. GET /readyz flips to 503 while draining; SIGTERM stops new rooms, waits up to NOITU_DRAIN_TIMEOUT for live games, then shuts down. GET /version and the startup log carry the build's git describe. Fuzz targets for the frame decoder, the text sanitizer and Vietnamese normalization; the last one found that composing before lowercasing could leave a non-NFC result, now recomposed after lowering. CI runs on dev as well as main, gates gofmt and golangci-lint, tracks the buf major instead of an exact pin, and dependabot watches every ecosystem. The lint findings that had been hidden by the default per-issue cap are fixed.
186 lines
5.7 KiB
YAML
186 lines
5.7 KiB
YAML
# Tests and packaging.
|
|
#
|
|
# Nothing here downloads the upstream wordlist except the release image
|
|
# build. Everything else plays against the small database derived from
|
|
# the checked-in word sample, which goes through the same builder the real one
|
|
# does.
|
|
#
|
|
# The wire contract has its own workflow: see proto.yml.
|
|
name: ci
|
|
|
|
on:
|
|
push:
|
|
branches: [main, dev]
|
|
pull_request:
|
|
release:
|
|
types: [published]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
go:
|
|
name: Go
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: server/go.mod
|
|
cache-dependency-path: server/go.sum
|
|
|
|
- name: Vet
|
|
run: go vet ./...
|
|
working-directory: server
|
|
|
|
# gofmt -l lists files that are not gofmt-clean; -d would only show the
|
|
# diff. Non-empty output is the failure signal, so it decides the exit
|
|
# code itself rather than leaning on the emptiness of a report nobody
|
|
# reads.
|
|
- name: Format check
|
|
run: |
|
|
unformatted="$(gofmt -l .)"
|
|
if [ -n "$unformatted" ]; then
|
|
echo "not gofmt-clean:"
|
|
echo "$unformatted"
|
|
exit 1
|
|
fi
|
|
working-directory: server
|
|
|
|
# Default linters only: this is a signal every PR has to pass, not a
|
|
# style debate, so nothing beyond golangci-lint's own defaults is
|
|
# enabled here.
|
|
- name: Lint
|
|
uses: golangci/golangci-lint-action@v9
|
|
with:
|
|
working-directory: server
|
|
|
|
# -race because the whole transport layer is goroutines and timers, and a
|
|
# data race there is exactly the kind of defect that passes without it.
|
|
- name: Test
|
|
run: go test ./... -race
|
|
working-directory: server
|
|
|
|
web:
|
|
name: Frontend
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
|
|
- run: npm ci
|
|
working-directory: web
|
|
|
|
- name: Type check
|
|
run: npm run check
|
|
working-directory: web
|
|
|
|
- name: Lint
|
|
run: npm run lint
|
|
working-directory: web
|
|
|
|
# npm test builds first, so this also proves the bundle compiles and
|
|
# carries no wordlist.
|
|
- name: Test
|
|
run: npm test
|
|
working-directory: web
|
|
|
|
e2e:
|
|
name: End to end
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: server/go.mod
|
|
cache-dependency-path: server/go.sum
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
|
|
- name: Build the fixture dictionary
|
|
run: go run ./cmd/build-dictionary --words ../testdata/fixture-words.txt --out ../data/fixture.db --min-words 150
|
|
working-directory: server
|
|
|
|
- run: npm ci
|
|
working-directory: web
|
|
|
|
- name: Install the browser
|
|
run: npx playwright install --with-deps chromium
|
|
working-directory: web
|
|
|
|
- name: Run the suite
|
|
run: npm run test:e2e
|
|
working-directory: web
|
|
|
|
# test-results holds the traces a failure leaves behind, which is what is
|
|
# actually worth downloading; the HTML report is not generated here.
|
|
- uses: actions/upload-artifact@v4
|
|
if: failure()
|
|
with:
|
|
name: playwright-traces
|
|
path: web/test-results/
|
|
retention-days: 7
|
|
|
|
image:
|
|
name: Container image
|
|
runs-on: ubuntu-latest
|
|
# e2e too: the moment this job gains a publish step, an image built past a
|
|
# red browser suite is an image nobody meant to ship.
|
|
needs: [go, web, e2e]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# On a release the image is built from the real upstream release, which
|
|
# is the only job in this file that downloads it. Every other run builds
|
|
# the same Dockerfile against the fixture word list, so a broken image is
|
|
# caught on the pull request rather than at release time.
|
|
- name: Build
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "release" ]; then
|
|
docker build -t noitu:ci .
|
|
else
|
|
docker build --build-arg FIXTURE_DICT=1 -t noitu:ci .
|
|
fi
|
|
|
|
# CC BY-SA 4.0 applies to the derived wordlist wherever it is
|
|
# distributed, and an image is distribution. This is the assertion that
|
|
# the obligation actually shipped.
|
|
- name: The licence travels with the data
|
|
run: |
|
|
set -eu
|
|
docker create --name check noitu:ci
|
|
docker export check | tar -t > files.txt
|
|
docker rm check
|
|
|
|
for required in app/data/LICENSE app/data/ATTRIBUTION.md app/NOTICE app/data/noitu.db; do
|
|
grep -qx "$required" files.txt || { echo "missing from the image: $required"; exit 1; }
|
|
done
|
|
|
|
# The upstream dump, compressed or not, must never reach the final
|
|
# image.
|
|
if grep -Eq '\.(bz2|xml)$' files.txt; then
|
|
echo "the upstream dump leaked into the image"
|
|
exit 1
|
|
fi
|
|
|
|
- name: It serves a game
|
|
run: |
|
|
set -eu
|
|
docker run -d --name noitu -p 8080:8080 noitu:ci
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS http://localhost:8080/healthz >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
curl -fsS http://localhost:8080/healthz
|
|
# A deep link is a client route, so the binary has to answer it with
|
|
# the app shell rather than a 404.
|
|
curl -fsS -o /dev/null -w '%{http_code}\n' 'http://localhost:8080/play?difficulty=2' | grep -qx 200
|
|
docker rm -f noitu
|