name: "Tests"

on:
  push:
  pull_request:
  # Allow manually triggering the workflow.
  workflow_dispatch:

permissions: {}

jobs:

  coding-standard:
    runs-on: ubuntu-22.04
    name: Coding standards

    permissions:
      contents: read # to fetch code (actions/checkout)

    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          persist-credentials: false

      - name: Set up PHP
        uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
        with:
          php-version: 'latest'
          coverage: none
          tools: cs2pr

      # Install dependencies and handle caching in one go.
      # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
      - name: Install Composer dependencies
        uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
        with:
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      - name: Check coding standards
        id: phpcs
        run: ./vendor/bin/phpcs -s --report-full --report-checkstyle=./phpcs-report.xml

      - name: Show PHPCS results in PR
        if: ${{ always() && steps.phpcs.outcome == 'failure' }}
        run: cs2pr ./phpcs-report.xml

  lint:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        php: ['5.5', '7.2', '8.0', '8.5']
        experimental: [false]
        include:
          - php: 'nightly'
            experimental: true

    name: "Lint: PHP ${{ matrix.php }}"
    continue-on-error: ${{ matrix.experimental }}

    permissions:
      contents: read # to fetch code (actions/checkout)

    steps:
      - name: Checkout code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          persist-credentials: false

      - name: Install PHP
        uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
        with:
          php-version: ${{ matrix.php }}
          ini-values: error_reporting=-1, display_errors=On, display_startup_errors=On
          coverage: none
          tools: cs2pr

      # Install dependencies and handle caching in one go.
      # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
      - name: Install Composer dependencies
        uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
        with:
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      - name: Lint against parse errors
        if: ${{ matrix.php != 'nightly' }}
        run: composer lint -- --checkstyle | cs2pr

      - name: Lint against future parse errors (PHP nightly)
        if: ${{ matrix.php == 'nightly' }}
        run: composer lint

  test:
    needs: ['coding-standard', 'lint']
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        php: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
        extensions: ['optimal', 'minimal']
        coverage: [false]
        experimental: [false]
        include:
          # Run code coverage on high/low PHP.
          - php: '5.5'
            extensions: 'optimal'
            coverage: true
            experimental: false
          - php: '5.5'
            extensions: 'minimal'
            coverage: true
            experimental: false
          - php: '8.5'
            extensions: 'optimal'
            coverage: true
            experimental: false
          - php: '8.5'
            extensions: 'minimal'
            coverage: true
            experimental: false

          # Experimental builds. These are allowed to fail.
          - php: '8.6'
            extensions: 'optimal'
            coverage: false
            experimental: true
          - php: '8.6'
            extensions: 'minimal'
            coverage: false
            experimental: true

    name: "Test: PHP ${{ matrix.php }} - ${{ matrix.extensions }}"

    continue-on-error: ${{ matrix.experimental }}

    permissions:
      contents: read # to fetch code (actions/checkout)

    steps:
      - name: Check out code
        uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          persist-credentials: false

        # About the "extensions":
        #
        # In a "normal" test run, the "default" extension set for a PHP version is used
        # and it is ensured that certain extensions will be available, no matter what.
        #
        # For the "minimal" test run, all extensions are disabled and then only
        # a limited set of minimally required extensions are re-enabled.
        # The minimal set is based on the required extensions from PHPUnit + PHPMailer combined
        # + Curl for Composer.
        # Whether Xdebug will be enabled depends on the code coverage settings.
        #
        # Also see:
        # https://github.com/shivammathur/setup-php/?tab=readme-ov-file#heavy_plus_sign-php-extension-support
        # https://github.com/shivammathur/setup-php/wiki
      - name: Determine extensions to use
        id: set_extensions
        run: |
          if [[ "${{ matrix.extensions }}" == "optimal" ]]; then
            # Optimal.
            echo 'EXT=imap, mbstring, openssl, intl, ctype, filter, hash' >> $GITHUB_OUTPUT
            echo 'COMPOSER_OPTIONS=' >> $GITHUB_OUTPUT
          else
            # Minimal.
            echo 'EXT=none, curl, dom, json, libxml, mbstring, tokenizer, xml, xmlwriter, ctype, filter, hash' >> $GITHUB_OUTPUT
            echo 'COMPOSER_OPTIONS=--ignore-platform-req=ext-simplexml' >> $GITHUB_OUTPUT
          fi

      - name: Set up PHP
        uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # 2.36.0
        with:
          php-version: ${{ matrix.php }}
          coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}
          ini-values: sendmail_path=/usr/sbin/sendmail -t -i, error_reporting=-1, display_errors=On, display_startup_errors=On
          extensions: ${{ steps.set_extensions.outputs.EXT }}

      # Install dependencies and handle caching in one go.
      # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
      - name: Install PHP packages - normal
        if: ${{ matrix.php != '8.6' }}
        uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
        with:
          composer-options: ${{ steps.set_extensions.outputs.COMPOSER_OPTIONS }}
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      - name: Install PHP packages - ignore-platform-reqs
        if: ${{ matrix.php == '8.6' }}
        uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
        with:
          composer-options: --ignore-platform-reqs ${{ steps.set_extensions.outputs.COMPOSER_OPTIONS }}
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      # Install postfix and automatically retry if the install failed, which happens reguarly.
      # @link https://github.com/marketplace/actions/retry-step
      - name: Install postfix
        uses: nick-invision/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
        with:
          timeout_minutes: 2
          max_attempts: 3
          retry_wait_seconds: 8
          command: |
            sudo apt-get install --fix-broken -y libsqlite3-0 postfix
            sudo systemctl stop postfix.service

      - name: Set up sendmail
        run: |
          smtp-sink -d "%d.%H.%M.%S" localhost:2500 1000 &
          mkdir -p build/logs
          sudo cp test/testbootstrap-dist.php test/testbootstrap.php
          sudo chmod +x test/fakesendmail.sh
          sudo mkdir -p /var/qmail/bin
          sudo cp test/fakesendmail.sh /var/qmail/bin/sendmail
          sudo cp test/fakesendmail.sh /usr/sbin/sendmail

      - name: Run tests, no code coverage
        if: ${{ matrix.coverage == false }}
        run: ./vendor/bin/phpunit --no-coverage

      - name: Run tests with code coverage
        if: ${{ matrix.coverage == true }}
        run: vendor/bin/phpunit

      - name: Send coverage report to Codecov
        if: ${{ success() && matrix.coverage == true && github.event.repository.fork == false }}
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        with:
          files: ./build/logs/clover.xml
          fail_ci_if_error: true
          verbose: true
