name: Test

on:
  # Run on pushes to `stable` and `develop` and on all pull requests.
  push:
    branches:
      - stable
      - develop
    paths-ignore:
      - '**.md'
  pull_request:
  # Allow manually triggering the workflow.
  workflow_dispatch:

# Cancels all previous workflow runs for the same branch that have not yet completed.
concurrency:
  # The concurrency group contains the workflow name and the branch name.
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    if: ${{ github.ref != 'refs/heads/develop' }}
    runs-on: ubuntu-latest

    strategy:
      matrix:
        php: ['5.4', '7.0', '7.4', '8.0', '8.4', 'nightly']

    name: "Lint: PHP ${{ matrix.php }}"

    continue-on-error: ${{ matrix.php == 'nightly' }}

    steps:
      - name: Checkout code
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false

      - name: Install PHP
        uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
        with:
          php-version: ${{ matrix.php }}
          ini-file: 'development'
          coverage: none
          tools: cs2pr

      - name: Install Composer dependencies
        uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
        with:
          # For the PHP "nightly", we need to install with ignore platform reqs as not all dependencies may allow it yet.
          composer-options: ${{ matrix.php == 'nightly' && '--ignore-platform-req=php+' || '' }}
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      - name: "Lint against parse errors"
        run: composer lint -- --checkstyle | cs2pr

  #### TEST STAGE ####
  test:
    if: ${{ github.ref != 'refs/heads/develop' }}
    runs-on: ubuntu-latest

    strategy:
      matrix:
        # The GHA matrix works different from Travis.
        # You can define jobs here and then augment them with extra variables in `include`,
        # as well as add extra jobs in `include`.
        # @link https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
        #
        # The matrix is set up so as not to duplicate the builds which are run for code coverage.
        php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.5']
        phpcs_version: ['lowest', '^3.13', '4.0.0', 'stable']
        phpcsutils_version: ['stable']

        exclude:
          # Note, since the release of PHPCS 4.0, 'stable' == latest 4.x.
          - php: '5.5'
            phpcs_version: '4.0.0'
          - php: '5.5'
            phpcs_version: 'stable'
          - php: '5.6'
            phpcs_version: '4.0.0'
          - php: '5.6'
            phpcs_version: 'stable'
          - php: '7.0'
            phpcs_version: '4.0.0'
          - php: '7.0'
            phpcs_version: 'stable'
          - php: '7.1'
            phpcs_version: '4.0.0'
          - php: '7.1'
            phpcs_version: 'stable'
          # Also exclude the 7.2/stable build as that's already run in code coverage, no need to duplicate.
          - php: '7.2'
            phpcs_version: 'stable'

        include:
          # Add some builds with variations of the dependency versions.
          # Note: the PHPCS low/stable + Utils stable combi is already run via the above matrix.
          # And the PHPCS low + Utils low combi is run in the code coverage builds.
          # So these are the only combis missing.
          - php: '5.4'
            phpcs_version: '^3.13'
            phpcsutils_version: 'lowest'
          - php: '7.2'
            phpcs_version: 'stable'
            phpcsutils_version: 'lowest'
          - php: '8.4'
            phpcs_version: '^3.13'
            phpcsutils_version: 'lowest'
          - php: '8.4'
            phpcs_version: 'stable'
            phpcsutils_version: 'lowest'

          # Test against dev versions of all dependencies with select PHP versions for early detection of issues.
          - php: '5.4'
            phpcs_version: '3.x-dev'
            phpcsutils_version: 'dev-develop'
          - php: '7.2'
            phpcs_version: '4.x-dev'
            phpcsutils_version: 'dev-develop'
          - php: '8.4'
            phpcs_version: '3.x-dev'
            phpcsutils_version: 'dev-develop'
          - php: '8.4'
            phpcs_version: '4.x-dev'
            phpcsutils_version: 'dev-develop'

    name: "Test: PHP ${{ matrix.php }} - PHPCS ${{ matrix.phpcs_version }} - Utils ${{ matrix.phpcsutils_version }}"

    continue-on-error: ${{ matrix.php == '8.5' }}

    steps:
      - name: Checkout code
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false

      # On stable PHPCS versions, allow for PHP deprecation notices.
      # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
      - name: Setup ini config
        id: set_ini
        # yamllint disable rule:line-length
        run: |
          if [[ "${{ contains( matrix.phpcs_version, 'dev') }}" == "false" && "${{ contains( matrix.phpcsutils_version, 'dev') }}" == "false" ]]; then
            echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT"
          else
            echo 'PHP_INI=error_reporting=-1, display_errors=On, display_startup_errors=On' >> "$GITHUB_OUTPUT"
          fi
          # yamllint enable rule:line-length

      - name: Install PHP
        uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
        with:
          php-version: ${{ matrix.php }}
          ini-values: ${{ steps.set_ini.outputs.PHP_INI }}
          coverage: none

      # Remove PHPCSDevCS as it would (for now) prevent the tests from being able to run against PHPCS 4.x.
      - name: 'Composer: remove PHPCSDevCS'
        run: composer remove --dev phpcsstandards/phpcsdevcs --no-update --no-interaction

      - name: "Composer: set PHPCS version for tests (dev/specific version)"
        if: ${{ matrix.phpcs_version != 'lowest' && matrix.phpcs_version != 'stable' }}
        run: composer require squizlabs/php_codesniffer:"${{ matrix.phpcs_version }}" --no-update --no-scripts --no-interaction

      - name: "Composer: set PHPCSUtils version for tests (dev/specific version)"
        if: ${{ matrix.phpcsutils_version != 'lowest' && matrix.phpcsutils_version != 'stable' }}
        run: composer require phpcsstandards/phpcsutils:"${{ matrix.phpcsutils_version }}" --no-update --no-scripts --no-interaction

      - name: "Composer: use lock file"
        run: composer config --unset lock

      # 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:
          # For the PHP "nightly", we need to install with ignore platform reqs as not all dependencies may allow it yet.
          composer-options: ${{ matrix.php == '8.5' && '--ignore-platform-req=php+' || '' }}
          # Bust the cache at least once a month - output format: YYYY-MM.
          custom-cache-suffix: $(date -u "+%Y-%m")

      - name: "Composer: set PHPCS version for tests (lowest)"
        if: ${{ matrix.phpcs_version == 'lowest' }}
        run: composer update squizlabs/php_codesniffer --prefer-lowest --no-scripts --no-interaction

      - name: "Composer: set PHPCSUtils version for tests (lowest)"
        if: ${{ matrix.phpcsutils_version == 'lowest' }}
        run: composer update phpcsstandards/phpcsutils --prefer-lowest --no-scripts --no-interaction

      - name: Composer info
        run: composer info

      - name: Grab PHPCS version
        id: phpcs_version
        # yamllint disable-line rule:line-length
        run: echo "VERSION=$(vendor/bin/phpcs --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT"

      - name: Run the unit tests (PHPCS 3.x)
        if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) }}
        run: composer test-phpcs3

      - name: Run the unit tests (PHPCS 4.x)
        if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) }}
        run: composer test-phpcs4


  #### CODE COVERAGE STAGE ####
  # N.B.: Coverage is only checked on the lowest and highest stable PHP versions
  # and a low/high for PHPCS and PHPCSUtils.
  # These builds are left out off the "test" stage so as not to duplicate test runs.
  coverage:
    # No use running the coverage builds if there are failing test builds.
    needs: test
    # The default condition is success(), but this is false when one of the previous jobs is skipped
    if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped')

    runs-on: ubuntu-latest

    strategy:
      matrix:
        # Note, since the release of PHPCS 4.0, 'stable' == latest 4.x.
        php: ['5.4', '8.4']
        dependencies: ['lowest', 'stable']

        exclude:
          - php: '5.4'
            dependencies: 'stable'

        include:
          # Replace the "low PHP" stable build for PHPCS 4.x.
          - php: '7.2'
            dependencies: 'stable'

    name: "Coverage: PHP ${{ matrix.php }} - PHPCS ${{ matrix.dependencies }}"

    steps:
      - name: Checkout code
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          persist-credentials: false

      - name: Install PHP
        uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
        with:
          php-version: ${{ matrix.php }}
          # On stable PHPCS versions, allow for PHP deprecation notices.
          # Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
          ini-values: PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On
          coverage: xdebug

      # Remove PHPCSDevCS as it would (for now) prevent the tests from being able to run against PHPCS 4.x.
      - name: 'Composer: remove PHPCSDevCS'
        run: composer remove --dev phpcsstandards/phpcsdevcs --no-update --no-interaction

      - name: "Composer: use lock file when necessary"
        if: ${{ matrix.dependencies == 'lowest' }}
        run: composer config --unset lock

      # 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: "Composer: set PHPCS/PHPCSUtils version for tests (lowest)"
        if: ${{ matrix.dependencies == 'lowest' }}
        run: >
          composer update --prefer-lowest --no-scripts --no-interaction
          squizlabs/php_codesniffer
          phpcsstandards/phpcsutils

      - name: Grab PHPCS version
        id: phpcs_version
        # yamllint disable-line rule:line-length
        run: echo "VERSION=$(vendor/bin/phpcs --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+')" >> "$GITHUB_OUTPUT"

      - name: Run the unit tests with code coverage (PHPCS 3.x)
        if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '3.' ) }}
        run: composer coverage-phpcs3

      - name: Run the unit tests with code coverage (PHPCS 4.x)
        if: ${{ startsWith( steps.phpcs_version.outputs.VERSION, '4.' ) }}
        run: composer coverage-phpcs4

      - name: Upload coverage results to Coveralls
        if: ${{ success() }}
        uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
        with:
          format: clover
          file: build/logs/clover.xml
          flag-name: php-${{ matrix.php }}-phpcs-${{ matrix.dependencies }}
          parallel: true

  coveralls-finish:
    needs: coverage
    if: always() && needs.coverage.result == 'success'

    runs-on: ubuntu-latest

    name: Coveralls Finish

    steps:
      - name: Coveralls Finished
        uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6
        with:
          parallel-finished: true
