name: make test

on: [push, pull_request]

env:
  CTEST_PARALLEL_LEVEL: "1"
  CMAKE_BUILD_PARALLEL_LEVEL: "4"

permissions:
  contents: read

jobs:
  ubuntu:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@1.67
    - name: Install deps
      run: |
        sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
        sudo pip3 install pexpect
        # Generate a locale that uses a comma as decimal separator.
        sudo locale-gen fr_FR.UTF-8
    - name: cmake
      env:
          # Some warnings upgraded to errors to match Open Build Service platforms
          CXXFLAGS: "-Werror=address -Werror=return-type"
      run: |
        mkdir build && cd build
        cmake ..
    - name: make
      run: |
        make
    - name: make test
      run: |
        make test

  ubuntu-32bit-static-pcre2:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@1.67
      with:
        targets: "i686-unknown-linux-gnu" # rust-toolchain wants this comma-separated
    - name: Install deps
      run: |
        sudo apt update
        sudo apt install gettext lib32ncurses5-dev python3-pip g++-multilib tmux
        sudo pip3 install pexpect
    - name: cmake
      env:
          CXXFLAGS: "-m32 -Werror=address -Werror=return-type"
          CFLAGS: "-m32"
      run: |
        mkdir build && cd build
        cmake -DFISH_USE_SYSTEM_PCRE2=OFF -DRust_CARGO_TARGET=i686-unknown-linux-gnu ..
    - name: make
      run: |
        make VERBOSE=1
    - name: make test
      run: |
        make test

  ubuntu-asan:

    runs-on: ubuntu-latest
    env:
        # Rust has two different memory sanitizers of interest; they can't be used at the same time:
        # * AddressSanitizer detects out-of-bound access, use-after-free, use-after-return,
        #   use-after-scope, double-free, invalid-free, and memory leaks.
        # * MemorySanitizer detects uninitialized reads.
        #
        RUSTFLAGS: "-Zsanitizer=address"
        # RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"

    steps:
    - uses: actions/checkout@v4
    # All -Z options require running nightly
    - uses: dtolnay/rust-toolchain@nightly
      with:
        # ASAN uses `cargo build -Zbuild-std` which requires the rust-src component
        # this is comma-separated
        components: rust-src
    - name: Install deps
      run: |
        sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
        sudo pip3 install pexpect
    - name: cmake
      env:
          CC: clang
          CXX: clang++
          CXXFLAGS: "-fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -DFISH_CI_SAN"
      run: |
        mkdir build && cd build
        # Rust's ASAN requires the build system to explicitly pass a --target triple. We read that
        # value from CMake variable Rust_CARGO_TARGET (shared with corrosion).
        cmake .. -DASAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu -DCMAKE_BUILD_TYPE=Debug
    - name: make
      run: |
        make
    - name: make test
      env:
          FISH_CI_SAN: 1
          ASAN_OPTIONS: check_initialization_order=1:detect_stack_use_after_return=1:detect_leaks=1:fast_unwind_on_malloc=0
          # use_tls=0 is a workaround for LSAN crashing with "Tracer caught signal 11" (SIGSEGV),
          # which seems to be an issue with TLS support in newer glibc versions under virtualized
          # environments. Follow https://github.com/google/sanitizers/issues/1342 and
          # https://github.com/google/sanitizers/issues/1409 to track this issue.
          # UPDATE: this can cause spurious leak reports for __cxa_thread_atexit_impl() under glibc.
          LSAN_OPTIONS: verbosity=0:log_threads=0:use_tls=1:print_suppressions=0
      run: |
        llvm_version=$(clang --version | awk 'NR==1 { split($NF, version, "."); print version[1] }')
        export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-$llvm_version
        export LSAN_OPTIONS="$LSAN_OPTIONS:suppressions=$PWD/build_tools/lsan_suppressions.txt"
        make test

  # Our clang++ tsan builds are not recognizing safe rust patterns (such as the fact that Drop
  # cannot be called while a thread is using the object in question). Rust has its own way of
  # running TSAN, but for the duration of the port from C++ to Rust, we'll keep this disabled.

  # ubuntu-threadsan:
  #
  #   runs-on: ubuntu-latest
  #
  #   steps:
  #   - uses: actions/checkout@v4
  #   - uses: dtolnay/rust-toolchain@1.67
  #   - name: Install deps
  #     run: |
  #       sudo apt install gettext libncurses5-dev libpcre2-dev python3-pip tmux
  #       sudo pip3 install pexpect
  #   - name: cmake
  #     env:
  #         FISH_CI_SAN: 1
  #         CC: clang
  #         CXX: clang++
  #         CXXFLAGS: "-fsanitize=thread"
  #     run: |
  #       mkdir build && cd build
  #       cmake ..
  #   - name: make
  #     run: |
  #       make
  #   - name: make test
  #     run: |
  #       make test

  macos:

    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@1.67
    - name: Install deps
      run: |
        sudo pip3 install pexpect
        brew install tmux
    - name: cmake
      run: |
        mkdir build && cd build
        cmake -DWITH_GETTEXT=NO ..
    - name: make
      run: |
        make
    - name: make test
      run: |
        make test