name: Sandbox IPC tests

# Lightweight, fast-iterating job for the out-of-process plugin sandbox IPC
# layer (src/audio/Sandbox). Deliberately separate from the heavyweight Build
# matrix (build.yml): it needs only JUCE + a C++20 compiler — no cmake-js,
# node-addon-api, ONNX, Python, or electron-builder — so it can run on every
# push / PR that touches the sandbox without the full release toolchain.
#
# This is the iteration loop for the macOS sandbox port (issue #264): a
# Linux-only developer pushes a branch and gets the macOS compiler + test
# results they can't produce locally. The POSIX backend is shared between Linux
# and macOS, so Linux gives fast signal and macOS confirms the Darwin-specific
# paths (POSIX_SPAWN_CLOEXEC_DEFAULT, SO_NOSIGPIPE, the macOS shm/sem caveats).
#
# The TSan run on the threaded audio loopback is the highest-value artifact:
# it validates the release/acquire memory ordering on arm64 (macos-14) that a
# Linux-only dev cannot otherwise exercise.

on:
  push:
    paths:
      - 'src/audio/Sandbox/**'
      - 'src/vst-host/**'
      - 'tests/sandbox/**'
      - '.github/workflows/sandbox.yml'
  pull_request:
    paths:
      - 'src/audio/Sandbox/**'
      - 'src/vst-host/**'
      - 'tests/sandbox/**'
      - '.github/workflows/sandbox.yml'
  workflow_dispatch:

# Least privilege: these jobs only check out code and upload artifacts.
# actions/checkout needs contents:read; upload-artifact uses its own runtime
# token. Everything else defaults to none.
permissions:
  contents: read

jobs:
  sandbox-tests:
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
          - os: macos-14
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive   # JUCE

      # JUCE on Linux needs the usual X11/ALSA dev headers even for juce_core's
      # transitive config; install the minimal set. (No-op on macOS.)
      - name: Install Linux deps
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            ninja-build libasound2-dev libx11-dev libxext-dev libxinerama-dev \
            libxrandr-dev libxcursor-dev libfreetype6-dev libfontconfig1-dev

      - name: Install Ninja (macOS)
        if: runner.os == 'macOS'
        run: brew install ninja

      # Build + run three ways: plain (Debug), ASan, TSan. Each is a fresh
      # configure of the JUCE-only standalone harness, then ctest.
      - name: Build + test (plain, ASan, TSan)
        shell: bash
        run: |
          set -euo pipefail
          for variant in plain address thread; do
            echo "::group::$variant"
            args=(-S tests/sandbox/standalone -B "build/sbx-$variant" -G Ninja \
                  -DCMAKE_BUILD_TYPE=Debug)
            if [[ "$variant" != "plain" ]]; then
              args+=(-DSLOPSMITH_SANITIZE="$variant")
            fi
            cmake "${args[@]}"
            cmake --build "build/sbx-$variant"
            # TSAN/ASAN: fail the job on any sanitizer diagnostic.
            TSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="halt_on_error=1" \
              ctest --test-dir "build/sbx-$variant" --output-on-failure
            echo "::endgroup::"
          done

      - name: Upload sandbox child log on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: sandbox-logs-${{ matrix.os }}
          path: |
            build/sbx-*/Testing/**/*.log
          if-no-files-found: ignore

  # Heavier end-to-end job: builds a passthrough VST3 + the real
  # slopsmith-vst-host, spawns it from a host-side driver, and round-trips audio
  # over the shm ring (posix_spawn + fd inheritance + handshake + process +
  # state + shutdown). Separate from the unit job because it pulls in
  # juce_audio_processors / juce_gui_basics / the VST3 plugin client.
  sandbox-e2e:
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
          - os: macos-14
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      # Full JUCE Linux GUI/audio dep set (gui_basics + audio_processors).
      - name: Install Linux deps
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y --no-install-recommends \
            ninja-build xvfb \
            libasound2-dev libjack-jackd2-dev libfreetype6-dev libfontconfig1-dev \
            libx11-dev libxcomposite-dev libxcursor-dev libxext-dev \
            libxinerama-dev libxrandr-dev libxrender-dev libglu1-mesa-dev \
            mesa-common-dev libcurl4-openssl-dev libwebkit2gtk-4.1-dev

      - name: Install Ninja (macOS)
        if: runner.os == 'macOS'
        run: brew install ninja

      - name: Build e2e harness
        run: |
          cmake -S tests/sandbox/e2e -B build/e2e -G Ninja -DCMAKE_BUILD_TYPE=Debug
          cmake --build build/e2e

      # The vst-host loads a no-editor VST3, so no display is strictly needed;
      # run under xvfb on Linux anyway as insurance against any X11 touch in
      # juce_gui_basics init. macOS has a window server.
      - name: Run e2e (Linux, xvfb)
        if: runner.os == 'Linux'
        run: xvfb-run -a ctest --test-dir build/e2e --output-on-failure

      - name: Run e2e (macOS)
        if: runner.os == 'macOS'
        run: ctest --test-dir build/e2e --output-on-failure

      - name: Upload vst-host log on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: sandbox-e2e-logs-${{ matrix.os }}
          path: |
            ${{ runner.temp }}/slopsmith-vst-host-*.log
            /tmp/slopsmith-vst-host-*.log
          if-no-files-found: ignore
          retention-days: 7
