From d6850e4369c04730d5b4fd9ffc91661b9a0e24be Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Mon, 20 Aug 2018 15:48:13 -0700 Subject: [PATCH] CircleCI 2.0 tests. --- .circleci/.gitignore | 4 + .circleci/behat.yml | 17 ++ .circleci/cleanup.sh | 33 +++ .circleci/config.yml | 58 +++++ .circleci/confirm-safety.sh | 48 ++++ .circleci/features/0-install.feature | 10 + .../features/bootstrap/FeatureContext.php | 232 ++++++++++++++++++ .circleci/features/content.feature | 12 + .circleci/features/pagecache.feature | 10 + .circleci/features/pantheonsolr.feature | 10 + .circleci/features/phpversion.feature | 10 + .circleci/local.test.dist | 12 + .circleci/prepare.sh | 25 ++ .circleci/set-up-globals.sh | 46 ++++ .circleci/test.sh | 27 ++ 15 files changed, 554 insertions(+) create mode 100644 .circleci/.gitignore create mode 100644 .circleci/behat.yml create mode 100755 .circleci/cleanup.sh create mode 100644 .circleci/config.yml create mode 100755 .circleci/confirm-safety.sh create mode 100644 .circleci/features/0-install.feature create mode 100644 .circleci/features/bootstrap/FeatureContext.php create mode 100644 .circleci/features/content.feature create mode 100644 .circleci/features/pagecache.feature create mode 100644 .circleci/features/pantheonsolr.feature create mode 100644 .circleci/features/phpversion.feature create mode 100755 .circleci/local.test.dist create mode 100755 .circleci/prepare.sh create mode 100755 .circleci/set-up-globals.sh create mode 100755 .circleci/test.sh diff --git a/.circleci/.gitignore b/.circleci/.gitignore new file mode 100644 index 00000000000..b0e880b7738 --- /dev/null +++ b/.circleci/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +local.test +local.env +local.ssh diff --git a/.circleci/behat.yml b/.circleci/behat.yml new file mode 100644 index 00000000000..fde56526c74 --- /dev/null +++ b/.circleci/behat.yml @@ -0,0 +1,17 @@ +# behat.yml +default: + suites: + default: + paths: + - features + contexts: + - Drupal\DrupalExtension\Context\DrupalContext + - Drupal\DrupalExtension\Context\MinkContext + - FeatureContext + extensions: + Behat\MinkExtension: + # base_url set by ENV + goutte: ~ + Drupal\DrupalExtension: + blackbox: ~ + api_driver: 'drush' diff --git a/.circleci/cleanup.sh b/.circleci/cleanup.sh new file mode 100755 index 00000000000..43f85c472ab --- /dev/null +++ b/.circleci/cleanup.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Echo commands as they are executed, but don't allow errors to stop the script. +set -x + +if [ -z "$TERMINUS_SITE" ] || [ -z "$TERMINUS_ENV" ]; then + echo "TERMINUS_SITE and TERMINUS_ENV environment variables must be set" + exit 1 +fi + +# Only delete old environments if there is a pattern defined to +# match environments eligible for deletion. Otherwise, delete the +# current multidev environment immediately. +# +# To use this feature, set MULTIDEV_DELETE_PATTERN to '^ci-' or similar +# in the CI server environment variables. +if [ -z "$MULTIDEV_DELETE_PATTERN" ] ; then + terminus env:delete $TERMINUS_SITE.$TERMINUS_ENV --delete-branch --yes + exit 0 +fi + +# List all but the newest two environments. +OLDEST_ENVIRONMENTS=$(terminus env:list "$TERMINUS_SITE" --format=list | grep -v dev | grep -v test | grep -v live | sort -k2 | grep "$MULTIDEV_DELETE_PATTERN" | sed -e '$d' | sed -e '$d') + +# Exit if there are no environments to delete +if [ -z "$OLDEST_ENVIRONMENTS" ] ; then + exit 0 +fi + +# Go ahead and delete the oldest environments. +for ENV_TO_DELETE in $OLDEST_ENVIRONMENTS ; do + terminus env:delete $TERMINUS_SITE.$ENV_TO_DELETE --delete-branch --yes +done diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..c35571db9ce --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,58 @@ +test-defaults: &test-defaults + docker: + - image: quay.io/pantheon-public/build-tools-ci:2.x + working_directory: ~/work/d7 + environment: + TZ: "/usr/share/zoneinfo/America/Los_Angeles" + TERM: dumb + +merge-defaults: &merge-defaults + docker: + - image: quay.io/getpantheon/upstream-update-build:1.x + working_directory: ~/work/d7 + environment: + TZ: "/usr/share/zoneinfo/America/Los_Angeles" + TERM: dumb + +version: 2 +jobs: + test: + <<: *test-defaults + steps: + - checkout + - run: + name: Set up environment + command: ./.circleci/set-up-globals.sh + - run: + name: Prepare + command: ./.circleci/prepare.sh + - run: + name: Test + command: ./.circleci/test.sh --strict + - run: + name: Cleanup + command: ./.circleci/cleanup.sh + - run: + name: Confirm that it is safe to merge + command: ./.circleci/confirm-safety.sh + merge: + <<: *merge-defaults + steps: + - checkout + - run: + # https://github.com/pantheon-systems/upstream-update-build/blob/1.x/bin/automerge.sh + name: Merge the default branch back to the master branch + command: automerge.sh + +workflows: + version: 2 + drops7: + jobs: + - test + - merge: + requires: + - test + filters: + branches: + only: + - default diff --git a/.circleci/confirm-safety.sh b/.circleci/confirm-safety.sh new file mode 100755 index 00000000000..1a6f7171019 --- /dev/null +++ b/.circleci/confirm-safety.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# +# The purpose of this script is to examine the base branch that this PR is +# set to merge into by usig the GitHub API. We are only querying a public +# repo here, so we do not need to use the GITHUB_TOKEN. +# + +# Exit if we are not running on Circle CI. +if [ -z "$CIRCLECI" ] ; then + exit 0 +fi + +# We only need to make this check for branches forked from default (right) / master (wrong). +# Skip the test for the default branch. (The .circleci directory will never be added to the master branch). +if [ "$CIRCLE_BRANCH" == "default" ] ; then + exit 0 +fi + +# We cannot continue unless we have a pull request. +if [ -z "$CIRCLE_PULL_REQUEST" ] ; then + echo "No CIRCLE_PULL_REQUEST defined; please create a pull request." + exit 1 +fi + +# CIRCLE_PULL_REQUEST=https://github.com/ORG/PROJECT/pull/NUMBER +PR_NUMBER=$(echo $CIRCLE_PULL_REQUEST | sed -e 's#.*/pull/##') + +# Display the API call we are using +echo curl https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$PR_NUMBER + +base=$(curl https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$PR_NUMBER 2>/dev/null | jq .base.ref) + +echo "The base branch is $base" + +# If the PR merges into 'default', then it is safe to merge. +if [ "$base" == '"default"' ] ; then + echo "It is safe to merge this PR into the $base branch" + exit 0 +fi + +# Force a test failure if the PR's base is the master branch. +if [ "$base" == '"master"' ] ; then + echo "ERROR: merging this PR into the $base branch is not allowed. Change the base branch for the PR to merge into the \"default\" branch instead." + exit 1 +fi + +echo "Merging probably okay, if you are merging one PR into another. Use caution; do not merge to the \"master\" branch." diff --git a/.circleci/features/0-install.feature b/.circleci/features/0-install.feature new file mode 100644 index 00000000000..625ae06d19a --- /dev/null +++ b/.circleci/features/0-install.feature @@ -0,0 +1,10 @@ +Feature: Installer + In order to know that we can install the site via drush + As a website user + I need to be able to install a Drupal site + + Scenario: Installer is ready + Given I have wiped the site + And I have reinstalled "CI Drops-7 [{site-name}.{env}]" + And I visit "/" + Then I should see "Welcome to CI Drops-7" diff --git a/.circleci/features/bootstrap/FeatureContext.php b/.circleci/features/bootstrap/FeatureContext.php new file mode 100644 index 00000000000..9dd9e81c2f7 --- /dev/null +++ b/.circleci/features/bootstrap/FeatureContext.php @@ -0,0 +1,232 @@ +getEnvironment(); + $this->minkContext = $environment->getContext('Drupal\DrupalExtension\Context\MinkContext'); + } + +// +// Place your definition and hook methods here: +// +// /** +// * @Given I have done something with :stuff +// */ +// public function iHaveDoneSomethingWith($stuff) { +// doSomethingWith($stuff); +// } +// + + /** + * Fills in form field with specified id|name|label|value + * Example: And I enter the value of the env var "TEST_PASSWORD" for "edit-account-pass-pass1" + * + * @Given I enter the value of the env var :arg1 for :arg2 + */ + public function fillFieldWithEnv($value, $field) + { + $this->minkContext->fillField($field, getenv($value)); + } + + /** + * Checks, that option from select with specified id|name|label|value is selected. + * + * @Then the :arg1 option from :arg2 should be selected + */ + public function theOptionFromShouldBeSelected($option, $select) + { + $selectField = $this->getSession()->getPage()->findField($select); + if (null === $selectField) { + throw new ElementNotFoundException($this->getSession(), 'select field', 'id|name|label|value', $select); + } + + $optionField = $selectField->find('named', array( + 'option', + $option, + )); + + if (null === $optionField) { + throw new ElementNotFoundException($this->getSession(), 'select option field', 'id|name|label|value', $option); + } + + if (!$optionField->isSelected()) { + throw new ExpectationException('Select option field with value|text "'.$option.'" is not selected in the select "'.$select.'"', $this->getSession()); + } + } + + /** + * @Given I have wiped the site + */ + public function iHaveWipedTheSite() + { + $site = getenv('TERMINUS_SITE'); + $env = getenv('TERMINUS_ENV'); + + passthru("terminus --yes env:wipe {$site}.{$env}"); + } + + /** + * @Given I have reinstalled :arg1 + */ + public function iHaveReinstalled($arg1) + { + $site = getenv('TERMINUS_SITE'); + $env = getenv('TERMINUS_ENV'); + $password = getenv('ADMIN_PASSWORD'); + + $replacements = [ + '{site-name}' => $site, + '{env}' => $env, + ]; + + $arg1 = str_replace(array_keys($replacements), array_values($replacements), $arg1); + + $cmd = "terminus --yes drush {$site}.{$env} -- site-install pantheon --yes --site-name=\"$arg1\" --account-name=admin"; + if (!empty($password)) { + $cmd .= " --account-pass='$password'"; + } + + passthru($cmd); + } + + /** + * @Given I have run the drush command :arg1 + */ + public function iHaveRunTheDrushCommand($arg1) + { + $site = getenv('TERMINUS_SITE'); + $env = getenv('TERMINUS_ENV'); + + $return = ''; + $output = array(); + exec("terminus --yes drush {$site}.{$env} -- --yes $arg1", $output, $return); + $output = implode("\n", $output); + + if ($return) { + throw new Exception("Error running Drush command:\n$output"); + } + + print "$output"; + } + + /** + * @Given I have committed my changes with comment :arg1 + */ + public function iHaveCommittedMyChangesWithComment($arg1) + { + $site = getenv('TERMINUS_SITE'); + $env = getenv('TERMINUS_ENV'); + + passthru("terminus --yes env:commit {$site}.{$env} --message='$arg1'"); + } + + /** + * @Given I wait :seconds seconds + */ + public function iWaitSeconds($seconds) + { + sleep($seconds); + } + + /** + * @When I print the page contents + */ + public function iPrintThePageContents() + { + $content = $this->getSession()->getPage()->getContent(); + print $content; + } + + /** + * @Given I wait :seconds seconds or until I see :text + */ + public function iWaitSecondsOrUntilISee($seconds, $text) + { + $errorNode = $this->spin( function($context) use($text) { + $node = $context->getSession()->getPage()->find('named', array('content', $text)); + if (!$node) { + return false; + } + return $node->isVisible(); + }, $seconds); + + // Throw to signal a problem if we were passed back an error message. + if (is_object($errorNode)) { + throw new Exception("Error detected when waiting for '$text': " . $errorNode->getText()); + } + } + + // http://docs.behat.org/en/v2.5/cookbook/using_spin_functions.html + // http://mink.behat.org/en/latest/guides/traversing-pages.html#selectors + public function spin ($lambda, $wait = 60) + { + for ($i = 0; $i <= $wait; $i++) + { + if ($i > 0) { + sleep(1); + } + + $debugContent = $this->getSession()->getPage()->getContent(); + file_put_contents("/tmp/mink/debug-" . $i, "\n\n\n=================================\n$debugContent\n=================================\n\n\n"); + + try { + if ($lambda($this)) { + return true; + } + } catch (Exception $e) { + // do nothing + } + + // If we do not see the text we are waiting for, fail fast if + // we see a Drupal 8 error message pane on the page. + $node = $this->getSession()->getPage()->find('named', array('content', 'Error')); + if ($node) { + $errorNode = $this->getSession()->getPage()->find('css', '.messages--error'); + if ($errorNode) { + return $errorNode; + } + $errorNode = $this->getSession()->getPage()->find('css', 'main'); + if ($errorNode) { + return $errorNode; + } + return $node; + } + } + + $backtrace = debug_backtrace(); + + throw new Exception( + "Timeout thrown by " . $backtrace[1]['class'] . "::" . $backtrace[1]['function'] . "()\n" . + $backtrace[1]['file'] . ", line " . $backtrace[1]['line'] + ); + + return false; + } +} diff --git a/.circleci/features/content.feature b/.circleci/features/content.feature new file mode 100644 index 00000000000..08e4ec9fe2d --- /dev/null +++ b/.circleci/features/content.feature @@ -0,0 +1,12 @@ +Feature: Create Content through Drupal Content UI + In order to know that the Drupal content UI is working + As a website user + I need to be able to add a basic page + + @api + Scenario: Add a basic page + Given I am logged in as a user with the "administrator" role + And I am on "/node/add/page" + And I enter "Test Page" for "Title" + And I press "Save" + Then I should see "Basic page Test Page has been created." diff --git a/.circleci/features/pagecache.feature b/.circleci/features/pagecache.feature new file mode 100644 index 00000000000..3b49193de44 --- /dev/null +++ b/.circleci/features/pagecache.feature @@ -0,0 +1,10 @@ +Feature: Check for settings on the cache configuration page + In order to know that the site was installed from the pantheon profile + As a website administrator + I need to know that Pantheon's initial settings were applied by the installer + + @api + Scenario: Check to see if the page cache setting is set + Given I am logged in as a user with the "administrator" role + And I am on "/admin/config/development/performance" + Then the "900" option from "page_cache_maximum_age" should be selected diff --git a/.circleci/features/pantheonsolr.feature b/.circleci/features/pantheonsolr.feature new file mode 100644 index 00000000000..1d8b4d4cd8f --- /dev/null +++ b/.circleci/features/pantheonsolr.feature @@ -0,0 +1,10 @@ +Feature: Check for existance of Pantheon Solr module + In order to know that the site was installed from Pantheon's upstream + As a website user + I need to know that Pantheon's Solr module is available + + @api + Scenario: Check to see if Pantheon Solr is available + Given I am logged in as a user with the "administrator" role + And I am on "/admin/modules" + Then I should see "Pantheon Apache Solr" diff --git a/.circleci/features/phpversion.feature b/.circleci/features/phpversion.feature new file mode 100644 index 00000000000..2e7670e88e4 --- /dev/null +++ b/.circleci/features/phpversion.feature @@ -0,0 +1,10 @@ +Feature: Check php version + In order to know that pantheon.upstream.yml is working + As a website user + I need to know that I am running the correct version of php + + @api + Scenario: Check the php version in the phpinfo output + Given I am logged in as a user with the "administrator" role + And I am on "/admin/reports/status/php" + Then I should see "PHP Version 5.6." diff --git a/.circleci/local.test.dist b/.circleci/local.test.dist new file mode 100755 index 00000000000..1a152540720 --- /dev/null +++ b/.circleci/local.test.dist @@ -0,0 +1,12 @@ +#!/bin/bash + +# Copy to local.test and customize +circleci \ + -e CIRCLE_BUILD_NUM=0 \ + -e TERMINUS_TOKEN=$TERMINUS_TOKEN \ + -e TERMINUS_SITE=$TERMINUS_SITE \ + -e TERMINUS_ENV=ci-$CIRCLE_BUILD_NUM \ + -e TERMINUS_HIDE_UPDATE_MESSAGE=1 \ + -e WORDPRESS_ADMIN_USERNAME=admin \ + -e WORDPRESS_ADMIN_PASSWORD=$ADMIN_PASSWORD \ + build --job test diff --git a/.circleci/prepare.sh b/.circleci/prepare.sh new file mode 100755 index 00000000000..994fa021bc3 --- /dev/null +++ b/.circleci/prepare.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +### +# Prepare a Pantheon site environment for the Behat test suite, by pushing the +# requested upstream branch to the environment. +### + +set -ex + +if [ -z "$TERMINUS_SITE" ] || [ -z "$TERMINUS_ENV" ]; then + echo "TERMINUS_SITE and TERMINUS_ENV environment variables must be set" + exit 1 +fi + +### +# Clean up old unused environments, and make sure the dev site is spun up. +### +terminus build:env:delete:ci -n "$TERMINUS_SITE" --keep=2 --yes +terminus env:wake -n "$TERMINUS_SITE.dev" + +### +# Create a new environment for this particular test run. +### +terminus build:env:create -n "$TERMINUS_SITE.dev" "$TERMINUS_ENV" --yes +terminus drush -n "$TERMINUS_SITE.$TERMINUS_ENV" -- updatedb -y diff --git a/.circleci/set-up-globals.sh b/.circleci/set-up-globals.sh new file mode 100755 index 00000000000..4e689efe8cd --- /dev/null +++ b/.circleci/set-up-globals.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Create a local .ssh directory if needed & available +SELF_DIRNAME="`dirname -- "$0"`" +[ -d "$HOME/.ssh" ] || [ ! -d "$SELF_DIRNAME/local.ssh" ] || cp -R "$SELF_DIRNAME/local.ssh" "$HOME/.ssh" + +# If an admin password has not been defined, write one to ~/WORDPRESS_ADMIN_PASSWORD +if [ ! -f ~/WORDPRESS_ADMIN_PASSWORD ] && [ -z "$WORDPRESS_ADMIN_PASSWORD" ] ; then + echo $(openssl rand -hex 8) > ~/WORDPRESS_ADMIN_PASSWORD +fi + +# If an admin password has not been defined, read it from ~/WORDPRESS_ADMIN_PASSWORD +if [ ! -f ~/WORDPRESS_ADMIN_PASSWORD ] && [ -z "$WORDPRESS_ADMIN_PASSWORD" ] ; then + WORDPRESS_ADMIN_PASSWORD="$(cat ~/WORDPRESS_ADMIN_PASSWORD)" +fi + +#===================================================================================================================== +# EXPORT needed environment variables +# +# Circle CI 2.0 does not yet expand environment variables so they have to be manually EXPORTed +# Once environment variables can be expanded this section can be removed +# See: https://discuss.circleci.com/t/unclear-how-to-work-with-user-variables-circleci-provided-env-variables/12810/11 +# See: https://discuss.circleci.com/t/environment-variable-expansion-in-working-directory/11322 +# See: https://discuss.circleci.com/t/circle-2-0-global-environment-variables/8681 +#===================================================================================================================== +mkdir -p $(dirname $BASH_ENV) +touch $BASH_ENV +( + echo 'export PATH=$PATH:$HOME/bin' + echo 'export TERMINUS_HIDE_UPDATE_MESSAGE=1' + echo 'export TERMINUS_ENV=ci-$CIRCLE_BUILD_NUM' + echo 'export WORDPRESS_ADMIN_USERNAME=pantheon' + echo "export WORDPRESS_ADMIN_PASSWORD=$WORDPRESS_ADMIN_PASSWORD" +) >> $BASH_ENV +source $BASH_ENV + +echo "Test site is $TERMINUS_SITE.$TERMINUS_ENV" +echo "Logging in with a machine token:" +terminus auth:login -n --machine-token="$TERMINUS_TOKEN" +terminus whoami +touch $HOME/.ssh/config +echo "StrictHostKeyChecking no" >> "$HOME/.ssh/config" +git config --global user.email "$GIT_EMAIL" +git config --global user.name "Circle CI" +# Ignore file permissions. +git config --global core.fileMode false diff --git a/.circleci/test.sh b/.circleci/test.sh new file mode 100755 index 00000000000..640e668300c --- /dev/null +++ b/.circleci/test.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +### +# Execute the Behat test suite against a prepared Pantheon site environment. +### + +set -ex + +SELF_DIRNAME="`dirname -- "$0"`" + +# Require a target site +if [ -z "$TERMINUS_SITE" ] || [ -z "$TERMINUS_ENV" ]; then + echo "TERMINUS_SITE and TERMINUS_ENV environment variables must be set" + exit 1 +fi + +PATH=$PATH:~/.composer/vendor/bin + +# Create a drush alias file so that Behat tests can be executed against Pantheon. +terminus aliases +# Drush Behat driver fails without this option. +echo "\$options['strict'] = 0;" >> ~/.drush/pantheon.aliases.drushrc.php + +export BEHAT_PARAMS='{"extensions" : {"Behat\\MinkExtension" : {"base_url" : "http://'$TERMINUS_ENV'-'$TERMINUS_SITE'.pantheonsite.io/"}, "Drupal\\DrupalExtension" : {"drush" : { "alias": "@pantheon.'$TERMINUS_SITE'.'$TERMINUS_ENV'" }}}}' + +# We expect 'behat' to be in our PATH. Our container symlinks it at /usr/local/bin +cd $SELF_DIRNAME && behat --config=behat.yml $*