Customizing DrupalCI Testing for Projects

Last updated on
24 September 2023

This documentation needs review. See "Help improve this page" in the sidebar.

When testing a project on drupal.org, DrupalCI will run a predefined set of tasks that:

  • establishes a testing environment (php/databases/etc)
  • constructs a testable codebase
  • runs validations and assessments (coding standards and tests).

Oftentimes a project may need to perform additional tasks that are not part of the predefined build process or may wish to configure how the build responds to certain types of failures conditions (some maintainers may wish for coding standards failures to immediately stop the build, while others may wish for the build to proceed).

This guide is intended to inform Drupal core and contributed project maintainers how they can customize DrupalCI to suit their testing needs.

The Build Definition

The DrupalCI testbot uses a YAML-formatted file to specify a 'build.' The build definition file specifies which tasks to run, in what order, and which configuration options to use for how those tasks should behave.

Build Definition Organization

The build definition file consists of a hierarchical list of build tasks to execute, along with their configuration values. The tasks are nested in major stages, minor phases, and individual tasks.

If you look at the default build definition file, you'll see that it has a top-level key of build.

Under the build level we have the three major stages:

  • environment
    • The environment stage is responsible for creating the environment that the test will run in, and start all of the services needed by the test, such as apache/php/database/chromedriver/phantomjs
  • codebase
    • The codebase stage is responsible for constructing a preliminary testable codebase for the project in question and involves git cloning/composer/patching and anything else
  • assessment
    • The assessment stage will run any codebase validation checks like coding standards and linting, and also executes any actual testing frameworks, like SimpleTest, PHPUnit, and Nightwatch.

Within these stages are 'phases,' such as validate_codebase and testing, which allow for logical grouping of child 'steps', 'stages', 'phases', and 'steps' are all considered "Tasks" and correspond directly to a plugin that runs in the DrupalCI codebase.

Some keys have "Labels", indicated by a period in the key, like the run_tests.standard and the run_tests.js. If the same plugin needs to be run more than once in a build, it must have a label. This will cause DrupalCI to run the same step twice but with different configuration values each time. The label is arbitrary but required if you need the same plugin to run more than once. Execution artifacts associated with that plugin will be stored in a directory with the label on Jenkins.

Some of those keys correspond to the annotated tag of a plugin that will be executed, and some keys are considered configuration for the parent plugin. As a general rule of thumb, plugin names have underscores in them, and configuration values will have dashes (unless they are just one word, in which case they're ambiguous)

Overriding Default Behavior

DrupalCI provides a mechanism for maintainers to completely replace the default assessment stage of the build, with their own configuration by placing a drupalci.yml file at the root of their project.

The drupalci.yml will look like a build.yml file, but with the codebase and environment phases omitted and having only the assessment phase. Drupal core ships with a drupalci.yml file that we can use as an example starting point.

See git.drupalcode.org/project/drupal/blob/HEAD/core/drupalci.yml:

# This is the DrupalCI testbot build file for Drupal core.
# Learn to make one for your own drupal.org project:
# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
build:
  assessment:
    validate_codebase:
      phplint:
      csslint:
        halt-on-fail: false
      eslint:
        # A test must pass eslinting standards check in order to continue processing.
        halt-on-fail: true
      phpcs:
        # phpcs will use core's specified version of Coder.
        sniff-all-files: false
        halt-on-fail: false
    testing:
      # run_tests task is executed several times in order of performance speeds.
      # halt-on-fail can be set on the run_tests tasks in order to fail fast.
      # suppress-deprecations is false in order to be alerted to usages of
      # deprecated code.
      run_tests.phpunit:
        types: 'PHPUnit-Unit'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      run_tests.kernel:
        types: 'PHPUnit-Kernel'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      run_tests.simpletest:
         types: 'Simpletest'
         testgroups: '--all'
         suppress-deprecations: false
         halt-on-fail: false
      run_tests.functional:
        types: 'PHPUnit-Functional'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      # Functional JavaScript tests require a concurrency of 1 because there is
      # only one instance of PhantomJS on the testbot machine.
      run_tests.javascript:
        concurrency: 1
        types: 'PHPUnit-FunctionalJavascript'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      # Run nightwatch testing.
      # @see https://www.drupal.org/project/drupal/issues/2869825
      nightwatchjs:

This example would perform the exact same build as the default because nothing additional has been configured or overridden.

The Assessment stage is broken up into two phases, 'validate_codebase' and 'testing'

The codebase validation phase is responsible for ensuring that code meets the coding standards and that it lints properly.

The testing phase is responsible for running any testing frameworks (PHPUnit/SimpleTest/Nightwatch etc) and gathering the test results.

In order to customize our build, we place a drupalci.yml file in the root of our module/theme project, and we modify the configuration values in the drupalci.yml to our desires. Below is a list of how to control each plugin.

validate_codebase:

  • phplint: Uses php -l to lint PHP files. If this is a patch test, it will only lint the modified files.

  • csslint: Runs CSSLint to check for css coding standards violations. For patch tests, it will check changed css files only, unless the .csslintrc has been modified, in which case it will lint everything. If the project does not specify a .csslintrc ruleset, then the .csslintrc that ships with Drupal core will be used. *** note that CSSLint is going to be deprecated in favor of stylelint when this issue is complete: https://www.drupal.org/project/drupalci_testbot/issues/2866840. It has one configuration option:

    • halt-on-fail: Stop the build and fail the test if CSS linting does not succeed.
  • eslint: Runs ESLint to check for javascript coding standards violations. If its a patch test, it will check changed JavaScript files only, unless .eslint has been modified. If the project does not specify a .eslintrc.json ruleset, then the 'Drupal' standard will be used.

    • halt-on-fail: Stop the build and fail the test if there are javascript errors.
  • phpcs: Runs PHP CodeSniffer on a project. Sniff changed files only unless phpcs.xml(.dist) has been modified. If the project does not specify a phpcs.xml ruleset, then the 'Drupal' standard will be used. If no phpcs executable has been installed, we require drupal/coder ^8.3.2 which should install phpcs, then we configure phpcs to use coder. If contrib doesn't declare a dependency on a version of coder, but does have a phpcs.xml file, then we use either core's version, or if none is specified in core, we use @stable.
    • halt-on-fail: Stop the build and fail the test if there are PHP coding standards violations.

testing:

  • nightwatchjs: Run Drupal core Nightwatch-based tests via yarn.

  • run_tests: Run Drupal core's run-tests.sh script, for Drupal 8. Most of run-tests.sh's command-line options are represented here as config. See: Running tests through the command line with run-tests.ssh.

    • concurrency: The number of tests to run concurrently. Future versions of this task may ignore or modify this number. JavaScript functional tests can only operate with a concurrency of 1.
    • repeat: Corresponds to the run-tests.sh --repeat option. Useful if you want to run a single test multiple times in a patch.
    • types: Corresponds to the run-tests.sh --types option. Defaults to running all test types, minus JavaScript functional: SimpleTest, PHPUnit-Unit, PHPUnit-Kernel, PHPUnit-Functional
    • testgroups: The test groups to run. This is dropped in as the last argument to run-tests.sh, so test groups could be '--all' to run all tests, '--class "Drupal\FunctionalTests\Datetime\TimestampTest"' to run a single class, or a test group name to run only that test group, such as 'book' or 'Entity'. To run multiple test groups use a comma-separated list, for example 'mymodule,mymodule_submodule1'. Defaults to '--all'.
    • die-on-fail: Halt testing immediately if a test fails, and fail the build. Defaults to false.
    • verbose: Adds the --verbose flag to run-tests.sh that will display on the console output of Jenkins. Defaults to false.
    • suppress-deprecations: The Symfony phpunit-bridge will report usages of deprecations as failures in testing. This can cause tests to fail for contrib modules that have dependencies that are using deprecated functions, so setting this to true will disable that functionality. Defaults to true.
  • run_tests_d7: Run Drupal 7's run-tests.sh script. This task has the same configuration as run_tests, but handles special cases for Drupal 7.

Executing Custom Shell Commands

Project maintainers may sometimes need additional testing and build steps in order to execute their code. Perhaps there is a composer lib that needs to be upgraded or a javascript library that you would like to download, or even manually perform additional steps, such as downloading a core patch and applying it before testing your module.

The testbots will now run any arbitrary commands that project maintainers need for testing by adding tasks to their drupalci.yml. These keys should be at the 'Step' (3rd) level of your drupalci.yml, so either nested below the validate_codebase key or below the testingkey.

  • container_command : This will execute the shell commands inside of the docker container that is this tests 'execution environment' - where the particular version of PHP and Apache are installed. This is generally what you will want.
    • commands: a YAML array of commands to execute. Alternatively, it could just be a single command. For applying a patch, the following is an example:
      commands: "cd ${SOURCE_DIR} && sudo -u www-data curl https://www.drupal.org/files/issues/12345.patch | git apply -"
    • halt-on-fail: tells DrupalCI to stop the build if the command returns a non-zero status code.
  • host_command : This will execute the shell commands on the host itself, outside of the testing environment. Potentially useful if you need to restart one of the docker containers after installing new PHP extensions on it.
    • commands: a YAML array of commands to execute. Alternatively, it could just be a single command
    • halt-on-fail: tells DrupalCI to stop the build if the command returns a non-zero status code.

There are two environment variables available to use in your command lines that define your project's location within the filesystem: SOURCE_DIR and PROJECT_DIR.

  • SOURCE_DIR will be the root of the entire codebase.
  • PROJECT_DIR will be the project's specific directory where it has been installed to based off of the composer installer's extensions.

For example, below is a modified version of the core drupalci.yml file that introduces a container_command that uses the SOURCE_DIR to execute a composer command to upgrade PHPUnit, because that version is dependent upon what PHP version you are using.

build:
  assessment:
    validate_codebase:
      phplint:
      csslint:
        halt-on-fail: false
      eslint:
        halt-on-fail: true
      phpcs:
        sniff-all-files: false
        halt-on-fail: false
    testing:
      # container command below updates phpunit as an additional build step for core, for example.
      container_command : 
        commands: "cd ${SOURCE_DIR} && sudo -u www-data composer update phpunit/phpunit --with-dependencies"
      run_tests.phpunit:
        types: 'PHPUnit-Unit'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      run_tests.kernel:
        types: 'PHPUnit-Kernel'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      run_tests.simpletest:
         types: 'Simpletest'
         testgroups: '--all'
         suppress-deprecations: false
         halt-on-fail: false
      run_tests.functional:
        types: 'PHPUnit-Functional'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      run_tests.javascript:
        concurrency: 1
        types: 'PHPUnit-FunctionalJavascript'
        testgroups: '--all'
        suppress-deprecations: false
        halt-on-fail: false
      nightwatchjs:

Almost any container_command you run should probably be prefixed with 'sudo -u www-data' so as to not get into permissions issues.

Adding Additional Containers

In some instances the project you are testing requires additional services running for your tests to function.  You can add containers to the build by using the host_command like the following example from the https://www.drupal.org/project/ics_field project:

      host_command:
        commands:
          - "docker run -d --name icalvalidator --rm stefanospetrakis/icalvalidator2http"
          - "docker network connect --alias icalvalidator drupalci_nw icalvalidator"
        halt-on-fail: true

This requires that the containers be published somewhere like docker hub where they can be found by name.
N.B.: Setting halt-on-fail to false may help alleviate issues with these commands blocking the whole build, even though the container(s) operation starts normally.

For further details on how this works, please see: https://www.drupal.org/drupalorg/docs/drupal-ci/custom-docker-images-in-...

Disabling Plugins

Sometimes certain build steps are not relevant to a particular project, and can thus be disabled. For example, a backend module with no front end might disable eslint and csslint validation steps, and disable the FunctionalJavascript testing run_tests build step.

*currently* the way to disable a build step is to comment it out (preferred), or delete it entirely (less desirable). There is a proposal (https://www.drupal.org/project/drupalci_testbot/issues/2975095) to add a configuration option disabled: true to every build step to allow maintainers to be explicit about which tasks are disabled. This will also allow DrupalCI to introduce new build steps and have them execute without maintainer intervention, because in the future we may introduce new build task plugins for things like code coverage, stylelint (for CSS), and additional build types (regression vs development vs static analysis).

Further Examples

Every build that executes on DrupalCI will create a fully configured, reproducible build.yml that can be found by navigating to any test result on drupal.org, clicking on 'view results on dispatcher' and clicking on the build artifacts link.

Example

select test result
Click on any test result in the issue queue.

Click on View Results on dispatcher
Select the View results on the dispatcher link to take you to the drupalci Jenkins instance.

Click on Build Artifacts
Select the Build Artifacts link

Click on artifacts
Select the second half of the line - the 'artifacts' link
​​​​

click on 'view' next to the build....yml filename
Clicking on view will show you a complete build. Right click on the filename to save it locally (or copy the url and wget/curl it from somewhere.
The __orig file is there for when you submit a patch that makes changes to the drupalci.yml file, that should show you what it was before your new changes were applied.

Help improve this page

Page status: Needs review

You can: