Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthieu Aubry <matt@piwik.org>2014-08-11 04:22:30 +0400
committerMatthieu Aubry <matt@piwik.org>2014-08-11 04:22:30 +0400
commitf69187c3633ee951c9c7643b03492474ab420c14 (patch)
tree8ee9f378e5075715507f7eed3c33a5a6741a5dd6 /plugins
parent487d0cdd0f80c56ceb78903473feb638841a2298 (diff)
parent63eaed6f7cfd30fde46724018a67fb8712bbb2d6 (diff)
Merge pull request #5962 from piwik/travis_system
Created unified system for running tests in travis for Piwik Core and any Piwik plugin.
Diffstat (limited to 'plugins')
-rw-r--r--plugins/CoreConsole/Commands/GenerateTravisYmlFile.php112
-rw-r--r--plugins/CoreConsole/TravisYmlView.php238
-rw-r--r--plugins/CoreConsole/templates/travis.yml.twig153
3 files changed, 503 insertions, 0 deletions
diff --git a/plugins/CoreConsole/Commands/GenerateTravisYmlFile.php b/plugins/CoreConsole/Commands/GenerateTravisYmlFile.php
new file mode 100644
index 0000000000..dc5ccf306f
--- /dev/null
+++ b/plugins/CoreConsole/Commands/GenerateTravisYmlFile.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CoreConsole\Commands;
+
+use Piwik\View;
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\CoreConsole\TravisYmlView;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Output\OutputInterface;
+use Exception;
+
+/**
+ * Command to generate an self-updating .travis.yml file either for Piwik Core or
+ * an individual Piwik plugin.
+ */
+class GenerateTravisYmlFile extends ConsoleCommand
+{
+ protected function configure()
+ {
+ $this->setName('generate:travis-yml')
+ ->setDescription('Generates a .travis.yml file for a plugin. The file can be auto-updating based on the parameters supplied.')
+ ->addOption('plugin', null, InputOption::VALUE_REQUIRED, 'The plugin for whom a .travis.yml file should be generated.')
+ ->addOption('core', null, InputOption::VALUE_NONE, 'Supplied when generating the .travis.yml file for Piwik core.'
+ . ' Should only be used by core developers.')
+ ->addOption('artifacts-pass', null, InputOption::VALUE_REQUIRED,
+ "Password to the Piwik build artifacts server. Will be encrypted in the .travis.yml file.")
+ ->addOption('github-token', null, InputOption::VALUE_REQUIRED,
+ "Github token of a user w/ push access to this repository. Used to auto-commit updates to the "
+ . ".travis.yml file and checkout dependencies. Will be encrypted in the .travis.yml file.\n\n"
+ . "If not supplied, the .travis.yml will fail the build if it needs updating.")
+ ->addOption('dump', null, InputOption::VALUE_REQUIRED, "Debugging option. Saves the output .travis.yml to the specified file.");
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $targetPlugin = $input->getOption('plugin');
+ $artifactsPass = $input->getOption('artifacts-pass');
+ $githubToken = $input->getOption('github-token');
+ $outputYmlPath = $this->getTravisYmlOutputPath($input, $targetPlugin);
+ $thisConsoleCommand = $this->getExecutedConsoleCommandForTravis($input);
+
+ $view = new TravisYmlView();
+ $view->processExistingTravisYml($outputYmlPath);
+ $view->configure($targetPlugin, $artifactsPass, $githubToken, $thisConsoleCommand, $output);
+ $travisYmlContents = $view->render();
+
+ $writePath = $input->getOption('dump');
+ if (empty($writePath)) {
+ $writePath = $outputYmlPath;
+ }
+
+ file_put_contents($writePath, $travisYmlContents);
+
+ $this->writeSuccessMessage($output, array("Generated .travis.yml file at '$writePath'!"));
+ }
+
+ private function getTravisYmlOutputPath(InputInterface $input, $targetPlugin)
+ {
+ if ($input->getOption('core')) {
+ return PIWIK_INCLUDE_PATH . '/.travis.yml';
+ } else if ($targetPlugin) {
+ $pluginDirectory = PIWIK_INCLUDE_PATH . '/plugins/' . $targetPlugin;
+ if (!is_writable($pluginDirectory)) {
+ throw new Exception("Cannot write to '$pluginDirectory'!");
+ }
+
+ return $pluginDirectory . '/.travis.yml';
+ } else {
+ throw new Exception("Neither --plugin option or --core option specified; don't know where to generate .travis.yml."
+ . " Execute './console help generate:travis-yml' for more info");
+ }
+ }
+
+ private function getExecutedConsoleCommandForTravis(InputInterface $input)
+ {
+ $command = "php ./console " . $this->getName();
+
+ $arguments = $input->getOptions();
+ if (isset($arguments['github-token'])) {
+ $arguments['github-token'] = '$GITHUB_USER_TOKEN';
+ }
+ if (isset($arguments['artifacts-pass'])) {
+ $arguments['artifacts-pass'] = '$ARTIFACTS_PASS';
+ }
+ unset($arguments['dump']);
+
+ foreach ($arguments as $name => $value) {
+ if ($value === false
+ || $value === null
+ ) {
+ continue;
+ }
+
+ if ($value === true) {
+ $command .= " --$name";
+ } else {
+ $command .= " --$name=\"". addslashes($value) . "\"";
+ }
+ }
+
+ return $command;
+ }
+} \ No newline at end of file
diff --git a/plugins/CoreConsole/TravisYmlView.php b/plugins/CoreConsole/TravisYmlView.php
new file mode 100644
index 0000000000..ee74d7c6ad
--- /dev/null
+++ b/plugins/CoreConsole/TravisYmlView.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+
+namespace Piwik\Plugins\CoreConsole;
+
+use Piwik\View;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * View class for the travis.yml.twig template file. Generates the contents for a .travis.yml file.
+ */
+class TravisYmlView extends View
+{
+ /**
+ * The .travis.yml section names that are overwritten by this command.
+ *
+ * @var string[]
+ */
+ private static $travisYmlSectionNames = array(
+ 'php',
+ 'language',
+ 'script',
+ 'before_install',
+ 'install',
+ 'before_script',
+ 'after_script',
+ 'after_success'
+ );
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct("@CoreConsole/travis.yml");
+ }
+
+ /**
+ * Parse existing data in a .travis.yml file that should be preserved in the output .travis.yml.
+ * Includes comments.
+ *
+ * @var string $existingYmlPath The path to the existing .travis.yml file.
+ */
+ public function processExistingTravisYml($existingYmlPath)
+ {
+ if (!file_exists($existingYmlPath)) {
+ return;
+ }
+
+ $existingYamlText = file_get_contents($existingYmlPath);
+ foreach ($this->getRootSectionsFromYaml($existingYamlText) as $sectionName => $offset) {
+ $section = $this->getRootSectionText($existingYamlText, $offset);
+ if ($sectionName == 'env') {
+ $this->existingEnv = $section;
+ } else if ($sectionName == 'matrix') {
+ $this->existingMatrix = $section;
+ } else if (!in_array($sectionName, self::$travisYmlSectionNames)) {
+ $this->extraSections .= "\n\n$sectionName:" . $section;
+ }
+ }
+ }
+
+ /**
+ * Configures the view for generation.
+ *
+ * @param string|null $targetPlugin The plugin target or `null` if generating for core.
+ * @param string|null $artifactsPass The password for the builds artifacts server. Encrypted in output.
+ * @param string $generateYmlCommand The command to use in travis when checking if a .travis.yml file is out
+ * of date.
+ * @param OutputInterface $output OutputInterface to output warnings and the like.
+ */
+ public function configure($targetPlugin, $artifactsPass, $githubToken, $generateYmlCommand, OutputInterface $output)
+ {
+ $this->pluginName = $targetPlugin;
+
+ if (empty($this->existingEnv)) {
+ $artifactsPass = $artifactsPass;
+ if (!empty($artifactsPass)) {
+ $this->artifactsPass = $this->travisEncrypt("ARTIFACTS_PASS=" . $artifactsPass, $output);
+ }
+
+ $githubToken = $githubToken;
+ if (!empty($githubToken)) {
+ $this->githubToken = $this->travisEncrypt("GITHUB_USER_TOKEN=" . $githubToken, $output);
+ }
+ } else {
+ $output->writeln("<info>Existing .yml files found, ignoring global variables specified on command line.</info>");
+ }
+
+ list($this->testsToRun, $this->testsToExclude) = $this->getTestsToRun();
+
+ $this->consoleCommand = $generateYmlCommand;
+ }
+
+ /**
+ * Extracts the name and offset of all root elements of a YAML document. This method does this by
+ * checking for text that starts at the beginning of a line and ends with a ':'.
+ *
+ * @param string $yamlText The YAML text to search through.
+ * @return array Array mapping string section names with the starting offset of the text in the YAML.
+ */
+ private function getRootSectionsFromYaml($yamlText)
+ {
+ preg_match_all("/^[a-zA-Z_]+:/m", $yamlText, $allMatches, PREG_OFFSET_CAPTURE);
+
+ $result = array();
+ foreach ($allMatches[0] as $match) {
+ $matchLength = strlen($match[0]);
+ $sectionName = substr($match[0], 0, $matchLength - 1);
+
+ $result[$sectionName] = $match[1] + $matchLength;
+ }
+ return $result;
+ }
+
+ /**
+ * Gets the text of a root YAML element in a YAML doc using the name of the element and the starting
+ * offset of the element's text. This is accomplished by searching for the first line that doesn't
+ * start with whitespace after the given offset and using the text between the given offset and the
+ * line w/o starting whitespace.
+ *
+ * @param string $yamlText The YAML text to search through.
+ * @param int $offset The offset start of the YAML text (does not include the element name and colon, ie
+ * the offset is after `'element:'`).
+ * @return string
+ */
+ private function getRootSectionText($yamlText, $offset)
+ {
+ preg_match("/^[^\s]/m", $yamlText, $endMatches, PREG_OFFSET_CAPTURE, $offset);
+
+ $endPos = isset($endMatches[0][1]) ? $endMatches[0][1] : strlen($yamlText);
+
+ return substr($yamlText, $offset, $endPos - $offset);
+ }
+
+ private function travisEncrypt($data, OutputInterface $output)
+ {
+ $output->writeln("Encrypting \"$data\"...");
+
+ $command = "travis encrypt \"$data\"";
+
+ // change dir to target plugin since plugin will be in its own git repo
+ if (!empty($this->pluginName)) {
+ $command = "cd \"" . $this->getPluginRootFolder() . "\" && " . $command;
+ }
+
+ exec($command, $output, $returnCode);
+ if ($returnCode !== 0) {
+ throw new Exception("Cannot encrypt \"$data\" for travis! Please make sure you have the travis command line "
+ . "utility installed (see http://blog.travis-ci.com/2013-01-14-new-client/).\n\n"
+ . "return code: $returnCode\n\n"
+ . "travis output:\n\n" . implode("\n", $output));
+ }
+
+ if (empty($output)) {
+ throw new Exception("Cannot parse travis encrypt output:\n\n" . implode("\n", $output));
+ }
+
+ // when not executed from a command line travis encrypt will return only the encrypted data
+ $encryptedData = $output[0];
+ if (substr($encryptedData, 0, 1) == '"') {
+ $encryptedData = substr($encryptedData, 1);
+ }
+ if (substr($encryptedData, -1) == '"') {
+ $encryptedData = substr($encryptedData, 0, strlen($encryptedData) - 1);
+ }
+
+ return $encryptedData;
+ }
+
+ private function getTestsToRun()
+ {
+ $testsToRun = array();
+ $testsToExclude = array();
+
+ if ($this->isTargetPluginContainsPluginTests()) {
+ $testsToRun[] = array('name' => 'PluginTests',
+ 'vars' => "MYSQL_ADAPTER=PDO_MYSQL");
+ $testsToRun[] = array('name' => 'PluginTests',
+ 'vars' => "MYSQL_ADAPTER=PDO_MYSQL TEST_AGAINST_CORE=latest_stable");
+
+ $testsToExclude[] = array('description' => 'execute latest stable tests only w/ PHP 5.5',
+ 'php' => '5.3',
+ 'env' => 'TEST_SUITE=PluginTests MYSQL_ADAPTER=PDO_MYSQL TEST_AGAINST_CORE=latest_stable');
+ $testsToExclude[] = array('php' => '5.4',
+ 'env' => 'TEST_SUITE=PluginTests MYSQL_ADAPTER=PDO_MYSQL TEST_AGAINST_CORE=latest_stable');
+ }
+ if ($this->isTargetPluginContainsUITests()) {
+ $testsToRun[] = array('name' => 'UITests',
+ 'vars' => "MYSQL_ADAPTER=PDO_MYSQL");
+
+ $testsToExclude[] = array('description' => 'execute UI tests only w/ PHP 5.5',
+ 'php' => '5.3',
+ 'env' => 'TEST_SUITE=UITests MYSQL_ADAPTER=PDO_MYSQL');
+ $testsToExclude[] = array('php' => '5.4',
+ 'env' => 'TEST_SUITE=UITests MYSQL_ADAPTER=PDO_MYSQL');
+ }
+
+ return array($testsToRun, $testsToExclude);
+ }
+
+ private function isTargetPluginContainsPluginTests()
+ {
+ $pluginPath = $this->getPluginRootFolder();
+ return $this->doesFolderContainPluginTests($pluginPath . "/tests")
+ || $this->doesFolderContainPluginTests($pluginPath . "/Test");
+ }
+
+ private function doesFolderContainPluginTests($folderPath)
+ {
+ $testFiles = glob($folderPath . "/**/*Test.php");
+ return !empty($testFiles);
+ }
+
+ private function isTargetPluginContainsUITests()
+ {
+ $pluginPath = $this->getPluginRootFolder();
+ return $this->doesFolderContainUITests($pluginPath . "/tests")
+ || $this->doesFolderContainUITests($pluginPath . "/Test");
+ }
+
+ private function doesFolderContainUITests($folderPath)
+ {
+ $testFiles = glob($folderPath . "/**/*_spec.js");
+ return !empty($testFiles);
+ }
+
+ private function getPluginRootFolder()
+ {
+ return PIWIK_INCLUDE_PATH . "/plugins/{$this->pluginName}";
+ }
+} \ No newline at end of file
diff --git a/plugins/CoreConsole/templates/travis.yml.twig b/plugins/CoreConsole/templates/travis.yml.twig
new file mode 100644
index 0000000000..6d25790458
--- /dev/null
+++ b/plugins/CoreConsole/templates/travis.yml.twig
@@ -0,0 +1,153 @@
+# do not edit this file manually, instead run the generate:travis-yml console command
+
+language: php
+
+# We want to test against PHP 5.3.3/5.4/5.5
+php:
+ - 5.5
+ - 5.4
+ - 5.3.3
+# - 5.6
+# - hhvm
+
+# Separate different test suites
+{% if existingEnv|default is empty -%}
+env:
+ global:
+ - PLUGIN_NAME={{ pluginName|raw }}
+{% if pluginName is empty %}
+ - PIWIK_ROOT_DIR=$TRAVIS_BUILD_DIR
+{% else %}
+ - PIWIK_ROOT_DIR=$TRAVIS_BUILD_DIR/piwik
+{% endif %}
+ {% if artifactsPass|default is not empty %}secure: "{{ artifactsPass|raw }}"{% endif %}
+ {% if githubToken|default is not empty %}secure: "{{ githubToken|raw }}"{% endif %}
+ matrix:
+{% for test in testsToRun %} - TEST_SUITE={{ test.name|raw }} {{ test.vars|raw }}
+{% endfor %}
+{%- else -%}
+env:
+ {{ existingEnv|trim|raw }}
+{% endif %}
+
+{% if existingMatrix|default is empty -%}
+{%- if testsToExclude is not empty -%}
+matrix:
+ exclude:
+{% for testExclude in testsToExclude %}
+{% if testExclude.description|default is not empty %} # {{ testExclude.description|raw }}
+{% endif %}
+ - php: {{ testExclude.php|raw }}
+ env: {{ testExclude.env|raw }}
+{% endfor %}
+{%- endif -%}
+{%- else -%}
+matrix:
+ {{ existingMatrix|trim|raw }}
+{% endif %}
+
+script: $PIWIK_ROOT_DIR/tests/travis/travis.sh
+
+before_install:
+ # do not use the Zend allocator on PHP 5.3 since it will randomly segfault after program execution
+ - '[[ "$TRAVIS_PHP_VERSION" == 5.3* ]] && export USE_ZEND_ALLOC=0 || true'
+{% if pluginName is not empty %}
+
+install:
+ # move all contents of current repo (which contains the plugin) to a new directory
+ - mkdir $PLUGIN_NAME
+ - cp -R !($PLUGIN_NAME) $PLUGIN_NAME
+ - cp -R .git/ $PLUGIN_NAME/
+
+ # checkout piwik in the current directory
+ - git clone https://github.com/piwik/piwik.git piwik
+ - cd piwik
+ - git fetch --all
+ - |
+ if [ "$TEST_AGAINST_PIWIK_BRANCH" == "" ]; then
+ if [ "$TEST_AGAINST_CORE" == "latest_stable" ]; then
+ export TEST_AGAINST_PIWIK_BRANCH=$(git describe --tags `git rev-list --tags --max-count=1`)
+ export TEST_AGAINST_PIWIK_BRANCH=`echo $TEST_AGAINST_PIWIK_BRANCH | tr -d ' ' | tr -d '\n'`
+ else
+ export TEST_AGAINST_PIWIK_BRANCH=master
+ fi
+ fi
+ - echo "Testing against '$TEST_AGAINST_PIWIK_BRANCH'"
+ - git checkout "$TEST_AGAINST_PIWIK_BRANCH"
+ - git submodule init
+ - git submodule update || true
+
+ # move plugin contents to folder in the plugins subdirectory
+ - rm -rf plugins/$PLUGIN_NAME
+ - mv ../$PLUGIN_NAME plugins
+{% endif %}
+
+before_script:
+ - ./tests/travis/configure_git.sh
+
+ # print out mysql information
+ - mysql --version
+ - mysql -e "SELECT VERSION();"
+
+ # configure mysql
+ - mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION'" # Travis default
+
+ # Uncomment to enable sql_mode STRICT_TRANS_TABLES (new default in Mysql 5.6)
+ #- mysql -e "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES'"
+ - mysql -e "SELECT @@sql_mode;"
+ - mysql -e "SHOW GLOBAL VARIABLES;"
+{% if pluginName is empty %}
+
+ # Start UI tests
+ - ./tests/travis/initiate_ui_tests.sh
+{%- endif %}
+
+ # travis now complains about this failing 9 times out of 10, so removing it. hopefully the random failures it prevented won't come back
+ # - travis_retry composer self-update
+
+ - travis_retry composer install
+
+ # print out more debugging info
+ - uname -a
+ - date
+ - php -r "var_dump(gd_info());"
+ - mysql -e 'create database piwik_tests;'
+
+ # Make sure we use Python 2.6
+ - travis_retry sudo add-apt-repository ppa:fkrull/deadsnakes -y
+ - travis_retry sudo apt-get update
+ - travis_retry sudo apt-get install python2.6 python2.6-dev
+
+ # Log Analytics works with Python 2.6 or 2.7 but we want to test on 2.6
+ - python2.6 --version
+ - python --version
+
+ - ./tests/travis/prepare.sh
+ - ./tests/travis/setup_webserver.sh
+
+ - export GENERATE_TRAVIS_YML_COMMAND='{{ consoleCommand|raw }}'
+ - ./tests/travis/autoupdate_travis_yml.sh
+
+ - cd tests/PHPUnit
+
+after_script:
+ # change directory back to root travis dir
+ - cd $PIWIK_ROOT_DIR
+
+ # output contents of files w/ debugging info to screen
+ - cat /var/log/nginx/error.log
+ - cat $PIWIK_ROOT_DIR/tmp/php-fpm.log
+ - cat $PIWIK_ROOT_DIR/tmp/logs/piwik.log
+ - cat $PIWIK_ROOT_DIR/config/config.ini.php
+
+ # upload test artifacts (for debugging travis failures)
+ - ./tests/travis/upload_artifacts.sh
+
+after_success:
+ - cd $PIWIK_ROOT_DIR
+{% if pluginName is empty %} - ./tests/travis/generate_docs.sh{%- endif %}
+
+
+{% if extraSections|default is not empty %}
+{{ extraSections|trim|raw }}
+{%- endif -%} \ No newline at end of file