Contributing
Tests
Both programs are tested to 100% coverage, and no test ever reaches the real machine: processes, HTTP calls and waits are all faked.
Rift
Rift's tests are written with Pest (opens in a new tab) and cover 100% of app/.
- One test file per class, mirroring
app/. Commands, and classes that use a Laravel facade, are tested intests/Feature, the rest intests/Unit. - Group a class's tests in one
describe('method()')per public method, and a command's tests in onedescribe('when …')per scenario. - Each test checks one behaviour. When a scenario has several effects, give each its own test and share the setup in a
beforeEach. - Name the outcome and the condition:
it('returns null for a key with an empty value'),it('exits with failure when Docker is not running'). - Use datasets for the same behaviour across several inputs.
- Nothing reaches the real machine. Fake processes with
Process::fake(), the router's admin API withHttp::fake()and waits withSleep::fake(). - Do not use
@codeCoverageIgnore. A line no test can reach is either dead code or needs a seam.
Command helpers
Command tests build on the helpers in tests/Helpers/Commands.php:
| Helper | What it does |
|---|---|
project() | Create a checkout in a temporary directory and move into it. |
host() | Fake every process as a healthy machine answers. Overrides are matched first. |
artisan() | Run a command, ready for output and exit code expectations. |
answerPrompts() | Let expectsQuestion() and expectsConfirmation() reply to prompts. |
Dashboard helpers
Dashboard tests build on the helpers in tests/Helpers/Dashboard.php:
| Helper | What it does |
|---|---|
dashboardSnapshot() | Build a snapshot of two projects, with a checkout in every state. |
contractSnapshot() | Build the snapshot dash/testdata/snapshot.json is made from. |
collectorHost() | Fake every process a snapshot reads, for a main checkout and a worktree. |
fakeTerminal() | Stand in for a terminal that is, or is not, one a person is at. |
Coverage reports and TIA
make test:coverage writes an HTML report to .coverage/html/index.html, showing which tests cover each line.
Locally, Pest's TIA engine (opens in a new tab) re-runs only the tests your changes affect, and replays the rest from a cache in .pest/tia.
| To | Run |
|---|---|
| Run every test | make test:php ARGS="--no-tia" |
| Rebuild the TIA cache | make test:php ARGS="--tia --fresh" |
make test:coverage and CI always run the whole suite, because replayed tests leave the HTML report empty.
rift-dash
rift-dash is held to the same standard, every internal package at 100% coverage, checked by dash/scripts/coverage.sh because go test has no minimum of its own. cmd/rift-dash is left out, as its main only hands over to internal/app.
- One
_test.gobeside each file it tests, with tests named after the behaviour:TestDownAsksThenStops. - The
uipackage draws screens as plain text.internal/ui/testdata/*.goldenholds them; after a deliberate change, rewrite them withmake test:go PKG=./internal/ui ARGS=-updateand review the diff. - Nothing runs rift or Docker.
internal/rift/testdata/fake-riftstands in for rift, printing the contract fixture, andui.Backendlets tests record what the dashboard asks for. - One test in
internal/appruns the whole program on that fake rift, to prove the parts work together. - CI also runs the tests with Go's race detector, which needs cgo and so is left out of the Alpine development image.
make test:coverage writes its Go HTML report to dash/coverage.html.