Closes Defects -> Known and still open item 12, both halves. include/akgl/SDL_GameControllerDB.h is tracked on purpose: it is the offline fallback that keeps the library buildable when upstream is unreachable. The script that writes it had no set -e and never checked curl, so a failed fetch wrote AKGL_SDL_GAMECONTROLLER_DB_LEN 0 and an empty array over the good copy and exited 0. The safety net destroyed itself, and because CMake re-ran the generator on every build, an offline build was enough to do it. The script now runs under set -euo pipefail, fetches into a temporary directory, and moves the result into place only after checking curl's exit status (with --fail, so an HTTP error is a status rather than an error page in the body), a plausible minimum mapping count, and that no mapping carries a quote or backslash that would break the C string literal it becomes. Any of those failing leaves the tracked header exactly as it was and exits non-zero. Verified against all five failure modes -- unresolvable host, 404, truncated response, empty-but-successful response (the original failure exactly), and a response containing a quote. Each refuses, and the header's checksum is unchanged after every one. AKGL_CONTROLLERDB_URL and AKGL_CONTROLLERDB_MIN_LINES are environment-overridable, which is how. The build no longer runs it at all. The add_custom_command declared a relative OUTPUT, which CMake resolves against the binary directory while the script writes to the source directory, so the declared output never appeared and every build re-ran the command -- needing network access and leaving the tree dirty. It is an explicit `controllerdb` target now, and the header is no longer listed as a library source, which is all it was there for. The empty-initializer problem goes with it: an empty array initializer is a constraint violation in ISO C that compiles only as a GCC extension, and the minimum-count check guarantees at least one entry. Also here, because it rested on a premise that turned out to be false: the character suite is no longer excluded from CI's coverage and memory-check jobs, nor from the mutation harness by default. It was excluded on the grounds that it failed deliberately. It did not -- it was reporting success while running one of its four tests. 25/25 pass, memcheck clean, shellcheck clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
211 lines
8.4 KiB
YAML
211 lines
8.4 KiB
YAML
name: libakgl CI Build
|
|
run-name: ${{ gitea.actor }} libakgl test
|
|
on: [push]
|
|
|
|
jobs:
|
|
cmake_build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
- name: Install build dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y \
|
|
cmake doxygen gcc gcovr pkg-config \
|
|
libasound2-dev libfreetype-dev libharfbuzz-dev \
|
|
libpng-dev libtiff-dev libwebp-dev \
|
|
libudev-dev libx11-dev libxcursor-dev libxext-dev \
|
|
libxfixes-dev libxi-dev libxrandr-dev libxrender-dev \
|
|
libxss-dev libxtst-dev
|
|
- name: Configure and build
|
|
run: |
|
|
cmake -S . -B build \
|
|
-DCMAKE_BUILD_TYPE=Debug \
|
|
-DAKGL_COVERAGE=ON
|
|
cmake --build build --parallel
|
|
- name: Build API documentation
|
|
run: doxygen Doxyfile
|
|
# The perf suites run here too -- they are ordinary CTest tests -- but this
|
|
# is a Debug build with coverage instrumentation, where a timing is a
|
|
# measurement of gcov. AKGL_BENCH_SCALE cuts their iteration counts so
|
|
# they contribute path coverage without spending ten minutes proving
|
|
# nothing; benchutil.h already refuses to enforce budgets in an
|
|
# unoptimized build. The performance job below is where the timings are
|
|
# taken and the budgets are checked.
|
|
- name: Test (JUnit)
|
|
env:
|
|
AKGL_BENCH_SCALE: '0.02'
|
|
run: |
|
|
export LD_LIBRARY_PATH="$PWD/build:$PWD/build/deps/SDL:$PWD/build/deps/SDL_image:$PWD/build/deps/SDL_mixer:$PWD/build/deps/SDL_ttf"
|
|
ctest \
|
|
--test-dir build \
|
|
--output-on-failure \
|
|
--output-junit "$(pwd)/ctest-junit.xml"
|
|
- name: Publish test results
|
|
if: always()
|
|
uses: mikepenz/action-junit-report@v4
|
|
with:
|
|
report_paths: 'ctest-junit.xml'
|
|
annotate_only: true
|
|
detailed_summary: true
|
|
include_passed: true
|
|
fail_on_failure: 'true'
|
|
- name: Upload coverage reports
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: code-coverage
|
|
path: build/coverage/
|
|
if-no-files-found: warn
|
|
|
|
# The benchmarks, in the only build where their numbers mean anything.
|
|
# tests/benchutil.h enforces a budget per measurement only when it is compiled
|
|
# optimized and running at full scale, which is exactly this job and no other:
|
|
# the coverage job above is Debug, and the memory job below is under valgrind.
|
|
# A budget is roughly ten times the baseline recorded in PERFORMANCE.md, which
|
|
# is what makes a shared, slower runner viable -- it catches an algorithmic
|
|
# regression and ignores a busy machine. If a budget still proves flaky here,
|
|
# raise that one budget with a measurement behind it and re-record the
|
|
# baseline; do not drop the gate.
|
|
performance:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
- name: Install build dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y \
|
|
cmake gcc pkg-config \
|
|
libasound2-dev libfreetype-dev libharfbuzz-dev \
|
|
libpng-dev libtiff-dev libwebp-dev \
|
|
libudev-dev libx11-dev libxcursor-dev libxext-dev \
|
|
libxfixes-dev libxi-dev libxrandr-dev libxrender-dev \
|
|
libxss-dev libxtst-dev
|
|
- name: Configure and build
|
|
run: |
|
|
cmake -S . -B build \
|
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
|
cmake --build build --parallel
|
|
# --verbose rather than --output-on-failure: a passing benchmark's output
|
|
# is the entire point of running it, and --output-on-failure prints
|
|
# nothing at all for a green run. pipefail because the step ends in a
|
|
# pipe, whose status would otherwise be tee's -- always 0 -- and a blown
|
|
# budget would be reported as a passing step.
|
|
- name: Benchmarks (JUnit)
|
|
run: |
|
|
set -o pipefail
|
|
export LD_LIBRARY_PATH="$PWD/build:$PWD/build/deps/SDL:$PWD/build/deps/SDL_image:$PWD/build/deps/SDL_mixer:$PWD/build/deps/SDL_ttf"
|
|
ctest \
|
|
--test-dir build \
|
|
-L perf \
|
|
--verbose \
|
|
--output-junit "$(pwd)/perf-junit.xml" \
|
|
2>&1 | tee "$(pwd)/perf-baseline.txt"
|
|
- name: Publish benchmark results
|
|
if: always()
|
|
uses: mikepenz/action-junit-report@v4
|
|
with:
|
|
report_paths: 'perf-junit.xml'
|
|
annotate_only: true
|
|
detailed_summary: true
|
|
include_passed: true
|
|
fail_on_failure: 'true'
|
|
# Kept whether the job passed or failed: the tables are the point. A run
|
|
# that went red says which measurement moved, and a run that went green is
|
|
# the next baseline if somebody re-records PERFORMANCE.md.
|
|
- name: Upload benchmark tables
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: performance-baseline
|
|
path: perf-baseline.txt
|
|
if-no-files-found: warn
|
|
|
|
# Every suite under valgrind. No memory-check programs of their own: the perf
|
|
# suites scale themselves down when they detect valgrind, which turns them
|
|
# into the broadest path coverage in the tree at a cost this job can afford.
|
|
#
|
|
# This job gates. A definite leak, a read past the end of an allocation, or a
|
|
# branch on uninitialised memory fails the build on the push that introduced
|
|
# it -- not on the day somebody gets around to caring. The six findings this
|
|
# job had on its first run were fixed to make that possible rather than
|
|
# excused into a warning.
|
|
memory_check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
- name: Install memory-check dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y \
|
|
cmake gcc pkg-config valgrind \
|
|
libasound2-dev libfreetype-dev libharfbuzz-dev \
|
|
libpng-dev libtiff-dev libwebp-dev \
|
|
libudev-dev libx11-dev libxcursor-dev libxext-dev \
|
|
libxfixes-dev libxi-dev libxrandr-dev libxrender-dev \
|
|
libxss-dev libxtst-dev
|
|
# RelWithDebInfo rather than Debug: valgrind needs the symbols, and -O0
|
|
# would leave every inlined frame in the stacks it prints.
|
|
- name: Configure and build
|
|
run: |
|
|
cmake -S . -B build \
|
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo
|
|
cmake --build build --parallel
|
|
# Every suite, character included. It was excluded from both this job
|
|
# and the coverage job on the grounds that it failed deliberately -- it
|
|
# did not. It was reporting success while running one of its four tests;
|
|
# see TODO.md, "Test suites that could not fail".
|
|
- name: Memory check
|
|
run: scripts/memcheck.sh
|
|
- name: Upload valgrind logs
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: valgrind-logs
|
|
path: build/Testing/Temporary/MemoryChecker.*.log
|
|
if-no-files-found: warn
|
|
|
|
mutation_test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
- name: Install mutation-test dependencies
|
|
run: |
|
|
sudo apt-get update -y
|
|
sudo apt-get install -y \
|
|
cmake gcc pkg-config python3 \
|
|
libasound2-dev libfreetype-dev libharfbuzz-dev \
|
|
libpng-dev libtiff-dev libwebp-dev \
|
|
libudev-dev libx11-dev libxcursor-dev libxext-dev \
|
|
libxfixes-dev libxi-dev libxrandr-dev libxrender-dev \
|
|
libxss-dev libxtst-dev
|
|
# Keep CI bounded to a focused source file. Run the default target locally
|
|
# for the complete libakgl-owned src/ tree.
|
|
- name: Mutation testing
|
|
run: |
|
|
python3 scripts/mutation_test.py \
|
|
--target src/staticstring.c \
|
|
--junit mutation-junit.xml \
|
|
--threshold 50
|
|
- 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'
|