Skip to content

Development

Local checks

Run python3 scripts/check-local.py --root . --sbcl sbcl from the repository root. It scans ntop.asd, run-tests.lisp, and .asd, .lisp, and .py files under src, t, and scripts, limiting each to 500 lines and 100 columns per line. Every NTOP.* package must have exactly COMMON-LISP in its :use list. The check also tests that a timed-out command and its children are terminated, and limits the SBCL package check to 60 seconds.

The macOS executable build command is documented in Getting started. The test and coverage commands for this reconciliation are below.

WP test and coverage reconciliation

Obtain the active implementation specification supplied for the release task; it is not versioned in this repository. Use its ownership tables as the source of truth for mapping production files and test files to work packages. Reconcile each owned src/**/*.lisp file separately: expressions and branches must each reach 90%. A file with no branch sites has no branch percentage to compare. Global totals do not replace the per-file check.

Run the Linux checks with nix flake check --print-build-logs on the Linux builder used by CI. Run the Darwin suite and coverage from the repository root:

python3 scripts/darwin-workflow.py test
python3 scripts/darwin-workflow.py coverage \
  --coverage-dir /tmp/ntop-coverage-darwin

Both coverage commands print one COVERAGE_FILE row per measured src/**/*.lisp file. The pipe-separated fields are path, expression covered, expression total, expression percentage, branch covered, branch total, and branch percentage. A file with no branch sites reports N/A for its branch percentage. The command fails if any file has expression coverage below 90%, or branch coverage below 90% when the file has branch sites. Join the rows from each platform to the specification's file ownership; record unavailable platform-specific values explicitly rather than filling them from the global percentage. The Darwin command also writes an sb-cover HTML report for the whole src/ tree.

Count each registered cl-weave case once for its owning work package. Count each row in a literal it-each table as a case; do not count property-test iterations or assertions as additional cases. Use the runner's results to separate passed, skipped, and failed cases. The specification's per-work-package minimum is about executed cases, so skipped registrations do not satisfy it.

Route harness

Run one subcase from the repository root with the project's pinned ASDF dependencies available. Put btm on PATH and build ./ntop first. Repeat --route to select multiple routes; --subcase uses the full route ID and three-digit subcase index:

sbcl --noinform --non-interactive \
  --eval '(require :asdf)' \
  --eval '(asdf:load-asd "ntop.asd")' \
  --eval '(asdf:load-system "ntop/e2e")' \
  --eval '
    (ntop.e2e:main
      (list "ntop-e2e" "routes" "--mode" "ntop"
            "--btm" "btm" "--ntop" "./ntop"
            "--route" "key-15" "--subcase" "key-15-002"))'

Mutation testing

Run a focused measurement from the repository root. Each mutant changes one source site in a temporary project copy, and the runner loads only the selected test file. The runner requires SBCL and uses nix flake archive to resolve the pinned dependencies. The JSON report records the baseline, mutant outcomes, and score.

python3 scripts/mutation_test.py \
  --source src/shared/domain-rate.lisp \
  --test t/shared/domain-rate-test.lisp \
  --jobs 1 \
  --report /tmp/domain-rate-mutations.json

Run the empty-test calibration with the same source and test arguments plus --empty-test-control. It requires zero selected tests and checks that every generated mutant survives:

python3 scripts/mutation_test.py \
  --source src/shared/domain-rate.lisp \
  --test t/shared/domain-rate-test.lisp \
  --jobs 1 \
  --empty-test-control \
  --report /tmp/domain-rate-empty-control.json

Measured results for the selected source and test pairs:

Source / selected test Generated Test kills Compile rejects Equivalent survivors Adjusted score
src/shared/domain-rate.lisp / t/shared/domain-rate-test.lisp 8 8 0 0 100%
src/shared/domain-units.lisp / t/shared/domain-units-test.lisp 49 43 2 4 100%
src/feature/process/domain-grouping.lisp / t/feature/process/domain-grouping-test.lisp 13 9 3 1 100%

The score counts test failures and production-load compile rejects as kills. The runner reports those outcomes separately and fails a run with runner errors. It excludes only the equivalent survivors from the adjusted score.

Equivalent survivors are domain-grouping M0009, which changes the initial array capacity from 0 to 1 in (make-array 0 :adjustable t :fill-pointer 0) at src/feature/process/domain-grouping.lisp line 60. The capacity is unobservable through its zero fill pointer and list result. domain-units M0041–M0044 change the 0 initforms at lines 30 and 36 of src/shared/domain-units.lisp; the make-quantity and make-scaled-quantity constructors require explicit values, so those initforms are not used by the supported constructors. The empty-test control selected zero tests and left all 8 generated domain-rate mutants alive.

Dependencies

flake.nix pins each nerima-lisp dependency to a release tag. flake.lock records the resolved source revisions. Run python3 scripts/darwin-workflow.py dependencies to print the dependency report. It follows ASDF runtime dependencies and fails if the longest path exceeds four edges between nerima-lisp repositories. The ntop/test depth is reported for information and does not affect this limit. For example, ntop -> cl-tty-kit -> cl-concurrent-kit -> cl-boundary-kit -> cl-host-kit contains four edges. Test systems are reported separately.

Continuous integration

CI runs on GitHub Actions ubuntu-latest with a 40-minute job limit. The workflow builds the N7 check, runs nix flake check --print-build-logs, and checks diff hygiene. The flake checks build the executable, run tests and coverage, report dependency depth, and run scripts/check-local.py with the pinned SBCL binary and a 300-second timeout.