Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 303 lines (274 sloc) 9.16 KB
#!/usr/bin/env php
<?php
function getFiles($glob, $base_dir)
{
$test_files = array();
$real_glob = $glob[0] == "/" ? $glob : joinPaths($base_dir, $glob);
$files = glob($real_glob, GLOB_MARK);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
if ($file[strlen($file)-1] === '/') {
$test_files = array_merge($test_files, getFiles('*', $file));
} else {
$test_files[] = $file;
}
}
}
return $test_files;
}
function joinPaths() {
$paths = array_filter(func_get_args());
return preg_replace('#/{2,}#', '/', implode('/', $paths));
}
function getTestMethodsFromFile($file)
{
$methods = array();
$file_str = file_get_contents($file);
preg_match_all("/function (test[^\(]+)\(/", $file_str, $matches, PREG_PATTERN_ORDER);
foreach ($matches[1] as $match) {
$methods[] = $match;
}
return $methods;
}
function runTestSets($all_tests, $num_procs)
{
echo "\n";
$outputs = array();
$active_procs = array();
$active_pipes = array();
$n_tests = $n_assertions = $n_errors = 0;
$errors = array();
$mismatch_outputs = array();
$start_time = time();
while (count($all_tests) || count($active_procs)) {
if (count($active_pipes))
collectStreamOutput($active_pipes, $outputs);
if (count($active_procs)) {
$stats = updateProcessStatus($active_procs, $active_pipes, $outputs);
$n_tests += $stats[0];
$n_assertions += $stats[1];
$n_errors += $stats[2];
foreach($stats[3] as $error)
$errors[] = $error;
foreach($stats[4] as $output)
$mismatch_outputs[] = $output;
}
if (count($active_procs) < $num_procs && count($all_tests))
startNewProcess($all_tests, $active_procs, $active_pipes, $outputs);
}
$n_secs = time() - $start_time;
echo "\n\n";
foreach ($errors as $i => $error) {
$num = $i+1;
echo "$num) $error\n\n";
}
if (count($mismatch_outputs)) {
echo "PHPUnit let us know there was an error or test failure, but we couldn't scrape it. Here's the entire output for the tests runs for which this is the case:\n";
foreach ($mismatch_outputs as $index => $output) {
$run_num = $index+1;
echo "========== Run $run_num ===========\n";
echo $output."\n";
for ($i=0; $i < $run_num; $i++)
echo "=";
echo "===========================\n\n";
}
}
echo "Time: $n_secs seconds\n\n";
echo "Tests: $n_tests, Assertions: $n_assertions, Errors/Failures: $n_errors\n\n";
if ($n_errors > 0) {
exit(1);
} else {
exit(0);
}
}
function collectStreamOutput($active_pipes, &$outputs)
{
$out_streams = array();
foreach ($active_pipes as $pipes) {
$out_streams[] = $pipes[1];
}
$e = NULL; $f = NULL;
$num_changed = stream_select($out_streams, $e, $f, 0, 200000);
if ($num_changed) {
foreach ($out_streams as $changed_stream) {
foreach ($active_pipes as $proc_id => $pipes) {
if ($changed_stream === $pipes[1]) {
$outputs[$proc_id] .= stream_get_contents($changed_stream);
}
}
}
}
}
function updateProcessStatus(&$active_procs, &$active_pipes, $outputs)
{
$total_tests = $total_assertions = $total_errors = 0;
$mismatch_outputs = array();
$errors = array();
foreach ($active_procs as $id => $proc) {
$status = proc_get_status($proc);
if (!$status['running']) {
//echo "Command {$status['command']} finished\n";
fclose($active_pipes[$id][0]);
fclose($active_pipes[$id][1]);
fclose($active_pipes[$id][2]);
proc_close($proc);
$stats = handleOutput($id, $outputs[$id]);
list($n_tests, $n_assertions, $n_errors, $errors, $was_mismatch,
$output) = $stats;
unset($active_procs[$id]);
unset($active_pipes[$id]);
$total_tests += $n_tests;
$total_assertions += $n_assertions;
$total_errors = $n_errors;
if ($was_mismatch)
$mismatch_outputs[] = $output;
}
}
return array($total_tests, $total_assertions, $total_errors, $errors,
$mismatch_outputs);
}
function handleOutput($id, $output)
{
$n_tests = $n_assertions = $n_errors = 0;
$errors = array();
// first, find dots, Es, etc...
preg_match("/^[\.FESI]+$/m", $output, $matches);
$short_results = array(
'.' => 0,
'F' => 0,
'E' => 0,
'S' => 0,
'I' => 0,
);
if (count($matches)) {
echo $matches[0];
foreach (preg_split('//', $matches[0], -1, PREG_SPLIT_NO_EMPTY) as $char)
if (isset($short_results[$char]))
$short_results[$char]++;
}
preg_match("/^OK \(([0-9]+) tests?, ([0-9]+) assertions?/m", $output, $matches);
if (count($matches) == 3) {
$n_tests = intval($matches[1]);
$n_assertions = intval($matches[2]);
if ($n_tests < $short_results['.'])
$n_tests = $short_results['.'];
} else {
//echo "Could not find OK tests\n";
//print_r($matches);
}
preg_match("/^Tests: ([0-9]+), Assertions: ([0-9]+)(, Errors: ([0-9]+))?(, Failures: ([0-9]+))?/m", $output, $matches);
if (count($matches) == 7) {
$n_tests += intval($matches[1]);
if ($n_tests < $short_results['.'])
$n_tests = $short_results['.'];
$n_assertions += intval($matches[2]);
$n_errors += intval($matches[4]) + intval($matches[6]);
} else {
// echo "Could not find Failed tests\n";
// print_r($matches);
}
preg_match("/^There ((was)|(were)) [0-9]+ ((error)|(failure)).+\n\n(.+)\nFAIL/Ums", $output, $matches);
if (count($matches) == 8) {
$error_str = trim($matches[7]);
$split_errors = preg_split("/^[0-9]+\) /m", $error_str);
foreach ($split_errors as $error) {
if (trim($error))
$errors[] = trim($error);
}
} else {
//echo "Could not find errors\n";
//print_r($matches);
}
$short_n_errors = $short_results['E'] + $short_results['F'];
if ($n_errors < $short_n_errors) {
$n_errors = $short_n_errors;
$was_mismatch = true;
} else {
$was_mismatch = false;
}
return array($n_tests, $n_assertions, $n_errors, $errors, $was_mismatch,
$output);
}
function startNewProcess(&$all_tests, &$active_procs, &$active_pipes, &$outputs)
{
list($proc, $pipes) = getProcessForTest(array_pop($all_tests));
$proc_id = uniqid();
$status = proc_get_status($proc);
//echo "Running {$status['command']}\n";
$active_procs[$proc_id] = $proc;
$active_pipes[$proc_id] = $pipes;
$outputs[$proc_id] = '';
}
function getProcessForTest($test)
{
global $PHPUNIT;
$dspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w"),
);
list($testName, $testFile) = $test;
$cmd = "$PHPUNIT --filter=$testName $testFile";
$process = proc_open($cmd,
$dspec,
$pipes,
NULL);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
return array($process, $pipes);
}
$PHPUNIT = dirname(__FILE__).'/../../../bin/phpunit';
$WIN_UNAMES = array(
'MINGW32_NT-6.0',
'UWIN-W7',
'WIN32',
'WINNT',
'Windows',
'Windows NT'
);
$IS_WIN = in_array(php_uname('s'), $WIN_UNAMES);
function main($argv)
{
global $PHPUNIT;
$usage = "Usage: paraunit (-p[NUM_PROCESSES]|--processes=[NUM_PROCESSES]) --path=TEST_PATH/TEST_GLOB --phpunit=PATH/TO/PHPUNIT\n";
if (!isset($argv[1]) || !$argv[1])
die($usage);
$opts = getopt("p::", array('path:', 'phpunit::', 'processes::'));
if (!isset($opts['path']))
die($usage);
if (isset($opts['processes']) && intval($opts['processes']) > 0)
$processes = intval($opts['processes']);
elseif (isset($opts['p']) && intval($opts['p']) > 0)
$processes = intval($opts['p']);
else
$processes = 1;
if (isset($opts['phpunit'])) {
$PHPUNIT = $opts['phpunit'];
if ($PHPUNIT[0] != '/') {
$PHPUNIT = getcwd(). '/' . $PHPUNIT;
}
}
if ($PHPUNIT != 'phpunit' && !is_file($PHPUNIT))
die("Couldn't find phpunit at '$PHPUNIT'\n");
echo "Using phpunit in $processes process(es) with $PHPUNIT\n";
$files = getFiles($opts['path'], getcwd());
if (!count($files))
die("Couldn't find any tests with path '{$opts['path']}'\n");
$all_tests = array();
foreach($files as $file) {
$methods = getTestMethodsFromFile($file);
foreach($methods as $method) {
$all_tests[] = array($method, $file);
}
}
if (!count($all_tests)) {
echo "Couldn't find any tests in the files paraunit collected. Here's the files we were looking in:\n\n";
print_r($files);
exit(1);
}
runTestSets($all_tests, $processes);
}
if (isset($argv[0]) && $argv[0])
{
main($argv);
}