name: CI (sanitize)

on:
  workflow_dispatch: # allows manual triggering
  push:
    branches:
      - master
    paths: [
      '.github/workflows/build-sanitize.yml',
      '**/CMakeLists.txt',
      '**/.cmake',
      '**/*.h',
      '**/*.hpp',
      '**/*.c',
      '**/*.cpp'
    ]

  pull_request:
    types: [opened, synchronize, reopened]
    paths: [
      '.github/workflows/build-sanitize.yml'
    ]

cache-mode: none
permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }}
  cancel-in-progress: true

env:
  GGML_NLOOP: 3
  GGML_N_THREADS: 1
  LLAMA_ARG_LOG_COLORS: 1
  LLAMA_ARG_LOG_PREFIX: 1
  LLAMA_ARG_LOG_TIMESTAMPS: 1

jobs:
  ctest:
    continue-on-error: true

    strategy:
      matrix:
        include:
        # thread and address doesn't run properly on some self hosted machines, so run it on Github instead
        - sanitizer: ADDRESS
          machine: ubuntu-24.04
        - sanitizer: THREAD
          machine: ubuntu-24.04
        - sanitizer: UNDEFINED
          machine: [self-hosted, X64, Linux]

    runs-on: ${{ matrix.machine }}

    steps:
      - name: Clone
        id: checkout
        uses: actions/checkout@v6

      # - name: ccache
      #   uses: ggml-org/ccache-action@v1.2.24
      #   if: ${{ matrix.sanitizer != 'UNDEFINED' }}
      #   with:
      #     key: ctest-${{ matrix.sanitizer }}-ubuntu-24.04
      #     variant: ccache
      #     evict-old-files: 1d
      #     save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}

      # with UNDEFINED sanitizer, we have to build in Debug to avoid GCC 13 false-positive warnings
      - name: Build (undefined)
        id: cmake_build_undefined
        if: ${{ matrix.sanitizer == 'UNDEFINED' }}
        run: |
          cmake -B build \
            -DCMAKE_BUILD_TYPE=Debug \
            -DLLAMA_FATAL_WARNINGS=ON \
            -DLLAMA_SANITIZE_${{ matrix.sanitizer }}=ON \
            -DGGML_SANITIZE_${{ matrix.sanitizer }}=ON

          cmake --build build --config Debug -j $(nproc)

      - name: Build
        id: cmake_build
        if: ${{ matrix.sanitizer == 'ADDRESS' }}
        run: |
          cmake -B build \
            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
            -DLLAMA_SANITIZE_${{ matrix.sanitizer }}=ON \
            -DGGML_SANITIZE_${{ matrix.sanitizer }}=ON

          cmake --build build --config RelWithDebInfo -j $(nproc)

      - name: Build (no OpenMP)
        id: cmake_build_no_openmp
        if: ${{ matrix.sanitizer == 'THREAD' }}
        run: |
          cmake -B build \
            -DCMAKE_BUILD_TYPE=RelWithDebInfo \
            -DLLAMA_SANITIZE_${{ matrix.sanitizer }}=ON \
            -DGGML_SANITIZE_${{ matrix.sanitizer }}=ON \
            -DGGML_OPENMP=OFF

          cmake --build build --config RelWithDebInfo -j $(nproc)

      - name: Test
        id: cmake_test
        run: |
          cd build
          ctest -L main -E tokenizer --verbose --timeout 900
