setName('tests:run'); $this->setDescription('Run Piwik PHPUnit tests one testsuite after the other'); $this->addArgument('variables', InputArgument::IS_ARRAY, 'Eg a path to a file or directory, the name of a testsuite, the name of a plugin, ... We will try to detect what you meant. You can define multiple values', array()); $this->addOption('options', 'o', InputOption::VALUE_OPTIONAL, 'All options will be forwarded to phpunit', ''); $this->addOption('filter', null, InputOption::VALUE_OPTIONAL, 'Adds the phpunit filter option to run only specific tests that start with the given name', ''); $this->addOption('xhprof', null, InputOption::VALUE_NONE, 'Profile using xhprof.'); $this->addOption('group', null, InputOption::VALUE_REQUIRED, 'Run only a specific test group. Separate multiple groups by comma, for instance core,plugins', ''); $this->addOption('file', null, InputOption::VALUE_REQUIRED, 'Execute tests within this file. Should be a path relative to the tests/PHPUnit directory.'); $this->addOption('testsuite', null, InputOption::VALUE_REQUIRED, 'Execute tests of a specific test suite, for instance unit, integration or system.'); $this->addOption('enable-logging', null, InputOption::VALUE_NONE, 'Enable logging to the configured log file during tests.'); } protected function execute(InputInterface $input, OutputInterface $output) { $options = $input->getOption('options'); $groups = $input->getOption('group'); $magics = $input->getArgument('variables'); $matomoDomain = $input->getOption('matomo-domain'); $enableLogging = $input->getOption('enable-logging'); $filter = $input->getOption('filter'); if (!empty($filter)) { $options .= ' --filter=' . escapeshellarg($filter); } $groups = $this->getGroupsFromString($groups); // bin is the composer executeable directory, where all vendors (should) place their executables $command = PIWIK_VENDOR_PATH . '/bin/phpunit'; if (!$this->isCoverageEnabled($options) && $this->isXdebugLoaded()) { $message = 'Did you know? You can run tests faster by disabling xdebug'; if($this->isXdebugCodeCoverageEnabled()) { $message .= ' (if you need xdebug, speed up tests by setting xdebug.coverage_enable=0)'; } $output->writeln('' . $message .''); } // force xdebug usage for coverage options if ($this->isCoverageEnabled($options) && !$this->isXdebugLoaded()) { $output->writeln('xdebug extension required for code coverage.'); $output->writeln('searching for xdebug extension...'); $extensionDir = shell_exec('php-config --extension-dir'); $xdebugFile = trim($extensionDir) . DIRECTORY_SEPARATOR . 'xdebug.so'; if (!file_exists($xdebugFile)) { $dialog = $this->getHelperSet()->get('dialog'); $xdebugFile = $dialog->askAndValidate($output, 'xdebug not found. Please provide path to xdebug.so', function($xdebugFile) { return file_exists($xdebugFile); }); } else { $output->writeln('xdebug extension found in extension path.'); } $output->writeln("using $xdebugFile as xdebug extension."); $phpunitPath = trim(shell_exec('which phpunit')); $command = sprintf('php -d zend_extension=%s %s', $xdebugFile, $phpunitPath); } if ($input->getOption('xhprof')) { Profiler::setupProfilerXHProf($isMainRun = true); putenv('PIWIK_USE_XHPROF=1'); } $suite = $this->getTestsuite($input); $testFile = $this->getTestFile($input); if (!empty($magics)) { foreach ($magics as $magic) { if (empty($suite) && (in_array($magic, $this->getTestsSuites()))) { $suite = $this->buildTestSuiteName($magic); } elseif (empty($testFile) && 'core' === $magic) { $testFile = $this->fixPathToTestFileOrDirectory('tests/PHPUnit'); } elseif (empty($testFile) && 'plugins' === $magic) { $testFile = $this->fixPathToTestFileOrDirectory('plugins'); } elseif (empty($testFile) && file_exists($magic)) { $testFile = $this->fixPathToTestFileOrDirectory($magic); } elseif (empty($testFile) && $this->getPluginTestFolderName($magic)) { $testFile = $this->getPluginTestFolderName($magic); } elseif (empty($groups)) { $groups = $this->getGroupsFromString($magic); } else { $groups[] = $magic; } } } // Tear down any DB that already exists Db::destroyDatabaseObject(); $this->executeTests($matomoDomain, $suite, $testFile, $groups, $options, $command, $output, $enableLogging); return $this->returnVar; } private function getPluginTestFolderName($name) { $pluginName = $this->getPluginName($name); $folder = ''; if (!empty($pluginName)) { $path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName; if (is_dir($path . '/tests')) { $folder = $this->fixPathToTestFileOrDirectory($path . '/tests'); } elseif (is_dir($path . '/Tests')) { $folder = $this->fixPathToTestFileOrDirectory($path . '/Tests'); } } return $folder; } private function getPluginName($name) { $pluginNames = Plugin\Manager::getInstance()->getAllPluginsNames(); foreach ($pluginNames as $pluginName) { if (strtolower($pluginName) === strtolower($name)) { return $pluginName; } } } private function getTestFile(InputInterface $input) { $testFile = $input->getOption('file'); if (empty($testFile)) { return ''; } return $this->fixPathToTestFileOrDirectory($testFile); } private function executeTests($piwikDomain, $suite, $testFile, $groups, $options, $command, OutputInterface $output, $enableLogging) { if (empty($suite) && empty($groups) && empty($testFile)) { foreach ($this->getTestsSuites() as $suite) { $suite = $this->buildTestSuiteName($suite); $this->executeTests($piwikDomain, $suite, $testFile, $groups, $options, $command, $output, $enableLogging); } return; } $params = $this->buildPhpUnitCliParams($suite, $groups, $options); if (!empty($testFile)) { $params = $params . " " . $testFile; } $this->executeTestRun($piwikDomain, $command, $params, $output, $enableLogging); } private function executeTestRun($piwikDomain, $command, $params, OutputInterface $output, $enableLogging) { $envVars = ''; if (!empty($piwikDomain)) { $envVars .= "PIWIK_DOMAIN=$piwikDomain"; } if (!empty($enableLogging)) { $envVars .= " MATOMO_TESTS_ENABLE_LOGGING=1"; } $cmd = $this->getCommand($envVars, $command, $params); $output->writeln('Executing command: ' . $cmd . ''); passthru($cmd, $returnVar); $output->writeln(""); $this->returnVar += $returnVar; } private function getTestsSuites() { return array('unit', 'integration', 'system', 'plugin'); } /** * @param $command * @param $params * @return string */ private function getCommand($envVars, $command, $params) { return sprintf('cd %s/tests/PHPUnit && %s %s %s', PIWIK_DOCUMENT_ROOT, $envVars, $command, $params); } private function buildPhpUnitCliParams($suite, $groups, $options) { $params = $options . " "; if (!empty($groups)) { $groups = implode(',', $groups); $params .= '--group ' . $groups . ' '; } else { $groups = ''; } if (!empty($suite)) { $params .= ' --testsuite ' . $suite; } else { $suite = ''; } $params = str_replace('%suite%', $suite, $params); $params = str_replace('%group%', $groups, $params); return $params; } private function getTestsuite(InputInterface $input) { $suite = $input->getOption('testsuite'); if (empty($suite)) { return; } $availableSuites = $this->getTestsSuites(); if (!in_array($suite, $availableSuites)) { throw new \InvalidArgumentException('Invalid testsuite specified. Use one of: ' . implode(', ', $availableSuites)); } $suite = $this->buildTestSuiteName($suite); return $suite; } private function buildTestSuiteName($suite) { return ucfirst($suite) . 'Tests'; } private function isCoverageEnabled($options) { return false !== strpos($options, '--coverage'); } private function isXdebugLoaded() { return extension_loaded('xdebug'); } private function isXdebugCodeCoverageEnabled() { return (bool)ini_get('xdebug.coverage_enable'); } private function fixPathToTestFileOrDirectory($testFile) { if ('/' !== substr($testFile, 0, 1)) { $testFile = '../../' . $testFile; } return $testFile; } private function getGroupsFromString($groups) { $groups = explode(",", $groups); $groups = array_filter($groups, 'strlen'); return $groups; } }