Keep make test under 2 GB of memory (closes #344) #404

Merged
clawbot merged 1 commits from issue-344-test-memory into next 2026-10-02 03:02:29 +02:00
Collaborator

Every test that starts a database seeds the admin account, which hashes its password with Argon2id at 64 MB. Under -race each of those hashes holds about 150 MB, and internal/handlers and internal/database run dozens of such tests in parallel. That is where the memory went; the product code does not leak.

  • HashPassword hashes at a 1 MB memory cost only when testing.Testing() reports a binary built by go test. Every test package gets the lower cost with nothing to add, and a binary built by go build always hashes at the shipped parameters. VerifyPassword reads the cost from the hash, so verification follows. DefaultPasswordConfig is unchanged.
  • TestHashPassword_ShippedParameters switches its test binary back to the shipped cost while it runs, then hashes and verifies once through HashPassword, so production hashing stays covered. It does not run in parallel, so no other test hashes at 64 MB beside it.
  • script/test adds -p 4 -parallel 8. Even with cheap hashing, every -race test binary and every link costs a few hundred MB, and the defaults (one per core) let them all run at once.

Before and after figures are on #344.

  • Judgement call: internal/database now imports testing in product code, for testing.Testing() alone.
  • Rule suppressed: gochecknoglobals on the switch that test sets, and paralleltest on the test itself.
  • Judgement call: -p 4 also limits compilation, so a cold build cache compiles more slowly.
  • This also removes the per-test hashing cost that #225 measured as most of internal/handlers' run time.

Model: opus-5-5

Every test that starts a database seeds the admin account, which hashes its password with Argon2id at 64 MB. Under `-race` each of those hashes holds about 150 MB, and `internal/handlers` and `internal/database` run dozens of such tests in parallel. That is where the memory went; the product code does not leak. - `HashPassword` hashes at a 1 MB memory cost only when `testing.Testing()` reports a binary built by `go test`. Every test package gets the lower cost with nothing to add, and a binary built by `go build` always hashes at the shipped parameters. `VerifyPassword` reads the cost from the hash, so verification follows. `DefaultPasswordConfig` is unchanged. - `TestHashPassword_ShippedParameters` switches its test binary back to the shipped cost while it runs, then hashes and verifies once through `HashPassword`, so production hashing stays covered. It does not run in parallel, so no other test hashes at 64 MB beside it. - `script/test` adds `-p 4 -parallel 8`. Even with cheap hashing, every `-race` test binary and every link costs a few hundred MB, and the defaults (one per core) let them all run at once. Before and after figures are on https://git.eeqj.de/sneak/webhooker/issues/344. - Judgement call: `internal/database` now imports `testing` in product code, for `testing.Testing()` alone. - Rule suppressed: `gochecknoglobals` on the switch that test sets, and `paralleltest` on the test itself. - Judgement call: `-p 4` also limits compilation, so a cold build cache compiles more slowly. - This also removes the per-test hashing cost that https://git.eeqj.de/sneak/webhooker/issues/225 measured as most of `internal/handlers`' run time. Model: opus-5-5
clawbot added the needs-review label 2026-10-01 22:25:08 +02:00
clawbot self-assigned this 2026-10-01 22:25:08 +02:00
Author
Collaborator

Review of #404 for #344: needs-rework.

  1. Production hashing can be lowered, and no test would notice. internal/database/testing.go, LowerPasswordHashCostForTest, with hashConfig in internal/database/password.go. This exported function sits in product code and cuts the admin password hash's memory cost from 64 MB to 1 MB for the whole process. Nothing stops non-test code from calling it. If something did, the suite would stay green: every test binary that hashes lowers the cost first, and TestHashPassword_ShippedParameters goes around HashPassword instead of through it. Acceptable: the lower cost cannot take effect in a binary built by go build. For example, HashPassword picks it only when testing.Testing() reports a test binary, so nothing that ships can weaken the hashing.

  2. Each test package has to opt in to the lower cost. The five copied main_test.go files (internal/database, internal/handlers, internal/server, internal/gormlog, internal/resetpw). A new test package that starts a database but lacks this TestMain hashes at the full 64 MB on every start. That brings back the memory growth this issue fixes, nothing reports it, and nothing in the tree tells a newcomer about the step. Acceptable: every test binary gets the lower cost with no step per package, for example through the same testing.Testing() choice from point 1, and the five copied TestMain files are removed.

  3. The branch no longer rebases onto next. script/test conflicts with next, which now runs "$ROOT/script/assets" before go test. Acceptable: rebased onto current next, with that line kept ahead of the new go test flags.

  • Judgement call: -p 4 -parallel 8 is not a finding. With a cold build cache make test takes about as long as before, an overage already tracked on #198.
  • Not measured: coverage. I judged it from the code instead: no test is removed or skipped, and every changed line runs. The repo has no coverage target.

Model: opus-5-5

Review of https://git.eeqj.de/sneak/webhooker/pulls/404 for https://git.eeqj.de/sneak/webhooker/issues/344: **needs-rework**. 1. **Production hashing can be lowered, and no test would notice.** `internal/database/testing.go`, `LowerPasswordHashCostForTest`, with `hashConfig` in `internal/database/password.go`. This exported function sits in product code and cuts the admin password hash's memory cost from 64 MB to 1 MB for the whole process. Nothing stops non-test code from calling it. If something did, the suite would stay green: every test binary that hashes lowers the cost first, and `TestHashPassword_ShippedParameters` goes around `HashPassword` instead of through it. Acceptable: the lower cost cannot take effect in a binary built by `go build`. For example, `HashPassword` picks it only when `testing.Testing()` reports a test binary, so nothing that ships can weaken the hashing. 2. **Each test package has to opt in to the lower cost.** The five copied `main_test.go` files (`internal/database`, `internal/handlers`, `internal/server`, `internal/gormlog`, `internal/resetpw`). A new test package that starts a database but lacks this `TestMain` hashes at the full 64 MB on every start. That brings back the memory growth this issue fixes, nothing reports it, and nothing in the tree tells a newcomer about the step. Acceptable: every test binary gets the lower cost with no step per package, for example through the same `testing.Testing()` choice from point 1, and the five copied `TestMain` files are removed. 3. **The branch no longer rebases onto `next`.** `script/test` conflicts with `next`, which now runs `"$ROOT/script/assets"` before `go test`. Acceptable: rebased onto current `next`, with that line kept ahead of the new `go test` flags. - Judgement call: `-p 4 -parallel 8` is not a finding. With a cold build cache `make test` takes about as long as before, an overage already tracked on https://git.eeqj.de/sneak/webhooker/issues/198. - Not measured: coverage. I judged it from the code instead: no test is removed or skipped, and every changed line runs. The repo has no coverage target. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-02 00:36:26 +02:00
clawbot force-pushed issue-344-test-memory from 0a112fad71 to e7ee1df8df 2026-10-02 00:38:56 +02:00 Compare
clawbot added 1 commit 2026-10-02 01:23:07 +02:00
Keep make test under 2 GB of memory (closes #344)
check / check (push) Successful in 3m31s
508f246b5b
Every test that starts a database hashed the bootstrap admin password
with Argon2id at 64 MB, which under -race holds about 150 MB per hash,
across dozens of parallel tests. HashPassword now hashes at 1 MB when
testing.Testing() reports a test binary, so every test package gets
the lower cost and a binary built by go build never does. One test
still hashes and verifies through HashPassword at the shipped
parameters, which are unchanged.

script/test also runs at most four packages and eight parallel tests
at once: unbounded, a many-core host linked and ran every test binary
together.

Model: opus-5-5
clawbot force-pushed issue-344-test-memory from e7ee1df8df to 508f246b5b 2026-10-02 01:23:07 +02:00 Compare
Author
Collaborator

Rework for the review above:

  1. HashPassword lowers the memory cost only when testing.Testing() reports a test binary, so a binary built by go build always hashes at 64 MB. The exported lowering function is gone, and TestHashPassword_ShippedParameters now hashes and verifies through HashPassword at the shipped parameters.
  2. The five copied TestMain files are removed; every test binary gets the lower cost from that same check.
  3. Rebased onto current next; script/test runs "$ROOT/script/assets" first, then go test with the new flags.
  • Rule suppressed: paralleltest on TestHashPassword_ShippedParameters, which switches the whole test binary back to 64 MB while it runs, so no other test may hash beside it; gochecknoglobals on that switch.
  • Not re-measured: the after figures on #344 are from the previous version, which lowered the cost in five test packages; this one lowers it in all of them.

Model: opus-5-5

Rework for the review above: 1. `HashPassword` lowers the memory cost only when `testing.Testing()` reports a test binary, so a binary built by `go build` always hashes at 64 MB. The exported lowering function is gone, and `TestHashPassword_ShippedParameters` now hashes and verifies through `HashPassword` at the shipped parameters. 2. The five copied `TestMain` files are removed; every test binary gets the lower cost from that same check. 3. Rebased onto current `next`; `script/test` runs `"$ROOT/script/assets"` first, then `go test` with the new flags. - Rule suppressed: `paralleltest` on `TestHashPassword_ShippedParameters`, which switches the whole test binary back to 64 MB while it runs, so no other test may hash beside it; `gochecknoglobals` on that switch. - Not re-measured: the after figures on https://git.eeqj.de/sneak/webhooker/issues/344 are from the previous version, which lowered the cost in five test packages; this one lowers it in all of them. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-02 01:23:24 +02:00
Author
Collaborator

Review passed: #404 meets the definition of done on #344 and fixes all three earlier findings.

Model: opus-5-5

Review passed: https://git.eeqj.de/sneak/webhooker/pulls/404 meets the definition of done on https://git.eeqj.de/sneak/webhooker/issues/344 and fixes all three earlier findings. Model: opus-5-5
clawbot merged commit bfdbc937c6 into next 2026-10-02 03:02:29 +02:00
clawbot deleted branch issue-344-test-memory 2026-10-02 03:02:29 +02:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#404