Weekly Memory & Sanitizer Checks #15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Weekly Memory & Sanitizer Checks | |
| on: | |
| schedule: | |
| # Run every Monday at 3 AM UTC | |
| - cron: '0 3 * * 1' | |
| workflow_dispatch: # Allow manual trigger | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| sanitizers: | |
| name: Sanitizer Checks | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| sanitizer: | |
| - name: AddressSanitizer | |
| cmake_flag: ENABLE_ASAN | |
| env_options: "ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:strict_init_order=1" | |
| - name: UndefinedBehaviorSanitizer | |
| cmake_flag: ENABLE_UBSAN | |
| env_options: "UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1" | |
| - name: AddressSanitizer+UndefinedBehaviorSanitizer | |
| cmake_flag: ENABLE_ASAN ENABLE_UBSAN | |
| env_options: "ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install system prerequisites | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgraphviz-dev \ | |
| libboost-all-dev \ | |
| nlohmann-json3-dev \ | |
| pybind11-dev | |
| - name: Cache SimGrid | |
| uses: actions/cache@v4 | |
| id: cache-simgrid | |
| with: | |
| path: /opt/simgrid | |
| key: ${{ runner.os }}-simgrid-python-v2 | |
| - name: Install SimGrid | |
| if: steps.cache-simgrid.outputs.cache-hit != 'true' | |
| run: | | |
| git clone --depth 1 https://framagit.org/simgrid/simgrid.git | |
| cd simgrid | |
| cmake -B build -Denable_smpi=OFF -Denable_model-checking=OFF -Denable_python=ON -DCMAKE_INSTALL_PREFIX=/opt/simgrid | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Cache FSMod | |
| uses: actions/cache@v4 | |
| id: cache-fsmod | |
| with: | |
| path: /opt/fsmod | |
| key: ${{ runner.os }}-fsmod-python-v2 | |
| - name: Install FSMod | |
| if: steps.cache-fsmod.outputs.cache-hit != 'true' | |
| run: | | |
| git clone --depth 1 https://github.com/simgrid/file-system-module.git | |
| cd file-system-module | |
| cmake -B build -Denable_lib_in_jar=OFF -Denable_sthread=OFF -Denable_python=ON -DCMAKE_INSTALL_PREFIX=/opt/fsmod -DCMAKE_PREFIX_PATH=/opt/simgrid | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Cache Google Test | |
| uses: actions/cache@v4 | |
| id: cache-gtest | |
| with: | |
| path: /opt/gtest | |
| key: ${{ runner.os }}-gtest-v1 | |
| - name: Install Google Test | |
| if: steps.cache-gtest.outputs.cache-hit != 'true' | |
| run: | | |
| wget -q https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz | |
| tar xf release-1.11.0.tar.gz | |
| cd googletest-release-1.11.0 | |
| cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/gtest | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Build with ${{ matrix.sanitizer.name }} | |
| env: | |
| CMAKE_PREFIX_PATH: /opt/simgrid:/opt/fsmod:/opt/gtest | |
| run: | | |
| FLAGS="" | |
| for flag in ${{ matrix.sanitizer.cmake_flag }}; do | |
| FLAGS="$FLAGS -D${flag}=ON" | |
| done | |
| cmake -B build $FLAGS -DCMAKE_BUILD_TYPE=Debug | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| cmake --build build --target unit_tests -j$(nproc) | |
| - name: Run tests with ${{ matrix.sanitizer.name }} | |
| env: | |
| LD_LIBRARY_PATH: /opt/simgrid/lib:/opt/fsmod/lib:/usr/local/lib | |
| run: | | |
| export ${{ matrix.sanitizer.env_options }} | |
| # Dynamically find Python paths for SimGrid, FSMod, and DTLMod | |
| SIMGRID_PYTHON_PATH=$(find /opt/simgrid/lib -type d -path "*/python*/site-packages" 2>/dev/null | head -1) | |
| [ -z "$SIMGRID_PYTHON_PATH" ] && SIMGRID_PYTHON_PATH=$(find /opt/simgrid/lib -type d -path "*/python*/dist-packages" 2>/dev/null | head -1) | |
| FSMOD_PYTHON_PATH=$(find /opt/fsmod/lib -type d -path "*/python*/site-packages" 2>/dev/null | head -1) | |
| [ -z "$FSMOD_PYTHON_PATH" ] && FSMOD_PYTHON_PATH=$(find /opt/fsmod/lib -type d -path "*/python*/dist-packages" 2>/dev/null | head -1) | |
| DTLMOD_PYTHON_PATH=$(find /usr/local/lib -type d -path "*/python*/dist-packages" 2>/dev/null | head -1) | |
| [ -z "$DTLMOD_PYTHON_PATH" ] && DTLMOD_PYTHON_PATH=$(find /usr/local/lib -type d -path "*/python*/site-packages" 2>/dev/null | head -1) | |
| export PYTHONPATH="$SIMGRID_PYTHON_PATH:$FSMOD_PYTHON_PATH:$DTLMOD_PYTHON_PATH" | |
| echo "PYTHONPATH=$PYTHONPATH" | |
| cd build | |
| ./unit_tests 2>&1 | tee sanitizer-output.txt | |
| # Skip Python tests when running with sanitizers - ASan/UBSan don't work well with Python | |
| # The C++ unit tests already cover the core functionality | |
| echo "Skipping Python tests with sanitizers (ASan runtime incompatible with Python interpreter)" | tee -a sanitizer-output.txt | |
| cd ../.. | |
| - name: Upload sanitizer report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: ${{ matrix.sanitizer.name }}-report | |
| path: build/sanitizer-output.txt | |
| retention-days: 30 | |
| - name: Check for sanitizer errors | |
| run: | | |
| # Filter out known benign warnings from SimGrid's context switching | |
| if grep -E "ERROR:|WARNING:" build/sanitizer-output.txt | grep -v "ASan is ignoring requested __asan_handle_no_return"; then | |
| echo "::error::${{ matrix.sanitizer.name }} detected issues!" | |
| exit 1 | |
| fi | |
| valgrind: | |
| name: Valgrind Memory Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install system prerequisites | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgraphviz-dev \ | |
| libboost-all-dev \ | |
| nlohmann-json3-dev \ | |
| pybind11-dev \ | |
| valgrind | |
| - name: Cache SimGrid | |
| uses: actions/cache@v4 | |
| id: cache-simgrid | |
| with: | |
| path: /opt/simgrid | |
| key: ${{ runner.os }}-simgrid-python-v2 | |
| - name: Install SimGrid | |
| if: steps.cache-simgrid.outputs.cache-hit != 'true' | |
| run: | | |
| git clone --depth 1 https://framagit.org/simgrid/simgrid.git | |
| cd simgrid | |
| cmake -B build -Denable_smpi=OFF -Denable_model-checking=OFF -Denable_python=ON -DCMAKE_INSTALL_PREFIX=/opt/simgrid | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Cache FSMod | |
| uses: actions/cache@v4 | |
| id: cache-fsmod | |
| with: | |
| path: /opt/fsmod | |
| key: ${{ runner.os }}-fsmod-python-v2 | |
| - name: Install FSMod | |
| if: steps.cache-fsmod.outputs.cache-hit != 'true' | |
| run: | | |
| git clone --depth 1 https://github.com/simgrid/file-system-module.git | |
| cd file-system-module | |
| cmake -B build -Denable_lib_in_jar=OFF -Denable_sthread=OFF -Denable_python=ON -DCMAKE_INSTALL_PREFIX=/opt/fsmod -DCMAKE_PREFIX_PATH=/opt/simgrid | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Cache Google Test | |
| uses: actions/cache@v4 | |
| id: cache-gtest | |
| with: | |
| path: /opt/gtest | |
| key: ${{ runner.os }}-gtest-v1 | |
| - name: Install Google Test | |
| if: steps.cache-gtest.outputs.cache-hit != 'true' | |
| run: | | |
| wget -q https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz | |
| tar xf release-1.11.0.tar.gz | |
| cd googletest-release-1.11.0 | |
| cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/gtest | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| - name: Build with Valgrind support | |
| env: | |
| CMAKE_PREFIX_PATH: /opt/simgrid:/opt/fsmod:/opt/gtest | |
| run: | | |
| cmake -B build -DENABLE_VALGRIND=ON -DCMAKE_BUILD_TYPE=Debug | |
| cmake --build build -j$(nproc) | |
| sudo cmake --install build | |
| cmake --build build --target unit_tests -j$(nproc) | |
| - name: Run Valgrind checks | |
| env: | |
| LD_LIBRARY_PATH: /opt/simgrid/lib:/opt/fsmod/lib:/usr/local/lib | |
| run: | | |
| cd build | |
| cmake --build . --target valgrind | |
| - name: Upload Valgrind report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: valgrind-report | |
| path: build/valgrind-output.txt | |
| retention-days: 30 | |
| - name: Parse Valgrind output | |
| if: always() | |
| run: | | |
| cd build | |
| echo "## Valgrind Memory Check Summary" > valgrind-summary.txt | |
| echo "" >> valgrind-summary.txt | |
| if [ -f valgrind-output.txt ]; then | |
| # Extract key metrics | |
| DEFINITELY_LOST=$(grep "definitely lost:" valgrind-output.txt | tail -1 | awk '{print $4}') | |
| INDIRECTLY_LOST=$(grep "indirectly lost:" valgrind-output.txt | tail -1 | awk '{print $4}') | |
| POSSIBLY_LOST=$(grep "possibly lost:" valgrind-output.txt | tail -1 | awk '{print $4}') | |
| STILL_REACHABLE=$(grep "still reachable:" valgrind-output.txt | tail -1 | awk '{print $4}') | |
| ERROR_SUMMARY=$(grep "ERROR SUMMARY:" valgrind-output.txt | tail -1) | |
| echo "### Memory Leak Summary:" >> valgrind-summary.txt | |
| echo "- Definitely lost: ${DEFINITELY_LOST:-0} bytes" >> valgrind-summary.txt | |
| echo "- Indirectly lost: ${INDIRECTLY_LOST:-0} bytes" >> valgrind-summary.txt | |
| echo "- Possibly lost: ${POSSIBLY_LOST:-0} bytes" >> valgrind-summary.txt | |
| echo "- Still reachable: ${STILL_REACHABLE:-0} bytes" >> valgrind-summary.txt | |
| echo "" >> valgrind-summary.txt | |
| echo "### ${ERROR_SUMMARY}" >> valgrind-summary.txt | |
| cat valgrind-summary.txt | |
| # Check for critical errors | |
| if grep -qP "ERROR SUMMARY: [1-9]" valgrind-output.txt; then | |
| echo "::error::Valgrind detected memory errors!" | |
| exit 1 | |
| fi | |
| if [ "${DEFINITELY_LOST:-0}" != "0" ]; then | |
| echo "::error::Valgrind detected definite memory leaks!" | |
| exit 1 | |
| fi | |
| else | |
| echo "::error::Valgrind output file not found!" | |
| exit 1 | |
| fi | |
| - name: Upload summary | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: valgrind-summary | |
| path: build/valgrind-summary.txt | |
| retention-days: 30 | |
| report: | |
| name: Generate Report | |
| needs: [sanitizers, valgrind] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: reports | |
| - name: Generate summary report | |
| run: | | |
| echo "# Weekly Memory & Sanitizer Checks Report" > weekly-report.md | |
| echo "" >> weekly-report.md | |
| echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> weekly-report.md | |
| echo "Commit: ${{ github.sha }}" >> weekly-report.md | |
| echo "" >> weekly-report.md | |
| echo "## Test Results" >> weekly-report.md | |
| echo "" >> weekly-report.md | |
| # Check sanitizer results | |
| if [ -d "reports" ]; then | |
| for report in reports/*-report/*.txt; do | |
| if [ -f "$report" ]; then | |
| echo "### $(basename $(dirname $report))" >> weekly-report.md | |
| # Filter out benign warnings from SimGrid context switching and Valgrind redirections | |
| if grep -E "ERROR:|WARNING:" "$report" 2>/dev/null | grep -v "ASan is ignoring requested __asan_handle_no_return" | grep -v "new redirection conflicts" | grep -q .; then | |
| echo "❌ Issues detected" >> weekly-report.md | |
| else | |
| echo "✅ No issues detected" >> weekly-report.md | |
| fi | |
| echo "" >> weekly-report.md | |
| fi | |
| done | |
| fi | |
| cat weekly-report.md | |
| - name: Upload weekly report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: weekly-report | |
| path: weekly-report.md | |
| retention-days: 90 | |
| - name: Comment on latest commit (on failure) | |
| if: failure() && github.event_name == 'schedule' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let body = '## ⚠️ Weekly Memory & Sanitizer Checks Failed\n\n'; | |
| body += 'The weekly automated checks detected issues. Please review the artifacts.\n\n'; | |
| body += `[View workflow run](${context.payload.repository.html_url}/actions/runs/${context.runId})`; | |
| github.rest.repos.createCommitComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| commit_sha: context.sha, | |
| body: body | |
| }); |