Repository navigation
Testing and the Gate
English · Русский
kotlin("test") on the JUnit 5 backend. No Kotest, no MockK, no ktlint — fakes are
hand-written and the lint gate is Android lint inside ./gradlew build. Coverage is Kover
(./gradlew koverHtmlReport).
./gradlew test allTests # `test` alone skips the multiplatform modulesTests live in commonTest unless the behaviour is genuinely platform-specific; UI tests use the
shell harness described in UI layer and need a display (CI runs xvfb-run --auto-servernum).
- RED — write the failing test first and confirm it fails for the intended reason, not on a compile error.
-
GREEN — the minimal implementation: contracts in
commonMain, platform libraries behindexpect/actual, dead code deleted in the same commit. - Gate — the stages the change actually owes.
- Review — the agent fan-out.
A new test is re-run with the fix reverted, to prove it actually catches the regression.
tools/harness/ enforces the loop instead of trusting it:
tools/harness/gate.py status # what this change is, and what it still owes
tools/harness/gate.py run # run exactly the stages owed, in order
tools/harness/gate.py red --tests '*Pattern*' --file path/to/Test.kt
tools/harness/gate.py task bug 133 # override the auto-detected kind
tools/harness/gate.py reviewers # which reviewers this change needsWhat a change owes depends on what it is:
| Kind | Detected by | Owes |
|---|---|---|
docs |
no code in the diff | nothing |
refactor |
refactor/ chore/ perf/ branch |
checks · tests · build · detekt · reviewers |
feature |
feat/ branch, or an unnamed branch with code |
the same, plus a test touched |
bug |
fix/ bug/ hotfix/ branch, or declared |
the same, plus a test recorded failing before the fix |
Areas add stages: UI or Android in the diff pulls in :androidApp:compileDebugKotlin, server
work pulls in a Java reviewer, terminal work a performance reviewer. A declaration can make the
gate stricter, never looser.
The runner executes the stage, reads the exit code, and pins the result to a digest of the tree it ran against. A Gradle run made by hand is not recorded — not because hand runs are forbidden, but because nothing outside the runner can prove which code an exit code belonged to. Editing anything afterwards reopens the gate, because the digest moved; committing does not, and reverting a change restores the green state it had.
Stages: checks (deterministic project rules), tests, build (lint on), detekt, and
android when the diff touches UI. Logs land in .git/skerry-gate/<stage>.log.
detekt fails on new findings only; existing ones sit in gradle/detekt-baseline-*.xml, and
re-baselining to silence your own finding is not allowed.
The deliberate bypass is SKERRY_GATE_OVERRIDE=1, which means saying out loud why. It does not
unprotect main.
The harness has its own tests: python3 tools/harness/selftest.py (~70 cases, two seconds, no
Gradle). Changing a rule means changing them too.
tools/harness/checks.py blocks a commit on the rules that need no judgement: translations
complete in en + ru + zh, no hardcoded UI literal, Txt/Sym instead of raw Text/Icon,
design tokens instead of hex, no Kotest or MockK, dependencies through the version catalog,
atomicWriteUtf8 on secret paths, a key binding shipping with its Settings row.
Once the branch is green, reviewers run in parallel against git diff main...HEAD plus the
working tree:
| Reviewer | Looks for |
|---|---|
skerry-reviewer |
this project's own rules — parity, primitives, vault, the abstraction catalogue |
skerry-kotlin-reviewer |
structured concurrency, Compose recomposition, Kotlin idioms for this stack |
skerry-security-reviewer |
the vault and its crypto, untrusted protocol input, the sync/team boundary |
silent-failure-hunter |
swallowed exceptions, bad fallbacks, errors that never propagate |
pr-test-analyzer |
whether the tests cover the behaviour, not just the lines |
The first three live in .claude/agents/, so the fan-out ships with the clone. Rules: reviewers
are read-only, never switch branches, every finding is either fixed or explicitly rejected with a
reason, a behaviour-changing fix goes back through step 1, and two rounds per branch — the
third needs the maintainer to ask for it. Findings are kept in .git/skerry-gate/reviews/.
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
pushes and PRs | JDK 21, Android SDK, test allTests under xvfb-run, detekt, uploads test reports |
release.yml |
v* tag (or manual) |
builds .deb/.rpm/.AppImage (x64 + arm64), .msi, .dmg (arm64 + x64), a signed .apk and SHA256SUMS.txt, publishes a draft release |
server-image.yml |
server-v* tag |
builds the multi-arch server image, smoke-tests that the container becomes healthy, pushes to Docker Hub |
codeql.yml |
schedule and PRs | code scanning |
CI is not a substitute for the local gate: it runs the same tests, but the gate is what ties a green result to the exact tree being committed.
User guide
- Installation
- Getting started
- Hosts and connections
- Vault and credentials
- Terminal
- Files (SFTP)
- Port forwarding
- Remote desktops
- Snippets and runbooks
- Monitoring and guard
- AI assistant
- Sync and devices
- Teams and sharing
- Settings and shortcuts
- Android
- Troubleshooting
Development
- Architecture
- Building and running
- Connections and transports
- Terminal engine
- Vault and crypto
- Sync protocol
- Remote desktop stacks
- UI layer
- Testing and the gate
- Contributing and releases
Русский — оглавление