Skip to content

Instantly share code, notes, and snippets.

@kriswallsmith
Last active December 20, 2015 00:29
Show Gist options
  • Save kriswallsmith/6042049 to your computer and use it in GitHub Desktop.
Save kriswallsmith/6042049 to your computer and use it in GitHub Desktop.
A test harness for your Symfony2 project. Runs functional tests in chunks.
#!/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