Testing Grove before tagging a version
Two paths: a quick smoke test on high ports (no sudo), and a full real test
with *.test domains in the browser (needs one elevated step).
0. Build
# Frontend (required before building the GUI binary)
cd crates/grove-gui/ui && pnpm install && pnpm build && cd -
# Binaries
cargo build --release # -> target/release/grove and grove-gui
Put the binaries on PATH for convenience (optional):
export PATH="$PWD/target/release:$PATH"
Build the macOS app / .dmg
cargo install tauri-cli --version "^2.0" --locked # once
cd crates/grove-gui/ui && pnpm install && pnpm build && cd -
cargo tauri build --manifest-path crates/grove-gui/Cargo.toml
# → target/release/bundle/dmg/Grove_<version>_<arch>.dmg (+ Grove.app)
Releases build these automatically: pushing a v* tag runs
.github/workflows/release.yml, which publishes the CLI tarballs and the
.dmg / .deb / .AppImage bundles to a GitHub Release.
1. Quick smoke test (no sudo, high ports)
export GROVE_HOME=/tmp/grove-test
mkdir -p "$GROVE_HOME"
cat > "$GROVE_HOME/config.toml" <<'EOF'
[general]
tld = "test"
default_php = "8.5"
http_port = 8080
https_port = 8443
dns_port = 5354
[services]
mail_enabled = true
mail_port = 11025
EOF
# PHP: download a bundled static build, or register an existing php-fpm
grove php install 8.5
# grove php register 8.4 "$HOME/Library/Application Support/Herd/bin/php84-fpm"
grove start
grove park ~/Code # or: grove link inside a project
grove list
# Serve a site (Host header simulates DNS on the high port)
curl -H "Host: <yoursite>.test" http://127.0.0.1:8080/
# Services
grove service install postgres && grove service start postgres
grove service install redis && grove service start redis
grove service list
grove env <yoursite> # .env snippet for the bundled services
# Node
grove node install 22
grove node use <yoursite> 22
# Mail-catcher (send a test mail to 127.0.0.1:11025, then:)
grove mail
# GUI
grove gui # launches the desktop app against this daemon
grove stop
2. Full real test (*.test in the browser)
Uses the default ports 80/443/53, the system resolver and a trusted CA.
unset GROVE_HOME # use the real ~/Library/Application Support/Grove
sudo grove init # CA + resolver + a PHP build (one elevated step)
sudo grove start # binds 80/443/53
grove park ~/Code
grove secure myproject # HTTPS
# Now open https://myproject.test in your browser — no hosts editing needed.
grove doctor
To undo everything afterwards:
sudo grove uninstall # removes service, resolver and CA trust
2b. The suites that don't run by default
cargo test --workspace deliberately skips two groups. Both exist because the
code they cover cannot be exercised honestly from an ordinary test run, so
neither being green in CI means they passed — they have to be run on purpose.
Privileged tests
One thing here can only be observed by a process that has privilege: what a
root install does with the CA private key. sudo grove install creates the CA
as root and then has to hand it to the user the daemon will run as, because
since 1.8.0 that daemon is not root and a key it cannot read is HTTPS that does
not work. The test skips itself unless it happens to be running as root — a
no-op on your machine, real evidence in a container:
docker run --rm -v "$PWD:/w" -w /w \
-v grove-linux-target:/target -e CARGO_TARGET_DIR=/target \
rust:alpine sh -c '
apk add --no-cache musl-dev openssl-dev &&
cargo test -p grove-tls --test ca_ownership_root -- --nocapture'
The named volume is worth the extra flags. Cargo keys artefacts by target
triple, so a container writing into ./target does not destroy your host
build — but it does grow the directory by a second platform's worth of objects,
and it starts from cold every run. In a volume the first run is a ~12-minute
build and every run after it is seconds.
It covers all four directions: a root install hands the key to the recorded run user; a key root already owns moves on the next load; with no run user recorded it stays with root rather than going to a guessed account; and the certificate stays world-readable throughout, since nothing can verify a chain it cannot read.
Two sibling suites used to live here — grove-core --test privdrop_root and
grove-runtime --test probe_root — and they went with the privilege-dropping
machinery they tested. There is no drop left to prove.
Property tests
grove-proxy has proptest tests for the request-path sanitizer and the
dotfile check (path_properties in handler.rs). They run with the normal
suite, 2000 cases each. If one fails, proptest prints a minimal failing
input and writes it to crates/grove-proxy/proptest-regressions/; commit that
file so the case is replayed first on every future run.
Network tests
Checksum verification is only as good as its agreement with what publishers
actually serve today: a parser that handles our idea of SHASUMS256.txt and
not Node's would pass every unit test and fail every install. These are
#[ignore]d because they download real artefacts:
cargo test -p grove-runtime --test download_verification -- --ignored --nocapture
The cheapest of them (published_checksum_documents_still_parse) fetches only
the manifests, and is the one that catches a publisher changing format from
under us. Worth running before a release even if you skip the rest.
2c. Releasing
A release is a v* tag. Pushing it runs .github/workflows/release.yml,
which builds and notarizes the macOS app, builds the Linux bundles, uploads
everything to the GitHub Release for that tag, and rewrites the updater's
latest.json to direct download URLs.
# on main, after bumping Cargo.toml + tauri.conf.json and dating the changelog
git tag -a v1.6.0 -m "Grove 1.6.0"
git push origin v1.6.0
The first job step, Ensure the GitHub Release exists, creates the release
before anything is compiled. It uses the RELEASE_TOKEN secret if one is set
and the Actions token otherwise. If neither is allowed to create a release, the
job fails there, within seconds, with the fix in the error — that is the point
of doing it first. 1.5.0 learned after a 14-minute build that the Actions token
was refused for creating a release while uploads to an existing one worked;
1.4.x had been created by the Actions token without trouble, so this is not a
fixed property of the repository. Do not add RELEASE_TOKEN pre-emptively;
add it if the pre-flight tells you to.
3. What to verify
-
*.testresolves and serves (Laravel / static / proxy drivers) - HTTPS works with the Grove CA (green padlock after
grove init) - Per-site PHP (
grove isolate) and Node (grove node use) take effect -
grove php install/grove node installdownload and run self-contained -
grove service install/start/stop/restartfor postgres/mysql/redis - Mail-catcher captures mail;
grove mail/ GUI Mail panel show it -
grove requests/ GUI Requests panel show proxied requests live -
grove path installputs php/composer/node on PATH;grove db snapshot/restoreround-trips -
grove up --writescaffoldsgrove.toml;grove uplinks + configures the project -
grove license activate/statusworks;grove secret set/pull/share/revokeround-trips against the Teams backend - GUI: Sites, Services, Mail, PHP, Node, Tunnels, Requests, Tools, Logs, Doctor + Settings (⌘,)
-
grove doctoris all green
Tip: run the daemon in the foreground with logs while testing:
GROVE_LOG=info grove daemon