Commit 6c410f71 authored by webchick's avatar webchick

Issue #771448 by Damien Tournoud, attiks: Changed Use proc_open() instead of...

Issue #771448 by Damien Tournoud, attiks: Changed Use proc_open() instead of pcntl_fork() in simpletest.
parent 8ffa6083
......@@ -16,10 +16,10 @@
exit;
}
if ($args['execute-batch']) {
if ($args['execute-test']) {
// Masquerade as Apache for running tests.
simpletest_script_init("Apache");
simpletest_script_execute_batch();
simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
}
else {
// Run administrative functions as CLI.
......@@ -67,8 +67,6 @@
exit;
}
$test_list = simpletest_script_get_test_list();
// Try to allocate unlimited time to run the tests.
drupal_set_time_limit(0);
......@@ -78,7 +76,7 @@
$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
// Execute tests.
simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
// Retrieve the last database prefix used for testing and the last test class
// that was run from. Use the information to read the lgo file in case any
......@@ -99,6 +97,9 @@
// Cleanup our test results.
simpletest_clean_results_table($test_id);
// Test complete, exit.
exit;
/**
* Print help text.
*/
......@@ -130,9 +131,7 @@ function simpletest_script_help() {
--concurrency [num]
Run tests in parallel, up to [num] tests at a time. This requires
the Process Control Extension (PCNTL) to be compiled in PHP, not
supported under Windows.
Run tests in parallel, up to [num] tests at a time.
--all Run all available tests.
......@@ -193,8 +192,8 @@ function simpletest_script_parse_args() {
'verbose' => FALSE,
'test_names' => array(),
// Used internally.
'test-id' => NULL,
'execute-batch' => FALSE,
'test-id' => 0,
'execute-test' => '',
'xml' => '',
);
......@@ -236,10 +235,6 @@ function simpletest_script_parse_args() {
simpletest_script_print_error("--concurrency must be a strictly positive integer.");
exit;
}
elseif ($args['concurrency'] > 1 && !function_exists('pcntl_fork')) {
simpletest_script_print_error("Parallel test execution requires the Process Control extension to be compiled in PHP. See http://php.net/manual/en/intro.pcntl.php for more information.");
exit;
}
return array($args, $count);
}
......@@ -310,93 +305,96 @@ function simpletest_script_init($server_software) {
/**
* Execute a batch of tests.
*/
function simpletest_script_execute_batch() {
function simpletest_script_execute_batch($test_id, $test_classes) {
global $args;
if (!isset($args['test-id'])) {
simpletest_script_print_error("--execute-batch should not be called interactively.");
exit;
}
if ($args['concurrency'] == 1) {
// Fallback to mono-threaded execution.
if (count($args['test_names']) > 1) {
foreach ($args['test_names'] as $test_class) {
// Execute each test in its separate Drupal environment.
simpletest_script_command(1, $args['test-id'], $test_class);
// Multi-process execution.
$children = array();
while (!empty($test_classes) || !empty($children)) {
while (count($children) < $args['concurrency']) {
if (empty($test_classes)) {
break;
}
exit;
}
else {
// Execute an individual test.
$test_class = array_shift($args['test_names']);
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
simpletest_script_run_one_test($args['test-id'], $test_class);
exit;
}
}
else {
// Multi-threaded execution.
$children = array();
while (!empty($args['test_names']) || !empty($children)) {
// Fork children safely since Drupal is not bootstrapped yet.
while (count($children) < $args['concurrency']) {
if (empty($args['test_names'])) break;
$child = array();
$child['test_class'] = $test_class = array_shift($args['test_names']);
$child['pid'] = pcntl_fork();
if (!$child['pid']) {
// This is the child process, bootstrap and execute the test.
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
simpletest_script_run_one_test($args['test-id'], $test_class);
exit;
}
else {
// Register our new child.
$children[] = $child;
}
// Fork a child process.
$test_class = array_shift($test_classes);
$command = simpletest_script_command($test_id, $test_class);
$process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
if (!is_resource($process)) {
echo "Unable to fork test process. Aborting.\n";
exit;
}
// Wait for children every 200ms.
usleep(200000);
// Register our new child.
$children[] = array(
'process' => $process,
'class' => $test_class,
'pipes' => $pipes,
);
}
// Check if some children finished.
foreach ($children as $cid => $child) {
if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) {
// This particular child exited.
unset($children[$cid]);
// Wait for children every 200ms.
usleep(200000);
// Check if some children finished.
foreach ($children as $cid => $child) {
$status = proc_get_status($child['process']);
if (empty($status['running'])) {
// The child exited, unregister it.
proc_close($child['process']);
if ($status['exitcode']) {
echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
}
unset($children[$cid]);
}
}
exit;
}
}
/**
* Run a single test (assume a Drupal bootstrapped environment).
* Bootstrap Drupal and run a single test.
*/
function simpletest_script_run_one_test($test_id, $test_class) {
$test = new $test_class($test_id);
$test->run();
$info = $test->getInfo();
try {
// Bootstrap Drupal.
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
$test = new $test_class($test_id);
$test->run();
$info = $test->getInfo();
$had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0);
$had_exceptions = (isset($test->results['#exception']) && $test->results['#exception'] > 0);
$status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
$status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
|| (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'fail' : 'pass');
simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
// Finished, kill this runner.
exit(0);
}
catch (Exception $e) {
echo (string) $e;
exit(1);
}
}
/**
* Execute a command to run batch of tests in separate process.
* Return a command used to run a test in a separate process.
*
* @param $test_id
* The current test ID.
* @param $test_class
* The name of the test class to run.
*/
function simpletest_script_command($concurrency, $test_id, $tests) {
function simpletest_script_command($test_id, $test_class) {
global $args, $php;
$command = "$php ./scripts/{$args['script']} --url {$args['url']}";
$command = escapeshellarg($php) . ' ' . escapeshellarg('./scripts/' . $args['script']) . ' --url ' . escapeshellarg($args['url']);
if ($args['color']) {
$command .= ' --color';
}
$command .= " --php " . escapeshellarg($php) . " --concurrency $concurrency --test-id $test_id --execute-batch $tests";
passthru($command);
$command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test $test_class";
return $command;
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment