From 6362bdb3d45007cc5819c99768407dfb90005fa1 Mon Sep 17 00:00:00 2001 From: Fabio M Date: Sun, 21 Jun 2026 11:39:08 -0500 Subject: [PATCH 1/5] Create codecov.yml to enforce target test coverage --- codecov.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..5dfbe0b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,12 @@ +coverage: + range: 60..80 + round: down + precision: 2 + status: + project: + default: + target: 80% + threshold: 0% + patch: + default: + target: 80% From 9d9b1b9f10729c17d205222f98178dfc9dc05150 Mon Sep 17 00:00:00 2001 From: fabiomarcellusdev <166572074+fabiomarcellusdev@users.noreply.github.com> Date: Sun, 21 Jun 2026 12:44:39 -0500 Subject: [PATCH 2/5] Explictly set threshold to 0% Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 5dfbe0b..8da3ee1 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,3 +10,4 @@ coverage: patch: default: target: 80% + threshold: 0% From 5094c29e7f40803be67b1470db02c5a196c54a51 Mon Sep 17 00:00:00 2001 From: Fabio M Date: Sun, 21 Jun 2026 17:19:23 -0500 Subject: [PATCH 3/5] Fix CheckCoverageThreshold.cmake regex --- cmake/CheckCoverageThreshold.cmake | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cmake/CheckCoverageThreshold.cmake b/cmake/CheckCoverageThreshold.cmake index e61180e..bbfebb0 100644 --- a/cmake/CheckCoverageThreshold.cmake +++ b/cmake/CheckCoverageThreshold.cmake @@ -21,15 +21,25 @@ file(READ "${COVERAGE_FILE}" COVERAGE_CONTENT) # Extract line coverage percentage # lcov format: LH: LF: -string(REGEX MATCH "LF:([0-9]+)" LF_MATCH "${COVERAGE_CONTENT}") -string(REGEX MATCH "LH:([0-9]+)" LH_MATCH "${COVERAGE_CONTENT}") +string(REGEX MATCHALL "LF:([0-9]+)" LF_ALL "${COVERAGE_CONTENT}") +string(REGEX MATCHALL "LH:([0-9]+)" LH_ALL "${COVERAGE_CONTENT}") -if(NOT LF_MATCH OR NOT LH_MATCH) +if(NOT LF_ALL OR NOT LH_ALL) message(FATAL_ERROR "Could not parse coverage data from ${COVERAGE_FILE}") endif() -string(REGEX REPLACE "LF:([0-9]+)" "\\1" LINES_FOUND "${LF_MATCH}") -string(REGEX REPLACE "LH:([0-9]+)" "\\1" LINES_HIT "${LH_MATCH}") +set(LINES_FOUND 0) +set(LINES_HIT 0) + +foreach(LF_ENTRY IN LISTS LF_ALL) + string(REGEX REPLACE "LF:([0-9]+)" "\\1" VALUE "${LF_ENTRY}") + math(EXPR LINES_FOUND "${LINES_FOUND} + ${VALUE}") +endforeach() + +foreach(LH_ENTRY IN LISTS LH_ALL) + string(REGEX REPLACE "LH:([0-9]+)" "\\1" VALUE "${LH_ENTRY}") + math(EXPR LINES_HIT "${LINES_HIT} + ${VALUE}") +endforeach() if(LINES_FOUND EQUAL 0) message(FATAL_ERROR "No lines found in coverage data") From 0982319ed1ea3e72b26d4ba3b119c962792029a4 Mon Sep 17 00:00:00 2001 From: Fabio M Date: Sun, 21 Jun 2026 20:21:13 -0500 Subject: [PATCH 4/5] Exclude gtest folders from testing coverage, add testable code to main, and update TESTING.md to use accurate information --- cmake/CodeCoverage.cmake | 7 +- docs/TESTING.md | 167 +++++++++++++++------------ include/my_project_name/greeting.hpp | 11 ++ src/main.cpp | 4 +- tests/CMakeLists.txt | 2 +- tests/test_main.cpp | 10 +- 6 files changed, 123 insertions(+), 78 deletions(-) create mode 100644 include/my_project_name/greeting.hpp diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index 9f73d83..4abc964 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -54,18 +54,19 @@ if(ENABLE_COVERAGE) ${LCOV_EXECUTABLE} --capture --directory src --directory tests --output-file ${COVERAGE_DIR}/coverage.info --base-directory ${CMAKE_SOURCE_DIR} --gcov-tool ${GCOV_EXECUTABLE} - --ignore-errors mismatch,format,unsupported,version,empty + --ignore-errors mismatch,format,unsupported,version,empty,inconsistent COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} ${LCOV_EXECUTABLE} --remove ${COVERAGE_DIR}/coverage.info - '/usr/*' '*/tests/*' '*/build/*' '*/external/*' + '/usr/*' '*/tests/*' '*/build/*' '*/external/*' '*/gtest/*' '*/gmock/*' --output-file ${COVERAGE_DIR}/coverage_filtered.info --gcov-tool ${GCOV_EXECUTABLE} - --ignore-errors unused,format,unsupported,version,empty + --ignore-errors unused,format,unsupported,version,empty,inconsistent COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} ${GENHTML_EXECUTABLE} ${COVERAGE_DIR}/coverage_filtered.info --output-directory ${COVERAGE_DIR}/html --title "${PROJECT_NAME} Code Coverage" --legend --show-details + --rc genhtml_med_limit=60 --rc genhtml_hi_limit=80 --ignore-errors empty COMMAND ${CMAKE_COMMAND} -E echo "Coverage report generated at: ${COVERAGE_DIR}/html/index.html" COMMAND ${CMAKE_COMMAND} diff --git a/docs/TESTING.md b/docs/TESTING.md index 27fad7b..fec742f 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -223,77 +223,83 @@ lcov: LCOV version 2.0 ### Usage -**Step 1: Configure with coverage enabled** +**Step 1: Install dependencies** -Use Debug build for accurate line-level coverage: +Download and build Conan packages (GTest, fmt, etc.) for Debug configuration: ```bash -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_COVERAGE=ON +conan install . --build=missing -s build_type=Debug -s compiler.cppstd=23 ``` -**Real example:** +**Step 2: Configure with coverage enabled** + +Read `CMakeLists.txt`, resolve dependencies, and generate build files in `build/debug-coverage/` with `--coverage` compiler flags. Does **not** compile anything: ```bash -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_COVERAGE=ON +cmake --preset debug-coverage ``` **Expected output:** ``` --- Code coverage enabled +-- Code coverage enabled (threshold: 80%) +-- Using gcov: /usr/bin/gcov-13 -- Configuring done -- Generating done ``` -**Step 2: Build** +**Step 3: Build** -```bash -cmake --build --preset debug -``` - -**Step 3: Run tests** +Invoke the compiler using the generated build files. Compiles `.cpp` files into object files and links them into executables (`cpp_executable_template` and `cpp_executable_template_tests`): ```bash -ctest --preset debug +cmake --build --preset debug-coverage ``` -**Step 4: Generate the HTML coverage report** +**Step 4: Run tests** + +Execute the test binary via CTest. This generates `.gcda` files containing execution counts for each instrumented line: ```bash -cmake --build build/debug --target coverage +ctest --preset debug-coverage --output-on-failure ``` -**Real example:** +**Step 5: Generate the HTML coverage report** + +Run the `coverage` custom target, which: +1. Captures coverage data from `.gcda` files using `lcov` +2. Filters out system headers, tests, and build artifacts +3. Generates an interactive HTML report using `genhtml` +4. Checks coverage against the 80% threshold (fails if below) ```bash -cmake --build build/debug --target coverage +cmake --build --preset debug-coverage --target coverage ``` **Expected output:** ``` [1/1] Generating code coverage report... -Resetting coverage counters... -Capturing coverage data from . +Capturing coverage data from src tests Found gcov version: 13.2.0 -Using 4 file(s) ... Overall coverage rate: lines......: 85.7% (42 of 49 lines) functions..: 100.0% (8 of 8 functions) -Coverage report generated at: /path/to/build/debug/coverage/html/index.html +Coverage report generated at: /path/to/build/debug-coverage/coverage/html/index.html +-- Coverage: 42/49 lines (85%) +-- Threshold: 80% +-- Coverage check passed: 85% >= 80% ``` -**Step 5: Open the report** +**Step 6: Open the report** ```bash # Linux -xdg-open build/debug/coverage/html/index.html +xdg-open build/debug-coverage/coverage/html/index.html # macOS -open build/debug/coverage/html/index.html +open build/debug-coverage/coverage/html/index.html ``` The HTML report shows: @@ -364,7 +370,7 @@ CMake Error: Code coverage 65% is below the required threshold of 80%. Please ad ```bash # Set threshold to 90% -cmake --preset debug -DENABLE_COVERAGE=ON -DCOVERAGE_THRESHOLD=90 +cmake --preset debug-coverage -DCOVERAGE_THRESHOLD=90 ``` Or modify `CMakePresets.json` to set it permanently for a preset. @@ -374,21 +380,24 @@ For Codecov integration and viewing coverage trends, see [Codecov Guide](CODECOV ### Typical Workflow ```bash -# 1. Configure with coverage -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_COVERAGE=ON +# 1. Install dependencies (downloads/builds Conan packages like GTest, fmt) +conan install . --build=missing -s build_type=Debug -s compiler.cppstd=23 -# 2. Build -cmake --build --preset debug +# 2. Configure (reads CMakeLists.txt, generates build files with --coverage flags) +cmake --preset debug-coverage -# 3. Run tests -ctest --preset debug +# 3. Build (compiles .cpp files and links executables) +cmake --build --preset debug-coverage -# 4. Generate report -cmake --build build/debug --target coverage +# 4. Run tests (executes tests, generates .gcda coverage data files) +ctest --preset debug-coverage --output-on-failure + +# 5. Generate report (captures coverage, filters noise, creates HTML, enforces 80% threshold) +cmake --build --preset debug-coverage --target coverage -# 5. View report -open build/debug/coverage/html/index.html +# 6. View report (opens interactive HTML in browser) +xdg-open build/debug-coverage/coverage/html/index.html # Linux +open build/debug-coverage/coverage/html/index.html # macOS ``` --- @@ -510,23 +519,20 @@ With UBSan: ### Combining with Other Options -Sanitizers can be combined with code coverage and linting: +Sanitizers can be combined with code coverage and linting using the `debug-full` preset: ```bash -cmake --preset debug \ - -DENABLE_SANITIZERS=ON \ - -DENABLE_COVERAGE=ON \ - -DENABLE_LINTING=ON +cmake --preset debug-full ``` **Real example:** ```bash -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_SANITIZERS=ON -DENABLE_COVERAGE=ON -cmake --build --preset debug -ctest --preset debug --output-on-failure -cmake --build build/debug --target coverage +conan install . --build=missing -s build_type=Debug -s compiler.cppstd=23 +cmake --preset debug-full +cmake --build --preset debug-full +ctest --preset debug-full --output-on-failure +cmake --build --preset debug-full --target coverage ``` This gives you: @@ -575,33 +581,33 @@ ctest --preset debug --output-on-failure ```bash # Full check: lint + build + test + coverage + sanitizers -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_SANITIZERS=ON -DENABLE_COVERAGE=ON -DENABLE_LINTING=ON -cmake --build --preset debug -ctest --preset debug --output-on-failure -cmake --build build/debug --target coverage +conan install . --build=missing -s build_type=Debug -s compiler.cppstd=23 +cmake --preset debug-full +cmake --build --preset debug-full +ctest --preset debug-full --output-on-failure +cmake --build --preset debug-full --target coverage # Also verify Release build works -conan install . --build=missing -cmake --preset release -cmake --build --preset release -ctest --preset release --output-on-failure +conan install . --build=missing -s build_type=Release +cmake --preset release-strict +cmake --build --preset release-strict +ctest --preset release-strict --output-on-failure ``` ### CI/CD Pipeline ```bash # Debug with sanitizers -conan install . --build=missing -s build_type=Debug -cmake --preset debug -DENABLE_SANITIZERS=ON -DENABLE_LINTING=ON -cmake --build --preset debug -ctest --preset debug --output-on-failure +conan install . --build=missing -s build_type=Debug -s compiler.cppstd=23 +cmake --preset debug-sanitizers +cmake --build --preset debug-sanitizers +ctest --preset debug-sanitizers --output-on-failure # Release -conan install . --build=missing -cmake --preset release -cmake --build --preset release -ctest --preset release --output-on-failure +conan install . --build=missing -s build_type=Release +cmake --preset release-strict +cmake --build --preset release-strict +ctest --preset release-strict --output-on-failure ``` --- @@ -622,6 +628,25 @@ sudo apt-get install lcov brew install lcov ``` +### "No rule to make target 'coverage'" + +**Cause:** `lcov` or `genhtml` is not installed, so the `coverage` target was not created during configure. + +**Solution:** + +```bash +# Install lcov (includes genhtml) +sudo apt-get install lcov # Ubuntu/Debian +brew install lcov # macOS + +# Verify installation +lcov --version + +# Re-configure and rebuild +cmake --preset debug-coverage +cmake --build --preset debug-coverage +``` + ### "Coverage report is empty" **Cause:** Tests were not run before generating the report. @@ -630,10 +655,10 @@ brew install lcov ```bash # Run tests first to collect coverage data -ctest --preset debug +ctest --preset debug-coverage --output-on-failure # Then generate report -cmake --build build/debug --target coverage +cmake --build --preset debug-coverage --target coverage ``` ### "Sanitizer error: ASan runtime does not come first" @@ -664,10 +689,10 @@ ctest --preset debug --output-on-failure |---|---| | Run tests | `ctest --preset release` | | Run tests (verbose) | `ctest --preset release --output-on-failure` | -| Enable coverage | `cmake --preset debug -DENABLE_COVERAGE=ON` | -| Generate coverage report | `cmake --build build/debug --target coverage` | -| Enable sanitizers | `cmake --preset debug -DENABLE_SANITIZERS=ON` | -| Combine all | `cmake --preset debug -DENABLE_COVERAGE=ON -DENABLE_SANITIZERS=ON -DENABLE_LINTING=ON` | +| Enable coverage | `cmake --preset debug-coverage` | +| Generate coverage report | `cmake --build --preset debug-coverage --target coverage` | +| Enable sanitizers | `cmake --preset debug-sanitizers` | +| Combine all | `cmake --preset debug-full` | For build configuration details, see [Build Guide](BUILD.md). For linting and formatting, see [Linting Guide](LINTING.md). diff --git a/include/my_project_name/greeting.hpp b/include/my_project_name/greeting.hpp new file mode 100644 index 0000000..8d7197a --- /dev/null +++ b/include/my_project_name/greeting.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace my_project_name { + + inline std::string get_greeting() { + return "Hello, World!"; + } + +} diff --git a/src/main.cpp b/src/main.cpp index 1cb398f..7f6854f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,8 @@ #include +#include "my_project_name/greeting.hpp" + int main() { - std::cout << "Hello, World!" << '\n'; + std::cout << my_project_name::get_greeting() << '\n'; return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b3b842..01a3e57 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,4 +14,4 @@ target_link_libraries(${PROJECT_NAME}_tests PRIVATE ) include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME}_tests) +gtest_discover_tests(${PROJECT_NAME}_tests DISCOVERY_MODE PRE_TEST) diff --git a/tests/test_main.cpp b/tests/test_main.cpp index c7ade2c..1f9d839 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -1,5 +1,11 @@ #include -TEST(SanityCheck, TrueIsTrue) { - EXPECT_TRUE(true); +#include "my_project_name/greeting.hpp" + +TEST(GreetingTest, ReturnsHelloWorld) { + EXPECT_EQ(my_project_name::get_greeting(), "Hello, World!"); +} + +TEST(GreetingTest, IsNotEmpty) { + EXPECT_FALSE(my_project_name::get_greeting().empty()); } From cba99ba763ce9ec677c332da11cad10b5955e283 Mon Sep 17 00:00:00 2001 From: Fabio M Date: Mon, 22 Jun 2026 05:48:32 -0500 Subject: [PATCH 5/5] Update .clang-format rules, LINTING.md docs, + fix linting issue in greeting.hpp --- .clang-format | 2 +- docs/LINTING.md | 76 ++++++++++++++++++---------- include/my_project_name/greeting.hpp | 2 +- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/.clang-format b/.clang-format index fcf9718..88b7113 100644 --- a/.clang-format +++ b/.clang-format @@ -11,7 +11,7 @@ IndentCaseLabels: false IndentPPDirectives: None IndentWrappedFunctionNames: false IndentGotoLabels: true -NamespaceIndentation: None +NamespaceIndentation: All AccessModifierOffset: -4 AlignAfterOpenBracket: true diff --git a/docs/LINTING.md b/docs/LINTING.md index 646f2e6..1238391 100644 --- a/docs/LINTING.md +++ b/docs/LINTING.md @@ -33,16 +33,20 @@ All three are integrated into CMake as custom targets and can be run manually or ### Checking Formatting -Checks if files comply with `.clang-format` without modifying them: +Checks if files comply with `.clang-format` without modifying them. + +**Prerequisites:** Install `clang-format` first (see [Common Issues](#clang-format-not-found)). ```bash -cmake --build build --target format-check +cmake --preset debug-full +cmake --build --preset debug-full --target format-check ``` **Real example:** ```bash -cmake --build build/release --target format-check +cmake --preset debug-full +cmake --build --preset debug-full --target format-check ``` **Expected output (clean):** @@ -65,16 +69,20 @@ The command exits with a non-zero status if any file is unformatted, making it s ### Auto-Fixing Formatting -Applies formatting rules to all source and header files: +Applies formatting rules to all source and header files. + +**Prerequisites:** Install `clang-format` first (see [Common Issues](#clang-format-not-found)). ```bash -cmake --build build --target format-fix +cmake --preset debug-full +cmake --build --preset debug-full --target format-fix ``` **Real example:** ```bash -cmake --build build/release --target format-fix +cmake --preset debug-full +cmake --build --preset debug-full --target format-fix ``` **Expected output:** @@ -117,14 +125,18 @@ clang-format -i src/*.cpp include/**/*.hpp tests/*.cpp ### Running the Linter +**Prerequisites:** Install `clang-tidy` first (see [Common Issues](#clang-tidy-not-found)). + ```bash -cmake --build build --target lint +cmake --preset debug-full +cmake --build --preset debug-full --target lint ``` **Real example:** ```bash -cmake --build build/release --target lint +cmake --preset debug-full +cmake --build --preset debug-full --target lint ``` **Expected output (clean):** @@ -192,14 +204,18 @@ Verifies that every header file in `include/` contains `#pragma once` at the top ### Running the Check +**Prerequisites:** Configure the project first with `cmake --preset debug-full` (or any preset). + ```bash -cmake --build build --target check-headers +cmake --preset debug-full +cmake --build --preset debug-full --target check-headers ``` **Real example:** ```bash -cmake --build build/release --target check-headers +cmake --preset debug-full +cmake --build --preset debug-full --target check-headers ``` **Expected output (clean):** @@ -405,16 +421,20 @@ Enforced by `.clang-tidy` via `readability-identifier-naming`: 1. **Write code** in your editor/IDE 2. **Save files** — your editor may auto-format on save if configured -3. **Run format-fix** to ensure consistent formatting: +3. **Configure once** (generates build files and finds clang-format/clang-tidy): ```bash - cmake --build build/release --target format-fix + cmake --preset debug-full ``` -4. **Run lint** to catch issues early: +4. **Run format-fix** to ensure consistent formatting: ```bash - cmake --build build/release --target lint + cmake --build --preset debug-full --target format-fix ``` -5. **Fix any warnings** reported by lint -6. **Commit** — pre-commit hooks run clang-format as a safety net: +5. **Run lint** to catch issues early: + ```bash + cmake --build --preset debug-full --target lint + ``` +6. **Fix any warnings** reported by lint +7. **Commit** — pre-commit hooks run clang-format as a safety net: ```bash git add . git commit -m "Add feature" @@ -425,20 +445,23 @@ Enforced by `.clang-tidy` via `readability-identifier-naming`: Run all checks to ensure CI will pass: ```bash +# Configure first (required for all lint targets) +cmake --preset debug-full + # Format check -cmake --build build/release --target format-check +cmake --build --preset debug-full --target format-check # Lint -cmake --build build/release --target lint +cmake --build --preset debug-full --target lint # Header check -cmake --build build/release --target check-headers +cmake --build --preset debug-full --target check-headers # Build -cmake --build --preset release +cmake --build --preset debug-full # Test -ctest --preset release +ctest --preset debug-full --output-on-failure # Pre-commit on all files pre-commit run --all-files @@ -519,11 +542,12 @@ pre-commit install | Task | Command | |---|---| -| Check formatting | `cmake --build build/release --target format-check` | -| Auto-fix formatting | `cmake --build build/release --target format-fix` | -| Run linter | `cmake --build build/release --target lint` | -| Check headers | `cmake --build build/release --target check-headers` | -| Enforce during build | `cmake --preset release -DENABLE_LINTING=ON` | +| Configure (required first) | `cmake --preset debug-full` | +| Check formatting | `cmake --build --preset debug-full --target format-check` | +| Auto-fix formatting | `cmake --build --preset debug-full --target format-fix` | +| Run linter | `cmake --build --preset debug-full --target lint` | +| Check headers | `cmake --build --preset debug-full --target check-headers` | +| Enforce during build | `cmake --preset debug-full` (linting enabled by default) | | Install pre-commit | `pip install pre-commit && pre-commit install` | | Run all hooks | `pre-commit run --all-files` | diff --git a/include/my_project_name/greeting.hpp b/include/my_project_name/greeting.hpp index 8d7197a..7afe0b7 100644 --- a/include/my_project_name/greeting.hpp +++ b/include/my_project_name/greeting.hpp @@ -8,4 +8,4 @@ namespace my_project_name { return "Hello, World!"; } -} +} // namespace my_project_name