MDL-39752 behat: Modified following for parallel run:

1. Create behat datadir within behat_dataroot not at same level
2. Define suffix for link and not use hard-coded values
3. Renamed ns_parallel to run.php
4. Rename variables to best understand them
5. Added support for each run to specify db, prefix, rerun and profile.
6. Showing number of steps in each line of parallel run.
This commit is contained in:
Rajesh Taneja
2015-03-09 06:52:54 +08:00
parent 08e7f97ee4
commit 3c71c15c26
16 changed files with 1327 additions and 330 deletions
+50 -33
View File
@@ -40,92 +40,109 @@ 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' => '',
'maxruns' => false,
'help' => false,
),
array(
'j' => 'parallel',
'm' => 'maxruns',
'h' => 'help',
)
);
// Checking run.php CLI script usage.
$help = "
Behat utilities to initialise behat tests
$nproc = (int) preg_filter('#.*(\d+).*#', '$1', $options['parallel']);
$suffixarg = $options['suffix'] ? "--suffix={$options['suffix']} --parallel=$nproc" : '';
Options:
-j, --parallel Number of parallel behat run to initialise
-m, --maxruns Max parallel processes to be executed at one time.
-h, --help Print out this help
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);
}
Example from Moodle root directory:
\$ php admin/tool/behat/cli/init.php --parallel=2
More info in http://docs.moodle.org/dev/Acceptance_testing#Running_tests
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
// Check which util file to call.
$utilfile = 'util.php';
$paralleloption = "";
// If parallel run then use utilparallel.
if ($options['parallel']) {
$utilfile = 'utilparallel.php';
$paralleloption = " --parallel=".$options['parallel'];
}
// Changing the cwd to admin/tool/behat/cli.
$cwd = getcwd();
chdir(__DIR__);
$output = null;
exec("php util.php --diag $suffixarg", $output, $code);
exec("php $utilfile --diag $paralleloption", $output, $code);
// Check if composer needs to be updated.
if (($code == BEHAT_EXITCODE_INSTALL) || $code == BEHAT_EXITCODE_REINSTALL || $code == BEHAT_EXITCODE_COMPOSER) {
testing_update_composer_dependencies();
}
if ($code == 0) {
echo "Behat test environment already installed\n";
} else if ($code == BEHAT_EXITCODE_INSTALL) {
testing_update_composer_dependencies();
// Behat and dependencies are installed and we need to install the test site.
chdir(__DIR__);
passthru("php util.php --install $suffixarg", $code);
passthru("php $utilfile --install $paralleloption", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
} else if ($code == BEHAT_EXITCODE_REINSTALL) {
testing_update_composer_dependencies();
// Test site data is outdated.
chdir(__DIR__);
passthru("php util.php --drop $suffixarg", $code);
passthru("php $utilfile --drop $paralleloption", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
passthru("php util.php --install $suffixarg", $code);
passthru("php $utilfile --install $paralleloption", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
} else if ($code == BEHAT_EXITCODE_COMPOSER) {
// Missing Behat dependencies.
testing_update_composer_dependencies();
// Returning to admin/tool/behat/cli.
chdir(__DIR__);
passthru("php util.php --install $suffixarg", $code);
passthru("php $utilfile --install $paralleloption", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
} else {
// Generic error, we just output it.
echo implode("\n", $output)."\n";
chdir($cwd);
exit($code);
}
// Enable editing mode according to config.php vars.
passthru("php util.php --enable $suffixarg", $code);
passthru("php $utilfile --enable $paralleloption", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
@@ -22,48 +22,106 @@
* @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('CACHE_DISABLE_ALL', true);
define('NO_OUTPUT_BUFFERING', true);
require_once(__DIR__ .'/../../../../config.php');
require_once(__DIR__.'/../../../../lib/clilib.php');
require_once(__DIR__.'/../../../../lib/behat/lib.php');
require_once(__DIR__.'/../../../../lib/behat/classes/behat_command.php');
require_once(__DIR__.'/../../../../lib/behat/classes/behat_config_manager.php');
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,
'verbose' => false,
'replace' => false,
'help' => false,
'tags' => '',
'profile' => '',
),
array(
'h' => 'help',
't' => 'tags',
'p' => 'profile',
)
);
// Checking run.php CLI script usage.
$help = "
Behat utilities to run behat tests in parallel
Options:
-t, --tags{{color_green}} Tags to execute.
-p, --profile{{color_green}} Profile to execute.
--stop-on-failure{{color_green}} Stop on failure in any parallel run.
--verbose{{color_green}} Verbose output
--replace{{color_green}} Replace args string with run process number, useful for output.
if (empty($options['parallel']) && $dirs = glob("{$CFG->dirroot}/behat*")) {
sort($dirs);
if ($max = preg_filter('#.*behat(\d+)#', '$1', end($dirs))) {
$options['parallel'] = $max;
-h, --help{{color_green}} Print out this help
Example from Moodle root directory:
\$ php admin/tool/behat/cli/run.php --parallel=2
More info in http://docs.moodle.org/dev/Acceptance_testing#Running_tests
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
// Ensure we have parallel runs initialised and it's >= 1.
$parallelrun = behat_config_manager::get_parallel_test_runs(1);
// Capture signals and ensure we clean symlinks.
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGINT, "signal_handler");
/**
* Signal handler for terminal exit.
*
* @param $signal
*/
function signal_handler($signal) {
switch ($signal) {
case SIGTERM:
case SIGKILL:
case SIGINT:
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
exit(1);
}
}
// If empty parallelrun then just check with user if it's a run single behat test.
if (empty($parallelrun)) {
if (cli_input("This is not a parallel site, do you want to run single behat run? (Y/N)", 'n', array('y', 'n')) == 'y') {
$runtestscommand = behat_command::get_behat_command();
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
exec("php $runtestscommand", $output, $code);
echo implode("\n", $output) . "\n";
exit($code);
} else {
exit(1);
}
}
// Create site symlink if necessary.
if (!behat_config_manager::create_parallel_site_links()) {
exit(1);
}
$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;
@@ -73,13 +131,6 @@ array_walk($unrecognised, function (&$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();
@@ -88,13 +139,51 @@ $exits = array();
$unused = null;
$linelencnt = 0;
$procs = array();
$behatdataroot = $CFG->behat_dataroot;
$tags = '';
// Options parameters to be added to each run.
$myopts = !empty($options['replace']) ? str_replace($options['replace'], $i, $extraopts) : $extraopts;
if ($options['profile']) {
$profile = $options['profile'];
if (empty($CFG->behat_config[$profile]['filters']['tags'])) {
echo "Invaid profile passed: " . $profile;
exit(1);
}
$tags = $CFG->behat_config[$profile]['filters']['tags'];
$myopts .= '--profile=\'' . $profile . "'";
} else if ($options['tags']) {
$tags = $options['tags'];
$myopts .= '--tags=' . $tags;
}
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);
// Update config file if tags defined.
if ($tags) {
// Hack to set proper dataroot and wwroot.
$behatdataroot = $CFG->behat_dataroot;
$behatwwwroot = $CFG->behat_wwwroot;
for ($i = 1; $i <= $parallelrun; $i++) {
$CFG->behatrunprocess = $i;
$CFG->behat_dataroot = $behatdataroot . $i;
$CFG->behat_wwwroot = $behatwwwroot . "/" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i;
behat_config_manager::update_config_file('', true, $tags);
}
$CFG->behat_dataroot = $behatdataroot;
$CFG->behat_wwwroot = $behatwwwroot;
unset($CFG->behatrunprocess);
}
for ($i = 1; $i <= $parallelrun; $i++) {
$CFG->behatrunprocess = $i;
$behatcommand = behat_command::get_behat_command();
$behatconfigpath = behat_config_manager::get_behat_cli_config_filepath($i);
// Command to execute behat run.
$cmd = $behatcommand .' --config ' . $behatconfigpath . " " . $myopts;
echo "[" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i . "] ". $cmd . "\n";
list($handle, $pipes) = cli_execute($cmd, true);
@fclose($pipes[0]);
unset($pipes[0]);
$exits[$i] = 1;
@@ -108,7 +197,7 @@ for ($i = 1; $i <= $nproc; $i++) {
stream_set_blocking($pipes[2], 0);
}
$progresscount = 0;
while (!empty($procs)) {
usleep(10000);
@@ -120,9 +209,11 @@ while (!empty($procs)) {
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))
for ($l = 2; $l >= 1; $l--) {
while ($part = @fread($handles[$i][$l], 8192)) {
$last .= $part;
}
}
$outputs[$i] = array_merge($outputs[$i], explode("\n", $last));
}
}
@@ -151,7 +242,8 @@ while (!empty($procs)) {
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))) {
if (preg_match('#^Started at [\d\-]+#', $line) || (strlen($line) > 3 &&
preg_match('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', $line))) {
$checkfail[$i] = true;
break;
}
@@ -181,23 +273,28 @@ while (!empty($procs)) {
$linelencnt += strlen($part);
echo $part;
if ($linelencnt >= 70) {
echo "\n";
$progresscount += 70;
echo " $progresscount\n";
$linelencnt = 0;
}
}
}
echo "\n\n";
$exits = array_filter($exits, function ($v) {return $v !== 0;});
$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] ";
if (!end($output)) {
array_pop($output);
}
$prefix = "[" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i . "] ";
array_walk($output, function (&$l) use ($prefix) {
$l = $prefix.$l;
});
@@ -206,7 +303,10 @@ if ($exits || $options['verbose']) {
$failed = true;
}
$time = round(microtime(true) - $time, 1);
echo "Finished in {$time}s\n";
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
exit(!empty($failed) ? 1 : 0);
+56 -33
View File
@@ -34,18 +34,18 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
require_once(__DIR__ . '/../../../../lib/clilib.php');
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
// CLI options.
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
'install' => false,
'parallel' => 0,
'suffix' => '',
'run' => '',
'drop' => false,
'enable' => false,
'disable' => false,
'diag' => false
'diag' => false,
'tags' => '',
),
array(
'h' => 'help'
@@ -65,10 +65,9 @@ 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
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/behat/cli/util.php --enable
@@ -81,32 +80,16 @@ if (!empty($options['help'])) {
exit(0);
}
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.
// Describe this script.
define('BEHAT_UTIL', true);
define('CLI_SCRIPT', true);
define('NO_OUTPUT_BUFFERING', true);
define('IGNORE_COMPONENT_CACHE', true);
define('BEHAT_SUFFIX', $options['suffix']);
// Set run value, to be used by setup for configuring proper CFG variables.
if ($options['run']) {
define('BEHAT_CURRENT_RUN', $options['run']);
}
// Only load CFG from config.php, stop ASAP in lib/setup.php.
define('ABORT_AFTER_CONFIG', true);
@@ -139,29 +122,69 @@ if ($unrecognized) {
// Behat utilities.
require_once($CFG->libdir . '/behat/classes/util.php');
require_once($CFG->libdir . '/behat/classes/behat_command.php');
require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
// Ensure run option is <= parallel run installed.
if ($options['run']) {
if (!$options['parallel']) {
$options['parallel'] = behat_config_manager::get_parallel_test_runs();
}
if (empty($options['parallel']) || $options['run'] > $options['parallel']) {
echo "Parallel runs can't be more then ".$options['parallel'].PHP_EOL;
exit(1);
}
$CFG->behatrunprocess = $options['run'];
}
// Run command (only one per time).
if ($options['install']) {
behat_util::install_site();
mtrace("Acceptance tests site installed");
// This is only displayed once for parallel install.
if (empty($options['run'])) {
mtrace("Acceptance tests site installed");
}
} else if ($options['drop']) {
// Ensure no tests are running.
test_lock::acquire('behat');
behat_util::drop_site();
mtrace("Acceptance tests site dropped");
// This is only displayed once for parallel install.
if (empty($options['run'])) {
mtrace("Acceptance tests site dropped");
}
} else if ($options['enable']) {
behat_util::start_test_mode();
$runtestscommand = behat_command::get_behat_command(true) .
' --config ' . behat_config_manager::get_behat_cli_config_filepath();
mtrace("Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:\n " . $runtestscommand);
// This is only displayed once for parallel install.
if (empty($options['run'])) {
$runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
mtrace("Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:\n " . $runtestscommand);
} else {
// Save parallel site info for enable and install options.
$filepath = behat_config_manager::get_parallel_test_file_path();
if (!file_put_contents($filepath, $options['parallel'])) {
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $filepath . ' can not be created');
}
}
} else if ($options['disable']) {
behat_util::stop_test_mode();
mtrace("Acceptance tests environment disabled");
// This is only displayed once for parallel install.
if (empty($options['run'])) {
mtrace("Acceptance tests environment disabled");
}
} else if ($options['diag']) {
$code = behat_util::get_behat_status();
exit($code);
} else {
echo $help;
exit(1);
}
exit(0);
+195
View File
@@ -0,0 +1,195 @@
<?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/>.
/**
* CLI tool with utilities to manage parallel Behat integration in Moodle
*
* All CLI utilities uses $CFG->behat_dataroot and $CFG->prefix_dataroot as
* $CFG->dataroot and $CFG->prefix
*
* @package tool_behat
* @copyright 2015 Rajesh Taneja
* @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('NO_OUTPUT_BUFFERING', true);
define('IGNORE_COMPONENT_CACHE', true);
require_once(__DIR__ . '/../../../../lib/clilib.php');
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
// CLI options.
list($options, $unrecognized) = cli_get_params(
array(
'help' => false,
'install' => false,
'drop' => false,
'enable' => false,
'disable' => false,
'diag' => false,
'parallel' => 0,
'maxruns' => false
),
array(
'h' => 'help',
'j' => 'parallel',
'm' => 'maxruns'
)
);
// Checking util.php CLI script usage.
$help = "
Behat utilities to manage the test environment
Options:
--install Installs the test environment for acceptance tests
--drop Drops the database tables and the dataroot contents
--enable Enables test environment and updates tests list
--disable Disables test environment
--diag Get behat test environment status code
-j, --parallel Number of parallel behat run operation
-m, --maxruns Max parallel processes to be executed at one time.
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/behat/cli/utilparallel.php --enable --parallel=4
More info in http://docs.moodle.org/dev/Acceptance_testing#Running_tests
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
if (empty($options['parallel'])) {
echo $help;
exit(1);
}
$status = 0;
$cmds = commands_to_execute($options);
$cwd = getcwd();
chdir(__DIR__);
// Start executing commands either sequential/parallel for options provided.
if ($options['diag'] || $options['drop'] || $options['enable'] || $options['disable']) {
$code = cli_execute_sequential($cmds, true);
// If any error then exit.
foreach ($code as $c) {
if ($c != 0) {
exit($c);
}
}
} else if ($options['install']) {
// This is intensive compared to behat itself so run them in chunk if $CFG->behat_max_parallel_init not set.
if ($options['maxruns']) {
foreach (array_chunk($cmds, maxruns, true) as $chunk) {
$chunkstatus = (bool)cli_execute_parallel($chunk, __DIR__, true, true);
$status = $chunkstatus || (bool) $status;
}
} else {
$status = (bool)cli_execute_parallel($cmds, __DIR__, true, true);
}
} else {
// We should never reach here.
echo $help;
exit(1);
}
// Ensure we have success status to show following information.
if ($status) {
echo "Unknown failure $status".PHP_EOL;
exit((int)$status);
}
// Only load CFG from config.php for 1st run amd stop ASAP in lib/setup.php.
define('ABORT_AFTER_CONFIG', true);
define('BEHAT_CURRENT_RUN', 1);
require_once(__DIR__ . '/../../../../config.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_command.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
// Remove first link from wwwroot, as it is set to first run.
$CFG->behat_wwwroot = str_replace('/'.BEHAT_PARALLEL_SITE_WWW_SUFFIX . '1', '', $CFG->behat_wwwroot);
// Show command o/p (only one per time).
if ($options['install']) {
echo "Acceptance tests site installed for sites:".PHP_EOL;
// Display all sites which are installed/drop/diabled.
for ($i = 1; $i <= $options['parallel']; $i++ ) {
echo $CFG->behat_wwwroot . "/" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i . PHP_EOL;
}
} else if ($options['drop']) {
echo "Acceptance tests site dropped for ".$options['parallel']." parallel sites".PHP_EOL;
} else if ($options['enable']) {
echo "Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:".PHP_EOL;
echo behat_command::get_behat_command(true, true);
echo PHP_EOL;
} else if ($options['disable']) {
echo "Acceptance tests environment disabled for ".$options['parallel']." parallel sites".PHP_EOL;
} else {
echo $help;
}
chdir($cwd);
exit(0);
/**
* Create commands to be executed for parallel run.
*
* @param array $options options provided by user.
* @return array commands to be executed.
*/
function commands_to_execute($options) {
$removeoptions = array('maxruns');
$cmds = array();
$extraoptions = $options;
$extra = "";
// Remove extra options not in util.php
foreach ($removeoptions as $ro) {
$extraoptions[$ro] = null;
unset($extraoptions[$ro]);
}
foreach ($extraoptions as $option => $value) {
if ($options[$option]) {
$extra .= " --$option";
if ($value) {
$extra .= "='$value'";
}
}
}
// Create commands which has to be executed for parallel site.
for ($i = 1; $i <= $options['parallel']; $i++) {
$prefix = BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i;
$cmds[$prefix] = "php util.php ".$extra." --run=".$i." 2>&1";
}
return $cmds;
}
+279 -105
View File
@@ -1,109 +1,283 @@
{
"course\/tests\/behat\/course_controls.feature": 117.2,
"backup\/util\/ui\/tests\/behat\/restore_moodle2_courses.feature": 108.2,
"mod\/forum\/tests\/behat\/discussion_subscriptions.feature": 92.8,
"course\/tests\/behat\/category_resort.feature": 75.4,
"course\/tests\/behat\/course_resort.feature": 58.9,
"admin\/tool\/behat\/tests\/behat\/data_generators.feature": 53.1,
"course\/tests\/behat\/category_management.feature": 50.1,
"blocks\/recent_activity\/tests\/behat\/structural_changes.feature": 48.3,
"grade\/tests\/behat\/grade_aggregation.feature": 1675.3,
"grade\/tests\/behat\/grade_scales.feature": 909,
"grade\/tests\/behat\/grade_scales_aggregation.feature": 831.6,
"course\/tests\/behat\/course_category_management_listing.feature": 781.1,
"grade\/tests\/behat\/grade_single_item_scales.feature": 730.8,
"grade\/tests\/behat\/grade_calculated_weights.feature": 729.7,
"admin\/tool\/monitor\/tests\/behat\/subscription.feature": 628.5,
"course\/tests\/behat\/course_controls.feature": 579.5,
"admin\/tool\/monitor\/tests\/behat\/rule.feature": 387.4,
"mod\/forum\/tests\/behat\/discussion_navigation.feature": 382.5,
"backup\/util\/ui\/tests\/behat\/restore_moodle2_courses.feature": 372,
"mod\/forum\/tests\/behat\/track_read_posts.feature": 367.2,
"mod\/forum\/tests\/behat\/discussion_display.feature": 357.4,
"badges\/tests\/behat\/award_badge.feature": 345,
"grade\/tests\/behat\/grade_natural_normalisation.feature": 330,
"grade\/tests\/behat\/grade_contribution_with_extra_credit.feature": 307.7,
"group\/tests\/behat\/update_groups.feature": 284.3,
"course\/tests\/behat\/course_resort.feature": 280.7,
"mod\/glossary\/tests\/behat\/search_entries.feature": 258.6,
"mod\/lesson\/tests\/behat\/link_to_gradebook.feature": 240.6,
"report\/outline\/tests\/behat\/outline.feature": 236.8,
"mod\/wiki\/tests\/behat\/wiki_search.feature": 227.9,
"grade\/tests\/behat\/grade_point_maximum.feature": 217.4,
"grade\/grading\/form\/rubric\/tests\/behat\/edit_rubric.feature": 215.9,
"grade\/tests\/behat\/grade_view.feature": 215.9,
"blocks\/navigation\/tests\/behat\/expand_courses_node.feature": 204.1,
"report\/participation\/tests\/behat\/filter_participation.feature": 197.9,
"report\/loglive\/tests\/behat\/loglive_report.feature": 196.9,
"course\/tests\/behat\/category_resort.feature": 196.9,
"mod\/workshop\/tests\/behat\/workshop_assessment.feature": 190.4,
"mod\/assign\/tests\/behat\/outcome_grading.feature": 186.5,
"mod\/forum\/tests\/behat\/edit_post_student.feature": 184,
"mod\/forum\/tests\/behat\/edit_post_teacher.feature": 182.7,
"mod\/assign\/tests\/behat\/quickgrading.feature": 178.5,
"completion\/tests\/behat\/restrict_section_availability.feature": 169.9,
"report\/outline\/tests\/behat\/user.feature": 167.4,
"availability\/tests\/behat\/edit_availability.feature": 166.8,
"course\/tests\/behat\/section_highlighting.feature": 162.5,
"mod\/assign\/tests\/behat\/prevent_submission_changes.feature": 161.1,
"mod\/wiki\/tests\/behat\/wiki_comments.feature": 155.8,
"mod\/assign\/feedback\/editpdf\/tests\/behat\/annotate_pdf.feature": 155.6,
"mod\/lesson\/tests\/behat\/lesson_practice.feature": 151.2,
"availability\/tests\/behat\/display_availability.feature": 150.6,
"mod\/assign\/tests\/behat\/grading_status.feature": 150.5,
"mod\/quiz\/tests\/behat\/editing_set_marks_with_attempts.feature": 148.9,
"cohort\/tests\/behat\/upload_cohorts.feature": 146.4,
"group\/tests\/behat\/delete_groups.feature": 143.8,
"group\/tests\/behat\/groups_import.feature": 143.6,
"mod\/lesson\/tests\/behat\/lesson_edit_pages.feature": 139.6,
"group\/tests\/behat\/auto_creation.feature": 136.6,
"course\/tests\/behat\/category_management.feature": 134.5,
"mod\/quiz\/tests\/behat\/editing_add.feature": 132.8,
"admin\/tool\/behat\/tests\/behat\/data_generators.feature": 131.7,
"mod\/choice\/tests\/behat\/publish_results.feature": 130.7,
"group\/tests\/behat\/create_groups.feature": 128.1,
"admin\/tool\/behat\/tests\/behat\/get_and_set_fields.feature": 126.7,
"availability\/condition\/profile\/tests\/behat\/availability_profile.feature": 124.6,
"grade\/tests\/behat\/grade_mingrade.feature": 123.8,
"mod\/lesson\/tests\/behat\/lesson_number_of_student_attempts.feature": 122.5,
"backup\/util\/ui\/tests\/behat\/backup_courses.feature": 122,
"mod\/lesson\/tests\/behat\/lesson_navigation.feature": 121.7,
"mod\/lesson\/tests\/behat\/lesson_with_clusters.feature": 119.9,
"blocks\/comments\/tests\/behat\/add_comment.feature": 118.8,
"mod\/forum\/tests\/behat\/separate_group_discussions.feature": 117.3,
"completion\/tests\/behat\/restrict_activity_by_date.feature": 117.2,
"lib\/editor\/tinymce\/tests\/behat\/edit_available_icons.feature": 113,
"mod\/glossary\/tests\/behat\/entries_always_editable.feature": 112.3,
"report\/log\/tests\/behat\/user_log.feature": 111.4,
"course\/tests\/behat\/activities_edit_completion.feature": 111.4,
"report\/log\/tests\/behat\/filter_log.feature": 108.2,
"mod\/assign\/tests\/behat\/group_submission.feature": 108.1,
"mod\/glossary\/tests\/behat\/categories.feature": 107.6,
"mod\/quiz\/tests\/behat\/editing_click_delete_icon.feature": 107.6,
"availability\/condition\/grade\/tests\/behat\/availability_grade.feature": 107.1,
"mod\/forum\/tests\/behat\/discussion_subscriptions.feature": 106,
"course\/tests\/behat\/category_change_visibility.feature": 105.5,
"mod\/wiki\/tests\/behat\/collaborative_individual.feature": 104,
"course\/tests\/behat\/max_number_sections.feature": 104,
"course\/tests\/behat\/activities_visibility_icons.feature": 102.3,
"admin\/tool\/behat\/tests\/behat\/edit_permissions.feature": 100.4,
"mod\/lesson\/tests\/behat\/time_limit.feature": 98.7,
"mod\/quiz\/tests\/behat\/editing_set_marks_no_attempts.feature": 97,
"admin\/tests\/behat\/filter_users.feature": 94.4,
"mod\/forum\/tests\/behat\/add_forum.feature": 92.8,
"user\/tests\/behat\/delete_users.feature": 92,
"grade\/tests\/behat\/grade_UI_settings.feature": 91.9,
"completion\/tests\/behat\/restrict_activity_by_grade.feature": 91.9,
"enrol\/self\/tests\/behat\/self_enrolment.feature": 91.5,
"blocks\/navigation\/tests\/behat\/view_my_courses.feature": 91.3,
"question\/tests\/behat\/preview_question.feature": 90.9,
"availability\/condition\/group\/tests\/behat\/availability_group.feature": 89.3,
"blog\/tests\/behat\/comment.feature": 85.9,
"mod\/forum\/tests\/behat\/forum_subscriptions_availability.feature": 85.8,
"mod\/lesson\/tests\/behat\/lesson_edit_cluster.feature": 85.6,
"course\/tests\/behat\/section_visibility.feature": 84.9,
"question\/tests\/behat\/question_categories.feature": 84.6,
"question\/format\/xml\/tests\/behat\/import_export.feature": 83.5,
"question\/tests\/behat\/delete_questions.feature": 82.5,
"mod\/forum\/tests\/behat\/completion_condition_number_discussions.feature": 82.1,
"mod\/forum\/tests\/behat\/separate_group_single_group_discussions.feature": 82,
"question\/tests\/behat\/sort_questions.feature": 81.6,
"group\/tests\/behat\/id_uniqueness.feature": 80,
"admin\/tool\/filetypes\/tests\/behat\/add_filetypes.feature": 79.5,
"mod\/lesson\/tests\/behat\/date_availability.feature": 78.1,
"cohort\/tests\/behat\/add_cohort.feature": 76.8,
"grade\/report\/singleview\/tests\/behat\/singleview.feature": 76.5,
"mod\/wiki\/tests\/behat\/wiki_formats.feature": 75.8,
"course\/tests\/behat\/force_group_mode.feature": 75.5,
"blocks\/news_items\/tests\/behat\/display_news.feature": 75.1,
"course\/tests\/behat\/frontpage_display_modes.feature": 74.9,
"mod\/assign\/tests\/behat\/edit_previous_feedback.feature": 74.1,
"availability\/condition\/grouping\/tests\/behat\/availability_grouping.feature": 73.1,
"course\/tests\/behat\/restrict_available_activities.feature": 72.9,
"mod\/assign\/feedback\/editpdf\/tests\/behat\/group_annotations.feature": 72.2,
"mod\/lesson\/tests\/behat\/questions_images.feature": 72,
"mod\/lesson\/tests\/behat\/completion_condition_end_reached.feature": 72,
"mod\/forum\/tests\/behat\/single_forum_discussion.feature": 71,
"mod\/lesson\/tests\/behat\/lesson_review.feature": 70.5,
"mod\/quiz\/tests\/behat\/editing_repaginate.feature": 70.2,
"course\/tests\/behat\/paged_course_navigation.feature": 69.3,
"repository\/tests\/behat\/create_shortcut.feature": 69.1,
"grade\/grading\/form\/rubric\/tests\/behat\/reuse_own_rubrics.feature": 67,
"mod\/choice\/tests\/behat\/publish_results_anonymously.feature": 66.9,
"mod\/book\/tests\/behat\/show_hide_chapters.feature": 65.9,
"blocks\/navigation\/tests\/behat\/expand_my_courses_setting.feature": 65.1,
"cohort\/tests\/behat\/access_visible_cohorts.feature": 64.9,
"user\/tests\/behat\/view_full_profile.feature": 64.3,
"course\/tests\/behat\/course_creation.feature": 64,
"mod\/glossary\/tests\/behat\/print_friendly_version.feature": 64,
"grade\/export\/txt\/tests\/behat\/export.feature": 63,
"question\/tests\/behat\/copy_questions.feature": 62.8,
"lib\/editor\/atto\/plugins\/accessibilitychecker\/tests\/behat\/accessibilitychecker.feature": 62.1,
"mod\/quiz\/tests\/behat\/settings_form_fields_disableif.feature": 61.7,
"admin\/tool\/uploadcourse\/tests\/behat\/create.feature": 61.6,
"badges\/tests\/behat\/add_badge.feature": 61.4,
"mod\/assign\/tests\/behat\/allow_another_attempt.feature": 61.1,
"completion\/tests\/behat\/enable_manual_complete_mark.feature": 60.5,
"mod\/wiki\/tests\/behat\/page_history.feature": 59.9,
"cohort\/tests\/behat\/upload_cohort_users.feature": 58.8,
"mod\/data\/tests\/behat\/add_entries.feature": 58.2,
"mod\/feedback\/tests\/behat\/show_nonrespondents.feature": 58,
"mod\/lesson\/tests\/behat\/lesson_progress_bar.feature": 57.5,
"mod\/lesson\/tests\/behat\/lesson_essay_question.feature": 57.1,
"blocks\/tests\/behat\/restrict_available_blocks.feature": 56.8,
"repository\/tests\/behat\/overwrite_file.feature": 56.6,
"course\/tests\/behat\/add_activities.feature": 56.5,
"availability\/condition\/date\/tests\/behat\/availability_date.feature": 56.2,
"mod\/choice\/tests\/behat\/multiple_options.feature": 55.4,
"grade\/report\/history\/tests\/behat\/basic_functionality.feature": 55.3,
"enrol\/guest\/tests\/behat\/guest_access.feature": 55.1,
"mod\/assign\/tests\/behat\/comment_inline.feature": 54.9,
"blocks\/tests\/behat\/hidden_block_region.feature": 54.3,
"mod\/quiz\/tests\/behat\/add_quiz.feature": 53.5,
"cohort\/tests\/behat\/view_cohorts.feature": 52.8,
"admin\/tool\/availabilityconditions\/tests\/behat\/manage_conditions.feature": 52.8,
"grade\/grading\/form\/rubric\/tests\/behat\/publish_rubric_templates.feature": 51.9,
"mod\/assign\/tests\/behat\/display_grade.feature": 51.6,
"mod\/lesson\/tests\/behat\/teacher_grade_essays.feature": 51.5,
"message\/tests\/behat\/send_message.feature": 51.1,
"blocks\/recent_activity\/tests\/behat\/structural_changes.feature": 50.9,
"availability\/condition\/completion\/tests\/behat\/availability_completion.feature": 50.5,
"mod\/lesson\/tests\/behat\/password_protection.feature": 49.6,
"admin\/tool\/behat\/tests\/behat\/basic_actions.feature": 49.5,
"mod\/quiz\/tests\/behat\/editing_click_move_icon.feature": 48.4,
"blocks\/activity_modules\/tests\/behat\/block_activity_modules.feature": 48.2,
"mod\/assign\/tests\/behat\/submission_comments.feature": 47.8,
"files\/tests\/behat\/course_files.feature": 47.5,
"admin\/tool\/behat\/tests\/behat\/list_steps.feature": 47.4,
"grade\/tests\/behat\/grade_override_letter.feature": 47.3,
"blocks\/activity_modules\/tests\/behat\/block_activity_modules.feature": 42.7,
"calendar\/tests\/behat\/calendar.feature": 42,
"grade\/grading\/form\/rubric\/tests\/behat\/edit_rubric.feature": 41.2,
"mod\/forum\/tests\/behat\/forum_subscriptions.feature": 32.6,
"course\/tests\/behat\/move_activities.feature": 30.8,
"mod\/workshep\/tests\/behat\/workshep_assessment.feature": 29.3,
"blocks\/glossary_random\/tests\/behat\/glossary_random.feature": 29,
"mod\/workshop\/tests\/behat\/workshop_assessment.feature": 28.8,
"blocks\/tests\/behat\/manage_blocks.feature": 28.6,
"badges\/tests\/behat\/navrequirecap.feature": 28,
"mod\/quiz\/tests\/behat\/completion_condition_attempts_used.feature": 26.9,
"mod\/quiz\/tests\/behat\/completion_condition_passing_grade.feature": 26,
"blocks\/html\/tests\/behat\/multiple_instances.feature": 26,
"message\/tests\/behat\/display_history.feature": 24.7,
"course\/tests\/behat\/category_change_visibility.feature": 24.2,
"local\/uneditableblocks\/tests\/behat\/enable_uneditableblocks.feature": 23.1,
"mod\/feedback\/tests\/behat\/defaultshortanswerlength.feature": 22.1,
"backup\/util\/ui\/tests\/behat\/backup_courses.feature": 22,
"course\/tests\/behat\/create_delete_course.feature": 21.9,
"blocks\/course_summary\/tests\/behat\/block_course_summary_course.feature": 21.2,
"course\/tests\/behat\/move_sections.feature": 21.1,
"course\/tests\/behat\/course_category_management_listing.feature": 21,
"local\/userpolicy\/tests\/behat\/fieldvisibility.feature": 20.9,
"admin\/tool\/behat\/tests\/behat\/get_and_set_fields.feature": 20.5,
"mod\/wiki\/tests\/behat\/edit_tags.feature": 19.6,
"mod\/attendance\/tests\/behat\/attendance_mod.feature": 19.5,
"blocks\/participants\/tests\/behat\/block_participants_course.feature": 19.5,
"blocks\/course_summary\/tests\/behat\/block_course_summary_frontpage.feature": 17.7,
"blocks\/tests\/behat\/configure_block_throughout_site.feature": 17.7,
"grade\/report\/singleview\/tests\/behat\/singleview.feature": 17.6,
"grade\/grading\/form\/rubric\/tests\/behat\/reuse_own_rubrics.feature": 16.3,
"mod\/wiki\/tests\/behat\/group_enhancements.feature": 16.2,
"admin\/tests\/behat\/forcelogin_makefrontpagepublic.feature": 16,
"message\/tests\/behat\/disablenotifications.feature": 16,
"completion\/tests\/behat\/teacher_manual_completion.feature": 16,
"mod\/glossary\/tests\/behat\/entries_require_approval.feature": 16,
"backup\/util\/ui\/tests\/behat\/import_course.feature": 15.9,
"admin\/tests\/behat\/set_admin_settings_value.feature": 15.8,
"mod\/mediagallery\/tests\/behat\/separategroups.feature": 15.6,
"blocks\/tests\/behat\/return_block_original_state.feature": 15.6,
"admin\/tests\/behat\/custom_maxbytes.feature": 15.4,
"mod\/oublog\/tests\/behat\/separate_individuals.feature": 15.4,
"mod\/forum\/tests\/behat\/default_displaywordcount.feature": 15.4,
"backup\/util\/ui\/tests\/behat\/restore_moodle2_course_numsections.feature": 14.9,
"question\/tests\/behat\/question_defaultpenalty.feature": 14.8,
"availability\/tests\/behat\/edit_availability.feature": 14.7,
"mod\/data\/tests\/behat\/view_entries.feature": 14.6,
"local\/catdelete\/tests\/behat\/catdelete.feature": 14.3,
"group\/tests\/behat\/showusernameingroup.feature": 14.3,
"mod\/forum\/tests\/behat\/edit_post_student.feature": 14.2,
"admin\/tool\/behat\/tests\/behat\/nasty_strings.feature": 14.1,
"mod\/hsuforum\/tests\/behat\/edit_post_student.feature": 13.8,
"mod\/quiz\/tests\/behat\/add_quiz.feature": 13.5,
"grade\/grading\/form\/rubric\/tests\/behat\/publish_rubric_templates.feature": 13.5,
"blocks\/autocreate_user\/tests\/behat\/add_introtext.feature": 13.5,
"question\/type\/truefalse\/tests\/behat\/custompenalty.feature": 13.3,
"blocks\/participants\/tests\/behat\/block_participants_frontpage.feature": 13.1,
"blocks\/html\/tests\/behat\/course_block.feature": 12.9,
"mod\/quiz\/tests\/behat\/configsubnethide.feature": 12.8,
"mod\/forum\/tests\/behat\/separate_group_single_group_discussions.feature": 12.6,
"blocks\/html\/tests\/behat\/configuring_html_block.feature": 12.3,
"course\/tests\/behat\/course_change_visibility.feature": 12.2,
"course\/tests\/behat\/modchooser_hidden.feature": 11.9,
"blocks\/login\/tests\/behat\/login_block.feature": 11.7,
"course\/tests\/behat\/paged_course_navigation.feature": 11.1,
"course\/tests\/behat\/limitrolerenaming.feature": 11,
"mod\/book\/tests\/behat\/create_chapters.feature": 11,
"mod\/forum\/tests\/behat\/my_forum_posts.feature": 10.7,
"auth\/tests\/behat\/login.feature": 10.5,
"blocks\/glossary_random\/tests\/behat\/glossary_random_frontpage.feature": 10.5,
"mod\/data\/tests\/behat\/add_entries.feature": 10.1,
"mod\/wiki\/tests\/behat\/preview_page.feature": 9.7,
"mod\/oublog\/tests\/behat\/personalblog.feature": 9.7,
"admin\/tests\/behat\/display_short_names.feature": 9.6,
"mod\/forum\/tests\/behat\/separate_group_discussions.feature": 9.5,
"course\/tests\/behat\/add_activities.feature": 9.4,
"mod\/forum\/tests\/behat\/forum_subscriptions_management.feature": 8.8,
"blocks\/tests\/behat\/restrict_available_blocks.feature": 8.7,
"blocks\/comments\/tests\/behat\/add_comment.feature": 8.7,
"mod\/survey\/tests\/behat\/survey_types.feature": 8.5,
"admin\/tool\/langimport\/tests\/behat\/manage_langpacks.feature": 7.3,
"local\/rolerenaming\/tests\/behat\/course_role_renaming.feature": 7.2,
"course\/tests\/behat\/edit_settings.feature": 7.1,
"group\/tests\/behat\/create_groups.feature": 6.8,
"blocks\/tests\/behat\/add_blocks.feature": 6.5,
"message\/tests\/behat\/manage_contacts.feature": 6.1,
"course\/tests\/behat\/course_search.feature": 6,
"course\/tests\/behat\/course_creation.feature": 5.8,
"my\/tests\/behat\/reset_page.feature": 5.4,
"my\/tests\/behat\/restrict_available_blocks.feature": 5.3,
"mod\/oublog\/tests\/behat\/basic.feature": 5.2,
"mod\/assign\/tests\/behat\/file_submission.feature": 46.6,
"mod\/wiki\/tests\/behat\/preview_page.feature": 46.5,
"mod\/lesson\/tests\/behat\/lesson_informations_at_end.feature": 46.4,
"repository\/tests\/behat\/zip_and_unzip.feature": 46.1,
"backup\/util\/ui\/tests\/behat\/import_course.feature": 45.5,
"course\/tests\/behat\/category_role_assignment.feature": 44.6,
"user\/tests\/behat\/table_sorting.feature": 44.5,
"course\/format\/social\/tests\/behat\/social_adjust_discussion_count.feature": 44.3,
"mod\/choice\/tests\/behat\/change_response.feature": 44.1,
"mod\/book\/tests\/behat\/log_entries.feature": 43.8,
"lib\/editor\/atto\/plugins\/align\/tests\/behat\/align.feature": 42.8,
"mod\/assign\/tests\/behat\/online_submissions.feature": 42.7,
"mod\/assign\/tests\/behat\/filter_by_marker.feature": 41.7,
"lib\/editor\/atto\/plugins\/image\/tests\/behat\/image.feature": 41.3,
"mod\/assign\/tests\/behat\/grant_extension.feature": 41.2,
"mod\/label\/tests\/behat\/label_visibility.feature": 40.9,
"mod\/forum\/tests\/behat\/forum_subscriptions.feature": 40.2,
"course\/tests\/behat\/move_activities.feature": 40.2,
"message\/tests\/behat\/manage_contacts.feature": 39.1,
"my\/tests\/behat\/restrict_available_blocks.feature": 38.3,
"report\/eventlist\/tests\/behat\/mainsection.feature": 37.5,
"blocks\/comments\/tests\/behat\/delete_comment.feature": 37.2,
"calendar\/tests\/behat\/calendar.feature": 37,
"backup\/util\/ui\/tests\/behat\/duplicate_activities.feature": 37,
"question\/tests\/behat\/edit_questions.feature": 36.5,
"repository\/recent\/tests\/behat\/add_recent.feature": 36.2,
"mod\/lesson\/tests\/behat\/import_fillintheblank_question.feature": 35.7,
"course\/tests\/behat\/activities_group_icons.feature": 35.2,
"admin\/tests\/behat\/upload_users.feature": 34.3,
"course\/tests\/behat\/course_change_visibility.feature": 32.9,
"blocks\/tests\/behat\/return_block_original_state.feature": 32.5,
"admin\/tool\/behat\/tests\/behat\/nasty_strings.feature": 31.9,
"mod\/glossary\/tests\/behat\/prevent_duplicate_entries.feature": 31.4,
"admin\/tool\/behat\/tests\/behat\/manipulate_forms.feature": 31.1,
"course\/tests\/behat\/course_search.feature": 30.7,
"mod\/lesson\/tests\/behat\/import_images.feature": 30.7,
"lib\/editor\/atto\/plugins\/bold\/tests\/behat\/bold.feature": 30.5,
"mod\/choice\/tests\/behat\/limit_responses.feature": 30.4,
"course\/tests\/behat\/activities_edit_with_block_dock.feature": 30.4,
"lib\/editor\/atto\/plugins\/indent\/tests\/behat\/indent.feature": 30.1,
"lib\/editor\/atto\/plugins\/italic\/tests\/behat\/italic.feature": 29.7,
"lib\/editor\/atto\/plugins\/equation\/tests\/behat\/equation.feature": 29.6,
"course\/tests\/behat\/edit_settings.feature": 29.5,
"mod\/scorm\/tests\/behat\/add_scorm.feature": 29.1,
"repository\/tests\/behat\/delete_files.feature": 28.7,
"admin\/tool\/uploadcourse\/tests\/behat\/update.feature": 28.7,
"course\/tests\/behat\/activities_indentation.feature": 28.4,
"mod\/quiz\/tests\/behat\/completion_condition_passing_grade.feature": 28.1,
"blocks\/glossary_random\/tests\/behat\/glossary_random.feature": 27.1,
"repository\/tests\/behat\/cancel_add_file.feature": 26,
"blocks\/html\/tests\/behat\/configuring_html_block.feature": 25.8,
"lib\/editor\/atto\/plugins\/table\/tests\/behat\/table.feature": 24.1,
"blocks\/tests\/behat\/add_blocks.feature": 24,
"course\/tests\/behat\/rename_roles.feature": 23.7,
"blocks\/tests\/behat\/manage_blocks.feature": 23.7,
"message\/tests\/behat\/block_users.feature": 23.7,
"course\/tests\/behat\/create_delete_course.feature": 23.6,
"message\/tests\/behat\/message_participants.feature": 23.6,
"mod\/quiz\/tests\/behat\/completion_condition_attempts_used.feature": 23.4,
"message\/tests\/behat\/display_history.feature": 23,
"mod\/choice\/tests\/behat\/add_choice.feature": 23,
"question\/format\/gift\/tests\/behat\/import_export.feature": 22.9,
"message\/tests\/behat\/search_history.feature": 22.8,
"repository\/tests\/behat\/create_folders.feature": 22.3,
"completion\/tests\/behat\/teacher_manual_completion.feature": 22.3,
"mod\/glossary\/tests\/behat\/entries_require_approval.feature": 21.7,
"mod\/wiki\/tests\/behat\/edit_tags.feature": 21,
"lib\/editor\/atto\/plugins\/link\/tests\/behat\/link.feature": 20.9,
"course\/tests\/behat\/move_sections.feature": 20,
"question\/format\/webct\/tests\/behat\/import.feature": 19.9,
"lib\/editor\/atto\/plugins\/accessibilityhelper\/tests\/behat\/accessibilityhelper.feature": 19.7,
"question\/format\/webct\/tests\/behat\/importcalculated.feature": 19.6,
"lib\/editor\/atto\/plugins\/media\/tests\/behat\/media.feature": 19.5,
"blocks\/course_summary\/tests\/behat\/block_course_summary_course.feature": 18.2,
"blocks\/participants\/tests\/behat\/block_participants_course.feature": 17.4,
"admin\/tests\/behat\/set_admin_settings_value.feature": 16.8,
"lib\/editor\/atto\/plugins\/clear\/tests\/behat\/clear.feature": 16.7,
"admin\/tool\/monitor\/tests\/behat\/disabled.feature": 16.4,
"blocks\/html\/tests\/behat\/multiple_instances.feature": 15.6,
"grade\/export\/xml\/tests\/behat\/export.feature": 15.4,
"auth\/tests\/behat\/login.feature": 14.9,
"blocks\/course_summary\/tests\/behat\/block_course_summary_frontpage.feature": 14.8,
"lib\/editor\/atto\/plugins\/charmap\/tests\/behat\/charmap.feature": 14.4,
"lib\/editor\/atto\/plugins\/unorderedlist\/tests\/behat\/unorderedlist.feature": 14.4,
"lib\/editor\/atto\/plugins\/strike\/tests\/behat\/strike.feature": 14,
"blocks\/tests\/behat\/configure_block_throughout_site.feature": 14,
"lib\/editor\/atto\/plugins\/superscript\/tests\/behat\/superscript.feature": 13.9,
"lib\/editor\/atto\/plugins\/title\/tests\/behat\/title.feature": 13.8,
"lib\/editor\/atto\/plugins\/subscript\/tests\/behat\/subscript.feature": 13.8,
"lib\/editor\/atto\/plugins\/underline\/tests\/behat\/underline.feature": 13.7,
"lib\/editor\/atto\/plugins\/orderedlist\/tests\/behat\/orderedlist.feature": 13.3,
"user\/tests\/behat\/edituserpassword.feature": 12.7,
"mod\/data\/tests\/behat\/view_entries.feature": 12.4,
"repository\/upload\/tests\/behat\/upload_file.feature": 12.2,
"blocks\/participants\/tests\/behat\/block_participants_frontpage.feature": 9.4,
"lib\/editor\/atto\/plugins\/html\/tests\/behat\/html.feature": 9.2,
"mod\/forum\/tests\/behat\/my_forum_posts.feature": 9.1,
"mod\/book\/tests\/behat\/create_chapters.feature": 8.8,
"blocks\/html\/tests\/behat\/course_block.feature": 8.6,
"lib\/editor\/atto\/plugins\/collapse\/tests\/behat\/collapse.feature": 8.5,
"blocks\/login\/tests\/behat\/login_block.feature": 8.4,
"admin\/tests\/behat\/display_short_names.feature": 7.8,
"mod\/forum\/tests\/behat\/forum_subscriptions_management.feature": 7.1,
"mod\/survey\/tests\/behat\/survey_types.feature": 6.8,
"admin\/tool\/langimport\/tests\/behat\/manage_langpacks.feature": 6,
"blocks\/glossary_random\/tests\/behat\/glossary_random_frontpage.feature": 5.6,
"my\/tests\/behat\/reset_page.feature": 5.3,
"my\/tests\/behat\/add_blocks.feature": 4.6,
"user\/tests\/behat\/reset_page.feature": 4,
"message\/tests\/behat\/search_history.feature": 3.9,
"blocks\/navigation\/tests\/behat\/expand_my_courses_setting.feature": 3.3,
"user\/tests\/behat\/add_blocks.feature": 2.8,
"report\/usersessions\/tests\/behat\/usersessions_report.feature": 2.7,
"admin\/tool\/behat\/tests\/behat\/test_environment.feature": 1
"user\/tests\/behat\/reset_page.feature": 4.2,
"user\/tests\/behat\/add_blocks.feature": 4.1,
"report\/usersessions\/tests\/behat\/usersessions_report.feature": 2.8,
"admin\/tool\/behat\/tests\/behat\/test_environment.feature": 1.5
}
+15
View File
@@ -724,6 +724,21 @@ $CFG->admin = 'admin';
// Example:
// $CFG->behat_faildump_path = '/my/path/to/save/failure/dumps';
//
// You can specify db, selenium wd_host etc. for behat parallel run by setting following variable.
// Example:
// $CFG->behat_parallel_run = array (
// array (
// 'dbtype' => 'mysqli',
// 'dblibrary' => 'native',
// 'dbhost' => 'localhost',
// 'dbname' => 'moodletest',
// 'dbuser' => 'moodle',
// 'dbpass' => 'moodle',
// 'behat_prefix' => 'mdl_',
// 'wd_host' => 'http://127.0.0.1:4444/wd/hub'
// ),
// );
//
//=========================================================================
// 12. DEVELOPER DATA GENERATOR
//=========================================================================
+20 -11
View File
@@ -44,12 +44,15 @@ class behat_command {
/**
* Ensures the behat dir exists in moodledata
* @param int $runprocess run process for which behat dir is returned.
* @return string Full path
*/
public static function get_behat_dir() {
public static function get_behat_dir($runprocess = 0) {
global $CFG;
$behatdir = $CFG->behat_dataroot . '/behat';
$runprocess = empty($runprocess) ? "" : $runprocess;
$behatdir = $CFG->behat_dataroot . $runprocess . '/behat';
if (!is_dir($behatdir)) {
if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
@@ -73,23 +76,29 @@ class behat_command {
* normal cmd.exe (in Windows).
*
* @param bool $custombyterm If the provided command should depend on the terminal where it runs
* @param bool $parallelrun If parallel run is installed.
* @return string
*/
public final static function get_behat_command($custombyterm = false) {
public final static function get_behat_command($custombyterm = false, $parallerun = false) {
$separator = DIRECTORY_SEPARATOR;
$exec = 'behat';
if (!$parallerun) {
$exec = 'behat';
// Cygwin uses linux-style directory separators.
if ($custombyterm && testing_is_cygwin()) {
$separator = '/';
// Cygwin uses linux-style directory separators.
if ($custombyterm && testing_is_cygwin()) {
$separator = '/';
// MinGW can not execute .bat scripts.
if (!testing_is_mingw()) {
$exec = 'behat.bat';
// MinGW can not execute .bat scripts.
if (!testing_is_mingw()) {
$exec = 'behat.bat';
}
}
$command = 'vendor' . $separator . 'bin' . $separator . $exec;
} else {
$command = 'php admin' . $separator . 'tool' . $separator . 'behat' . $separator . 'cli' . $separator . 'run.php';
}
return 'vendor' . $separator . 'bin' . $separator . $exec;
return $command;
}
/**
+158 -31
View File
@@ -53,9 +53,10 @@ class behat_config_manager {
*
* @param string $component Restricts the obtained steps definitions to the specified component
* @param string $testsrunner If the config file will be used to run tests
* @param string $tags features files including tags.
* @return void
*/
public static function update_config_file($component = '', $testsrunner = true) {
public static function update_config_file($component = '', $testsrunner = true, $tags = '') {
global $CFG;
// Behat must have a separate behat.yml to have access to the whole set of features and steps definitions.
@@ -108,7 +109,7 @@ class behat_config_manager {
// Behat config file specifing the main context class,
// the required Behat extensions and Moodle test wwwroot.
$contents = self::get_config_file_contents($features, $stepsdefinitions);
$contents = self::get_config_file_contents(self::get_fetaures_with_tags($features, $tags), $stepsdefinitions);
// Stores the file.
if (!file_put_contents($configfilepath, $contents)) {
@@ -117,6 +118,40 @@ class behat_config_manager {
}
/**
* Search feature files for set of tags.
*
* @param array $features set of feature files.
* @param string $tags list of tags (currently support && only.)
* @return array filtered list of feature files with tags.
*/
public static function get_fetaures_with_tags($features, $tags) {
if (empty($tags)) {
return $features;
}
$newfeaturelist = array();
$tagstosearch = explode('&&', $tags);
foreach ($features as $featurefile) {
$contents = file_get_contents($featurefile);
$includefeature = true;
foreach ($tagstosearch as $tag) {
// If negitive tag, then ensure it don't exist.
if (strpos($tag, '~') !== false) {
$tag = substr($tag, 1);
if ($contents && strpos($contents, $tag) !== false) {
$includefeature = false;
}
} else if ($contents && strpos($contents, $tag) === false) {
$includefeature = false;
}
}
if ($includefeature) {
$newfeaturelist[] = $featurefile;
}
}
return $newfeaturelist;
}
/**
* Gets the list of Moodle steps definitions
*
@@ -172,12 +207,18 @@ class behat_config_manager {
/**
* Returns the behat config file path used by the behat cli command.
*
* @param int $runprocess Runprocess.
* @return string
*/
public static function get_behat_cli_config_filepath() {
public static function get_behat_cli_config_filepath($runprocess = 0) {
global $CFG;
$command = $CFG->behat_dataroot . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
if ($runprocess) {
$command = $CFG->behat_dataroot . $runprocess;
} else {
$command = $CFG->behat_dataroot;
}
$command .= DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
// Cygwin uses linux-style directory separators.
if (testing_is_cygwin()) {
@@ -187,6 +228,92 @@ class behat_config_manager {
return $command;
}
/**
* Returns the path to the parallel run file which specifies if parallel test environment is enabled
* and how many parallel runs to execute.
*
* @param int $runprocess run process for which behat dir is returned.
* @return string
*/
public final static function get_parallel_test_file_path($runprocess = 0) {
return behat_command::get_behat_dir($runprocess) . '/parallel_environment_enabled.txt';
}
/**
* Returns number of parallel runs for which site is initialised.
*
* @param int $runprocess run process for which behat dir is returned.
* @return int
*/
public final static function get_parallel_test_runs($runprocess = 0) {
$parallelrun = 0;
// Get parallel run info from first file and last file.
$parallelrunconfigfile = self::get_parallel_test_file_path($runprocess);
if (file_exists($parallelrunconfigfile)) {
if ($parallel = file_get_contents($parallelrunconfigfile)) {
$parallelrun = (int) $parallel;
}
}
return $parallelrun;
}
/**
* Drops parallel site links.
*
* @return bool true on success else false.
*/
public final static function drop_parallel_site_links() {
global $CFG;
// Get parallel test runs from first run.
$parallelrun = self::get_parallel_test_runs(1);
if (empty($parallelrun)) {
return false;
}
// If parallel run then remove links and original file.
clearstatcache();
for ($i = 1; $i <= $parallelrun; $i++) {
$link = $CFG->dirroot . '/' . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $i;
if (file_exists($link) && is_link($link)) {
@unlink($link);
}
}
return true;
}
/**
* Create parallel site links.
*
* @return bool true for sucess, else false.
*/
public final static function create_parallel_site_links() {
global $CFG;
// Get parallel test runs from first run.
$parallelrun = self::get_parallel_test_runs(1);
// Create site symlink if necessary.
clearstatcache();
for ($i = 1; $i <= $parallelrun; $i++) {
$link = $CFG->dirroot.'/'.BEHAT_PARALLEL_SITE_WWW_SUFFIX.$i;
clearstatcache();
if (file_exists($link)) {
if (!is_link($link) || !is_dir($link)) {
echo "File exists at link location ($link) but is not a link or directory!\n";
return false;
}
} else if (!symlink($CFG->dirroot, $link)) {
// Try create link in case it's not already present.
echo "Unable to create behat site symlink ($link)\n";
return false;
}
}
return true;
}
/**
* Behat config file specifing the main context class,
* the required Behat extensions and Moodle test wwwroot.
@@ -201,27 +328,26 @@ class behat_config_manager {
// We require here when we are sure behat dependencies are available.
require_once($CFG->dirroot . '/vendor/autoload.php');
$instance = 1;
$parallel = 0;
foreach ($_SERVER['argv'] as $arg) {
if (strpos($arg, '--suffix=') === 0) {
$instance = intval(substr($arg, strlen('--suffix=')));
}
if (empty($parallel)) {
$parallel = preg_filter('#--parallel=(\d+)#', '$1', $arg);
}
}
$selenium2wdhost = array('wd_host' => 'http://localhost:4444/wd/hub');
// Attempt to split into weighted buckets using timing information, if available.
if ($alloc = self::profile_guided_allocate($features, max(1, $parallel), $instance)) {
$features = $alloc;
} else {
// Divide the list of feature files amongst the parallel runners.
srand(crc32(floor(time() / 3600 / 24).var_export($features,true)));
shuffle($features);
// Pull out the features for just this worker.
$features = array_chunk($features, ceil(count($features) / max(1, $parallel)));
$features = $features[$instance-1];
$parallelruns = self::get_parallel_test_runs();
// If parallel run, then only divide features.
if (!empty($CFG->behatrunprocess) && !empty($parallelruns)) {
// Attempt to split into weighted buckets using timing information, if available.
if ($alloc = self::profile_guided_allocate($features, max(1, $parallelruns), $CFG->behatrunprocess)) {
$features = $alloc;
} else {
// Divide the list of feature files amongst the parallel runners.
srand(crc32(floor(time() / 3600 / 24) . var_export($features, true)));
shuffle($features);
// Pull out the features for just this worker.
$features = array_chunk($features, ceil(count($features) / max(1, $parallelruns)));
$features = $features[$CFG->behatrunprocess - 1];
}
// Set proper selenium2 wd_host if defined.
if (!empty($CFG->behat_parallel_run[$CFG->behatrunprocess - 1]['wd_host'])) {
$selenium2wdhost = array('wd_host' => $CFG->behat_parallel_run[$CFG->behatrunprocess - 1]['wd_host']);
}
}
// It is possible that it has no value as we don't require a full behat setup to list the step definitions.
@@ -230,6 +356,7 @@ class behat_config_manager {
}
$basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
$config = array(
'default' => array(
'paths' => array(
@@ -243,7 +370,7 @@ class behat_config_manager {
'Behat\MinkExtension\Extension' => array(
'base_url' => $CFG->behat_wwwroot,
'goutte' => null,
'selenium2' => null
'selenium2' => $selenium2wdhost
),
'Moodle\BehatExtension\Extension' => array(
'formatters' => array(
@@ -273,7 +400,7 @@ class behat_config_manager {
* Simply add each one to lightest buckets until all files allocated.
* PGA = Profile Guided Allocation. I made it up just now.
* CAUTION: workers must agree on allocation, do not be random anywhere!
*
*
* @param array $features Behat feature files array
* @param int $nbuckets Number of buckets to divide into
* @param int $instance Index number of this instance
@@ -296,7 +423,7 @@ class behat_config_manager {
$buckets = array_fill(0, $nbuckets, array());
$totalweight = 0;
// Re-key the features list to match pga data.
// Re-key the features list to match timing data.
foreach ($features as $k => $file) {
$key = str_replace($realroot, '', $file);
$features[$key] = $file;
@@ -316,9 +443,9 @@ class behat_config_manager {
// Finally, add each feature one by one to the lightest bucket.
foreach ($pgaorder as $key => $weight) {
$file = $features[$key];
$light_bucket = array_search(min($weights), $weights);
$weights[$light_bucket] += $weight;
$buckets[$light_bucket][] = $file;
$lightbucket = array_search(min($weights), $weights);
$weights[$lightbucket] += $weight;
$buckets[$lightbucket][] = $file;
$totalweight += $weight;
}
@@ -330,7 +457,7 @@ class behat_config_manager {
}
// Return the features for this worker.
return $buckets[$instance-1];
return $buckets[$instance - 1];
}
/**
-16
View File
@@ -213,22 +213,6 @@ class behat_util extends testing_util {
// Updates all the Moodle features and steps definitions.
behat_config_manager::update_config_file();
// Create suffix symlink if necessary.
global $CFG;
if ($CFG->behat_suffix) {
$extra = preg_filter('#.*/(.+)$#', '$1', $CFG->behat_wwwroot);
$link = $CFG->dirroot.'/'.$extra;
if (file_exists($link)) {
if (!is_link($link)) {
throw new coding_exception("File exists at link location ($link) but is not a link!");
}
@unlink($link);
}
if (!symlink($CFG->dirroot, $link)) {
throw new coding_exception("Unable to create behat suffix symlink ($link)");
}
}
if (self::is_test_mode_enabled()) {
return;
}
+44 -15
View File
@@ -41,6 +41,11 @@ define('BEHAT_EXITCODE_INSTALL', 254);
define('BEHAT_EXITCODE_COMPOSER', 255);
define('BEHAT_EXITCODE_INSTALLED', 256);
/**
* The behat test site fullname and shortname.
*/
define('BEHAT_PARALLEL_SITE_WWW_SUFFIX', "behatrun");
/**
* Exits with an error code
*
@@ -224,6 +229,7 @@ function behat_check_config_vars() {
behat_error(BEHAT_EXITCODE_CONFIG,
'Define $CFG->behat_dataroot in config.php');
}
clearstatcache();
if (!file_exists($CFG->behat_dataroot)) {
$permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
umask(0);
@@ -273,30 +279,53 @@ function behat_is_test_site() {
}
/**
* Add behat suffix to $CFG vars for parallel testing.
* Fix variables for parallel behat testing.
* - behat_wwwroot = behat_wwwroot{behatrunprocess}
* - behat_dataroot = behat_dataroot{behatrunprocess}
* - behat_prefix = behat_prefix.{behatrunprocess}_ (For oracle it will be firstletter of prefix and behatrunprocess)
*
* @param string $behatrunprocess process index for which variables will be set.
**/
function behat_add_suffix_to_vars($suffix = '') {
function behat_update_vars_for_process($behatrunprocess = '') {
global $CFG;
if (empty($suffix)) {
if (!empty($CFG->behat_suffix)) {
$suffix = $CFG->behat_suffix;
$allowedconfigoverride = array('dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass');
$behatrunprocess = $CFG->behatrunprocess;
} else if (defined('BEHAT_SUFFIX') && BEHAT_SUFFIX) {
$suffix = BEHAT_SUFFIX;
if ($behatrunprocess) {
// Set www root for run process.
if (isset($CFG->behat_wwwroot) && !preg_match("#/" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $behatrunprocess . "\$#",
$CFG->behat_wwwroot)) {
$CFG->behat_wwwroot .= "/" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . $behatrunprocess;
}
}
$CFG->behat_suffix = $suffix;
// Set behat_dataroot.
if (!preg_match("#" . $behatrunprocess . "\$#", $CFG->behat_dataroot)) {
$CFG->behat_dataroot .= $behatrunprocess;
}
if ($suffix) {
if (isset($CFG->behat_wwwroot) && !preg_match("#/behat$suffix\$#", $CFG->behat_wwwroot)) {
$CFG->behat_wwwroot .= "/behat{$suffix}";
// Set behat_prefix for db, just suffix run process number, to avoid max length exceed.
// For oracle only 2 letter prefix is possible.
// NOTE: This will not work for parallel process > 9.
if ($CFG->dbtype === 'oci') {
$CFG->behat_prefix = substr($CFG->behat_prefix, 0, 1);
$CFG->behat_prefix .= "{$behatrunprocess}";
} else {
$CFG->behat_prefix .= "{$behatrunprocess}_";
}
if (!preg_match("#/behat{$suffix}\$#", $CFG->behat_dataroot)) {
$CFG->behat_dataroot = dirname($CFG->behat_dataroot)."/behat{$suffix}";
if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) {
// Override allowed config vars.
foreach ($allowedconfigoverride as $config) {
if (isset($CFG->behat_parallel_run[$behatrunprocess - 1][$config])) {
$CFG->$config = $CFG->behat_parallel_run[$behatrunprocess - 1][$config];
}
}
// Override behat prefix, if specified.
if (isset($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_prefix'])) {
$CFG->behat_prefix = $CFG->behat_parallel_run[$behatrunprocess - 1]['behat_prefix'];
}
}
$CFG->behat_prefix = "behat{$suffix}_";
}
}
+211
View File
@@ -0,0 +1,211 @@
<?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/>.
/**
* Moodle implementation of process manager, to execute external commands.
*
* @package core
* @copyright 2015 Rajesh Taneja
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Moodle implementation of process manager, to execute external commands.
*
* @package core
* @copyright 2015 Rajesh Taneja
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class process_manager implements Countable {
/**
* Standard in.
*/
const STDIN = 0;
/**
* Standard out.
*/
const STDOUT = 1;
/**
* Standard error.
*/
const STDERR = 2;
/**
* Non blocking mode.
*/
const NON_BLOCKING = 0;
/**
* Blocking mode.
*/
const BLOCKING = 1;
/** @var array Descriptor used for process. */
private static $DESCRIPTORSPEC = array(
self::STDIN => array('pipe', 'r'),
self::STDOUT => array('pipe', 'w'),
self::STDERR => array('pipe', 'w'),
);
/** @var array list of processes. */
private $processes = array();
/** @var array list of stdin */
private $stdins = array();
/** @var array list of stdout */
private $stdouts = array();
/** @var array list of stderr. */
private $stderrs = array();
/**
* Create new process and keep track of it.
*
* @param string $name name of the process, has to be unique for process identification.
* @param string $cmd command to execute.
* @param string $cwd absolute path of working directory for command to execute.
* @return false if failed to create process.
*/
public function create($name, $cmd, $cwd = NULL) {
$process = proc_open($cmd, self::$DESCRIPTORSPEC, $pipes, $cwd);
if (false === is_resource($process)) {
throw new Exception('Error starting worker');
}
stream_set_blocking($pipes[self::STDOUT], self::NON_BLOCKING);
stream_set_blocking($pipes[self::STDERR], self::NON_BLOCKING);
$this->processes[$name] = $process;
$this->stdins[$name] = $pipes[self::STDIN];
$this->stdouts[$name] = $pipes[self::STDOUT];
$this->stderrs[$name] = $pipes[self::STDERR];
return true;
}
/**
* Keep listing to process and return status of it.
*
* @retrun array status, stdout and stderr.
*/
public function listen() {
$read = array();
foreach ($this->processes as $i => $p) {
// Update process info.
if (!($s = @proc_get_status($p)) || !$s['running']) {
$status[$i] = $this->detach($p);
} else {
$status[$i] = 0;
$read[] = $this->stdouts[$i];
$read[] = $this->stderrs[$i];
}
}
if ($read) {
$changednum = stream_select($read, $write, $expect, 0);
} else {
return;
}
if (false === $changednum) {
throw new \RuntimeException();
}
if (0 === $changednum) {
return;
}
foreach ($read as $stream) {
$i = array_search($stream, $this->stdouts, true);
if (false === $i) {
$i = array_search($stream, $this->stderrs, true);
if (false === $i) {
continue;
}
}
$stdout[$i] = stream_get_contents($this->stdouts[$i]);
$stderr[$i] = stream_get_contents($this->stderrs[$i]);
}
return (array($status, $stdout, $stderr));
}
/**
* Detach process.
*
* @param $process process to detatch.
* @return int status of process.
*/
public function detach($process) {
$i = array_search($process, $this->processes, true);
if (false === $i) {
throw new \RuntimeException();
}
fclose($this->stdins[$i]);
fclose($this->stdouts[$i]);
fclose($this->stderrs[$i]);
$status = proc_close($this->processes[$i]);
unset($this->processes[$i]);
unset($this->stdins[$i]);
unset($this->stdouts[$i]);
unset($this->stderrs[$i]);
return $status;
}
/**
* Detach all processes.
*/
public function detachall() {
foreach ($this->stdins as $stdin) {
fclose($stdin);
}
foreach ($this->stdouts as $stdout) {
fclose($stdout);
}
foreach ($this->stderrs as $stderr) {
fclose($stderr);
}
foreach ($this->processes as $processs) {
proc_close($processs);
}
}
/**
* Return count of active processes.
*
* @return int count of active processes.
*/
public function count() {
return count($this->processes);
}
/**
* Destructor.
*/
public function __destruct() {
$this->detachall();
}
}
+81 -14
View File
@@ -176,8 +176,15 @@ function cli_error($text, $errorcode=1) {
die($errorcode);
}
function ns_proc_open($cmd, $die = false) {
/**
* Executes cli command and return handle.
*
* @param string $cmd command to be executed.
* @param bool $die exit if command is not executed.
* @return array list of handles and pipe.
* @throws Exception if worker is not started,
*/
function cli_execute($cmd, $die = false) {
$desc = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
@@ -189,20 +196,80 @@ function ns_proc_open($cmd, $die = false) {
return array($handle, $pipes);
}
/**
* Execute commands in parallel.
*
* @param array $cmds list of commands to be executed.
* @param string $cwd aabsolute path of working directory.
* @param bool $returnonfirstfail Will stop all process and return.
*
* @return bool status of all process.
*/
function cli_execute_parallel($cmds, $cwd = NULL, $returnonfirstfail = false, $addprefix = true) {
require_once(__DIR__ . '/classes/process_manager.php');
function ns_parallel_popen($cmds, $doexit = false) {
$overallstatus = false;
$processmanager = new process_manager();
// Create child process.
foreach ($cmds as $name => $cmd) {
if (!$processmanager->create($name, $cmd, $cwd) && $returnonfirstfail) {
throw new Exception('Error starting worker');
}
}
while (0 < count($processmanager)) {
usleep(10000);
list($status, $stdout, $stderr) = $processmanager->listen();
if (!empty($status)) {
foreach ($status as $name => $value) {
// Something went wrong.
if ((0 > $value)) {
throw new \RuntimeException();
}
$overallstatus = $overallstatus || (bool)$value;
// Add prefix to process.
$prefix = "";
if ($addprefix && ($value === 0)) {
$prefix = '[' . $name . '] ';
}
if (!empty($stdout[$name]) && trim($stdout[$name])) {
echo $prefix . $stdout[$name];
}
if (!empty($stderr[$name]) && trim($stderr[$name])) {
echo $prefix . $stderr[$name];
}
}
// Return if fail found.
if ($returnonfirstfail && (bool)$value) {
unset($processmanager);
$processmanager = null;
echo PHP_EOL;
return $value;
}
}
}
echo PHP_EOL;
return $overallstatus;
}
/**
* Execute commands in sequence and return status code for each process.
*
* @param array $cmds commands to execute.
* @param bool $returnonfirstfail if true then returns on any fail.
* @return array status codes for each process.
*/
function cli_execute_sequential($cmds, $returnonfirstfail = false) {
$procs = array();
foreach ($cmds as $k => $cmd) {
$procs[] = popen($cmd, 'r');
$procs[$k] = popen($cmd, 'r');
passthru($cmd, $procs[$k]);
if (($procs[$k] != 0) && $returnonfirstfail) {
return $procs;
}
}
$status = false;
foreach ($procs as $p) {
if (!$p) continue;
while ($out = fgets($p)) echo $out;
$status |= (bool) pclose($p);
}
if ($doexit && $status) {
exit($status);
}
return $status;
return $procs;
}
+27 -15
View File
@@ -78,34 +78,46 @@ if (defined('BEHAT_SITE_RUNNING')) {
} else if (!empty($CFG->behat_wwwroot) or !empty($CFG->behat_dataroot) or !empty($CFG->behat_prefix)) {
global $argv;
$suffix = '';
$behatrunprocess = false;
if (defined('BEHAT_SUFFIX') && BEHAT_SUFFIX) {
$suffix = BEHAT_SUFFIX;
require_once(__DIR__ . '/../lib/behat/lib.php');
// Get behat run process, if set.
if (defined('BEHAT_CURRENT_RUN') && BEHAT_CURRENT_RUN) {
$behatrunprocess = BEHAT_CURRENT_RUN;
} else if (!empty($_SERVER['REMOTE_ADDR'])) {
if (preg_match('#/behat(.+?)/#', $_SERVER['REQUEST_URI'])) {
$afterpath = str_replace(realpath($CFG->dirroot).'/', '', realpath($_SERVER['SCRIPT_FILENAME']));
if (!$suffix = preg_filter("#.*/behat(.+?)/$afterpath#", '$1', $_SERVER['SCRIPT_FILENAME'])) {
throw new coding_exception("Unable to determine behat suffix [afterpath=$afterpath, scriptfilename={$_SERVER['SCRIPT_FILENAME']}]!");
if (preg_match('#/' . BEHAT_PARALLEL_SITE_WWW_SUFFIX . '(.+?)/#', $_SERVER['REQUEST_URI'])) {
$dirrootrealpath = str_replace("\\", "/", realpath($CFG->dirroot));
$serverrealpath = str_replace("\\", "/", realpath($_SERVER['SCRIPT_FILENAME']));
$afterpath = str_replace($dirrootrealpath.'/', '', $serverrealpath);
if (!$behatrunprocess = preg_filter("#.*/" . BEHAT_PARALLEL_SITE_WWW_SUFFIX . "(.+?)/$afterpath#", '$1',
$_SERVER['SCRIPT_FILENAME'])) {
throw new Exception("Unable to determine behat process [afterpath=" . $afterpath .
", scriptfilename=" . $_SERVER['SCRIPT_FILENAME'] . "]!");
}
}
} else if (defined('BEHAT_TEST') || defined('BEHAT_UTIL')) {
if ($match = preg_filter('#--suffix=(.+)#', '$1', $argv)) {
$suffix = reset($match);
if ($match = preg_filter('#--run=(.+)#', '$1', $argv)) {
$behatrunprocess = reset($match);
}
if ($k = array_search('--config', $argv)) {
$behatconfig = $argv[$k+1];
$suffix = preg_filter("#^{$CFG->behat_dataroot}(.+?)/behat/behat\.yml#", '$1', $behatconfig);
$behatconfig = str_replace("\\", "/", $argv[$k + 1]);
$behatdataroot = str_replace("\\", "/", $CFG->behat_dataroot);
$behatrunprocess = preg_filter("#^{$behatdataroot}" .
"(.+?)[/|\\\]behat[/|\\\]behat\.yml#", '$1', $behatconfig);
}
}
$CFG->behatrunprocess = $behatrunprocess;
// The behat is configured on this server, we need to find out if this is the behat test
// site based on the URL used for access.
require_once(__DIR__ . '/../lib/behat/lib.php');
behat_add_suffix_to_vars($suffix);
behat_update_vars_for_process($behatrunprocess);
if (behat_is_test_site()) {
clearstatcache();
// Checking the integrity of the provided $CFG->behat_* vars and the
// selected wwwroot to prevent conflicts with production and phpunit environments.
behat_check_config_vars();
@@ -117,7 +129,7 @@ if (defined('BEHAT_SITE_RUNNING')) {
if ($file === 'behat' or $file === '.' or $file === '..' or $file === '.DS_Store' or is_numeric($file)) {
continue;
}
behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot');
behat_error(BEHAT_EXITCODE_CONFIG, "$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot");
}
closedir($dh);
unset($dh);
+1 -1
View File
@@ -1365,7 +1365,7 @@ function make_writable_directory($dir, $exceptiononerror = true) {
umask($CFG->umaskpermissions);
if (!file_exists($dir)) {
if (!mkdir($dir, $CFG->directorypermissions, true)) {
if (!@mkdir($dir, $CFG->directorypermissions, true)) {
clearstatcache();
// There might be a race condition when creating directory.
if (!is_dir($dir)) {
-1
View File
@@ -52,7 +52,6 @@ class test_lock {
*/
public static function acquire($framework) {
global $CFG;
$datarootpath = $CFG->{$framework . '_dataroot'} . '/' . $framework;
$lockfile = $datarootpath . '/lock';
if (!file_exists($datarootpath)) {
+50 -15
View File
@@ -31,6 +31,8 @@ require_once(__DIR__ . '/../../behat/behat_base.php');
use Behat\Behat\Event\SuiteEvent as SuiteEvent,
Behat\Behat\Event\ScenarioEvent as ScenarioEvent,
Behat\Behat\Event\FeatureEvent as FeatureEvent,
Behat\Behat\Event\OutlineExampleEvent as OutlineExampleEvent,
Behat\Behat\Event\StepEvent as StepEvent,
Behat\Mink\Exception\DriverException as DriverException,
WebDriver\Exception\NoSuchWindow as NoSuchWindow,
@@ -84,17 +86,25 @@ class behat_hooks extends behat_base {
*/
protected static $faildumpdirname = false;
/**
* Keeps track of time taken by feature to execute.
*
* @var array list of feature timings
*/
protected static $timings = array();
/**
* Gives access to moodle codebase, ensures all is ready and sets up the test lock.
*
* Includes config.php to use moodle codebase with $CFG->behat_*
* instead of $CFG->prefix and $CFG->dataroot, called once per suite.
*
* @param SuiteEvent $event event before suite.
* @static
* @throws Exception
* @BeforeSuite
*/
public static function before_suite($event) {
public static function before_suite(SuiteEvent $event) {
global $CFG;
// Defined only when the behat CLI command is running, the moodle init setup process will
@@ -153,17 +163,33 @@ class behat_hooks extends behat_base {
}
}
protected static $timings = array();
/** @BeforeFeature */
public static function before_feature($obj) {
$file = $obj->getFeature()->getFile();
/**
* Gives access to moodle codebase, to keep track of feature start time.
*
* @param FeatureEvent $event event fired before feature.
* @static
* @BeforeFeature
*/
public static function before_feature(FeatureEvent $event) {
if (!defined('BEHAT_FEATURE_TIMING')) {
return;
}
$file = $event->getFeature()->getFile();
self::$timings[$file] = microtime(true);
}
/** @AfterFeature */
public static function teardownFeature($obj) {
$file = $obj->getFeature()->getFile();
/**
* Gives access to moodle codebase, to keep track of feature end time.
*
* @param FeatureEvent $event event fired after feature.
* @static
* @AfterFeature
*/
public static function after_feature(FeatureEvent $event) {
if (!defined('BEHAT_FEATURE_TIMING')) {
return;
}
$file = $event->getFeature()->getFile();
self::$timings[$file] = microtime(true) - self::$timings[$file];
// Probably didn't actually run this, don't output it.
if (self::$timings[$file] < 1) {
@@ -171,9 +197,14 @@ class behat_hooks extends behat_base {
}
}
/** @AfterSuite */
public static function tearDown($obj) {
global $CFG;
/**
* Gives access to moodle codebase, to keep track of suite timings.
*
* @param SuiteEvent $event event fired after suite.
* @static
* @AfterSuite
*/
public static function after_suite(SuiteEvent $event) {
if (!defined('BEHAT_FEATURE_TIMING')) {
return;
}
@@ -193,6 +224,7 @@ class behat_hooks extends behat_base {
/**
* Resets the test environment.
*
* @param OutlineExampleEvent|ScenarioEvent $event event fired before scenario.
* @throws coding_exception If here we are not using the test database it should be because of a coding error
* @BeforeScenario
*/
@@ -281,9 +313,10 @@ class behat_hooks extends behat_base {
* default would be at framework level, which will stop the execution of
* the run.
*
* @param StepEvent $event event fired before step.
* @BeforeStep @javascript
*/
public function before_step_javascript($event) {
public function before_step_javascript(StepEvent $event) {
try {
$this->wait_for_pending_js();
@@ -305,9 +338,10 @@ class behat_hooks extends behat_base {
* default would be at framework level, which will stop the execution of
* the run.
*
* @param StepEvent $event event fired after step.
* @AfterStep @javascript
*/
public function after_step_javascript($event) {
public function after_step_javascript(StepEvent $event) {
global $CFG;
// Save a screenshot if the step failed.
@@ -340,9 +374,10 @@ class behat_hooks extends behat_base {
*
* This includes creating an HTML dump of the content if there was a failure.
*
* @param StepEvent $event event fired after step.
* @AfterStep
*/
public function after_step($event) {
public function after_step(StepEvent $event) {
global $CFG;
// Save the page content if the step failed.