SDPT Lab 7

De la WikiLabs
Versiunea din 26 aprilie 2026 23:58, autor: Rhobincu (discuție | contribuții) (Pagină nouă: = Week 7 Lab Activity: Adding a Static Analysis Quality Gate = == Introduction == In Week 6, you built a CI pipeline that automatically compiles and tests your Oven Controller co...)
(dif) ← Versiunea anterioară | Versiunea curentă (dif) | Versiunea următoare → (dif)
Jump to navigationJump to search

Week 7 Lab Activity: Adding a Static Analysis Quality Gate

Introduction

In Week 6, you built a CI pipeline that automatically compiles and tests your Oven Controller code on every push. Today you will add a new stage that runs before the build: a lint stage that uses static analysis to catch entire classes of bugs that the compiler and the unit tests cannot.

By the end of this lab, you will have:

  1. Extended your Docker build environment to include cppcheck and clang-tidy.
  2. Created a .clang-tidy configuration file at the root of your project.
  3. Hooked clang-tidy into every CMake compile via CMAKE_CXX_CLANG_TIDY.
  4. Added a new lint stage to your .gitlab-ci.yml that runs cppcheck.
  5. Verified that introducing a deliberate bug causes the pipeline to fail and block a Merge Request.
  6. Fixed the deliberate bug and watched the pipeline turn green again.

Prerequisites

You should be starting from your finished Week 6 project, which has:

  • A working CMakeLists.txt for the Oven Controller.
  • A passing Google Test suite.
  • A Dockerfile producing a multi-arch build environment.
  • A .gitlab-ci.yml with at least build and test stages running on a custom GitLab Runner.

If any of those is missing or broken, fix it before continuing. Static analysis added on top of a broken pipeline does not help anyone.

Step 1: Create a Feature Branch

As always, do not work on main. Open an Issue first ("Add static analysis quality gate") and then create a feature branch:

git checkout main
git pull
git checkout -b feature/static-analysis

This branch will contain everything you do in this lab. At the end you will open a Merge Request just like in Week 2.

Step 2: Update the Dockerfile

Open your Dockerfile and add cppcheck and clang-tidy to the apt-get install line.

RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    g++-aarch64-linux-gnu \
    qemu-user-static \
    libgtest-dev \
    cppcheck \
    clang-tidy \
 && rm -rf /var/lib/apt/lists/*

Rebuild the image locally to make sure it still works:

docker build -t oven-build:lint-test .
docker run --rm oven-build:lint-test cppcheck --version
docker run --rm oven-build:lint-test clang-tidy --version

You should see version strings for both tools. If either command is "not found," double-check the package names for your base image (Debian/Ubuntu use cppcheck and clang-tidy; on minimal images you may need clang-tools instead of clang-tidy).

Step 3: Create the .clang-tidy Configuration File

In the root of your repository (next to CMakeLists.txt), create a new file named exactly .clang-tidy (note the leading dot). Paste the following:

---
Checks: >
  -*,
  bugprone-*,
  cert-*,
  clang-analyzer-*,
  cppcoreguidelines-pro-*,
  cppcoreguidelines-slicing,
  misc-unused-*,
  modernize-use-nullptr,
  modernize-use-override,
  modernize-use-nodiscard,
  performance-*,
  portability-*,
  readability-braces-around-statements,
  readability-misleading-indentation,
  readability-redundant-*,
  -bugprone-easily-swappable-parameters,
  -cppcoreguidelines-pro-bounds-pointer-arithmetic

WarningsAsErrors: '*'
HeaderFilterRegex: '.*'
FormatStyle: 'file'

A few notes on what this configuration does:

  • The first line -* disables every check, then we re-enable specific families. This is the safer approach because clang-tidy ships hundreds of checks and many are too noisy.
  • WarningsAsErrors: '*' turns every remaining warning into an error. The pipeline will fail if any check fires.
  • HeaderFilterRegex: '.*' tells clang-tidy to also analyze the project's headers (otherwise it skips them).
  • The two trailing -... entries suppress specific checks that produce too many false positives in embedded code.

Commit this file:

git add .clang-tidy
git commit -m "Add .clang-tidy configuration"

Step 4: Hook clang-tidy into CMake

Open CMakeLists.txt and add the following near the top, after the project(...) line but before any add_executable or add_library calls:

# Generate compile_commands.json for tooling
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Enable clang-tidy on every compile
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
if(CLANG_TIDY_EXE)
    set(CMAKE_CXX_CLANG_TIDY
        ${CLANG_TIDY_EXE}
        --config-file=${CMAKE_SOURCE_DIR}/.clang-tidy)
    message(STATUS "clang-tidy enabled: ${CLANG_TIDY_EXE}")
else()
    message(WARNING "clang-tidy not found; lint disabled")
endif()

The reason we wrap it in if(CLANG_TIDY_EXE) is so that team members who do not have clang-tidy installed locally can still build. The CI environment does have it, so the pipeline will still enforce it.

Test it locally:

rm -rf build
docker run --rm -v $PWD:/work -w /work oven-build:lint-test \
    bash -c "cmake -B build && cmake --build build"

You should see -- clang-tidy enabled: /usr/bin/clang-tidy in the configure step, and during the build clang-tidy runs alongside the compiler. If your code is clean, the build still succeeds. If clang-tidy finds something, the build fails with a clang-tidy error message.

Step 5: Add a Cppcheck Custom Target

cppcheck does not need CMake to drive it (it parses source directly), but adding a CMake target makes it convenient to run locally. Append this to CMakeLists.txt:

# Cppcheck target (optional locally, mandatory in CI)
find_program(CPPCHECK_EXE NAMES cppcheck)
if(CPPCHECK_EXE)
    add_custom_target(cppcheck
        COMMAND ${CPPCHECK_EXE}
            --enable=warning,style,performance,portability
            --inline-suppr
            --error-exitcode=1
            --suppress=missingIncludeSystem
            --std=c++17
            -I ${CMAKE_SOURCE_DIR}/include
            ${CMAKE_SOURCE_DIR}/src
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        COMMENT "Running cppcheck on src/")
endif()

Adjust the -I include path and the source path to match your project layout if it differs.

You can now run cppcheck locally with:

cmake --build build --target cppcheck

If your code is clean, the command prints nothing and exits with status 0.

Commit your CMake changes:

git add CMakeLists.txt Dockerfile
git commit -m "Wire clang-tidy and cppcheck into the build"

Step 6: Add the lint Stage to GitLab CI

Open .gitlab-ci.yml. Add lint as the first stage in your stages: list:

stages:
  - lint
  - build
  - test

Then add a new job that runs cppcheck. Place it above your existing build job:

cppcheck:
  stage: lint
  image: $CI_REGISTRY_IMAGE/oven-build:latest
  script:
    - cppcheck
        --enable=warning,style,performance,portability
        --inline-suppr
        --error-exitcode=1
        --suppress=missingIncludeSystem
        --std=c++17
        -I include
        src

Note: clang-tidy is not a separate CI job. Because we wired it into CMAKE_CXX_CLANG_TIDY, it runs during the existing build job, on every translation unit. If clang-tidy reports an error, the build job fails just as if the compiler had failed. This is exactly the behavior we want: one pipeline run, multiple gates.

Commit and push your branch:

git add .gitlab-ci.yml
git commit -m "Add lint stage running cppcheck"
git push -u origin feature/static-analysis

Open a Merge Request from feature/static-analysis into main. The pipeline should run, the new lint stage should appear, and (assuming your code is clean) all stages should pass.

Step 7: Trigger a Failure on Purpose

Quality gates are only useful if they actually catch things. Let us prove ours does.

Pick any .cpp file in your project --- for example src/OvenController.cpp. Add the following intentionally broken function near the top of the file, but inside the same namespace and class so it actually compiles:

// Intentional bug for Week 7 lab. Remove before merge.
void OvenController::leakyDiagnostic() {
    int* readings = new int[100];
    if (sensors_.empty()) {
        return;   // <-- LEAK: never delete[] readings
    }
    for (int i = 0; i < 100; ++i) {
        readings[i] = i;
    }
    delete[] readings;
}

Also declare it in the corresponding header. Commit and push:

git add src/OvenController.cpp include/OvenController.hpp
git commit -m "Add deliberate leak (lab demo, will revert)"
git push

Now watch your Merge Request. The new pipeline run should:

  1. Start the lint stage.
  2. cppcheck reports the leak and exits non-zero.
  3. The lint job turns red.
  4. The build and test jobs are skipped (because lint failed).
  5. The Merge Request is automatically blocked from merging.

Click into the failed job and read the cppcheck output. You should see something close to:

src/OvenController.cpp:42:5: error: Memory leak: readings [memleak]

This is exactly what should happen. The pipeline did its job.

Step 8: Fix and Merge

Revert the deliberate bug:

git revert HEAD
git push

(Or remove the function manually and commit. Either is fine.)

The new pipeline should pass all stages: lint green, build green (with clang-tidy clean), test green. Merge the MR via GitLab.

You now have a working, multi-stage CI pipeline that enforces both unit tests and static analysis on every change to the codebase.

Deliverables

To be marked as complete, your main branch must contain:

  1. An updated Dockerfile with cppcheck and clang-tidy installed.
  2. A .clang-tidy configuration file at the repo root.
  3. A CMakeLists.txt that exports compile_commands.json, sets CMAKE_CXX_CLANG_TIDY, and defines a cppcheck custom target.
  4. A .gitlab-ci.yml with a lint stage running cppcheck before the build stage.
  5. A merged Merge Request whose pipeline shows four green jobs (lint, build, test, plus any others you already had).
  6. Evidence in the MR's pipeline history of at least one failed pipeline caused by the deliberate bug, followed by the fix.

Common Issues

clang-tidy reports many warnings on third-party headers (Google Test, system includes).

  • Make sure HeaderFilterRegex in .clang-tidy only matches your own headers, e.g. '^src/|^include/'.

cppcheck complains about missing system headers.

  • That is what --suppress=missingIncludeSystem is for. Make sure you kept that flag.

clang-tidy fails because it cannot find compile flags.

  • Confirm that set(CMAKE_EXPORT_COMPILE_COMMANDS ON) is in CMakeLists.txt, that build/compile_commands.json exists after configure, and that you ran CMake before clang-tidy.

The pipeline runs forever, then times out.

  • clang-tidy is significantly slower than the compiler. If a full build now takes more than 10 minutes, consider narrowing your Checks: list. Start tight, loosen as you learn which checks pull their weight.

My capstone project codebase already has dozens of warnings.

  • That is normal for an existing codebase. Two strategies: (1) fix them all in one MR, or (2) configure clang-tidy to warn-but-not-fail until you have cleaned things up. Option (1) is cleaner. Option (2) is realistic when you inherit legacy code.

Looking Ahead

Next week we leave the CI pipeline alone and turn to the system architecture of your capstone: how does your Raspberry Pi actually talk to the VM server? Sockets, REST, MQTT, and the design trade-offs between them.