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;
}
}