diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 689a88a..96cc00f 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ - cmake doxygen gcc pkg-config \ + 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 \ @@ -22,7 +22,9 @@ jobs: libxss-dev libxtst-dev - name: Configure and build run: | - cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Debug \ + -DAKGL_COVERAGE=ON cmake --build build --parallel - name: Build API documentation run: doxygen Doxyfile @@ -43,6 +45,13 @@ jobs: 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 mutation_test: runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 91c8462..df3ee9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,8 @@ Run one test while iterating, for example `ctest --test-dir build -R sprite --ou Run mutation testing with `cmake --build build --target mutation`. For a quick smoke run, use `scripts/mutation_test.py --target src/tilemap.c --max-mutants 10`; the harness mutates only a scratch copy and excludes the intentionally failing character test. +Generate HTML and Cobertura coverage reports with `cmake -S . -B build-coverage -DAKGL_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug`, then build and run CTest. Reports are written to `build-coverage/coverage/`; this mode requires `gcovr` and GCC or Clang. + ## Coding Style & Naming Conventions Follow the surrounding C style: braces on the next line for function bodies, spaces inside control-flow parentheses, and short, focused functions. Preserve the indentation of the file being edited; older files contain both spaces and tabs. Public symbols use the `akgl_` prefix, public types use `akgl_TypeName`, and constants/macros use `AKGL_UPPER_SNAKE_CASE`. Name matching header/source pairs by feature, such as `include/akgl/sprite.h` and `src/sprite.c`. No repository-wide formatter or linter is configured, so avoid unrelated formatting churn. @@ -39,7 +41,7 @@ Follow the surrounding C style: braces on the next line for function bodies, spa ## Testing Guidelines -Tests use simple executable return codes and are registered through CTest in `CMakeLists.txt`; there is no declared coverage threshold. Add focused tests as `tests/.c`, create a matching `test_` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. +Tests use simple executable return codes and are registered through CTest in `CMakeLists.txt`; there is no declared coverage threshold. Add focused tests as `tests/.c`, create a matching `test_` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. Coverage mode wraps the suite in a CTest fixture so counters are reset before tests and reports are generated afterward. ## Commit & Pull Request Guidelines diff --git a/CMakeLists.txt b/CMakeLists.txt index f86c2ae..511fcce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,14 @@ cmake_minimum_required(VERSION 3.10) project(akgl LANGUAGES C) include(CTest) +option(AKGL_COVERAGE "Instrument libakgl and generate coverage reports with CTest" OFF) + +if(AKGL_COVERAGE) + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + message(FATAL_ERROR "AKGL_COVERAGE requires GCC or Clang") + endif() + find_program(GCOVR_EXECUTABLE gcovr REQUIRED) +endif() if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) @@ -141,6 +149,44 @@ target_include_directories(akgl PUBLIC deps/semver/ ) +if(AKGL_COVERAGE) + target_compile_options(akgl PRIVATE --coverage -O0 -g) + target_link_options(akgl PRIVATE --coverage) + + set(AKGL_COVERAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/coverage") + file(MAKE_DIRECTORY "${AKGL_COVERAGE_DIR}") + + add_test( + NAME coverage_reset + COMMAND ${GCOVR_EXECUTABLE} + --root "${CMAKE_CURRENT_SOURCE_DIR}" + --object-directory "${CMAKE_CURRENT_BINARY_DIR}" + --delete + ) + set_tests_properties(coverage_reset PROPERTIES FIXTURES_SETUP akgl_coverage) + set_tests_properties( + actor bitmasks character registry sprite staticstring tilemap util semver_unit + PROPERTIES FIXTURES_REQUIRED akgl_coverage + ) + + add_test( + NAME coverage_report + COMMAND ${GCOVR_EXECUTABLE} + --root "${CMAKE_CURRENT_SOURCE_DIR}" + --object-directory "${CMAKE_CURRENT_BINARY_DIR}" + --filter "${CMAKE_CURRENT_SOURCE_DIR}/src/" + --xml-pretty + --xml "${AKGL_COVERAGE_DIR}/coverage.xml" + --html-details "${AKGL_COVERAGE_DIR}/index.html" + ) + set_tests_properties( + coverage_report + PROPERTIES + FIXTURES_CLEANUP akgl_coverage + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) +endif() + target_link_libraries(akgl PUBLIC SDL3::SDL3