Commit Graph

3 Commits

Author SHA1 Message Date
f7511678b8 Cover -DAKBASIC_WITH_AKGL=ON in CI
A fifth push job, akgl_build: the text sink and the graphics, audio and input
backends, plus the akgl_backends suite that drives them against a real software
renderer under the dummy video and audio drivers and reads the pixels back.

It stays a separate job rather than a flag on cmake_build because
AKBASIC_WITH_AKGL is off by default and that default is the claim being made:
the interpreter and its whole 70-case suite build and pass on a machine with no
SDL. Keeping the two jobs apart proves that on every push instead of asserting
it.

Deferring this assumed it needed submodules: recursive and a long build. Both
guesses were wrong, and measurement is the only reason we know:

- Recursive is not needed and costs 461 MB. It descends into SDL_image/external,
  SDL_mixer/external and SDL_ttf/external -- aom, dav1d, libjxl, libtiff,
  mpg123, opus, flac and a dozen more -- and not one of them is used. Every one
  configures as "Could NOT find". Six submodules initialised non-recursively
  take about 25 seconds instead.
- The build is roughly a minute of CPU across 330 object files, because SDL3
  compiles out almost every backend that is not wanted here.

The one real system dependency is libfreetype-dev and libharfbuzz-dev. SDL_ttf
reports "Using system freetype library" and links libfreetype.so.6; without them
it would reach for deps/SDL_ttf/external/freetype, which the checkout
deliberately does not clone. Two apt packages against two more submodules.

Every step was run verbatim from a clean clone before being written down --
checkout, submodule init, configure, build and test -- rather than inferred from
the working tree. 71/71.

Also corrects three counts elsewhere in the file that had gone stale: the suite
is 70 cases rather than 61, line coverage is 93.5% rather than 92.3%, and branch
coverage reads 18% rather than 17%. Those numbers are assertions about the gate,
so a wrong one is worse than none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:06:24 -04:00
506af093a1 Add mutation testing, and split the release gates into their own workflow
Ports libakstdlib's mutation harness (the newest of the three) to scripts/
mutation_test.py, adds the namespaced `mutation` CMake target the sibling
libraries have, and splits CI in two.

.gitea/workflows/ci.yaml keeps the push path: suite, ASan+UBSan, coverage, and a
mutation run bounded to src/convert.c and src/symtab.c -- about four minutes,
scoring 77.8% against a gate of 65.

.gitea/workflows/release.yaml is new and manual (workflow_dispatch). It carries
the doxygen gate, moved out of ci.yaml, and a whole-tree mutation run: 3675
mutants and hours of runner time, which is a release cost rather than a
per-commit one. Both artifacts a release wants -- api-documentation and
mutation-report -- come out of it. Two optional inputs narrow the run or change
the threshold; they arrive through the environment rather than being
interpolated into the shell, because ${{ }} substitution happens before the
shell sees the line.

The harness paid for itself immediately, which is the point of it. Three real
gaps, each checked to be a genuine bug rather than an equivalent mutant:

  - errno was never asserted clear before a strtoll. Confirmed with a standalone
    probe that strtoll leaves a stale errno untouched on success, so without the
    `errno = 0` a valid conversion raises ERANGE.
  - Nothing exercised a maximum-length symbol-table key, so every MAX_KEY - 1
    off-by-one in a strncpy and its NUL terminator survived. The same hole exists
    for strings in src/value.c and is filed.
  - Nothing asserted a freshly initialised table was actually zeroed.

Closing the first two took the measured score from 73.1% to 77.8%.

I set the push-path threshold to 75 first, on an estimate. Measuring gave 73.1%
and the job would have failed on its first run -- the earlier per-file figure was
too high because the captured output had been truncated to its last lines and I
counted fewer survivors than there were. It is 65 now, and the gate was run as
written and confirmed to exit 0.

src/value.c is the file most worth mutating and is deliberately off the push
path: 368 mutants at ~11s each is about 70 minutes, because almost everything
links against it. A partial run over it found the same maximum-length-string
hole plus two genuinely equivalent mutants that only exist because of reference
defect section 6 item 5 -- adding both of the right operand's numeric fields
works only while the unused one is zero, so + and - are interchangeable there.
Recorded in TODO.md.

ctest 61/61; doxygen exits 0; both workflows' steps were executed locally, both
input paths included.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:46:53 -04:00
2ddee571a1 Add a Gitea workflow
Fills the gap TODO.md recorded last commit: the sibling libraries all carry
.gitea/workflows/ci.yaml and this one did not, so every gate was run by hand.
Four jobs, in the shape libakerror and libakstdlib use, down to the JUnit
reporting and the annotate_only note about Gitea 404ing on the Checks API.

cmake_build configures, builds and runs the 61-case suite with JUnit output.
docs runs `doxygen Doxyfile` -- a real gate, since WARN_AS_ERROR is set -- and
uploads build/docs/html as the api-documentation artifact. sanitizers runs the
whole suite under ASan and UBSan, which libakstdlib's TODO.md calls its own
highest-value missing item and which matters here because this library is all
fixed pools and manual buffer arithmetic. coverage gates at 90% of lines against
an actual 92.3% and uploads the html report.

Two things worth knowing, both checked rather than assumed.

The checkout is submodules: true, not recursive. The build needs libakerror,
libakstdlib and basicinterpret; it does not need libakgl, which is guarded
behind AKBASIC_WITH_AKGL and defaults OFF, and recursing would clone SDL,
SDL_image, SDL_mixer, SDL_ttf and jansson for a target that is never configured.
The nested submodules are uninitialized in this working tree today and the build
is green, which is the proof. The docs job takes no submodules at all -- verified
by running doxygen against a tree with deps/ absent.

There is no branch-coverage gate. Branch coverage reads 17.3% and is not a
meaningful number here, for the reason libakgl's and libakstdlib's TODO.md both
record: the akerror control-flow macros expand into large branch trees at every
call site, most unreachable in normal operation. Gating on it would mean writing
tests for libakerror's macros.

All four jobs were executed locally end to end before committing, not just
eyeballed: 61/61 plain, 61/61 under ASan+UBSan, doxygen exit 0 producing 441
files, and gcovr exit 0 at the 90 gate. The gate was also confirmed to bite by
raising it to 99.

Not added: a mutation-testing job, which all three siblings have. There is no
scripts/mutation_test.py in this repository to run, and porting libakerror's is
its own piece of work -- filed in TODO.md, where it matters more than usual
because the akerror macros expand at their call sites and coverage cannot see
them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:09:38 -04:00