Last active
December 20, 2015 00:29
-
-
Save kriswallsmith/6042049 to your computer and use it in GitHub Desktop.
A test harness for your Symfony2 project. Runs functional tests in chunks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env php | |
<?php | |
use Symfony\Component\Console\Application; | |
use Symfony\Component\Console\Input\ArrayInput as Input; | |
use Symfony\Component\Console\Input\InputOption as Option; | |
require_once __DIR__.'/../vendor/autoload.php'; | |
/** Outputs and runs a command. */ | |
function run($output, $command, $exit = true) | |
{ | |
$output->writeln('+ <info>'.str_replace(__DIR__, basename(__DIR__), $command).'</info>'); | |
passthru($command, $code); | |
if ($code && $exit) { | |
exit($code); | |
} | |
return $code; | |
} | |
/** Creates the test chunks. */ | |
function chunk_tests($size) | |
{ | |
$chunks = array(array()); | |
$counter = 0; | |
$out = shell_exec(sprintf('find %s/../src -name *Test.php -print0 | xargs -0 grep -l "@group functional"', __DIR__)); | |
foreach (explode("\n", trim($out)) as $file) { | |
$count = shell_exec('grep "public function" '.escapeshellarg($file).' | wc -l'); | |
if ($size >= $counter + $count) { | |
$chunks[count($chunks) - 1][] = $file; | |
$counter += $count; | |
} else { | |
$chunks[] = array($file); | |
$counter = $count; | |
} | |
} | |
return $chunks; | |
} | |
/** Maps our options to PHPUnit flags. */ | |
function flags($input, $output) | |
{ | |
$flags = '-d memory_limit='.escapeshellarg($input->getOption('memory-limit')); | |
if ($input->getOption('stop-on-failure')) { | |
$flags .= ' --stop-on-failure'; | |
} | |
if ($input->getOption('stop-on-error')) { | |
$flags .= ' --stop-on-error'; | |
} | |
if ($output->isDecorated()) { | |
$flags .= ' --colors'; | |
} | |
if ($input->getOption('debug')) { | |
$flags .= ' --debug'; | |
} | |
return $flags; | |
} | |
$app = new Application(); | |
$app->register('all') | |
->setDescription('Run the unit and functional test suites') | |
->addOption('debug', null, Option::VALUE_NONE, 'Run tests in debug mode') | |
->addOption('memory-limit', null, Option::VALUE_REQUIRED, 'Memory limit for each chunk', '512M') | |
->addOption('stop-on-failure', null, Option::VALUE_NONE, 'Stop if there is a failure') | |
->addOption('stop-on-error', null, Option::VALUE_NONE, 'Stop if there is an error') | |
->addOption('reset-cache', null, Option::VALUE_NONE, 'Warmup the cache before running tests') | |
->addOption('reset-dbs', null, Option::VALUE_NONE, 'Reset the databases before running tests') | |
->addOption('chunk', null, Option::VALUE_REQUIRED, 'Run a specific chunk of tests') | |
->addOption('chunk-size', null, Option::VALUE_REQUIRED, 'Approximate number of tests to run in each chunk', 100) | |
->setCode(function($input, $output) use($app) { | |
$app->setAutoExit(false); | |
// unit | |
$code = $app->run(new Input(array( | |
'command' => 'unit', | |
'--debug' => $input->getOption('debug'), | |
'--memory-limit' => $input->getOption('memory-limit'), | |
'--stop-on-failure' => $input->getOption('stop-on-failure'), | |
'--stop-on-error' => $input->getOption('stop-on-error'), | |
)), $output); | |
if ($code && ($input->getOption('stop-on-failure') || $input->getOption('stop-on-error'))) { | |
return $code; | |
} | |
// functional | |
$args = array( | |
'command' => 'functional', | |
'--debug' => $input->getOption('debug'), | |
'--memory-limit' => $input->getOption('memory-limit'), | |
'--stop-on-failure' => $input->getOption('stop-on-failure'), | |
'--stop-on-error' => $input->getOption('stop-on-error'), | |
'--reset-cache' => $input->getOption('reset-cache'), | |
'--reset-dbs' => $input->getOption('reset-dbs'), | |
'--chunk' => $input->getOption('chunk'), | |
'--chunk-size' => $input->getOption('chunk-size'), | |
); | |
if (null === $args['--chunk']) { | |
unset($args['--chunk']); | |
} | |
$app->run(new Input($args), $output); | |
$app->setAutoExit(true); | |
}) | |
; | |
$app->register('unit') | |
->setDescription('Run the unit test suite') | |
->addOption('debug', null, Option::VALUE_NONE, 'Run tests in debug mode') | |
->addOption('memory-limit', null, Option::VALUE_REQUIRED, 'Memory limit for each chunk', '512M') | |
->addOption('stop-on-failure', null, Option::VALUE_NONE, 'Stop if there is a failure') | |
->addOption('stop-on-error', null, Option::VALUE_NONE, 'Stop if there is an error') | |
->setCode(function($input, $output) { | |
run($output, sprintf('phpunit -c %s --exclude-group functional %s', __DIR__, flags($input, $output))); | |
}) | |
; | |
$app->register('functional') | |
->setDescription('Run the functional test suite') | |
->addOption('debug', null, Option::VALUE_NONE, 'Run tests in debug mode') | |
->addOption('memory-limit', null, Option::VALUE_REQUIRED, 'Memory limit for each chunk', '512M') | |
->addOption('stop-on-failure', null, Option::VALUE_NONE, 'Stop if there is a failure') | |
->addOption('stop-on-error', null, Option::VALUE_NONE, 'Stop if there is an error') | |
->addOption('reset-cache', null, Option::VALUE_NONE, 'Warmup the cache before running tests') | |
->addOption('reset-dbs', null, Option::VALUE_NONE, 'Reset the databases before running tests') | |
->addOption('chunk', null, Option::VALUE_REQUIRED, 'Run a specific chunk of tests') | |
->addOption('chunk-size', null, Option::VALUE_REQUIRED, 'Approximate number of tests to run in each chunk', 100) | |
->setCode(function($input, $output) { | |
// create chunks | |
$chunks = chunk_tests($input->getOption('chunk-size')); | |
$total = count($chunks); | |
if ($pos = $input->getOption('chunk')) { | |
$chunks = array($pos - 1 => $chunks[$pos - 1]); | |
} | |
// reset cache | |
if ($input->getOption('reset-cache')) { | |
run($output, sprintf('php %s/console cache:clear --env=test', __DIR__)); | |
} | |
// reset databases | |
if ($input->getOption('reset-dbs')) { | |
run($output, sprintf('php %s/console doctrine:mongodb:schema:drop --env=test', __DIR__)); | |
run($output, sprintf('php %s/console doctrine:mongodb:schema:create --index --env=test', __DIR__)); | |
run($output, sprintf('php %s/console doctrine:database:drop --force --env=test', __DIR__), false); | |
run($output, sprintf('php %s/console doctrine:database:create --env=test', __DIR__)); | |
run($output, sprintf('php %s/console doctrine:migrations:migrate --no-interaction --env=test > /dev/null', __DIR__)); | |
} | |
// load the config into memory | |
$src = file_get_contents(is_file($file = __DIR__.'/phpunit.xml') ? $file : __DIR__.'/phpunit.xml.dist'); | |
$xml = simplexml_load_string(str_replace(array('../', './'), array(__DIR__.'/../', __DIR__.'/'), $src)); | |
// temp config file | |
$config = tempnam('/tmp', 'phpunitxml'); | |
register_shutdown_function(function() use($config) { | |
unlink($config); | |
}); | |
// run functional test chunks | |
$output->writeln('Running <info>functional</info> tests in chunks of <info>'.$input->getOption('chunk-size').'</info>'); | |
foreach ($chunks as $i => $chunk) { | |
unset($xml->testsuites[0]->testsuite); | |
$suite = $xml->testsuites[0]->addChild('testsuite'); | |
foreach ($chunk as $file) { | |
$suite->addChild('file', $file); | |
} | |
$output->writeln(sprintf('Chunk <comment>%d</comment> of <comment>%d</comment>', $i + 1, $total)); | |
file_put_contents($config, $xml->asXml()); | |
$code = run($output, sprintf( | |
'phpunit -c %s --group functional %s', | |
escapeshellarg($config), | |
flags($input, $output) | |
), false); | |
if ($code && ($input->getOption('stop-on-failure') || $input->getOption('stop-on-error'))) { | |
return $code; | |
} | |
} | |
}) | |
; | |
$app->run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment