From 07674a48af106758a15dc7d33250f0800f33bbed Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Fri, 26 Oct 2018 17:25:08 +0200 Subject: [PATCH] Add support for env, server and ini settings in parallel test runs --- .appveyor.yml | 4 +- .travis.yml | 6 +- .../PhpUnit/Tests/SimplePhpUnitTest.php | 131 ++++++ .../Modul1/phpunit.txml.dist | 36 ++ .../Modul1/tests/ModulTest.tphp | 53 +++ .../Modul1/tests/bootstrap.php | 5 + .../Modul2/phpunit.txml.dist | 36 ++ .../Modul2/tests/ModulTest.tphp | 53 +++ .../Modul2/tests/bootstrap.php | 5 + src/Symfony/Bridge/PhpUnit/bin/simple-phpunit | 393 ++++++++++++------ 10 files changed, 601 insertions(+), 121 deletions(-) create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/phpunit.txml.dist create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/ModulTest.tphp create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/bootstrap.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/phpunit.txml.dist create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/ModulTest.tphp create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/bootstrap.php diff --git a/.appveyor.yml b/.appveyor.yml index 34d3a703337e9..e333bbd37f5ec 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -54,7 +54,7 @@ test_script: - SET X=0 - SET SYMFONY_PHPUNIT_SKIPPED_TESTS=phpunit.skipped - copy /Y c:\php\php.ini-min c:\php\php.ini - - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --bootstrap vendor\autoload.php --exclude-group benchmark,intl-data || SET X=!errorlevel! - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --bootstrap vendor\autoload.php --exclude-group benchmark,intl-data || SET X=!errorlevel! - exit %X% diff --git a/.travis.yml b/.travis.yml index 913f031fd20e0..00cb2cf4e53a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,7 +69,7 @@ before_install: [ -d ~/.composer ] || mkdir ~/.composer cp .composer/* ~/.composer/ export PHPUNIT=$(readlink -f ./phpunit) - export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" + export PHPUNIT_X="$PHPUNIT --bootstrap vendor/autoload.php --exclude-group tty,benchmark,intl-data" export COMPOSER_UP='composer update --no-progress --no-suggest --ansi' export COMPONENTS=$(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n') find ~/.phpenv -name xdebug.ini -delete @@ -238,10 +238,10 @@ install: echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock else echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" - tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --bootstrap vendor/autoload.php --group tty if [[ $PHP = ${MIN_PHP%.*} ]]; then export PHP=$MIN_PHP - tfold src/Symfony/Component/Process.sigchild SYMFONY_DEPRECATIONS_HELPER=weak php-$MIN_PHP/sapi/cli/php ./phpunit --colors=always src/Symfony/Component/Process/ + tfold src/Symfony/Component/Process.sigchild SYMFONY_DEPRECATIONS_HELPER=weak php-$MIN_PHP/sapi/cli/php ./phpunit --bootstrap vendor/autoload.php --colors=always src/Symfony/Component/Process/ fi fi } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest.php b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest.php new file mode 100644 index 0000000000000..5bd098a0d30ef --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; + +class SimplePhpUnitTest extends TestCase +{ + private static $testFiles = [ + __DIR__.'/SimplePhpUnitTest/Modul1/phpunit.xml.dist', + __DIR__.'/SimplePhpUnitTest/Modul2/phpunit.xml.dist', + ]; + + private $currentCwd; + + public static function setUpBeforeClass() + { + foreach (self::$testFiles as $testFile) { + $renamedFile = str_replace('.xml.dist', '.txml.dist', $testFile); + + if (file_exists($renamedFile)) { + rename($renamedFile, $testFile); + } + } + } + + public static function tearDownAfterClass() + { + foreach (self::$testFiles as $testFile) { + if (file_exists($testFile)) { + rename($testFile, str_replace('.xml.dist', '.txml.dist', $testFile)); + } + } + } + + protected function setUp() + { + $this->currentCwd = getcwd(); + chdir(\dirname(__DIR__)); + } + + protected function tearDown() + { + chdir($this->currentCwd); + } + + public function testInstall() + { + $cmd = 'bin/simple-phpunit install'; + $this->execute($cmd, $output, $exitCode); + $this->assertSame(0, $exitCode); + } + + public function testSimplePhpunitShortConfigurationFile() + { + $cmd = 'bin/simple-phpunit -c Tests/SimplePhpUnitTest/Modul1/phpunit.xml.dist'; + $this->execute($cmd, $output); + $this->assertContains('OK (7 tests, 11 assertions)', implode(PHP_EOL, $output)); + } + + public function testSimplePhpunitWithConfigurationWithFilter() + { + $cmd = 'bin/simple-phpunit --filter=testEnv --configuration Tests/SimplePhpUnitTest/Modul1/phpunit.xml.dist'; + $this->execute($cmd, $output); + $this->assertContains('OK (1 test, 1 assertion)', implode(PHP_EOL, $output)); + } + + public function testParallelTests() + { + $cmd = 'bin/simple-phpunit Tests/SimplePhpUnitTest'; + $this->execute($cmd, $output); + + // Check parallel test suites are runned successfully + $testSuites = explode('Test Suite', implode(PHP_EOL, $output)); + + unset($testSuites[0]); // Remove header output + $testSuites = array_values($testSuites); + $this->assertCount(2, $testSuites); + + $this->assertContains('OK (7 tests, 11 assertions)', $testSuites[0]); + $this->assertContains('OK (7 tests, 11 assertions)', $testSuites[1]); + + // Check different phpunit versions are installed + $this->assertFileExists(\dirname(__DIR__).'/.phpunit/phpunit-6.5-remove-symfony_yaml-phpspec_prophecy/phpunit'); + $this->assertFileExists(\dirname(__DIR__).'/.phpunit/phpunit-7.4-remove-phpspec_prophecy-symfony_yaml/phpunit'); + } + + private function execute($command, &$output = null, &$return_var = null) + { + $oldPhpUnitRootDirectory = getenv('SYMFONY_PHPUNIT_ROOT_DIRECTORY'); + $oldPhpUnitDirectory = getenv('SYMFONY_PHPUNIT_DIR'); + + // Use putenv vor windows compatible setting of environment variables + putenv('SYMFONY_PHPUNIT_ROOT_DIRECTORY='.\dirname(__DIR__)); + putenv('SYMFONY_PHPUNIT_DIR='.\dirname(__DIR__).'/.phpunit'); + + $result = exec( + sprintf('php %s', $command), + $output, + $return_var + ); + + // Reset env variables + if (false !== $oldPhpUnitRootDirectory) { + // Set to old value + putenv('SYMFONY_PHPUNIT_ROOT_DIRECTORY='.$oldPhpUnitRootDirectory); + } else { + // Remove when no old value exists + putenv('SYMFONY_PHPUNIT_ROOT_DIRECTORY'); + } + + if (false !== $oldPhpUnitDirectory) { + // Set to old value + putenv('SYMFONY_PHPUNIT_DIR='.$oldPhpUnitDirectory); + } else { + // Remove when no old value exists + putenv('SYMFONY_PHPUNIT_DIR'); + } + + return $result; + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/phpunit.txml.dist b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/phpunit.txml.dist new file mode 100644 index 0000000000000..f033948ab75fd --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/phpunit.txml.dist @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + tests + + + + + + . + + + + + + + diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/ModulTest.tphp b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/ModulTest.tphp new file mode 100644 index 0000000000000..ab4d9f0b423b0 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/ModulTest.tphp @@ -0,0 +1,53 @@ +assertSame('ENV_VAR_MODUL_1', getenv('ENV_VAR')); + } + + public function testServer() + { + $this->assertSame('SERVER_VAR_MODUL_1', $_SERVER['SERVER_VAR']); + } + + public function testIni() + { + $this->assertSame('7', ini_get('precision')); + } + + public function testBootstrapEnv() + { + $this->assertSame('BOOTSTRAP_ENV_VAR_MODUL_1', getenv('BOOTSTRAP_ENV_VAR')); + + sleep(1); // To Check if the output is streamed + + $this->assertTrue(true); + } + + public function testBootstrapServer() + { + $this->assertSame('BOOTSTRAP_SERVER_VAR_MODUL_1', $_SERVER['BOOTSTRAP_SERVER_VAR']); + } + + public function testBootstrapIni() + { + $this->assertSame('15', ini_get('serialize_precision')); + } + + public function testSymfonyEnvs() + { + $this->assertSame('disabled', getenv('SYMFONY_DEPRECATIONS_HELPER')); + $this->assertSame('symfony/yaml phpspec/prophecy', getenv('SYMFONY_PHPUNIT_REMOVE')); + $this->assertSame('6.5', getenv('SYMFONY_PHPUNIT_VERSION')); + exec((defined('PHP_BINARY') ? PHP_BINARY : 'php') . ' ' . $_SERVER['SCRIPT_NAME'] . ' --version', $output); + $this->assertContains('PHPUnit 6.5', $output[0]); + + @trigger_error('Deprecation Error which should be ignored'); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/bootstrap.php new file mode 100644 index 0000000000000..2c7e2631c9748 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul1/tests/bootstrap.php @@ -0,0 +1,5 @@ + + + + + + + + + + + + + + + + + + tests + + + + + + . + + + + + + + diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/ModulTest.tphp b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/ModulTest.tphp new file mode 100644 index 0000000000000..2479c608a4f75 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/ModulTest.tphp @@ -0,0 +1,53 @@ +assertSame('ENV_VAR_MODUL_2', getenv('ENV_VAR')); + } + + public function testServer() + { + $this->assertSame('SERVER_VAR_MODUL_2', $_SERVER['SERVER_VAR']); + } + + public function testIni() + { + $this->assertSame('9', ini_get('precision')); + } + + public function testBootstrapEnv() + { + $this->assertSame('BOOTSTRAP_ENV_VAR_MODUL_2', getenv('BOOTSTRAP_ENV_VAR')); + + sleep(1); // To Check if the output is streamed + + $this->assertTrue(true); + } + + public function testBootstrapServer() + { + $this->assertSame('BOOTSTRAP_SERVER_VAR_MODUL_2', $_SERVER['BOOTSTRAP_SERVER_VAR']); + } + + public function testBootstrapIni() + { + $this->assertSame('11', ini_get('serialize_precision')); + } + + public function testSymfonyDeprecationHelper() + { + $this->assertSame('weak', getenv('SYMFONY_DEPRECATIONS_HELPER')); + $this->assertSame('phpspec/prophecy symfony/yaml', getenv('SYMFONY_PHPUNIT_REMOVE')); + $this->assertSame('7.4', getenv('SYMFONY_PHPUNIT_VERSION')); + exec((defined('PHP_BINARY') ? PHP_BINARY : 'php') . ' ' . $_SERVER['SCRIPT_NAME'] . ' --version', $output); + $this->assertContains('PHPUnit 7.4', $output[0]); + + @trigger_error('Deprecation Error which should be ignored'); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/bootstrap.php b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/bootstrap.php new file mode 100644 index 0000000000000..703630903f2bd --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/SimplePhpUnitTest/Modul2/tests/bootstrap.php @@ -0,0 +1,5 @@ +load($phpunitConfigFilename); - } else { - $phpunitConfig = false; - } - } - if (false !== $phpunitConfig) { - $var = new DOMXpath($phpunitConfig); +/** + * Read environment variable from system or given phpunit config file. + * + * @param string $phpUnitConfigFile + * @param string $name + * @param bool|string $default + * + * @return bool|string + */ +$getEnvVar = function ($phpUnitConfigFile, $name, $default = false) +{ + $value = getenv($name); + + if (file_exists($phpUnitConfigFile)) { + // Load phpunit config files and search for the env variable + $phpUnitConfig = new DomDocument(); + $phpUnitConfig->load($phpUnitConfigFile); + + $var = new DOMXpath($phpUnitConfig); foreach ($var->query('//php/env[@name="'.$name.'"]') as $var) { - return $var->getAttribute('value'); + $currentValue = $var->getAttribute('value'); + + if ('true' === $var->getAttribute('force')) { + return $currentValue; + } elseif (false === $value) { + return $currentValue; + } } } + if (false !== $value) { + return $value; + } + return $default; }; -if (PHP_VERSION_ID >= 70100) { - // PHPUnit 7 requires PHP 7.1+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.4'); -} elseif (PHP_VERSION_ID >= 70000) { - // PHPUnit 6 requires PHP 7.0+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '6.5'); -} elseif (PHP_VERSION_ID >= 50600) { - // PHPUnit 5 requires PHP 5.6+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '5.7'); -} else { - $PHPUNIT_VERSION = '4.8'; -} +/** + * Get the phpunit binary if not exist download phpunit install it with given configuration. + * + * @param string $PHP + * @param callable $getEnvVar + * @param string $phpUnitVersion + * @param string|null $phpUnitConfigFile + * + * @return string + */ +$getPHPUnit = function ($PHP, $getEnvVar, $phpUnitVersion, $phpUnitConfigFile = null) +{ + // Get project root directory where composer.json exists + static $root; + + if (!$root) { + $root = getenv('SYMFONY_PHPUNIT_ROOT_DIRECTORY'); + + $COMPOSER_JSON = getenv('COMPOSER') ?: 'composer.json'; + + if (!$root) { + // Go the folders up until find a composer.json file + $root = __DIR__; + while (!file_exists($root.'/'.$COMPOSER_JSON) || file_exists($root.'/DeprecationErrorHandler.php')) { + if ($root === dirname($root)) { + break; + } + $root = dirname($root); + } + } + } -$COMPOSER_JSON = getenv('COMPOSER') ?: 'composer.json'; + // Get directory where the phpunit versions get installed + $PHPUNIT_DIR = $getEnvVar($phpUnitConfigFile, 'SYMFONY_PHPUNIT_DIR', $root.'/vendor/bin/.phpunit'); -$root = __DIR__; -while (!file_exists($root.'/'.$COMPOSER_JSON) || file_exists($root.'/DeprecationErrorHandler.php')) { - if ($root === dirname($root)) { - break; - } - $root = dirname($root); -} + $phpUnit = $PHPUNIT_DIR . '/phpunit-' . $phpUnitVersion; -$oldPwd = getcwd(); -$PHPUNIT_DIR = $getEnvVar('SYMFONY_PHPUNIT_DIR', $root.'/vendor/bin/.phpunit'); -$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; -$PHP = escapeshellarg($PHP); -if ('phpdbg' === PHP_SAPI) { - $PHP .= ' -qrr'; -} + // make own phpunit version when dependencies are removed + $SYMFONY_PHPUNIT_REMOVE = $getEnvVar($phpUnitConfigFile, 'SYMFONY_PHPUNIT_REMOVE', ''); + + if ($SYMFONY_PHPUNIT_REMOVE) { + $phpUnit .= '-remove-' . str_replace(array('/', ' '), array('_', '-'), $SYMFONY_PHPUNIT_REMOVE); + } -$defaultEnvs = array( - 'COMPOSER' => 'composer.json', - 'COMPOSER_VENDOR_DIR' => 'vendor', - 'COMPOSER_BIN_DIR' => 'bin', -); + $phpUnit .= '/phpunit'; -foreach ($defaultEnvs as $envName => $envValue) { - if ($envValue !== getenv($envName)) { - putenv("$envName=$envValue"); - $_SERVER[$envName] = $_ENV[$envName] = $envValue; + // If phpunit was installed before return path to binary. + if (file_exists($phpUnit)) { + return $phpUnit; } -} -$COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) - ? $PHP.' '.escapeshellarg($COMPOSER) - : 'composer'; + // Build a standalone phpunit without symfony/yaml nor prophecy by default + $oldPwd = getcwd(); + // Override COMPOSER_VENDOR_DIR and COMPOSER_BIN_DIR with their default values in PHPUnit Bridge + $defaultEnvs = array( + 'COMPOSER' => 'composer.json', + 'COMPOSER_VENDOR_DIR' => 'vendor', + 'COMPOSER_BIN_DIR' => 'bin', + ); -$SYMFONY_PHPUNIT_REMOVE = $getEnvVar('SYMFONY_PHPUNIT_REMOVE', 'phpspec/prophecy symfony/yaml'); + foreach ($defaultEnvs as $envName => $envValue) { + if ($envValue !== getenv($envName)) { + putenv("$envName=$envValue"); + $_SERVER[$envName] = $_ENV[$envName] = $envValue; + } + } -if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__)."\n".$SYMFONY_PHPUNIT_REMOVE !== @file_get_contents("$PHPUNIT_DIR/.$PHPUNIT_VERSION.md5")) { - // Build a standalone phpunit without symfony/yaml nor prophecy by default + // Get composer binary for phpunit installation + $COMPOSER = file_exists($COMPOSER = $oldPwd.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar 2> /dev/null`)) + ? $PHP.' '.escapeshellarg($COMPOSER) + : 'composer'; @mkdir($PHPUNIT_DIR, 0777, true); chdir($PHPUNIT_DIR); - if (file_exists("phpunit-$PHPUNIT_VERSION")) { - passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION.old")); - rename("phpunit-$PHPUNIT_VERSION", "phpunit-$PHPUNIT_VERSION.old"); - passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION.old")); + + $phpUnitDirectory = basename(dirname($phpUnit)); + $extractDirectory = getcwd() . '/extract-' . $phpUnitDirectory; + + if (file_exists($extractDirectory)) { + passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL': 'rm -rf %s', "$extractDirectory.old")); + rename($extractDirectory, "$extractDirectory.old"); + passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "$extractDirectory.old")); } - passthru("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\""); - chdir("phpunit-$PHPUNIT_VERSION"); + + passthru("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit $extractDirectory \"$phpUnitVersion.*\""); + chdir($extractDirectory); + + // Remove dependencies like symfony/yaml and prophecy if ($SYMFONY_PHPUNIT_REMOVE) { passthru("$COMPOSER remove --no-update ".$SYMFONY_PHPUNIT_REMOVE); } - if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) { + if (5.1 <= $phpUnitVersion && $phpUnitVersion < 5.4) { passthru("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); } if (file_exists($path = $root.'/vendor/symfony/phpunit-bridge')) { @@ -124,7 +161,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ passthru("$COMPOSER require --no-update symfony/phpunit-bridge \"*\""); } $prevRoot = getenv('COMPOSER_ROOT_VERSION'); - putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); + putenv("COMPOSER_ROOT_VERSION=$phpUnitVersion.99"); // --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true))); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); @@ -152,69 +189,187 @@ Symfony\Bridge\PhpUnit\TextUI\Command::main(); EOPHP ); - chdir('..'); - file_put_contents(".$PHPUNIT_VERSION.md5", md5_file(__FILE__)."\n".$SYMFONY_PHPUNIT_REMOVE); + + // After finishing dependency install move directory to correct place + rename($extractDirectory, $PHPUNIT_DIR . '/' . $phpUnitDirectory); + chdir($oldPwd); -} + return $phpUnit; +}; -global $argv, $argc; -$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); -$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; -$components = array(); -$cmd = array_map('escapeshellarg', $argv); -$exit = 0; +/** + * Get the phpunit version from SYMFONY_PHPUNIT_VERSION env variable or the given phpunit config file. + * + * @param callable $getEnvVar + * @param string|null $phpUnitConfigFile + * + * @return mixed|string + */ +$getPHPUnitVersion = function ($getEnvVar, $phpUnitConfigFile = null) +{ + if (PHP_VERSION_ID >= 70100) { + // PHPUnit 7 is required for PHP 7.1+ + $phpUnitVersion = $getEnvVar($phpUnitConfigFile, 'SYMFONY_PHPUNIT_VERSION', '7.4'); + } elseif (PHP_VERSION_ID >= 70000) { + // PHPUnit 6 does not support PHP 7.0 + $phpUnitVersion = $getEnvVar($phpUnitConfigFile, 'SYMFONY_PHPUNIT_VERSION', '6.5'); + } elseif (PHP_VERSION_ID >= 50600) { + // PHPUnit 5 requires PHP 5.6+ + $phpUnitVersion = $getEnvVar($phpUnitConfigFile, 'SYMFONY_PHPUNIT_VERSION', '5.7'); + } else { + $phpUnitVersion = '4.8'; + } -if (isset($argv[1]) && 'symfony' === $argv[1] && !file_exists('symfony') && file_exists('src/Symfony')) { - $argv[1] = 'src/Symfony'; -} -if (isset($argv[1]) && is_dir($argv[1]) && !file_exists($argv[1].'/phpunit.xml.dist')) { - // Find Symfony components in plain PHP for Windows portability + return $phpUnitVersion; +}; - $finder = new RecursiveDirectoryIterator($argv[1], FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::UNIX_PATHS); - $finder = new RecursiveIteratorIterator($finder); - $finder->setMaxDepth(getenv('SYMFONY_PHPUNIT_MAX_DEPTH') ?: 3); +/** + * Find all phpunit config files by given arguments. + * + * bin/simple-phpunit -> return root phpunit config file + * bin/simple-phpunit -c path/to/phpunit.xml.dist -> return given phpunit file + * bin/simple-phpunit path/to/folder -> return all phpunit config files in given folder + * + * @param array $argv + * + * @return array + */ +$loadPHPUnitConfigFiles = function (&$argv) { + $outputError = function($text, $exitCode = 1) + { + echo "\033[41m" . $text . "\033[0m" . PHP_EOL; - foreach ($finder as $file => $fileInfo) { - if ('phpunit.xml.dist' === $file) { - $components[] = dirname($fileInfo->getPathname()); + exit($exitCode); + }; + + $phpUnitConfigFiles = array(); + + // Use configuration file current folder when no arguments given + if (!isset($argv[1])) { + if (file_exists('phpunit.xml')) { + $phpUnitConfigFiles[] = realpath('phpunit.xml'); + } elseif (file_exists('phpunit.xml.dist')) { + $phpUnitConfigFiles[] = realpath('phpunit.xml.dist'); + } else { + $outputError('No phpunit.xml.dist file found!'); } + + return $phpUnitConfigFiles; } - if ($components) { - array_shift($cmd); + + // Use config file when given it over -c or --configuration argument + if (($configArgumentNr = array_search('-c', $argv)) + || ($configArgumentNr = array_search('--configuration', $argv))) + { + ++$configArgumentNr; + + if (!isset($argv[$configArgumentNr])) { + $outputError('Please provide a configuration file when using "-c" argument!'); + + exit(1); + } + + $phpUnitConfigFiles[] = realpath($argv[$configArgumentNr]); + + // Following arguments are given manually to the phpunit process + unset($argv[$configArgumentNr]); + unset($argv[$configArgumentNr - 1]); + + return $phpUnitConfigFiles; } -} -$cmd[0] = sprintf('%s %s --colors=always', $PHP, escapeshellarg("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit")); -$cmd = str_replace('%', '%%', implode(' ', $cmd)).' %1$s'; + // If symfony if given set the folder to src/Symfony + if (isset($argv[1]) && 'symfony' === $argv[1] && !file_exists('symfony') && file_exists('src/Symfony')) { + $argv[1] = 'src/Symfony'; + } -if ('\\' === DIRECTORY_SEPARATOR) { - $cmd = 'cmd /v:on /d /c "('.$cmd.')%2$s"'; -} else { - $cmd .= '%2$s'; -} + // Find all config file when folder given + if (is_dir($argv[1])) { + $finder = new RecursiveDirectoryIterator($argv[1], FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::UNIX_PATHS); + $finder = new RecursiveIteratorIterator($finder); + $finder->setMaxDepth(getenv('SYMFONY_PHPUNIT_MAX_DEPTH') ?: 3); + + foreach ($finder as $file => $fileInfo) { + // Prefer phpunit.xml over phpunit.xml.dist file + $component = dirname($fileInfo->getPathname()); + if ('phpunit.xml.dist' === $file && !isset($phpUnitConfigFiles[$component])) { + $phpUnitConfigFiles[$component] = realpath($fileInfo->getPathname()); + } elseif ('phpunit.xml' === $file) { + $phpUnitConfigFiles[$component] = realpath($fileInfo->getPathname()); + } + } -if ($components) { - $skippedTests = isset($_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS']) ? $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] : false; - $runningProcs = array(); + ksort($phpUnitConfigFiles); - foreach ($components as $component) { - // Run phpunit tests in parallel + array_values($phpUnitConfigFiles); - if ($skippedTests) { - putenv("SYMFONY_PHPUNIT_SKIPPED_TESTS=$component/$skippedTests"); + if (0 === count($phpUnitConfigFiles)) { + $outputError('No configuration files found in: ' . getcwd() . $argv[1]); } - $c = escapeshellarg($component); + unset($argv[1]); // $argv[1] should not provided to phpunit process + } elseif(file_exists($argv[1])) { + $phpUnitConfigFiles[] = $argv[1]; + + unset($argv[1]); // $argv[1] should not provided to phpunit process + } + + return $phpUnitConfigFiles; +}; + +// ------------------------------------------------------------------ // +// Helper Functions End // +// ------------------------------------------------------------------ // + +$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); +$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; +$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; + +$phpUnitConfigFiles = $loadPHPUnitConfigFiles($argv); - if ($proc = proc_open(sprintf($cmd, $c, " > $c/phpunit.stdout 2> $c/phpunit.stderr"), array(), $pipes)) { +$runningProcs = array(); +$exit = 0; + +if (!isset($argv[1]) || 'install' !== $argv[1]) { + array_splice($argv, 1, 0, array('--colors=always')); +} + +$phpUnitVersion = $getPHPUnitVersion($getEnvVar); +$phpUnitConfigFile = null; + +if (1 === count($phpUnitConfigFiles)) { + // When only one phpunit config file run it directly in current process for streamed output + $phpUnitConfigFile = reset($phpUnitConfigFiles); + $phpUnitVersion = $getPHPUnitVersion($getEnvVar, $phpUnitConfigFile); + $argv[] = '-c'; + $argv[] = $phpUnitConfigFile; +} else { + foreach ($phpUnitConfigFiles as $phpUnitConfigFile) { + $phpUnitVersion = $getPHPUnitVersion($getEnvVar, $phpUnitConfigFile); + $phpUnit = $getPHPUnit($PHP, $getEnvVar, $phpUnitVersion, $phpUnitConfigFile); + $component = dirname($phpUnitConfigFile); + + $cmd = sprintf('%s %s -c %s %s > %s/phpunit.stdout 2> %s/phpunit.stderr', + escapeshellcmd($PHP), + escapeshellcmd($phpUnit), + escapeshellarg($phpUnitConfigFile), + implode(' ', array_map('escapeshellarg', array_slice($argv, 1))), + escapeshellarg($component), + escapeshellarg($component) + ); + + if ($proc = proc_open($cmd, array(), $pipes)) { $runningProcs[$component] = $proc; } else { $exit = 1; echo "\033[41mKO\033[0m $component\n\n"; } } +} +if (count($runningProcs)) { + // Wait for the processes to be finished and output there result while ($runningProcs) { usleep(300000); $terminatedProcs = array(); @@ -228,7 +383,7 @@ if ($components) { } foreach ($terminatedProcs as $component => $procStatus) { - foreach (array('out', 'err') as $file) { + foreach (['out', 'err'] as $file) { $file = "$component/phpunit.std$file"; readfile($file); unlink($file); @@ -246,14 +401,20 @@ if ($components) { } } } -} elseif (!isset($argv[1]) || 'install' !== $argv[1] || file_exists('install')) { +} elseif (isset($argv[1]) && 'install' === $argv[1] || file_exists('install')) { + // If not yet installed install phpunit + if (0 === count($phpUnitConfigFiles)) { + $getPHPUnit($PHP, $getEnvVar, $phpUnitVersion, $phpUnitConfigFile); + } +} else { + // If only one configuration file is found call phpunit directly if (!class_exists('SymfonyBlacklistSimplePhpunit', false)) { class SymfonyBlacklistSimplePhpunit {} } - array_splice($argv, 1, 0, array('--colors=always')); + $_SERVER['argv'] = $argv; $_SERVER['argc'] = ++$argc; - include "$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit"; + include $getPHPUnit($PHP, $getEnvVar, $phpUnitVersion, $phpUnitConfigFile); } exit($exit);