Unit tests

August 5, 2026 ยท View on GitHub

The canonical description of the F# test suite: how to run some of it, what the filter flags do, and what used to go wrong. AGENTS.md has the short version.

Running them

scripts/run-backend-tests

A full run is a few minutes and logs to rundir/logs/fsharp-tests.log. The entry point is backend/tests/Tests/Tests.fs; tests are not discovered automatically, they have to be added there.

Running some of them

Finding what to run used to be the hard part, so start there rather than guessing at a filter:

scripts/run-backend-tests --groups              the test tree, with counts
scripts/run-backend-tests --groups Interpreter  just that part of it
scripts/run-backend-tests --find mergeFavoring  which tests match, and how to run them

Neither touches the database or reloads packages, and both print the command that runs what they found. The underlying listing is slow, so it's cached until the next build.

Then there are three filter flags, and they do three different things:

--filter <path>            a prefix of the slash-separated path, from the root
--filter-test-list <sub>   substring, matches test lists, case-sensitive
--filter-test-case <sub>   substring, matches test cases, case-sensitive

Everything is nested under testList "tests", so paths start with tests/. --filter tests/Interpreter runs 90 tests; --filter Interpreter runs none.

Why this was worse than it looked

Three things compounded, and each hid the next.

--filter's help says it takes "a hierarchy that's slash (/) separated", but Expecto's default separator is a dot. So the filter you wrote after reading the help matched nothing. We now pass JoinWith "/" so the documented form is the working one.

A filter that matches nothing was not an error. Expecto reports it as 0 tests run - Success! and exits 0, so a filter with a typo in it looked exactly like a suite that passed. run-backend-tests now fails when a filter you supplied matched nothing.

--list-tests, the obvious way out, ignores every filter and prints ten thousand lines after a slow startup. That's what --groups and --find are for.

Running two at once

Two runs in the same clone destroy each other: they share test-data.db, the httpclient port, and a killall -9 Tests. run-backend-tests takes a lock and refuses instead.

Two runs in different clones are fine. Each clone has its own container, and so its own PID namespace, bridge network and bind-mounted rundir. This was genuinely forbidden until recently, when every clone's scripts re-execed into whichever container was newest and four clones' runs really did land in one.

Dark tests

backend/testfiles/README.md covers the .dark test files, which are a separate thing from the F# tests here.