Continuous Integration in Drupal 8

An overview of the tools available to assess code before it reaches master

Juampy NR - @juampynr

Drupal Madrid - January the 23rd 2018

Juampy NR - @juampynr


Senior Developer at Lullabot

What sort of Continuous Integration are we focusing in?

When a developer creates a pull request:

  • Run Unit and Kernel tests
  • Run Behat tests
  • Check how much code is covered by tests
  • Check that the changes follow Drupal's coding standards

Everything should happen in an isolated, ephemeral environment

Disclaimer

I have used Jenkins for years and recently I have started using CircleCI. I have never used Travis CI in a real project.

I am sharing what I discovered with an open heart. Please, do let me know if there are things that I am missing or that I should try. It's being a lot of fun testing CI tools.

CI tools that I have tried

CircleCI

Sample

Open it at GitHub

              
                # Runs a set of CI jobs for Drupal 8 projects.

# Reusable steps.
## Defines images and working directory.
defaults: &defaults
  docker:
    - image: juampynr/drupal8ci:latest

    - image: selenium/standalone-chrome-debug:3.7.1-beryllium

    - image: mariadb:10.3
      environment:
        MYSQL_ALLOW_EMPTY_PASSWORD: 1

  working_directory: /var/www/html

## Defines the cache restoring mechanism.
restore_cache: &restore_cache
  # We use the composer.json as a way to determine if we can cache our build.
  keys:
  - v1-dependencies-{{ checksum "composer.json" }}
  # fallback to using the latest cache if no exact match is found
  - v1-dependencies-

## Defines the cache saving mechanism.
save_cache: &save_cache
  paths:
    - ./vendor
  key: v1-dependencies-{{ checksum "composer.json" }}

#Jobs
## Job to run Unit and Kernel tests.
unit_kernel_tests: &unit_kernel_tests
  <<: *defaults
      steps:
      - checkout
      - restore_cache: *restore_cache
      - run:
      name: Set up and run Unit and Kernel tests
      command: |
      apache2-foreground&
      robo install:dependencies
      robo setup:drupal || true
      cp .circleci/config/phpunit.xml web/core/
          mkdir -p artifacts/phpunit
          chmod -R 777 artifacts
          cd web
          sudo -E -u www-data ../vendor/bin/phpunit -c core --debug --verbose --log-junit ../artifacts/phpunit/phpunit.xml modules/custom
    - store_test_results:
        path: /var/www/html/artifacts/phpunit
    - store_artifacts:
        path: /var/www/html/artifacts
    - save_cache: *save_cache

## Job to run the update path and Behat tests.
behat_tests: &behat_tests
  <<: *defaults
      steps:
      - checkout
      - restore_cache: *restore_cache
      - run:
      name: Wait for the database service to be ready
      command: dockerize -wait tcp://localhost:3306 -timeout 1m
    - run:
        name: Install database and run the update path
        command: |
          robo install:dependencies
          robo setup:drupal || true
          cd web
          # For a quick start, set the following environment variable to a URL that contains
          # a database dump. Alternativelly, give CircleCI access to your development environment
          # and use Drush site aliases to run `drush sql-sync`.
          wget -O dump.sql $DB_DUMP_URL
          ../vendor/bin/drush sql-cli < dump.sql
          ../vendor/bin/drush updatedb -y -v
          ../vendor/bin/drush config-import -y -v
          cd ..
    - save_cache: *save_cache
    - run:
        name: Set up and run Behat tests
        command: |
          apache2-foreground&
          cp .circleci/config/behat.yml tests/
          chown -R www-data:www-data /var/www/html/web/sites/default/files
          vendor/bin/behat --verbose -c tests/behat.yml
    - store_test_results:
        path: /var/www/html/artifacts/behat
    - store_artifacts:
        path: /var/www/html/artifacts

## Job to check coding standards.
code_sniffer: &code_sniffer
  <<: *defaults
      steps:
      - checkout
      - restore_cache: *restore_cache
      - run:
      name: Set up and inspect coding standards
      command: |
      robo install:dependencies
      vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer
          mkdir -p artifacts/phpcs
          vendor/bin/phpcs --standard=Drupal --report=junit --report-junit=artifacts/phpcs/phpcs.xml web/modules/custom
          vendor/bin/phpcs --standard=DrupalPractice --report=junit --report-junit=artifacts/phpcs/phpcs.xml web/modules/custom
    - store_test_results:
        path: /var/www/html/artifacts/phpcs
    - store_artifacts:
        path: /var/www/html/artifacts
    - save_cache: *save_cache

## Job to check test coverage.
code_coverage: &code_coverage
  <<: *defaults
      steps:
      - checkout
      - restore_cache: *restore_cache
      - run:
      name: Set up and inspect code coverage
      environment:
      SIMPLETEST_BASE_URL: "http://localhost"
          SIMPLETEST_DB: "sqlite://localhost//tmp/drupal.sqlite"
          BROWSERTEST_OUTPUT_DIRECTORY: "/var/www/html/sites/simpletest"
        command: |
          robo install:dependencies
          robo setup:drupal || true
          cp .circleci/config/phpunit.xml web/core/
          mkdir -p artifacts/coverage-xml
          mkdir -p artifacts/coverage-html
          chmod -R 777 artifacts
          cd web
          timeout 60m sudo -E -u www-data ../vendor/bin/phpunit --verbose --debug -c core --coverage-xml ../artifacts/coverage-xml --coverage-html ../artifacts/coverage-html --testsuite nonfunctional modules/custom
          cd ../
          tar czf artifacts/coverage.tar.gz -C artifacts coverage-html coverage-xml
    - store_artifacts:
        path: /var/www/html/artifacts
    - save_cache: *save_cache

# Declare all of the jobs we should run.
version: 2
jobs:
  run-unit-kernel-tests:
     <<: *unit_kernel_tests
         run-behat-tests:
     <<: *behat_tests
         run-code-sniffer:
     <<: *code_sniffer
         run-code-coverage:
     <<: *code_coverage

         # Declare a workflow that runs all of our jobs in parallel.
         workflows:
         version: 2
         test_and_lint:
         jobs:
         - run-unit-kernel-tests
         - run-behat-tests
         - run-code-sniffer
         - run-code-coverage
              
            

Setup

  1. Add a .circleci/config.yml file to the repository
  2. Authenticate with your GitHub account at https://circleci.com
  3. Allow CircleCI to watch for repository changes to run jobs

Pros: parallel processing

Via workflows: split a job in several sub-jobs that run in parallel

Pros: docker-compose-style environments

              
## Defines images and working directory.
defaults: &defaults
  docker:
    - image: juampynr/drupal8ci:latest

    - image: selenium/standalone-chrome-debug:3.7.1-beryllium

    - image: mariadb:10.3
      environment:
        MYSQL_ALLOW_EMPTY_PASSWORD: 1
              
            

Pros: SSH access to a build's environment

Pros: Run jobs locally

Cons

  • Not free! May not be suitable for teams with long, slow jobs, and many repositories
  • Not able to customize commit messages to things like "3 unit tests tests failed"

Sample installation clip

Drupal 8 demo project

Jenkins CI

Sample: shell step to run tests

Sample (2): script used to run tests

Setup

  1. Install Jenkins CI in a server
  2. Install a plugin that listens to repository changes: GitHub Pull Request Builder or Generic Webhook Trigger
  3. Allow Jenkins access to the repository
  4. Set up the job(s)

Pros: free and customisable

Install it, create the jobs, and adapt them to your team's workflow. It takes time, but you will end up with what you need

Pros: lots of documentation and plugins

Jenkins has been out there for a long time. It's hard to find a problem that is not solved via an article or a plugin.

Pros: temporary testing environments

Make an environment persistent by attaching a port to it

Cons

  • Lot of setup, tweaking, and server maintenance required
  • It's tricky to keep the job implementation under version control

Travis CI

Sample

View on GitHub

              
language: php
dist: trusty
sudo: false

php:
  - 7.1

env:
  global:
    - DRUPAL_DB=sqlite://tmp/site.sqlite
    - DRUPAL_BASE_URL="http://127.0.0.1:8080"

before_install:
  - echo 'sendmail_path = /bin/true' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini

install:
  - composer --verbose install

script:
  # Install Drupal and start the web server.
  - cd web
  - ../vendor/bin/drush site-install --verbose --yes --db-url=$DRUPAL_DB
  - ../vendor/bin/drush runserver $DRUPAL_BASE_URL &
  - until curl -s $DRUPAL_BASE_URL; do true; done > /dev/null
  - cd ..

  # Run unit and kernel tests.
  - cp .travis/config/phpunit.xml web/core/
  - cd web
  - ../vendor/bin/phpunit -c core --debug --verbose modules/custom
  - cd ..

  # Check Drupal's coding standards.
  - vendor/bin/phpcs --config-set installed_paths vendor/drupal/coder/coder_sniffer
  - vendor/bin/phpcs --standard=Drupal web/modules/custom
  - vendor/bin/phpcs --standard=DrupalPractice web/modules/custom

              
            

Setup

  1. Add a .travis.yml file to the repository
  2. Authenticate with your GitHub account at https://travis-ci.org
  3. Allow Travis CI to watch for repository changes to run jobs

Pros: tons of documentation

On top of the official documentation, there are a lot of private and open source projects that use Travis CI so it's relatively easy to find tutorials and examples.

Cons

Sample

Drupal 8 demo project

Conclusion

  • Start with CircleCI or Travis CI. They both have free subscription tiers.
  • If you need more power, consider upgrading your account or migrating the scripts to a Jenkins infrastructure that uses docker-compose.

Want more? Check out these links

Thanks! Questions?

@juampynr

If you liked this session, please support my DrupalCon Nashville proposal by posting a comment