Repository rules
- Use Go 1.27 only; normal builds and tests use CGO_ENABLED=0.
- Keep a lightweight hexagonal architecture: core imports only stdlib and ports.
- Owner goroutines own window state; no mutex on window state.
- Use zerowrap logging with a
componentfield on every entry. - Run tests with
-race. - Use signed conventional commits:
type(scope): description.
Mocks (hard rule)
Test doubles are always generated by Mockery v3 (testify template). Never handwrite them.
- No handwritten fakes, stubs or spies. This covers any test type standing in for a project interface, and function fields swapped by tests (
x.ioctl = func...).make checkrunsfakes-check, which rejectsfake/stub/spy/dummy/mocktype names in tests. - Every seam is an interface. When a test needs to replace a dependency, add an interface and generate its mock:
- Cross-layer dependencies go in
internal/ports. Mocks:internal/mocks/ports, packageportsmocks, typeMockX. - Adapter-internal seams (e.g. a kernel ioctl) stay unexported in the adapter. Mocks:
<name>_mock_test.goin the same package.
- Cross-layer dependencies go in
- Add a
.mockery.ymlentry for each new interface, runmake mocksand commit the generated files.make mocks-check(CI) fails if they are stale or missing. - Use mocks through
.EXPECT(). Record calls withRunAndReturnwhen a test inspects arguments. - Channel ports (sealed message interfaces such as
ports.ClientEventorports.OutputEvent) are tested by sending messages, not with mocks. - Not doubles, so allowed:
- real protocol clients in wayland tests (
wlturboproxies); - real in-memory implementations of std interfaces (e.g. a locked
bytes.Bufferasio.Writer); - production constructor parameters such as
NewRenderer func(w, h) (ports.Renderer, error), where tests return a generated mock; - channels passed to
app.runto inject input or observe scenes (channel ports).
- real protocol clients in wayland tests (
End-to-end tests (headless backend)
Unit tests are not enough for user-facing behavior (binds, gestures, window placement, config keys). Test it end to end on the headless backend before declaring it done; see docs/headless.md.
- Build the branch binary (
CGO_ENABLED=0 go build -o <tmp>/neferwl ./cmd/neferwl) and run it with a throwaway config:<tmp>/neferwl --backend=headless --config <tmp>/config --no-xwayland --screenshot <tmp>/shots --input <script> --timeout 10s. - Input scripts drive keys and gestures (
key Super+s,swipe4 down,sleep 1s); see the table in docs/headless.md. - Logs go to
$XDG_STATE_HOME/neferwl/runs/headless/latest.log, not stderr. Read the headlessWAYLAND_DISPLAYfrom itslisteningentry. - Isolate from the user's live session: run the headless compositor, its clients and
neferwl statewithenv -u NEFERWL_STATE -u NEFERWL_SLOT WAYLAND_DISPLAY=<headless>.NEFERWL_STATEwins overWAYLAND_DISPLAYand would read the live session's state. - Check results with
WAYLAND_DISPLAY=<headless> neferwl state(windows, stash index/count, visibility) and thelatest.pngscreenshot.
Checks before merge
make check, make race, make mocks-check and staticcheck ./... must pass.
User-facing changes are also tested end to end on the headless backend.