MDL-39752 behat: Parallel execution support
This commit is contained in:
@@ -40,10 +40,41 @@ define('CACHE_DISABLE_ALL', true);
|
||||
require_once(__DIR__ . '/../../../../lib/clilib.php');
|
||||
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
|
||||
|
||||
|
||||
list($options, $unrecognized) = cli_get_params(
|
||||
array(
|
||||
'parallel' => 0,
|
||||
'suffix' => '',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$nproc = (int) preg_filter('#.*(\d+).*#', '$1', $options['parallel']);
|
||||
$suffixarg = $options['suffix'] ? "--suffix={$options['suffix']} --parallel=$nproc" : '';
|
||||
|
||||
|
||||
if ($nproc && !$suffixarg) {
|
||||
foreach ((array)glob(__DIR__."/../../../../behat*") as $dir) {
|
||||
if (file_exists($dir) && is_link($dir) && preg_match('#/behat\d+$#', $dir)) {
|
||||
unlink($dir);
|
||||
}
|
||||
}
|
||||
$cmds = array();
|
||||
for ($i = 1; $i <= $nproc; $i++) {
|
||||
$cmds[] = "php ".__FILE__." --suffix=$i --parallel=$nproc 2>&1";
|
||||
}
|
||||
// This is intensive compared to behat itself so halve the parallelism.
|
||||
foreach (array_chunk($cmds, max(1, floor($nproc/2)), true) as $chunk) {
|
||||
ns_parallel_popen($chunk, true);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
// Changing the cwd to admin/tool/behat/cli.
|
||||
chdir(__DIR__);
|
||||
$output = null;
|
||||
exec("php util.php --diag", $output, $code);
|
||||
exec("php util.php --diag $suffixarg", $output, $code);
|
||||
if ($code == 0) {
|
||||
echo "Behat test environment already installed\n";
|
||||
|
||||
@@ -53,7 +84,7 @@ if ($code == 0) {
|
||||
|
||||
// Behat and dependencies are installed and we need to install the test site.
|
||||
chdir(__DIR__);
|
||||
passthru("php util.php --install", $code);
|
||||
passthru("php util.php --install $suffixarg", $code);
|
||||
if ($code != 0) {
|
||||
exit($code);
|
||||
}
|
||||
@@ -64,12 +95,12 @@ if ($code == 0) {
|
||||
|
||||
// Test site data is outdated.
|
||||
chdir(__DIR__);
|
||||
passthru("php util.php --drop", $code);
|
||||
passthru("php util.php --drop $suffixarg", $code);
|
||||
if ($code != 0) {
|
||||
exit($code);
|
||||
}
|
||||
|
||||
passthru("php util.php --install", $code);
|
||||
passthru("php util.php --install $suffixarg", $code);
|
||||
if ($code != 0) {
|
||||
exit($code);
|
||||
}
|
||||
@@ -81,7 +112,7 @@ if ($code == 0) {
|
||||
|
||||
// Returning to admin/tool/behat/cli.
|
||||
chdir(__DIR__);
|
||||
passthru("php util.php --install", $code);
|
||||
passthru("php util.php --install $suffixarg", $code);
|
||||
if ($code != 0) {
|
||||
exit($code);
|
||||
}
|
||||
@@ -93,7 +124,7 @@ if ($code == 0) {
|
||||
}
|
||||
|
||||
// Enable editing mode according to config.php vars.
|
||||
passthru("php util.php --enable", $code);
|
||||
passthru("php util.php --enable $suffixarg", $code);
|
||||
if ($code != 0) {
|
||||
exit($code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Wrapper to run previously set-up behat tests in parallel.
|
||||
*
|
||||
* @package tool_behat
|
||||
* @copyright 2014 NetSpot Pty Ltd
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
die(); // No access from web!
|
||||
}
|
||||
|
||||
|
||||
define('BEHAT_UTIL', true);
|
||||
define('CLI_SCRIPT', true);
|
||||
define('ABORT_AFTER_CONFIG', true);
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
|
||||
error_reporting(E_ALL | E_STRICT);
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
|
||||
|
||||
require_once __DIR__ .'/../../../../config.php';
|
||||
require_once __DIR__.'/../../../../lib/clilib.php';
|
||||
require_once __DIR__.'/../../../../lib/behat/lib.php';
|
||||
|
||||
|
||||
list($options, $unrecognised) = cli_get_params(
|
||||
array(
|
||||
'stop-on-failure' => 0,
|
||||
'parallel' => 0,
|
||||
'verbose' => false,
|
||||
'replace' => false,
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
if (empty($options['parallel']) && $dirs = glob("{$CFG->dirroot}/behat*")) {
|
||||
sort($dirs);
|
||||
if ($max = preg_filter('#.*behat(\d+)#', '$1', end($dirs))) {
|
||||
$options['parallel'] = $max;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$suffix = '';
|
||||
$time = microtime(true);
|
||||
$nproc = (int) preg_filter('#.*(\d+).*#', '$1', $options['parallel']);
|
||||
array_walk($unrecognised, function (&$v) {
|
||||
if ($x = preg_filter("#^(-+\w+)=(.+)#", "\$1='\$2'", $v)) {
|
||||
$v = $x;
|
||||
} else if (!preg_match("#^-#", $v)) {
|
||||
$v = escapeshellarg($v);
|
||||
}
|
||||
});
|
||||
$extraopts = implode(' ', $unrecognised);
|
||||
|
||||
|
||||
if (empty($nproc)) {
|
||||
fwrite(STDERR, "Invalid or missing --parallel parameter, must be >= 1.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
$checkfail = array();
|
||||
$outputs = array();
|
||||
$handles = array();
|
||||
$pipe2i = array();
|
||||
$exits = array();
|
||||
$unused = null;
|
||||
$linelencnt = 0;
|
||||
$procs = array();
|
||||
|
||||
|
||||
for ($i = 1; $i <= $nproc; $i++) {
|
||||
$myopts = !empty($options['replace']) ? str_replace($options['replace'], $i, $extraopts) : $extraopts;
|
||||
$dirroot = dirname($CFG->behat_dataroot)."/behat$i";
|
||||
$cmd = "exec {$CFG->dirroot}/vendor/bin/behat --config $dirroot/behat/behat.yml $myopts";
|
||||
list($handle, $pipes) = ns_proc_open($cmd, true);
|
||||
@fclose($pipes[0]);
|
||||
unset($pipes[0]);
|
||||
$exits[$i] = 1;
|
||||
$handles[$i] = array($handle, $pipes[1], $pipes[2]);
|
||||
$procs[$i] = $handle;
|
||||
$checkfail[$i] = false;
|
||||
$outputs[$i] = array('');
|
||||
$pipe2i[(int) $pipes[1]] = $i;
|
||||
$pipe2i[(int) $pipes[2]] = $i;
|
||||
stream_set_blocking($pipes[1], 0);
|
||||
stream_set_blocking($pipes[2], 0);
|
||||
}
|
||||
|
||||
|
||||
while (!empty($procs)) {
|
||||
usleep(10000);
|
||||
|
||||
foreach ($handles as $i => $p) {
|
||||
if (!($status = @proc_get_status($p[0])) || !$status['running']) {
|
||||
if ($exits[$i] !== 0) {
|
||||
$exits[$i] = !empty($status) ? $status['exitcode'] : 1;
|
||||
}
|
||||
unset($procs[$i]);
|
||||
unset($handles[$i][0]);
|
||||
$last = array_pop($outputs[$i]);
|
||||
for ($l=2; $l>=1; $l--)
|
||||
while ($part = @fread($handles[$i][$l], 8192))
|
||||
$last .= $part;
|
||||
$outputs[$i] = array_merge($outputs[$i], explode("\n", $last));
|
||||
}
|
||||
}
|
||||
|
||||
$ready = array();
|
||||
foreach ($handles as $i => $set) {
|
||||
$ready[] = $set[1];
|
||||
$ready[] = $set[2];
|
||||
}
|
||||
|
||||
// Poll for any process with output or ended.
|
||||
if (!$result = @stream_select($ready, $unused, $unused, 1)) {
|
||||
// Nothing; try again.
|
||||
continue;
|
||||
}
|
||||
if (!$fh = reset($ready)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$i = $pipe2i[(int) $fh];
|
||||
$last = array_pop($outputs[$i]);
|
||||
$read = fread($fh, 4096);
|
||||
$newlines = explode("\n", $last.$read);
|
||||
$outputs[$i] = array_merge($outputs[$i], $newlines);
|
||||
|
||||
if (!$checkfail[$i]) {
|
||||
foreach ($newlines as $l => $line) {
|
||||
unset($newlines[$l]);
|
||||
if (preg_match('#^Started at [\d\-]+#', $line) || (strlen($line) > 3 && preg_match('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', $line))) {
|
||||
$checkfail[$i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($progress = preg_filter('#^\s*([FUS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $newlines)) {
|
||||
if ($checkfail[$i] && preg_filter('#^\s*[S\.\-]*[FU][S\.\-]*(?:\s+\d+)?\s*$#', '$1', $progress)) {
|
||||
$exits[$i] = 1;
|
||||
if ($options['stop-on-failure']) {
|
||||
foreach ($handles as $l => $p) {
|
||||
$exits[$l] = $l != $i ? 0 : $exits[$i];
|
||||
@proc_terminate($p[0], SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Process has gone, assume this is the last output for it.
|
||||
if (empty($procs[$i])) {
|
||||
unset($handles[$i]);
|
||||
}
|
||||
if (empty($checkfail[$i]) || !($update = preg_filter('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $read))) {
|
||||
continue;
|
||||
}
|
||||
while ($update) {
|
||||
$part = substr($update, 0, 70 - $linelencnt);
|
||||
$update = substr($update, strlen($part));
|
||||
$linelencnt += strlen($part);
|
||||
echo $part;
|
||||
if ($linelencnt >= 70) {
|
||||
echo "\n";
|
||||
$linelencnt = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
echo "\n\n";
|
||||
|
||||
|
||||
$exits = array_filter($exits, function ($v) {return $v !== 0;});
|
||||
|
||||
|
||||
if ($exits || $options['verbose']) {
|
||||
echo "Exit codes: ".implode(" ", $exits)."\n\n";
|
||||
foreach ($outputs as $i => $output) {
|
||||
unset($outputs[$i]);
|
||||
if (!end($output)) array_pop($output);
|
||||
$prefix = "[behat$i] ";
|
||||
array_walk($output, function (&$l) use ($prefix) {
|
||||
$l = $prefix.$l;
|
||||
});
|
||||
echo implode("\n", $output)."\n\n";
|
||||
}
|
||||
$failed = true;
|
||||
}
|
||||
|
||||
|
||||
$time = round(microtime(true) - $time, 1);
|
||||
echo "Finished in {$time}s\n";
|
||||
exit(!empty($failed) ? 1 : 0);
|
||||
@@ -40,6 +40,8 @@ list($options, $unrecognized) = cli_get_params(
|
||||
array(
|
||||
'help' => false,
|
||||
'install' => false,
|
||||
'parallel' => 0,
|
||||
'suffix' => '',
|
||||
'drop' => false,
|
||||
'enable' => false,
|
||||
'disable' => false,
|
||||
@@ -63,6 +65,7 @@ Options:
|
||||
--drop Drops the database tables and the dataroot contents
|
||||
--enable Enables test environment and updates tests list
|
||||
--disable Disables test environment
|
||||
--parallel Run operation for all parallel behat environments.
|
||||
--diag Get behat test environment status code
|
||||
|
||||
-h, --help Print out this help
|
||||
@@ -78,11 +81,32 @@ if (!empty($options['help'])) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Describe this script.
|
||||
|
||||
if (!empty($options['parallel']) && empty($options['suffix'])) {
|
||||
foreach ((array)glob(__DIR__."/../../../../behat*") as $dir) {
|
||||
if (file_exists($dir) && is_dir($dir)) {
|
||||
unlink($dir);
|
||||
}
|
||||
}
|
||||
$cmds = array();
|
||||
$extra = preg_filter('#(.*)\s*--parallel=\d+\s*(.*?)#', '$1 $2', implode(' ', array_slice($argv, 1)));
|
||||
for ($i = 1; $i <= $options['parallel']; $i++) {
|
||||
$cmds[] = "php ".__FILE__." $extra --suffix=$i 2>&1";
|
||||
}
|
||||
// This is intensive compared to behat itself so halve the parallelism.
|
||||
foreach (array_chunk($cmds, min(1, floor($options['parallel']/2)), true) as $chunk) {
|
||||
ns_parallel_popen($chunk, true);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
// Checking $CFG->behat_* vars and values.
|
||||
define('BEHAT_UTIL', true);
|
||||
define('CLI_SCRIPT', true);
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
define('IGNORE_COMPONENT_CACHE', true);
|
||||
define('BEHAT_SUFFIX', $options['suffix']);
|
||||
|
||||
// Only load CFG from config.php, stop ASAP in lib/setup.php.
|
||||
define('ABORT_AFTER_CONFIG', true);
|
||||
|
||||
Reference in New Issue
Block a user