name: pika

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

# Jobs that publish documentation raise this themselves; see RELEASE.md.
permissions:
  contents: read

jobs:
  docs:
    uses: ./.github/workflows/docs.yaml

  test-modern:
    uses: ./.github/workflows/_test.yaml
    with:
      python-versions: '["3.10","3.11","3.12","3.13","3.14"]'
      ubuntu-runner: ubuntu-latest
      legacy: false

  test-legacy:
    uses: ./.github/workflows/_test.yaml
    with:
      python-versions: '["3.7","3.8","3.9"]'
      ubuntu-runner: ubuntu-22.04
      legacy: true

  # Pre-release Python. Deliberately absent from the `tests-passed` needs list:
  # this leg reports breakage early without letting an unreleased interpreter
  # block a merge. That non-blocking property depends on branch protection
  # requiring only the `tests-passed` check, not every individual job; keep it
  # that way. Promote it into `test-modern` once 3.15 ships a final release,
  # and add the trove classifier at the same time.
  #
  # Linux-only and no coverage upload: this is an early-warning smoke test, so
  # it skips the slow, expensive Windows legs and keeps pre-release
  # coverage out of the Codecov report.
  test-preview:
    uses: ./.github/workflows/_test.yaml
    with:
      python-versions: '["3.15"]'
      ubuntu-runner: ubuntu-latest
      allow-prereleases: true
      linux-only: true
      upload-coverage: false

  # macOS is slow and occasionally flaky, so it is not run across the full
  # matrix like Linux and Windows. This single leg exercises only the latest
  # supported CPython on macos-latest (both TLS modes), enough to keep the
  # BSD-only KQueuePoller path covered by real runs. Deliberately absent from
  # the `tests-passed` needs list so a macOS flake does not block a merge; its
  # coverage still feeds Codecov (see the `coverage` job). Bump the Python
  # version here when a newer stable release joins `test-modern`.
  test-macos:
    name: build/test on macos-latest py${{ matrix.python-version }} tls=${{ matrix.test-tls }}
    runs-on: macos-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ['3.14']
        test-tls: [true, false]
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install and start RabbitMQ
        env:
          GH_TOKEN: ${{ github.token }}
        run: ./.ci/macos/gha-setup.sh
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip wheel
          python -m pip install hatch
      - name: Test with pytest
        run: hatch run test -v --cov=pika --cov-report=xml:coverage.xml ${{ matrix.test-tls && '--use-tls' || '' }}
      - name: Upload coverage artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: coverage-macos-py${{ matrix.python-version }}-tls-${{ matrix.test-tls }}
          path: coverage.xml
          retention-days: 7
          if-no-files-found: error

  coverage:
    name: upload coverage to Codecov
    runs-on: ubuntu-latest
    # Wait for the macOS leg too so its coverage (the only source for the
    # BSD-only KQueuePoller lines) is merged, but tolerate a macOS flake: run
    # as long as the blocking legs passed.
    needs: [test-modern, test-legacy, test-macos]
    if: ${{ !cancelled() && needs.test-modern.result == 'success' && needs.test-legacy.result == 'success' }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
      - name: Download coverage artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: coverage-*
          path: coverage
      - name: Verify coverage reports were collected
        run: |
          count=$(find coverage -name coverage.xml | wc -l)
          echo "collected $count coverage reports"
          test "$count" -gt 0
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          directory: ./coverage
          fail_ci_if_error: false

  tests-passed:
    runs-on: ubuntu-latest
    needs: [docs, test-modern, test-legacy, validate-docs-deploy]
    if: always()
    steps:
      - name: Check docs status
        if: ${{ needs.docs.result != 'success' }}
        run: exit 1
      - name: Check test-modern status
        if: ${{ needs.test-modern.result != 'success' }}
        run: exit 1
      - name: Check test-legacy status
        if: ${{ needs.test-legacy.result != 'success' }}
        run: exit 1
      - name: Check validate-docs-deploy status
        # Failure, not success: it is skipped on a push, where it does not run.
        if: ${{ contains(fromJSON('["failure", "cancelled"]'), needs.validate-docs-deploy.result) }}
        run: exit 1

  # Rehearse the publish path on pull requests without touching the remote, so a
  # deploy-only breakage fails here rather than mid-release. See RELEASE.md for
  # why it asks for an alias and a release version.
  validate-docs-deploy:
    if: ${{ github.event_name == 'pull_request' }}
    uses: ./.github/workflows/_deploy-docs.yaml
    with:
      ref: ${{ github.sha }}
      # Arbitrary and never published; `push: false` keeps it on the runner.
      version: '9999.0'
      aliases: latest
      set-default: true
      push: false

  # Publish `dev` from `main`, gated on the full matrix: a `push` trigger in
  # deploy-docs.yaml would race it and publish from a commit whose tests failed.
  #
  # Asking for `latest` every push is the bootstrap described in RELEASE.md, and
  # is idempotent: the alias guard declines it once a release holds the alias.
  deploy-dev-docs:
    needs: tests-passed
    if: ${{ github.event_name == 'push' }}
    # `mike` pushes `gh-pages`.
    permissions:
      contents: write
    uses: ./.github/workflows/_deploy-docs.yaml
    with:
      ref: ${{ github.sha }}
      version: dev
      aliases: latest
      set-default: true
