Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m24s
akbasic CI Build / sanitizers (push) Successful in 4m59s
akbasic CI Build / coverage (push) Failing after 4m55s
akbasic CI Build / akgl_build (push) Failing after 4m54s
akbasic CI Build / mutation_test (push) Has been cancelled
Every job in ci.yaml has failed on every run since #2, on main and on every branch, for two reasons that have nothing to do with any of the code under test. moreutils, for errno(1). deps/libakerror/scripts/generrno.sh shells out to it to generate its errno name table and to stamp AKERR_LAST_ERRNO_VALUE into the generated akerror.h. The runner does not have it, and its absence does not fail the build -- which is what made this expensive to find. The script prints "errno: command not found", emits an empty akerr_init_errno() so no errno ever gets a name, and substitutes an empty AKERR_LAST_ERRNO_VALUE, so every code in libakerror's reserved band collapses from (134 + n) to ( + n), i.e. to n. AKERR_OUTOFBOUNDS is (AKERR_LAST_ERRNO_VALUE + 2) and therefore becomes literally 2, so ERR(2) answers "Out Of Bounds Error" where ENOENT's "No such file or directory" belongs. That is the docs_examples failure at docs/15-error-codes.md:105, and it took cmake_build, sanitizers and coverage down with it; mutation_test then aborted on "baseline is not green" because the same suite is its baseline. Reproduced locally by shimming errno(1) out of PATH: identical failure, same test #47, same two lines of diff. With errno present the suite is 112/112. The nine X11 dev packages, for akgl_build, which has never once been green. SDL_X11 and its sub-options default ON on Linux, and CheckX11() calls SDL_missing_dependency() -- a hard CMake error, not a downgrade -- for any whose header is absent. The runner has libx11-dev but none of the extension packages, so configuration stopped at the first of them: "Couldn't find dependency package for XCURSOR". The list is SDL's own, from deps/SDL/docs/README-linux.md, reduced to the X11 entries; the audio, Wayland, KMSDRM and Vulkan backends all degrade to "not found" and skip themselves rather than erroring, so they cost nothing to leave out. Turning the missing options OFF would also configure and is the wrong answer -- the point of that job is that an AKGL build is a real SDL program. The tests still run headless under SDL_VIDEODRIVER=dummy. release.yaml's full_mutation job builds the same tree and gets moreutils for the same reason. No source file changes. The port itself was never the problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
141 lines
6.4 KiB
YAML
141 lines
6.4 KiB
YAML
name: akbasic Release Build
|
|
run-name: ${{ gitea.actor }} akbasic release checks
|
|
# Manual only. Nothing here runs on push: the full mutation set is thousands of
|
|
# mutants and hours of runner time, which is a release-gate cost, not a
|
|
# per-commit one. Trigger it from the Actions tab.
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
mutation_threshold:
|
|
description: "Fail if the mutation score falls below this percentage"
|
|
required: false
|
|
default: "65"
|
|
mutation_targets:
|
|
description: "Space-separated files to mutate; empty means the whole src/ tree"
|
|
required: false
|
|
default: ""
|
|
|
|
jobs:
|
|
# Moved here from ci.yaml. The docs are wanted for a release, not on every
|
|
# push, and building them beside the release mutation run keeps the two
|
|
# artefacts a release needs in one place.
|
|
docs:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
# No submodules at all. Doxyfile's INPUT is include/akbasic, src and
|
|
# examples, none of which live under deps/, and doxygen does not need to
|
|
# resolve <akerror.h> to parse a declaration. Verified by running it
|
|
# against a tree with no deps/ present.
|
|
- name: dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y doxygen graphviz
|
|
# This is a gate, not a convenience. The Doxyfile sets
|
|
# WARN_AS_ERROR = FAIL_ON_WARNINGS, so a doc block that documents some of a
|
|
# function's parameters but not all of them fails the build. All 114 public
|
|
# declarations under include/akbasic carry a @brief, a @param per
|
|
# parameter, a @return and their @throws; keep it that way.
|
|
#
|
|
# OUTPUT_DIRECTORY is build/docs and doxygen will not create a missing
|
|
# parent, so make it first -- there is no cmake step in this job to do it.
|
|
- name: build API documentation
|
|
run: |
|
|
mkdir -p build
|
|
doxygen Doxyfile
|
|
- name: upload API documentation
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: api-documentation
|
|
path: build/docs/html/
|
|
if-no-files-found: error
|
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
|
|
|
full_mutation:
|
|
runs-on: ubuntu-latest
|
|
# 3675 mutants across the whole src/ tree. Cost per mutant is one incremental
|
|
# rebuild plus one full ctest run, which measures at roughly 2s for a leaf
|
|
# file and 11s for src/value.c, where almost everything links against it.
|
|
# Budget most of a day and expect it to finish well inside that; the ceiling
|
|
# exists so a mutant that wedges the runner cannot hold it forever.
|
|
timeout-minutes: 720
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
# The harness copies the repo and configures a build inside the copy,
|
|
# so it needs the same submodules the main build does. The golden
|
|
# corpus is part of what kills mutants and it is checked in now, so
|
|
# that is libakerror and libakstdlib and nothing else.
|
|
submodules: true
|
|
# moreutils for errno(1), which deps/libakerror's scripts/generrno.sh needs
|
|
# to generate its errno table. Without it the generated table is empty and
|
|
# every AKERR_* code collapses onto a low integer -- see the long note on
|
|
# ci.yaml's cmake_build job for what that breaks and how it presents.
|
|
- name: dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y cmake gcc python3 moreutils
|
|
# The whole akbasic-owned src/ tree. ci.yaml runs a two-file subset on every
|
|
# push; this is the one that actually covers the interpreter.
|
|
#
|
|
# Mutation testing is the only gate that sees the error-handling control
|
|
# flow at all: libakerror's ATTEMPT/CATCH/PASS macros expand at their call
|
|
# sites, so gcov attributes them to the caller and line coverage cannot
|
|
# measure them.
|
|
#
|
|
# The default threshold matches ci.yaml's rather than being stricter.
|
|
# Whole-tree coverage is uneven -- src/symtab.c is the only file measured
|
|
# on the push path, at 74.1%, and the rest is unmeasured, so a tighter
|
|
# number here would be a guess. Raise it with the workflow input once a
|
|
# full run has established a real baseline.
|
|
#
|
|
# The two inputs arrive through the environment rather than being
|
|
# interpolated straight into the script. ${{ }} substitution happens before
|
|
# the shell sees the line, so a value containing shell metacharacters would
|
|
# otherwise run as code. This workflow is manual and owner-triggered, but
|
|
# the safe form costs nothing.
|
|
- name: mutation testing (full tree)
|
|
env:
|
|
MUTATION_TARGETS: ${{ gitea.event.inputs.mutation_targets }}
|
|
MUTATION_THRESHOLD: ${{ gitea.event.inputs.mutation_threshold }}
|
|
run: |
|
|
set -eu
|
|
# Word-splitting is the point here: the input is a space-separated
|
|
# list. An empty input leaves $targets empty and the harness falls
|
|
# through to its own default, which is the whole src/ tree.
|
|
targets=""
|
|
for f in ${MUTATION_TARGETS:-}; do
|
|
targets="$targets --target $f"
|
|
done
|
|
# shellcheck disable=SC2086
|
|
python3 scripts/mutation_test.py \
|
|
$targets \
|
|
--junit mutation-junit.xml \
|
|
--threshold "${MUTATION_THRESHOLD:-65}"
|
|
# Publish even when the threshold gate fails, so survivors are visible --
|
|
# each one is a missing test. Display-only (fail_on_failure: false); the
|
|
# --threshold above is the gate. annotate_only avoids the Checks API 404
|
|
# on Gitea (mikepenz/action-junit-report#23).
|
|
- name: publish mutation results
|
|
if: always()
|
|
uses: mikepenz/action-junit-report@v4
|
|
with:
|
|
report_paths: 'mutation-junit.xml'
|
|
annotate_only: true
|
|
detailed_summary: true
|
|
include_passed: true
|
|
fail_on_failure: 'false'
|
|
# Keep the raw report as well as the annotations: a release wants the
|
|
# survivor list on file, and the job summary is not durable.
|
|
- name: upload mutation report
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: mutation-report
|
|
path: mutation-junit.xml
|
|
if-no-files-found: warn
|
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|