diff --git a/.github/workflows/main-ci-pipeline.yml b/.github/workflows/main-ci-pipeline.yml new file mode 100644 index 00000000..f9bc2589 --- /dev/null +++ b/.github/workflows/main-ci-pipeline.yml @@ -0,0 +1,180 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + + build-job: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up CMake + uses: lukka/get-cmake@v3.31.0 + + # Run the setup script to install dependencies + - name: Run setup script + run: | + ./build-utils/pipe_setup.sh + + - name: Build project + run: | + cmake -S . -B cmake-build-debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DENABLE_COVERAGE=ON -DCOVERAGE_DIR_NAME="coverage" + cmake --build cmake-build-debug + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-artifacts + path: | + cmake-build-debug + retention-days: 7 + + test-job: + name: Test + runs-on: ubuntu-latest + needs: build-job + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up CMake + uses: lukka/get-cmake@v3.31.0 + + # Run the setup script to install dependencies + - name: Run setup script + run: | + ./build-utils/pipe_setup.sh + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifacts + + - name: Run tests + run: | + ctest --test-dir cmake-build-debug/test --rerun-failed --output-on-failure + cmake --build cmake-build-debug --target coverage + COVERAGE=$(lcov --summary cmake-build-debug/../coverage/coverage.info | awk '/lines/ {print $2}') + echo "Line coverage $COVERAGE" + + # Remove percentage symbol and compare numeric value + COVERAGE_VALUE=$(echo "$COVERAGE" | sed 's/%//') + if [ "$COVERAGE_VALUE" -lt 80 ]; then + echo "Coverage is below 80%! Failing pipeline." + # TODO once the test coverage is high enough, we should fail the pipeline here + # exit 1 + else + echo "Coverage is sufficient: $COVERAGE_VALUE%" + fi + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/report + retention-days: 7 + + clang-format: + name: Clang Format + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up CMake + uses: lukka/get-cmake@v3.31.0 + + # Run the setup script to install dependencies + - name: Run setup script + run: | + ./build-utils/pipe_setup.sh + + - name: Debug LLVM Binaries + run: | + sudo find /usr /lib /opt -name clang-format-18 -o -name clang-tidy-18 + dpkg-query -L clang-format-18 || echo "clang-format-18 not found in package files" + llvm-config --bindir || echo "llvm-config not installed or misconfigured" + echo $PATH + + + - name: Run clang-format + run: | + clang-format -style=file $(find src/ test/ -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \)) --Werror --dry-run + + clang-tidy: + name: Clang Tidy + runs-on: ubuntu-latest + needs: [build-job, test-job] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up CMake + uses: lukka/get-cmake@v3.31.0 + + # Run the setup script to install dependencies + - name: Run setup script + run: | + ./build-utils/pipe_setup.sh + + - name: Check clang-tidy version + run: | + # Check and install clang-tidy version 18 if needed + if command -v clang-tidy &> /dev/null; then + INSTALLED_VERSION=$(clang-tidy --version | grep -oP '(?<=version )\d+' | head -n 1) # Extracts major version after "version" + if [ "$INSTALLED_VERSION" -lt 18 ]; then + echo "Clang-tidy version $INSTALLED_VERSION is less than 18. Installing clang-tidy version 18..." + sudo apt-get install -y clang-tidy-18 + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-18 100 + else + echo "Clang-tidy version $INSTALLED_VERSION is sufficient." + fi + else + echo "Clang-tidy is not installed. Installing clang-tidy version 18..." + sudo apt-get install -y clang-tidy-18 + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-18 100 + fi + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifacts + + - name: Run clang-tidy + run: | + clang-tidy -p=cmake-build-debug/ $(find src/ test/ -type f \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \)) --warnings-as-errors=* --header-filter="^((src|test)/.*)$" --use-color + + run-exe: + name: Run Executable + runs-on: ubuntu-latest + needs: build-job + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up CMake + uses: lukka/get-cmake@v3.31.0 + + # Run the setup script to install dependencies + - name: Run setup script + run: | + ./build-utils/pipe_setup.sh + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: build-artifacts + + - name: Run Executable + run: ./build-utils/run_check.sh diff --git a/build-utils/bisect.py b/build-utils/bisect.py new file mode 100644 index 00000000..3e21aa04 --- /dev/null +++ b/build-utils/bisect.py @@ -0,0 +1,27 @@ +import subprocess + +def run_command(command): + """Run a shell command and check for errors.""" + result = subprocess.run(command, shell=True, text=True) + if result.returncode != 0: + print(f"Command failed: {command}") + exit(result.returncode) + +# Welcome message +print("Welcome to Bisection Hell") + +# Get user inputs +last = input('Please enter the latest (bad) commit of the bisection: ').strip() +first = input('Please enter the earliest (good) commit of the bisection: ').strip() + +# Start the bisection +print("Starting Bisection...") +run_command(f'git bisect start {last} {first}') + +# Run the test script +try: + run_command('git bisect run ./build-utils/bisect_test.sh') +finally: + # Always reset bisect, even if the script fails + print("Resetting bisection...") + run_command('git bisect reset') diff --git a/build-utils/bisect_test.sh b/build-utils/bisect_test.sh new file mode 100755 index 00000000..e70bb82f --- /dev/null +++ b/build-utils/bisect_test.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Cross-platform bisect test script +# In contrast to test.sh, this exits with a code for bisect to work + +# Ensure the project is built +source build-utils/build.sh + +# Refer to the coverage scripts if you want to add coverage testing + +# mkdir -p cmake-build-debug/coverage +# lcov -c -i -d cmake-build-debug/src -o cmake-build-debug/coverage/base.info + +# Run tests +cd ./cmake-build-debug/test || exit + +if [[ "$OSTYPE" == "cygwin"* || "$OSTYPE" == "msys"* || "$OSTYPE" == "win32"* ]]; then + ./test-surviving-sarntal.exe + res=$? +else + ./test-surviving-sarntal + res=$? +fi + +cd ../.. + +if [ $res -ne 0 ]; then + exit 1 +fi +exit 0 \ No newline at end of file diff --git a/build-utils/pipe_setup.sh b/build-utils/pipe_setup.sh new file mode 100755 index 00000000..d462b379 --- /dev/null +++ b/build-utils/pipe_setup.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Update the package list +sudo apt-get update + +# Install required dependencies +sudo apt-get install -y \ + build-essential \ + cmake \ + xorg-dev \ + libsdl2-dev \ + clang-tidy \ + clang-format \ + git \ + pre-commit \ + python3 \ + lcov + +sudo apt-get install -y software-properties-common wget build-essential cmake xorg-dev libsdl2-dev git pre-commit python3 lcov + +# Add the official LLVM APT repository for the latest Clang versions +wget https://apt.llvm.org/llvm.sh +chmod +x llvm.sh +sudo ./llvm.sh 18 # Install LLVM version 18 + +# Verify installation paths +CLANG_FORMAT_PATH=$(find /usr /lib /opt -name clang-format-18 | head -n 1) +CLANG_TIDY_PATH=$(find /usr /lib /opt -name clang-tidy-18 | head -n 1) + +# Update alternatives to point to LLVM tools +if [ -f "$CLANG_FORMAT_PATH" ]; then + echo "Setting up clang-format version 18..." + sudo update-alternatives --install /usr/bin/clang-format clang-format "$CLANG_FORMAT_PATH" 100 +else + echo "Error: clang-format-18 binary not found at $CLANG_FORMAT_PATH" +fi + +if [ -f "$CLANG_TIDY_PATH" ]; then + echo "Setting up clang-tidy version 18..." + sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy "$CLANG_TIDY_PATH" 100 +else + echo "Error: clang-tidy-18 binary not found at $CLANG_TIDY_PATH" +fi + +# Echo installed software versions +echo "Installed software versions:" +echo "--------------------------------" +echo "gcc version: $(gcc --version | head -n 1)" +echo "cmake version: $(cmake --version | head -n 1)" +echo "clang-tidy version: $(clang-tidy --version | head -n 1)" +echo "clang-format version: $(clang-format --version | head -n 1)" +echo "git version: $(git --version)" +echo "pre-commit version: $(pre-commit --version)" +echo "python3 version: $(python3 --version)" +echo "lcov version: $(lcov --version | head -n 1)" +echo "--------------------------------" diff --git a/build-utils/test.sh b/build-utils/test.sh old mode 100644 new mode 100755 diff --git a/src/output/audio/AudioService.hpp b/src/output/audio/AudioService.hpp index 9dbd30fe..b19d0c2c 100644 --- a/src/output/audio/AudioService.hpp +++ b/src/output/audio/AudioService.hpp @@ -19,7 +19,7 @@ class AudioService { void playMovingSound(floatType hikerSpeed, floatType maxHikerSpeed); void interruptMovingSound(); void playSoundWithSpeedIfNotAlreadyPlaying(const std::string &soundName, float speed) const; - void interruptSound(const std::string &soundName) const; + virtual void interruptSound(const std::string &soundName) const; private: ResourceManager &resourceManager; diff --git a/test/entities/HikerTest.cpp b/test/entities/HikerTest.cpp index e5293430..eb1f233a 100644 --- a/test/entities/HikerTest.cpp +++ b/test/entities/HikerTest.cpp @@ -66,6 +66,8 @@ TEST_F(HikerTestFixture, JumpOnceTest) { MockAudioService mockAudioService(mockResourceManager); ON_CALL(mockAudioService, playSound("jump")).WillByDefault(::testing::Return()); EXPECT_CALL(mockAudioService, playSound("jump")).Times(1); + ON_CALL(mockAudioService, interruptSound("moving")).WillByDefault(::testing::Return()); + EXPECT_CALL(mockAudioService, interruptSound("moving")).Times(1); Hiker hiker(Vector{0, 0}, mockAudioService, inputHandler, gameConstants.hikerConstants); hiker.jump(); @@ -80,6 +82,8 @@ TEST_F(HikerTestFixture, JumpTwiceTest) { MockAudioService mockAudioService(mockResourceManager); ON_CALL(mockAudioService, playSound("jump")).WillByDefault(::testing::Return()); EXPECT_CALL(mockAudioService, playSound("jump")).Times(2); + ON_CALL(mockAudioService, interruptSound("moving")).WillByDefault(::testing::Return()); + EXPECT_CALL(mockAudioService, interruptSound("moving")).Times(2); Hiker hiker(Vector{0, 0}, mockAudioService, inputHandler, gameConstants.hikerConstants); hiker.jump(); diff --git a/test/entities/MockAudioService.h b/test/entities/MockAudioService.h index 0148b278..3a512913 100644 --- a/test/entities/MockAudioService.h +++ b/test/entities/MockAudioService.h @@ -12,6 +12,7 @@ class MockAudioService : public AudioService { public: explicit MockAudioService(ResourceManager &resourceManager) : AudioService(resourceManager) {} MOCK_METHOD(void, playSound, (const std::string &soundName), (const, override)); + MOCK_METHOD(void, interruptSound, (const std::string &soundName), (const, override)); }; #endif // SURVIVING_SARNTAL_MOCKAUDIOSERVICE_H