You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
499 lines
17 KiB
499 lines
17 KiB
2 years ago
|
<?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 2012 David Monllaó
|
||
|
* @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);
|
||
|
define('ABORT_AFTER_CONFIG', true);
|
||
|
|
||
|
require_once(__DIR__ . '/../../../../lib/clilib.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,
|
||
|
'updatesteps' => false,
|
||
|
'fromrun' => 1,
|
||
|
'torun' => 0,
|
||
|
'optimize-runs' => '',
|
||
|
'add-core-features-to-theme' => false,
|
||
|
),
|
||
|
array(
|
||
|
'h' => 'help',
|
||
|
'j' => 'parallel',
|
||
|
'm' => 'maxruns',
|
||
|
'o' => 'optimize-runs',
|
||
|
'a' => 'add-core-features-to-theme',
|
||
|
)
|
||
|
);
|
||
|
|
||
|
// Checking util.php CLI script usage.
|
||
|
$help = "
|
||
|
Behat utilities to manage the test environment
|
||
|
|
||
|
Usage:
|
||
|
php util.php [--install|--drop|--enable|--disable|--diag|--updatesteps|--help] [--parallel=value [--maxruns=value]]
|
||
|
|
||
|
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
|
||
|
--updatesteps Update feature step file.
|
||
|
|
||
|
-j, --parallel Number of parallel behat run operation
|
||
|
-m, --maxruns Max parallel processes to be executed at one time.
|
||
|
-o, --optimize-runs Split features with specified tags in all parallel runs.
|
||
|
-a, --add-core-features-to-theme Add all core features to specified theme's
|
||
|
|
||
|
-h, --help Print out this help
|
||
|
|
||
|
Example from Moodle root directory:
|
||
|
\$ php admin/tool/behat/cli/util.php --enable --parallel=4
|
||
|
|
||
|
More info in http://docs.moodle.org/dev/Acceptance_testing#Running_tests
|
||
|
";
|
||
|
|
||
|
if (!empty($options['help'])) {
|
||
|
echo $help;
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
$cwd = getcwd();
|
||
|
|
||
|
// If Behat parallel site is being initiliased, then define a param to be used to ignore single run install.
|
||
|
if (!empty($options['parallel'])) {
|
||
|
define('BEHAT_PARALLEL_UTIL', true);
|
||
|
}
|
||
|
|
||
|
require_once(__DIR__ . '/../../../../config.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');
|
||
|
|
||
|
// Remove error handling overrides done in config.php. This is consistent with admin/tool/behat/cli/util_single_run.php.
|
||
|
$CFG->debug = (E_ALL | E_STRICT);
|
||
|
$CFG->debugdisplay = 1;
|
||
|
error_reporting($CFG->debug);
|
||
|
ini_set('display_errors', '1');
|
||
|
ini_set('log_errors', '1');
|
||
|
|
||
|
// Import the necessary libraries.
|
||
|
require_once($CFG->libdir . '/setuplib.php');
|
||
|
require_once($CFG->libdir . '/behat/classes/util.php');
|
||
|
|
||
|
// For drop option check if parallel site.
|
||
|
if ((empty($options['parallel'])) && ($options['drop']) || $options['updatesteps']) {
|
||
|
$options['parallel'] = behat_config_manager::get_behat_run_config_value('parallel');
|
||
|
}
|
||
|
|
||
|
// If not a parallel site then open single run.
|
||
|
if (empty($options['parallel'])) {
|
||
|
// Set run config value for single run.
|
||
|
behat_config_manager::set_behat_run_config_value('singlerun', 1);
|
||
|
|
||
|
chdir(__DIR__);
|
||
|
// Check if behat is initialised, if not exit.
|
||
|
passthru("php util_single_run.php --diag", $status);
|
||
|
if ($status) {
|
||
|
exit ($status);
|
||
|
}
|
||
|
$cmd = commands_to_execute($options);
|
||
|
$processes = cli_execute_parallel(array($cmd), __DIR__);
|
||
|
$status = print_sequential_output($processes, false);
|
||
|
chdir($cwd);
|
||
|
exit($status);
|
||
|
}
|
||
|
|
||
|
// Default torun is maximum parallel runs.
|
||
|
if (empty($options['torun'])) {
|
||
|
$options['torun'] = $options['parallel'];
|
||
|
}
|
||
|
|
||
|
$status = false;
|
||
|
$cmds = commands_to_execute($options);
|
||
|
|
||
|
// Start executing commands either sequential/parallel for options provided.
|
||
|
if ($options['diag'] || $options['enable'] || $options['disable']) {
|
||
|
// Do it sequentially as it's fast and need to be displayed nicely.
|
||
|
foreach (array_chunk($cmds, 1, true) as $cmd) {
|
||
|
$processes = cli_execute_parallel($cmd, __DIR__);
|
||
|
print_sequential_output($processes);
|
||
|
}
|
||
|
|
||
|
} else if ($options['drop']) {
|
||
|
$processes = cli_execute_parallel($cmds, __DIR__);
|
||
|
$exitcodes = print_combined_drop_output($processes);
|
||
|
foreach ($exitcodes as $exitcode) {
|
||
|
$status = (bool)$status || (bool)$exitcode;
|
||
|
}
|
||
|
|
||
|
// Remove run config file.
|
||
|
$behatrunconfigfile = behat_config_manager::get_behat_run_config_file_path();
|
||
|
if (file_exists($behatrunconfigfile)) {
|
||
|
if (!unlink($behatrunconfigfile)) {
|
||
|
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete behat run config file');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove test file path.
|
||
|
if (file_exists(behat_util::get_test_file_path())) {
|
||
|
if (!unlink(behat_util::get_test_file_path())) {
|
||
|
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete test file enable info');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if ($options['install']) {
|
||
|
// This is intensive compared to behat itself so run them in chunk if option maxruns not set.
|
||
|
if ($options['maxruns']) {
|
||
|
foreach (array_chunk($cmds, $options['maxruns'], true) as $chunk) {
|
||
|
$processes = cli_execute_parallel($chunk, __DIR__);
|
||
|
$exitcodes = print_combined_install_output($processes);
|
||
|
foreach ($exitcodes as $name => $exitcode) {
|
||
|
if ($exitcode != 0) {
|
||
|
echo "Failed process [[$name]]" . PHP_EOL;
|
||
|
echo $processes[$name]->getOutput();
|
||
|
echo PHP_EOL;
|
||
|
echo $processes[$name]->getErrorOutput();
|
||
|
echo PHP_EOL . PHP_EOL;
|
||
|
}
|
||
|
$status = (bool)$status || (bool)$exitcode;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
$processes = cli_execute_parallel($cmds, __DIR__);
|
||
|
$exitcodes = print_combined_install_output($processes);
|
||
|
foreach ($exitcodes as $name => $exitcode) {
|
||
|
if ($exitcode != 0) {
|
||
|
echo "Failed process [[$name]]" . PHP_EOL;
|
||
|
echo $processes[$name]->getOutput();
|
||
|
echo PHP_EOL;
|
||
|
echo $processes[$name]->getErrorOutput();
|
||
|
echo PHP_EOL . PHP_EOL;
|
||
|
}
|
||
|
$status = (bool)$status || (bool)$exitcode;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else if ($options['updatesteps']) {
|
||
|
// Rewrite config file to ensure we have all the features covered.
|
||
|
if (empty($options['parallel'])) {
|
||
|
behat_config_manager::update_config_file('', true, '', $options['add-core-features-to-theme'], false, false);
|
||
|
} else {
|
||
|
// Update config file, ensuring we have up-to-date behat.yml.
|
||
|
for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
|
||
|
$CFG->behatrunprocess = $i;
|
||
|
|
||
|
// Update config file for each run.
|
||
|
behat_config_manager::update_config_file('', true, $options['optimize-runs'], $options['add-core-features-to-theme'],
|
||
|
$options['parallel'], $i);
|
||
|
}
|
||
|
unset($CFG->behatrunprocess);
|
||
|
}
|
||
|
|
||
|
// Do it sequentially as it's fast and need to be displayed nicely.
|
||
|
foreach (array_chunk($cmds, 1, true) as $cmd) {
|
||
|
$processes = cli_execute_parallel($cmd, __DIR__);
|
||
|
print_sequential_output($processes);
|
||
|
}
|
||
|
exit(0);
|
||
|
|
||
|
} 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);
|
||
|
}
|
||
|
|
||
|
// 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 = $options['fromrun']; $i <= $options['torun']; $i++) {
|
||
|
if (empty($CFG->behat_parallel_run[$i - 1]['behat_wwwroot'])) {
|
||
|
echo $CFG->behat_wwwroot . "/" . BEHAT_PARALLEL_SITE_NAME . $i . PHP_EOL;
|
||
|
} else {
|
||
|
echo $CFG->behat_parallel_run[$i - 1]['behat_wwwroot'] . 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);
|
||
|
|
||
|
// Save fromrun and to run information.
|
||
|
if (isset($options['fromrun'])) {
|
||
|
behat_config_manager::set_behat_run_config_value('fromrun', $options['fromrun']);
|
||
|
}
|
||
|
|
||
|
if (isset($options['torun'])) {
|
||
|
behat_config_manager::set_behat_run_config_value('torun', $options['torun']);
|
||
|
}
|
||
|
if (isset($options['parallel'])) {
|
||
|
behat_config_manager::set_behat_run_config_value('parallel', $options['parallel']);
|
||
|
}
|
||
|
|
||
|
echo PHP_EOL;
|
||
|
|
||
|
} else if ($options['disable']) {
|
||
|
echo "Acceptance tests environment disabled for " . $options['parallel'] . " parallel sites" . PHP_EOL;
|
||
|
|
||
|
} else if ($options['diag']) {
|
||
|
// Valid option, so nothing to do.
|
||
|
} else {
|
||
|
echo $help;
|
||
|
chdir($cwd);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
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', 'fromrun', 'torun');
|
||
|
$cmds = array();
|
||
|
$extraoptions = $options;
|
||
|
$extra = "";
|
||
|
|
||
|
// Remove extra options not in util_single_run.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\"";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (empty($options['parallel'])) {
|
||
|
$cmds = "php util_single_run.php " . $extra;
|
||
|
} else {
|
||
|
// Create commands which has to be executed for parallel site.
|
||
|
for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
|
||
|
$prefix = BEHAT_PARALLEL_SITE_NAME . $i;
|
||
|
$cmds[$prefix] = "php util_single_run.php " . $extra . " --run=" . $i . " 2>&1";
|
||
|
}
|
||
|
}
|
||
|
return $cmds;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print drop output merging each run.
|
||
|
*
|
||
|
* @param array $processes list of processes.
|
||
|
* @return array exit codes of each process.
|
||
|
*/
|
||
|
function print_combined_drop_output($processes) {
|
||
|
$exitcodes = array();
|
||
|
$maxdotsonline = 70;
|
||
|
$remainingprintlen = $maxdotsonline;
|
||
|
$progresscount = 0;
|
||
|
echo "Dropping tables:" . PHP_EOL;
|
||
|
|
||
|
while (count($exitcodes) != count($processes)) {
|
||
|
usleep(10000);
|
||
|
foreach ($processes as $name => $process) {
|
||
|
if ($process->isRunning()) {
|
||
|
$op = $process->getIncrementalOutput();
|
||
|
if (trim($op)) {
|
||
|
$update = preg_filter('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $op);
|
||
|
$strlentoprint = strlen($update);
|
||
|
|
||
|
// If not enough dots printed on line then just print.
|
||
|
if ($strlentoprint < $remainingprintlen) {
|
||
|
echo $update;
|
||
|
$remainingprintlen = $remainingprintlen - $strlentoprint;
|
||
|
} else if ($strlentoprint == $remainingprintlen) {
|
||
|
$progresscount += $maxdotsonline;
|
||
|
echo $update . " " . $progresscount . PHP_EOL;
|
||
|
$remainingprintlen = $maxdotsonline;
|
||
|
} else {
|
||
|
while ($part = substr($update, 0, $remainingprintlen) > 0) {
|
||
|
$progresscount += $maxdotsonline;
|
||
|
echo $part . " " . $progresscount . PHP_EOL;
|
||
|
$update = substr($update, $remainingprintlen);
|
||
|
$remainingprintlen = $maxdotsonline;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// Process exited.
|
||
|
$process->clearOutput();
|
||
|
$exitcodes[$name] = $process->getExitCode();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
echo PHP_EOL;
|
||
|
return $exitcodes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print install output merging each run.
|
||
|
*
|
||
|
* @param array $processes list of processes.
|
||
|
* @return array exit codes of each process.
|
||
|
*/
|
||
|
function print_combined_install_output($processes) {
|
||
|
$exitcodes = array();
|
||
|
$line = array();
|
||
|
|
||
|
// Check what best we can do to accommodate all parallel run o/p on single line.
|
||
|
// Windows command line has length of 80 chars, so default we will try fit o/p in 80 chars.
|
||
|
if (defined('BEHAT_MAX_CMD_LINE_OUTPUT') && BEHAT_MAX_CMD_LINE_OUTPUT) {
|
||
|
$lengthofprocessline = (int)max(10, BEHAT_MAX_CMD_LINE_OUTPUT / count($processes));
|
||
|
} else {
|
||
|
$lengthofprocessline = (int)max(10, 80 / count($processes));
|
||
|
}
|
||
|
|
||
|
echo "Installing behat site for " . count($processes) . " parallel behat run" . PHP_EOL;
|
||
|
|
||
|
// Show process name in first row.
|
||
|
foreach ($processes as $name => $process) {
|
||
|
// If we don't have enough space to show full run name then show runX.
|
||
|
if ($lengthofprocessline < strlen($name) + 2) {
|
||
|
$name = substr($name, -5);
|
||
|
}
|
||
|
// One extra padding as we are adding | separator for rest of the data.
|
||
|
$line[$name] = str_pad('[' . $name . '] ', $lengthofprocessline + 1);
|
||
|
}
|
||
|
ksort($line);
|
||
|
$tableheader = array_keys($line);
|
||
|
echo implode("", $line) . PHP_EOL;
|
||
|
|
||
|
// Now print o/p from each process.
|
||
|
while (count($exitcodes) != count($processes)) {
|
||
|
usleep(50000);
|
||
|
$poutput = array();
|
||
|
// Create child process.
|
||
|
foreach ($processes as $name => $process) {
|
||
|
if ($process->isRunning()) {
|
||
|
$output = $process->getIncrementalOutput();
|
||
|
if (trim($output)) {
|
||
|
$poutput[$name] = explode(PHP_EOL, $output);
|
||
|
}
|
||
|
} else {
|
||
|
// Process exited.
|
||
|
$exitcodes[$name] = $process->getExitCode();
|
||
|
}
|
||
|
}
|
||
|
ksort($poutput);
|
||
|
|
||
|
// Get max depth of o/p before displaying.
|
||
|
$maxdepth = 0;
|
||
|
foreach ($poutput as $pout) {
|
||
|
$pdepth = count($pout);
|
||
|
$maxdepth = $pdepth >= $maxdepth ? $pdepth : $maxdepth;
|
||
|
}
|
||
|
|
||
|
// Iterate over each process to get line to print.
|
||
|
for ($i = 0; $i <= $maxdepth; $i++) {
|
||
|
$pline = "";
|
||
|
foreach ($tableheader as $name) {
|
||
|
$po = empty($poutput[$name][$i]) ? "" : substr($poutput[$name][$i], 0, $lengthofprocessline - 1);
|
||
|
$po = str_pad($po, $lengthofprocessline);
|
||
|
$pline .= "|". $po;
|
||
|
}
|
||
|
if (trim(str_replace("|", "", $pline))) {
|
||
|
echo $pline . PHP_EOL;
|
||
|
}
|
||
|
}
|
||
|
unset($poutput);
|
||
|
$poutput = null;
|
||
|
|
||
|
}
|
||
|
echo PHP_EOL;
|
||
|
return $exitcodes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print install output merging showing one run at a time.
|
||
|
* If any process fail then exit.
|
||
|
*
|
||
|
* @param array $processes list of processes.
|
||
|
* @param bool $showprefix show prefix.
|
||
|
* @return bool exitcode.
|
||
|
*/
|
||
|
function print_sequential_output($processes, $showprefix = true) {
|
||
|
$status = false;
|
||
|
foreach ($processes as $name => $process) {
|
||
|
$shownname = false;
|
||
|
while ($process->isRunning()) {
|
||
|
$op = $process->getIncrementalOutput();
|
||
|
if (trim($op)) {
|
||
|
// Show name of the run once for sequential.
|
||
|
if ($showprefix && !$shownname) {
|
||
|
echo '[' . $name . '] ';
|
||
|
$shownname = true;
|
||
|
}
|
||
|
echo $op;
|
||
|
}
|
||
|
}
|
||
|
// If any error then exit.
|
||
|
$exitcode = $process->getExitCode();
|
||
|
if ($exitcode != 0) {
|
||
|
exit($exitcode);
|
||
|
}
|
||
|
$status = $status || (bool)$exitcode;
|
||
|
}
|
||
|
return $status;
|
||
|
}
|