name: C++ Tests

# Trigger keywords in commit message (checked via head_commit.message):
#   /test-cgal      — full CGAL test suite (277 tests, ~5 min build)
#   /quality-gates  — license, codespell, shellcheck, CGAL conventions
#
# ⚠  /ci-all was removed: the Pi runner (3-4 GB RAM) cannot sustain
#    multiple Docker containers simultaneously.  Use one keyword per commit.
#
# Examples:
#   git commit -m "fix: correct angle formula /test-cgal"
#   git commit -m "chore: update headers /quality-gates"
#
# test-fast always runs on every push — it is fast (< 5 s) and cheap.

on:
  push:
    branches:
      - main
      - "claude/**"
      - "feature/**"
      - "review/**"
  pull_request:

# ─────────────────────────────────────────────────────────────────────────────
# Job 1 — test-fast
#   Pure-math tests (Clausen, ImLi₂, Hyper-ideal geometry).
#   No CGAL, no Boost. Eigen + GTest only. Runs on EVERY push.
# ─────────────────────────────────────────────────────────────────────────────
jobs:
  test-fast:
    runs-on: eulernest
    container:
      image: git.eulernest.eu/conformallab/ci-cpp:latest
      options: "--memory=800m --memory-swap=1200m"

    steps:
      - uses: actions/checkout@v4

      - name: Configure (tests-only)
        run: cmake -S code -B build -DCMAKE_BUILD_TYPE=Release

      - name: Build
        run: nice -n 19 cmake --build build --target conformallab_tests -j$(nproc)

      - name: Run tests
        run: >
          ctest --test-dir build
          --output-on-failure
          --output-junit test-results.xml

      - name: Summary
        if: always()
        run: |
          if [ -f test-results.xml ]; then
            total=$(grep -o 'tests="[0-9]*"'     test-results.xml | grep -o '[0-9]*' | head -1)
            failed=$(grep -o 'failures="[0-9]*"' test-results.xml | grep -o '[0-9]*' | head -1)
            skipped=$(grep -o 'skipped="[0-9]*"' test-results.xml | grep -o '[0-9]*' | head -1)
            passed=$(( ${total:-0} - ${failed:-0} - ${skipped:-0} ))
            echo "FAST  ▸ TOTAL ${total:-0} | PASSED $passed | FAILED ${failed:-0} | SKIPPED ${skipped:-0}"
          fi

# ─────────────────────────────────────────────────────────────────────────────
# Job 2 — test-cgal
#
# Trigger: include "/test-cgal" anywhere in the commit message.
#
#   git commit -m "fix: correct angle formula /test-cgal"
#
# Why keyword-triggered (not automatic on every push):
#   The Pi runner (3-4 GB RAM, swap heavily loaded) cannot sustain a
#   CGAL build on every WIP commit.  Adding the keyword to a commit
#   message explicitly signals "this commit is ready for full testing".
#
# LOW_MEMORY_BUILD applies four RAM-saving measures so the build fits in
# a 2000 MB container: -O0, no PCH, unity batch 1, --no-keep-memory.
# See code/tests/cgal/CMakeLists.txt for the full explanation.
# ─────────────────────────────────────────────────────────────────────────────
  test-cgal:
    needs: test-fast
    if: contains(github.event.head_commit.message, '/test-cgal')
    runs-on: eulernest
    container:
      image: git.eulernest.eu/conformallab/ci-cpp:latest
      # 2000 MB hard limit; 1000 MB swap headroom (memory-swap = RAM + swap).
      # With LOW_MEMORY_BUILD peak per TU is ~150-200 MB → well within limit.
      options: "--memory=2000m --memory-swap=3000m"

    steps:
      - uses: actions/checkout@v4

      - name: Configure (LOW_MEMORY_BUILD — -O0, no PCH, unity batch 1)
        run: |
          cmake -S code -B build \
            -DWITH_CGAL_TESTS=ON \
            -DCMAKE_BUILD_TYPE=Release \
            -DCONFORMALLAB_LOW_MEMORY_BUILD=ON

      - name: Build CGAL-Tests
        run: nice -n 19 cmake --build build --target conformallab_cgal_tests -j1

      - name: Run CGAL-Tests
        run: >
          ctest --test-dir build
          -R "^cgal\."
          --output-on-failure
          --output-junit cgal-results.xml

      - name: Summary
        if: always()
        run: |
          if [ -f cgal-results.xml ]; then
            total=$(grep -o 'tests="[0-9]*"'     cgal-results.xml | grep -o '[0-9]*' | head -1)
            failed=$(grep -o 'failures="[0-9]*"' cgal-results.xml | grep -o '[0-9]*' | head -1)
            skipped=$(grep -o 'skipped="[0-9]*"' cgal-results.xml | grep -o '[0-9]*' | head -1)
            passed=$(( ${total:-0} - ${failed:-0} - ${skipped:-0} ))
            echo "CGAL  ▸ TOTAL ${total:-0} | PASSED $passed | FAILED ${failed:-0} | SKIPPED ${skipped:-0}"
          fi

      - name: Verify test-count consistency (doc/api/tests.md)
        run: BUILD_DIR=build bash scripts/check-test-counts.sh

      - name: End-to-end smoke test (scripts/try_it.sh)
        run: bash scripts/try_it.sh

# ─────────────────────────────────────────────────────────────────────────────
# Job 3 — quality-gates
#
# Trigger: include "/quality-gates" anywhere in the commit message.
#
#   git commit -m "chore: update docs /quality-gates"
#
# Cheap (~30 s): license headers, CGAL conventions, codespell, shellcheck.
# ─────────────────────────────────────────────────────────────────────────────
  quality-gates:
    if: contains(github.event.head_commit.message, '/quality-gates')
    runs-on: eulernest
    container:
      image: git.eulernest.eu/conformallab/ci-cpp:latest
      options: "--memory=600m --memory-swap=900m"

    steps:
      - uses: actions/checkout@v4

      - name: Install codespell + shellcheck (job-local)
        run: |
          apt-get update -qq
          apt-get install -y --no-install-recommends \
            codespell shellcheck

      - name: License headers (every C++ source carries MIT SPDX)
        run: bash scripts/quality/license-headers.sh

      - name: CGAL conventions (6 rules over CGAL public headers)
        run: python3 scripts/quality/cgal-conventions.py

      - name: codespell (docs + source comments + script messages)
        run: bash scripts/quality/codespell.sh

      - name: shellcheck (scripts/**/*.sh, severity=warning, strict)
        run: bash scripts/quality/shellcheck.sh --strict

      - name: Install lcov (coverage gate)
        run: apt-get install -y --no-install-recommends lcov

      - name: Coverage gate (fast-test suite, ramp-up mode)
        # SKIP_COVERAGE_GATE=1: reports numbers but does not fail on threshold.
        # Remove once I5 is resolved (coverage is wired to the full CGAL suite,
        # not just the 26 fast tests).  Threshold: 80% line / 70% branch / 90% func.
        run: SKIP_COVERAGE_GATE=1 bash scripts/quality/coverage.sh

      - name: Summary
        if: always()
        run: |
          echo "QUALITY ▸ all gates passed."
          echo "  see scripts/quality/README.md for the full catalogue"
          echo "  (sanitizers, clang-tidy, coverage report in build-coverage/)"
