version: 2.1

orbs:
  codecov: codecov/codecov@1
  coveralls:
    commands:
      upload:
        parameters:
          file:
            description: Path to the code coverage data file to upload.
            type: string
            default: ""
        steps:
          - run:
              name: Upload Coverage Results
              command: |
                npx @cedx/coveralls@^8.6 "<< parameters.file >>" || echo 'Coveralls upload failed'

commands:
  clear-test-app-cache:
    steps:
      - run:
          name: Clear test app cache
          command: tests/Fixtures/app/console cache:clear
  disable-php-memory-limit:
    steps:
      - run:
          name: Disable PHP memory limit
          command: echo 'memory_limit=-1' | sudo tee -a /usr/local/etc/php/php.ini
  disable-xdebug-php-extension:
    steps:
      - run:
          name: Disable Xdebug PHP extension
          command: sudo rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
  install-mongodb-php-extension:
    steps:
      - run:
          name: Install mongodb PHP extension
          command: |
            sudo pecl install mongodb-1.5.4
            sudo docker-php-ext-enable mongodb
  install-pcov-php-extension:
    steps:
      - run:
          name: Install pcov PHP extension
          command: |
            sudo pecl install pcov-1.0.6
            sudo docker-php-ext-enable pcov
  merge-code-coverage-reports:
    parameters:
      dir:
        description: Path to the directory containing the code coverage reports to merge.
        type: string
      out:
        description: Path to output the merged code coverage report.
        type: string
    steps:
      - run:
          name: Download phpcov
          command: |
            if ! command -v phpcov; then
              wget https://phar.phpunit.de/phpcov.phar
              chmod +x phpcov.phar
              sudo mv phpcov.phar /usr/local/bin/phpcov
            fi
      - run:
          name: Merge code coverage reports
          command: |
            mkdir -p "$(dirname -- "<< parameters.out >>")"
            phpcov merge --clover "<< parameters.out >>" "<< parameters.dir >>"
  merge-test-reports:
    parameters:
      dir:
        description: Path to the directory containing the test reports to merge.
        type: string
      out:
        description: Path to output the merged test report.
        type: string
    steps:
      - run:
          name: Merge test reports
          command: |
            tmpout=$(mktemp)
            npx junit-merge@^2.0 --dir "<< parameters.dir >>" --recursive --out "$tmpout"
            rm -r "<< parameters.dir >>"
            mkdir -p "$(dirname -- "<< parameters.out >>")"
            mv "$tmpout" "<< parameters.out >>"
  restore-composer-cache:
    steps:
      - restore_cache:
          keys:
            - composer-cache-{{ .Revision }}
            - composer-cache-{{ .Branch }}
            - composer-cache
  restore-npm-cache:
    steps:
      - restore_cache:
          keys:
            - npm-cache-{{ .Revision }}
            - npm-cache-{{ .Branch }}
            - npm-cache
  restore-php-cs-fixer-cache:
    steps:
      - restore_cache:
          keys:
            - php-cs-fixer-cache-{{ .Revision }}
            - php-cs-fixer-cache-{{ .Branch }}
            - php-cs-fixer-cache
  restore-phpstan-cache:
    steps:
      - restore_cache:
          keys:
            - phpstan-cache-{{ .Revision }}
            - phpstan-cache-{{ .Branch }}
            - phpstan-cache
  save-composer-cache:
    steps:
      - save_cache:
          paths:
            - ~/.composer/cache
          key: composer-cache-{{ .Branch }}-{{ .BuildNum }}
      - save_cache:
          paths:
            - ~/.composer/cache
          key: composer-cache-{{ .Revision }}-{{ .BuildNum }}
  save-npm-cache:
    steps:
      - save_cache:
          paths:
            - ~/.npm
          key: npm-cache-{{ .Branch }}-{{ .BuildNum }}
      - save_cache:
          paths:
            - ~/.npm
          key: npm-cache-{{ .Revision }}-{{ .BuildNum }}
  save-php-cs-fixer-cache:
    steps:
      - save_cache:
          paths:
            - .php_cs.cache
          key: php-cs-fixer-cache-{{ .Branch }}-{{ .BuildNum }}
      - save_cache:
          paths:
            - .php_cs.cache
          key: php-cs-fixer-cache-{{ .Revision }}-{{ .BuildNum }}
  save-phpstan-cache:
    steps:
      - save_cache:
          paths:
            - /tmp/phpstan/cache
          key: phpstan-cache-{{ .Branch }}-{{ .BuildNum }}
      - save_cache:
          paths:
            - /tmp/phpstan/cache
          key: phpstan-cache-{{ .Revision }}-{{ .BuildNum }}
  update-composer:
    steps:
      - run:
          name: Update Composer
          command: sudo composer self-update
  update-project-dependencies:
    steps:
      - run:
          name: Update project dependencies
          command: composer update --prefer-dist --no-progress --no-suggest --ansi
  wait-for-elasticsearch:
    steps:
      - wait-for-service:
          label: Elasticsearch
          port: '9200'
  wait-for-mongodb:
    steps:
      - wait-for-service:
          label: MongoDB
          port: '27017'
  wait-for-service:
    parameters:
      label:
        description: Label for the service.
        type: string
        default: service
      port:
        description: Target port to connect to.
        type: string
      timeout:
        description: Maximum number of seconds to wait.
        type: integer
        default: 30
    steps:
      - run:
          name: Wait for << parameters.label >>
          command: |
            timeout=<< parameters.timeout >>
            elapsed=0
            echo "Waiting for << parameters.label >> to be ready..."
            until nc -z localhost "<< parameters.port >>" > /dev/null 2>&1; do
              if _=$((elapsed > timeout)); then
                echo 'Timed out waiting for << parameters.label >>' >&2
                exit 1
              fi
              sleep 1
              elapsed=$((elapsed + 1))
            done

executors:
  php:
    docker:
      - image: circleci/php:7.3-node
  php-and-elasticsearch:
    docker:
      - image: circleci/php:7.3-node
      # https://github.com/elastic/elasticsearch-docker/issues/84
      - image: docker.elastic.co/elasticsearch/elasticsearch:6.7.2
  php-and-mongodb:
    docker:
      - image: circleci/php:7.3-node
      - image: circleci/mongo:4

jobs:
  php-cs-fixer:
    executor: php
    environment:
      PHP_CS_FIXER_FUTURE_MODE: 1
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - run:
          name: Install PHP-CS-Fixer
          command: |
            composer global require --prefer-dist --no-progress --no-suggest --ansi \
              friendsofphp/php-cs-fixer:^2.14
      - save-composer-cache
      - restore-php-cs-fixer-cache
      - run:
          name: Run PHP-CS-Fixer
          command: |
            export PATH="$PATH:$HOME/.composer/vendor/bin"
            php-cs-fixer fix --dry-run --diff --ansi
      - save-php-cs-fixer-cache

  phpstan:
    executor: php
    environment:
      # https://github.com/phpstan/phpstan-symfony/issues/37
      APP_DEBUG: 1
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - restore-phpstan-cache
      - run:
          name: Run PHPStan
          command: vendor/bin/phpstan analyse --ansi
      - save-phpstan-cache

  phpunit-coverage:
    executor: php
    parallelism: 2
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - install-pcov-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - run:
          name: Run PHPUnit tests
          command: |
            mkdir -p build/logs/phpunit build/coverage
            split_tests=$(find tests -name '*Test.php' | circleci tests split --split-by=timings)
            export PARALLEL='-j10% --joblog build/logs/parallel.log --rpl {_}\ s/\\//_/g;'
            phpunit_cmd='vendor/bin/phpunit --coverage-php build/coverage/coverage-{_}.cov --log-junit build/logs/phpunit/{_}.xml --exclude-group mongodb,resource-hog --colors=always {}'
            echo "$split_tests" | parallel "$phpunit_cmd" || echo "$split_tests" | parallel --retry-failed "$phpunit_cmd"
      - restore-npm-cache
      - merge-test-reports:
          dir: build/logs/phpunit
          out: build/logs/phpunit/junit.xml
      - store_test_results:
          path: build/logs
      - store_artifacts:
          path: build/logs/phpunit/junit.xml
          destination: build/logs/phpunit/junit.xml
      - store_artifacts:
          path: build/logs/parallel.log
          destination: build/logs/parallel.log
      - merge-code-coverage-reports:
          dir: build/coverage
          out: build/logs/clover.xml
      - store_artifacts:
          path: build/logs/clover.xml
          destination: build/logs/clover.xml
      - codecov/upload:
          file: build/logs/clover.xml
          flags: phpunit
      - coveralls/upload:
          file: build/logs/clover.xml
      - save-npm-cache

  behat-coverage:
    executor: php
    parallelism: 2
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - install-pcov-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - run:
          name: Run Behat tests
          command: |
            mkdir -p build/logs/behat build/coverage
            for f in $(find features -name '*.feature' -not -path 'features/main/exposed_state.feature' -not -path 'features/elasticsearch/*' -not -path 'features/mongodb/*' | circleci tests split --split-by=timings); do
              _f=$(echo "$f" | tr / _)
              FEATURE="${_f}" vendor/bin/behat --format=progress --out=std --format=junit --out=build/logs/behat/"${_f}" --profile=default-coverage --no-interaction "$f"
            done
      - restore-npm-cache
      - merge-test-reports:
          dir: build/logs/behat
          out: build/logs/behat/junit.xml
      - store_test_results:
          path: build/logs
      - store_artifacts:
          path: build/logs/behat/junit.xml
          destination: build/logs/behat/junit.xml
      - merge-code-coverage-reports:
          dir: build/coverage
          out: build/logs/clover.xml
      - store_artifacts:
          path: build/logs/clover.xml
          destination: build/logs/clover.xml
      - codecov/upload:
          file: build/logs/clover.xml
          flags: behat
      - coveralls/upload:
          file: build/logs/clover.xml
      - save-npm-cache

  phpunit-mongodb-coverage:
    executor: php-and-mongodb
    environment:
      APP_ENV: mongodb
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - install-pcov-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - wait-for-mongodb
      - run:
          name: Run PHPUnit tests
          command: |
            mkdir -p build/logs/phpunit
            vendor/bin/phpunit --coverage-clover build/logs/clover.xml --log-junit build/logs/phpunit/junit.xml --exclude-group resource-hog --colors=always --configuration phpunit_mongodb.xml
      - store_test_results:
          path: build/logs
      - store_artifacts:
          path: build/logs/phpunit/junit.xml
          destination: build/logs/phpunit/junit.xml
      - store_artifacts:
          path: build/logs/clover.xml
          destination: build/logs/clover.xml
      - restore-npm-cache
      - codecov/upload:
          file: build/logs/clover.xml
          flags: phpunit_mongodb
      - coveralls/upload:
          file: build/logs/clover.xml
      - save-npm-cache

  behat-mongodb-coverage:
    executor: php-and-mongodb
    environment:
      APP_ENV: mongodb
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - install-pcov-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - wait-for-mongodb
      - run:
          name: Run Behat tests
          command: |
            mkdir -p build/logs/behat
            vendor/bin/behat --format=progress --out=std --format=junit --out=build/logs/behat --profile=mongodb-coverage --no-interaction
      - restore-npm-cache
      - merge-test-reports:
          dir: build/logs/behat
          out: build/logs/behat/junit.xml
      - store_test_results:
          path: build/logs
      - store_artifacts:
          path: build/logs/behat/junit.xml
          destination: build/logs/behat/junit.xml
      - merge-code-coverage-reports:
          dir: build/coverage
          out: build/logs/clover.xml
      - store_artifacts:
          path: build/logs/clover.xml
          destination: build/logs/clover.xml
      - codecov/upload:
          file: build/logs/clover.xml
          flags: behat_mongodb
      - coveralls/upload:
          file: build/logs/clover.xml
      - save-npm-cache

  behat-elasticsearch-coverage:
    executor: php-and-elasticsearch
    environment:
      APP_ENV: elasticsearch
    working_directory: ~/api-platform/core
    steps:
      - checkout
      - install-mongodb-php-extension
      - install-pcov-php-extension
      - disable-xdebug-php-extension
      - disable-php-memory-limit
      - update-composer
      - restore-composer-cache
      - update-project-dependencies
      - save-composer-cache
      - clear-test-app-cache
      - wait-for-elasticsearch
      - run:
          name: Run Behat tests
          command: |
            mkdir -p build/logs/behat
            vendor/bin/behat --format=progress --out=std --format=junit --out=build/logs/behat --profile=elasticsearch-coverage --no-interaction
      - restore-npm-cache
      - merge-test-reports:
          dir: build/logs/behat
          out: build/logs/behat/junit.xml
      - store_test_results:
          path: build/logs
      - store_artifacts:
          path: build/logs/behat/junit.xml
          destination: build/logs/behat/junit.xml
      - merge-code-coverage-reports:
          dir: build/coverage
          out: build/logs/clover.xml
      - store_artifacts:
          path: build/logs/clover.xml
          destination: build/logs/clover.xml
      - codecov/upload:
          file: build/logs/clover.xml
          flags: behat_elasticsearch
      - coveralls/upload:
          file: build/logs/clover.xml
      - save-npm-cache

workflows:
  version: 2
  lint-and-coverage:
    jobs:
      - php-cs-fixer
      - phpstan
      - phpunit-coverage
      - behat-coverage
      - phpunit-mongodb-coverage
      - behat-mongodb-coverage
      - behat-elasticsearch-coverage
