Development¶
Build, test and formatting commands for working on cl-cli itself.
Contribution process, review expectations and the code of conduct are org-wide and live in nerima-lisp/.github.
Environment¶
The dev shell provides SBCL, ECL, rlwrap, and every tool the
shell-verification suite pipes generated output through: bash, zsh,
fish, nushell, pwsh, elvish, and mandoc. It also exports the
CL_*_SOURCE_DIR variables that point the test loader at the sibling
packages, so no checkout layout is assumed.
Without Nix, install SBCL and load the system through ASDF. cl-cli itself
depends only on uiop, which ships with every modern ASDF.
Commands¶
$ nix flake check # the full gate: SBCL, ECL, formatting, docs
$ nix run .#test # SBCL suite only, fastest feedback
$ nix fmt # format Nix sources (treefmt/nixfmt)
$ nix build .#docs # render the documentation site with --strict
Granularity lives in the flake's checks.* attributes rather than in extra CI
jobs, so nix flake check runs exactly what CI runs:
| Check | What it covers |
|---|---|
default |
SBCL, core suite plus shell verification |
ecl |
ECL, core suite only |
formatting |
every tracked .nix file is nixfmt-clean |
docs |
mkdocs build --strict — broken links and unlisted pages fail |
nix flake check only evaluates the outputs for the system it runs on, so a
local run verifies your machine and nothing else. CI covers x86_64-linux and
aarch64-darwin on separate runners for that reason.
To run a suite without Nix:
$ sbcl --non-interactive --load run-tests.lisp --eval '(cl-cli/test:run-tests)' --quit
$ ecl --norc --load run-tests.lisp --eval '(cl-cli/test:run-tests)'
The suite is split in two¶
run-tests.lisp prints which half it loaded on every run, because a suite that
quietly shrinks when a dependency goes missing reads exactly like a suite that
passed.
cl-cli/testis the portable core and runs everywhere. Its dependencies are all portable Common Lisp.cl-cli/test/shell-verificationadditionally pipes every generated completion script and man page through the realbash,zsh,fish,nushell,pwsh,elvish, andmandocthat will consume them. It depends oncl-process-kitfor subprocess timeouts, whose owncl-log-kitdependency hard-codessb-thread:*(nerima-lisp/cl-log-kit#1, open upstream), so this half runs under SBCL only.
The split lives at the system boundary precisely so that a non-SBCL implementation runs the core suite for real instead of failing to compile the whole thing. Both implementations are release-blocking.
Two suites are gated rather than run everywhere: the fuzz suite needs a harness
timeout capability cl-weave does not offer on every implementation, and the
benchmark budgets are absolute millisecond thresholds calibrated against SBCL.
Both report as skipped, with a reason, rather than as failures. See
Compatibility for the supported implementation matrix.
Writing tests¶
Test files are named after the source file they exercise:
t/<source>-test.lisp for src/<source>.lisp, or
t/<source>-<aspect>-test.lisp when one source has several aspects worth
separating (t/parser-value-storage-count-test.lisp and
t/parser-value-storage-key-value-test.lisp both cover
src/parser-value-storage.lisp). Shared fixtures and assertion helpers are
not tests and are named t/helpers-<concern>.lisp.
Add or update a focused test in the file matching the source you changed, and
register any new file in cl-cli.asd. run-tests.lisp is the loader, not a
place to put tests. A test that shells out to a real tool belongs in
t/completion-commands-shell-verification-test.lisp under the
cl-cli/test/shell-verification system; everything else goes in the portable
cl-cli/test.
Cover both the success path and the expected failure mode when relevant.
Prefer an assertion that would fail if the behavior were wrong over one that
merely records what the code prints today. A completion renderer emitting
e:'app' shipped broken because a test asserted that exact string; the
question to ask is "what property does this encode", not "does this match".
If a test cannot run on a given implementation, gate it on the capability that is actually missing and let it report as a skip with a reason. Do not widen a threshold or delete an assertion so that a red run turns green.
Changing the public surface¶
Keep public exports intentional: add one only when it is reusable outside a single local consumer. Preserve constructor fail-fast behavior for invalid specs, and reuse the shared normalization helpers instead of re-implementing string or list validation in several places.
When the user-visible surface changes, update
API Reference and the matching guide page in the same pull
request, and record the change under ## [Unreleased] in CHANGELOG.md.
Releasing¶
The org-wide procedure is in RELEASE_STANDARD.md.
Two things are specific to this repository:
:versionappears three times incl-cli.asd— incl-cli,cl-cli/testandcl-cli/test/shell-verification— and all three must match.flake.nixreads the first:versionline, andrelease.ymlrefuses to publish a tag that disagrees with it.- A release verified only by the portable core has not exercised the generated completion scripts. Confirm the runner's own line about which half it loaded.