Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Brought base file in, wrote initial README
- Loading branch information
Showing
3 changed files
with
235 additions
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.swp | ||
*.un~ |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,19 @@ | ||
paraunit | ||
======== | ||
|
||
A parallel wrapper for PHPUnit | ||
A parallel wrapper for PHPUnit. Run this file instead of PHPUnit and you can | ||
pass in the number of parallel PHPUnit processes you want to use on your tests. | ||
|
||
Tests are found by grepping files for test*() and run in individual processes, | ||
up to the max concurrency you set, until all tests are finished. This means | ||
_tests must be logically independent!_ | ||
|
||
Usage | ||
----- | ||
```php paraunit.php -p[PROCESSES] --PATH=TEST_PATH/TEST_GLOB``` | ||
|
||
Todo | ||
---- | ||
* Handle more kinds of output from phpunit | ||
* Try to use PHPUnit to get file list / test name list | ||
* See if anything can be done about test dependencies |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
<?php | ||
|
||
function getFiles($glob, $base_dir) | ||
{ | ||
$test_files = array(); | ||
$files = glob(joinPaths($base_dir, $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(); | ||
$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; | ||
} | ||
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"; | ||
} | ||
echo "Time: $n_secs seconds\n\n"; | ||
echo "Tests: $n_tests, Assertions: $n_assertions, Errors: $n_errors\n\n"; | ||
} | ||
|
||
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; | ||
$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) = $stats; | ||
unset($active_procs[$id]); | ||
unset($active_pipes[$id]); | ||
$total_tests += $n_tests; | ||
$total_assertions += $n_assertions; | ||
$total_errors = $n_errors; | ||
} | ||
} | ||
return array($total_tests, $total_assertions, $total_errors, $errors); | ||
} | ||
|
||
function handleOutput($id, $output) | ||
{ | ||
$n_tests = $n_assertions = $n_errors = 0; | ||
$errors = array(); | ||
// first, find dots, Es, etc... | ||
preg_match("/^[\.FESI]+$/m", $output, $matches); | ||
if (count($matches)) | ||
echo $matches[0]; | ||
|
||
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]); | ||
} else { | ||
// echo "Could not find OK tests\n"; | ||
// print_r($matches); | ||
} | ||
|
||
preg_match("/^Tests: ([0-9]+), Assertions: ([0-9]+), Errors: ([0-9]+)/m", $output, $matches); | ||
if (count($matches) == 4) { | ||
$n_tests += intval($matches[1]); | ||
$n_assertions += intval($matches[2]); | ||
$n_errors += intval($matches[3]); | ||
} 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); | ||
} | ||
return array($n_tests, $n_assertions, $n_errors, $errors); | ||
} | ||
|
||
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) | ||
{ | ||
$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); | ||
} | ||
|
||
|
||
function main($argv) | ||
{ | ||
$usage = "Usage: php paraunit.php -p[NUM_PROCESSES] --path=TEST_PATH/TEST_GLOB\n"; | ||
if (!isset($argv[1]) || !$argv[1]) | ||
die($usage); | ||
|
||
$opts = getopt("p::", array('path:')); | ||
if (!isset($opts['path'])) | ||
die($usage); | ||
if (isset($opts['p']) && intval($opts['p']) > 0) | ||
$processes = intval($opts['p']); | ||
else | ||
$processes = 1; | ||
|
||
$files = getFiles($opts['path'], dirname(__FILE__)); | ||
|
||
$all_tests = array(); | ||
foreach($files as $file) { | ||
$methods = getTestMethodsFromFile($file); | ||
foreach($methods as $method) { | ||
$all_tests[] = array($method, $file); | ||
} | ||
} | ||
|
||
runTestSets($all_tests, $processes); | ||
} | ||
|
||
if (isset($argv[0]) && $argv[0]) | ||
{ | ||
main($argv); | ||
} |