Add fast iteration tooling
nix run .#fast, .#test and .#webui-dev; see DEVELOPING.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Developing
|
||||
|
||||
Three tools, from instant to slow. Run them from the repository root; the
|
||||
flake only sees files git knows about, so `git add` new files first.
|
||||
|
||||
## `nix run .#webui-dev`: the web UI on your machine
|
||||
|
||||
Sample settings, sample DHCP leases and your real interface counters, on
|
||||
<https://127.0.0.1:8443> (password `devpassword1`). Templates and CSS are
|
||||
re-read on every request: edit, reload the browser. A template with a typo
|
||||
keeps the last good version and logs the error. Pings need root and show "no
|
||||
answer"; Apply does nothing useful here.
|
||||
|
||||
## `nix run .#fast`: no VMs
|
||||
|
||||
Go formatting, vet and tests, then builds the installer, a router built from
|
||||
settings (through the release) and the home gateway. That runs shellcheck over
|
||||
the scripts and evaluates the modules: most failures of the VM tests
|
||||
(typos, type errors, a script that does not build) show up here, in about a
|
||||
minute (half of that when little changed).
|
||||
|
||||
nix run .#fast -- --drivers also builds every VM test's driver: type-checks
|
||||
the test scripts, builds the VMs' systems
|
||||
nix run .#fast -- --all also the checks that need no VM and `nix flake check`
|
||||
|
||||
## `nix run .#test -- NAME...`: VM tests
|
||||
|
||||
One at a time (each needs gigabytes of memory), a line per test with its time,
|
||||
and for a failure the step that failed and what it said; the full log is in
|
||||
`${TMPDIR:-/tmp}/router-test-logs/NAME.log`.
|
||||
|
||||
nix run .#test -- update setup
|
||||
nix run .#test -- --all
|
||||
nix run .#test -- --list
|
||||
|
||||
`nix run .#test -- -i update` builds the test's interactive driver and gives a
|
||||
Python prompt: `start_all()`, `target.succeed("...")`, `test_script()`. Boot
|
||||
the VMs once and try a step again instead of running the whole test.
|
||||
|
||||
### Where the time goes
|
||||
|
||||
A VM test spends most of its time in the system under test, not in building:
|
||||
`router-install` (about a minute), booting (20 s each), and everything that
|
||||
runs `router-apply` on the router (building a NixOS system there takes about a
|
||||
minute) or waits for a rollback timer. A source change rebuilds the systems
|
||||
again on the host. So:
|
||||
|
||||
- Try a change with `webui-dev` or `fast` first; run a VM test only to confirm.
|
||||
- Run only the test for what you changed: `update` for `router-update` and
|
||||
the Update page, `setup` for `router-setup`, `apply` for the settings and
|
||||
the web UI, `basic` for the firewall and DHCP.
|
||||
- Use `-i` to iterate on a test script.
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDevAssetsAreReloaded(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.CopyFS(dir, os.DirFS(".")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := assets
|
||||
assets = os.DirFS(dir)
|
||||
defer func() { assets = old }()
|
||||
|
||||
sys := System{ConfigDir: filepath.Join(dir, "config"), StateDir: dir}
|
||||
a := newApp(sys, newCollector(sys, ""), true)
|
||||
h := a.routes()
|
||||
if strings.Contains(do(h, "GET", "/login", nil, nil).Body.String(), "reloaded-marker") {
|
||||
t.Fatal("marker already there")
|
||||
}
|
||||
|
||||
login := filepath.Join(dir, "templates", "login.html")
|
||||
data, _ := os.ReadFile(login)
|
||||
i := strings.LastIndex(string(data), "{{end}}")
|
||||
os.WriteFile(login, []byte(string(data)[:i]+"<p>reloaded-marker</p>"+string(data)[i:]), 0o600)
|
||||
if !strings.Contains(do(h, "GET", "/login", nil, nil).Body.String(), "reloaded-marker") {
|
||||
t.Fatal("an edited template is not picked up")
|
||||
}
|
||||
|
||||
// A typo keeps the last good version.
|
||||
os.WriteFile(login, []byte("{{define \"content\"}}{{ broken"), 0o600)
|
||||
if !strings.Contains(do(h, "GET", "/login", nil, nil).Body.String(), "reloaded-marker") {
|
||||
t.Fatal("a broken template replaced the working one")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# The quick checks: no VM boots. Catches what most failed runs of the VM tests
|
||||
# were about (Go and Nix errors, shellcheck, type errors in the test scripts,
|
||||
# assertions). Evaluating NixOS is the cost: about a minute for the default.
|
||||
# router-fast Go checks; the installer, a settings-built router
|
||||
# (via the release) and the home gateway build
|
||||
# router-fast --drivers also build every VM test's driver (type-checks the
|
||||
# test scripts, builds the VMs' systems): minutes
|
||||
# router-fast --all also every flake check without VMs (assertions,
|
||||
# lib) and `nix flake check --no-build`: many minutes
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
cd "$root"
|
||||
drivers=0 all=0
|
||||
case ${1:-} in
|
||||
--drivers) drivers=1 ;;
|
||||
--all) drivers=1 all=1 ;;
|
||||
"") ;;
|
||||
*) echo "usage: router-fast [--drivers | --all]" >&2; exit 2 ;;
|
||||
esac
|
||||
start=$SECONDS
|
||||
last=$SECONDS
|
||||
step() {
|
||||
printf '\n\033[1m== %s\033[0m (previous step: %ds)\n' "$*" $((SECONDS - last))
|
||||
last=$SECONDS
|
||||
}
|
||||
|
||||
step "web UI: gofmt, vet, tests"
|
||||
(
|
||||
cd pkgs/router-webui
|
||||
if [ -n "$(gofmt -l .)" ]; then echo "gofmt wants to change:"; gofmt -l .; exit 1; fi
|
||||
go vet ./...
|
||||
go test ./...
|
||||
)
|
||||
|
||||
# New files must be known to git for a flake to see them.
|
||||
untracked=$(git ls-files --others --exclude-standard)
|
||||
if [ -n "$untracked" ]; then
|
||||
printf '\nwarning: not known to git, so the flake ignores them (git add):\n%s\n' "$untracked"
|
||||
fi
|
||||
|
||||
step "systems build (shellcheck of the scripts, units, firewall rules)"
|
||||
nix build --no-link --max-jobs 1 \
|
||||
.#nixosConfigurations.installer.config.system.build.toplevel \
|
||||
.#nixosConfigurations.home.config.system.build.toplevel \
|
||||
.#release
|
||||
|
||||
if [ "$drivers" = 1 ]; then
|
||||
step "VM test drivers build (type check)"
|
||||
for t in basic pppoe vlans onestick rollback dns installer apply setup update image wireguard; do
|
||||
echo " $t"
|
||||
nix build --no-link --max-jobs 1 ".#checks.x86_64-linux.$t.driver"
|
||||
done
|
||||
fi
|
||||
if [ "$all" = 1 ]; then
|
||||
step "the checks that need no VM"
|
||||
nix build --no-link --max-jobs 1 .#checks.x86_64-linux.{lib,assertions,example,home}
|
||||
step "everything evaluates"
|
||||
nix flake check --no-build
|
||||
fi
|
||||
printf '\n\033[1mok\033[0m (%ds)\n' $((SECONDS - start))
|
||||
@@ -0,0 +1,45 @@
|
||||
# Runs VM tests one at a time (they need a lot of memory), says what failed.
|
||||
# router-test NAME... run these tests
|
||||
# router-test --all run all of them
|
||||
# router-test -i NAME build the test's interactive driver and run it:
|
||||
# a Python prompt where start_all(), machine.succeed(...)
|
||||
# and test_script() work; boot once, retry steps
|
||||
# router-test --list
|
||||
# Logs: ${TMPDIR:-/tmp}/router-test-logs/NAME.log
|
||||
vm_tests="basic pppoe vlans onestick rollback dns installer apply setup update image wireguard"
|
||||
flake=${FLAKE:-.}
|
||||
logs=${TMPDIR:-/tmp}/router-test-logs
|
||||
mkdir -p "$logs"
|
||||
|
||||
case ${1:-} in
|
||||
--list) echo "$vm_tests" | tr ' ' '\n'; exit 0 ;;
|
||||
-i|--interactive)
|
||||
[ $# -eq 2 ] || { echo "usage: router-test -i NAME" >&2; exit 2; }
|
||||
driver=$(nix build --no-link --print-out-paths "$flake#checks.x86_64-linux.$2.driverInteractive")
|
||||
exec "$driver/bin/nixos-test-driver" --interactive ;;
|
||||
--all) set -- $vm_tests ;;
|
||||
""|-*) echo "usage: router-test NAME... | --all | -i NAME | --list" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
failed=0
|
||||
total=$SECONDS
|
||||
for name in "$@"; do
|
||||
log=$logs/$name.log
|
||||
start=$SECONDS
|
||||
printf '%-12s ' "$name"
|
||||
if nix build "$flake#checks.x86_64-linux.$name" --max-jobs 1 -L --no-link > "$log" 2>&1; then
|
||||
printf 'ok %4ds\n' $((SECONDS - start))
|
||||
else
|
||||
failed=1
|
||||
printf 'FAILED %4ds %s\n' $((SECONDS - start)) "$log"
|
||||
# The failing step and what it said, or the build error before any VM ran.
|
||||
if grep -aq '!!! Test "' "$log"; then
|
||||
grep -a -m1 '!!! Test "' "$log" | sed -E 's/^[^>]*> //; s/^/ /' | cut -c1-300
|
||||
grep -a '!!! ' "$log" | sed -n '2,12p' | sed -E 's/^[^>]*> //; s/^/ /' | cut -c1-240
|
||||
else
|
||||
grep -a -E '^error|error:' -A8 "$log" | head -24 | sed 's/^/ /' | cut -c1-240
|
||||
fi
|
||||
fi
|
||||
done
|
||||
printf '\n%d test(s), %ds\n' $# $((SECONDS - total))
|
||||
exit $failed
|
||||
@@ -0,0 +1,28 @@
|
||||
# The web UI on your own machine, with sample settings, leases and your real
|
||||
# interface counters. Templates and CSS are re-read on every request: edit,
|
||||
# reload the browser. (Pings need root and fail here; applying does nothing.)
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
mkdir -p "$dir/config"
|
||||
wan=$(ip -j route show default 2>/dev/null | jq -r '.[0].dev // empty')
|
||||
lan=$(ip -j link show 2>/dev/null | jq -r --arg wan "$wan" '[.[] | select(.link_type == "ether" and .ifname != $wan) | .ifname][0] // "eth1"')
|
||||
cat > "$dir/config/settings.json" <<JSON
|
||||
{"version": 0, "system": {"hostName": "dev"},
|
||||
"router": {"wan": {"interface": "${wan:-eth0}", "ipv4": {"type": "dhcp"}},
|
||||
"lans": {"lan": {"interface": "$lan", "internet": true, "ipv4": {"address": "192.168.10.1"},
|
||||
"dhcp": {"pool": {"start": "192.168.10.100", "end": "192.168.10.200"}}}}}}
|
||||
JSON
|
||||
now=$(date +%s)
|
||||
cat > "$dir/leases" <<LEASES
|
||||
address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id
|
||||
192.168.10.101,52:54:00:12:34:56,,3600,$((now + 3000)),1,0,0,laptop,0,,0
|
||||
192.168.10.120,52:54:00:aa:bb:cc,,3600,$((now + 1200)),1,0,0,phone,0,,0
|
||||
LEASES
|
||||
echo '{"version": "0.0.0-dev", "serial": 1, "schema": 0}' > "$dir/router-release"
|
||||
cd "$root/pkgs/router-webui"
|
||||
echo devpassword1 | go run . passwd "$dir/webui-password"
|
||||
echo
|
||||
echo " https://127.0.0.1:8443 password: devpassword1 (the certificate is self-signed)"
|
||||
echo
|
||||
exec go run . -listen 127.0.0.1:8443 -config "$dir/config" -state "$dir" -leases "$dir/leases" -assets .
|
||||
Reference in New Issue
Block a user