first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+206
View File
@@ -0,0 +1,206 @@
<?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 script to set up all the behat test environment.
*
* @package tool_behat
* @copyright 2013 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!
}
// Force OPcache reset if used, we do not want any stale caches
// when preparing test environment.
if (function_exists('opcache_reset')) {
opcache_reset();
}
// Is not really necessary but adding it as is a CLI_SCRIPT.
define('CLI_SCRIPT', true);
define('CACHE_DISABLE_ALL', true);
// Basic functions.
require_once(__DIR__ . '/../../../../lib/clilib.php');
require_once(__DIR__ . '/../../../../lib/behat/lib.php');
list($options, $unrecognized) = cli_get_params(
array(
'parallel' => 0,
'maxruns' => false,
'help' => false,
'fromrun' => 1,
'torun' => 0,
'optimize-runs' => '',
'add-core-features-to-theme' => false,
'axe' => null,
'disable-composer' => false,
'composer-upgrade' => true,
'composer-self-update' => true,
'scss-deprecations' => false,
),
array(
'j' => 'parallel',
'm' => 'maxruns',
'h' => 'help',
'o' => 'optimize-runs',
'a' => 'add-core-features-to-theme',
)
);
// Checking run.php CLI script usage.
$help = "
Behat utilities to initialise behat tests
Usage:
php init.php [--parallel=value [--maxruns=value] [--fromrun=value --torun=value]]
[--no-axe] [--scss-deprecations] [-o | --optimize-runs] [-a | --add-core-features-to-theme]
[--no-composer-self-update] [--no-composer-upgrade]
[--disable-composer]
[--help]
Options:
-j, --parallel Number of parallel behat run to initialise
-m, --maxruns Max parallel processes to be executed at one time
--fromrun Execute run starting from (Used for parallel runs on different vms)
--torun Execute run till (Used for parallel runs on different vms)
--no-axe Disable axe accessibility tests.
--scss-deprecations Enable SCSS deprecation checks.
-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
--no-composer-self-update
Prevent upgrade of the composer utility using its self-update command
--no-composer-upgrade
Prevent update development dependencies using composer
--disable-composer
A shortcut to disable composer self-update and dependency update
Note: Installation of composer and/or dependencies will still happen as required
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/behat/cli/init.php --parallel=2
More info in https://moodledev.io/general/development/tools/behat/running
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
if ($options['axe']) {
echo "Axe accessibility tests are enabled by default, to disable them, use the --no-axe option.\n";
} else if ($options['axe'] === false) {
echo "Axe accessibility tests have been disabled.\n";
}
// Check which util file to call.
$utilfile = 'util_single_run.php';
$commandoptions = "";
// If parallel run then use utilparallel.
if ($options['parallel'] && $options['parallel'] > 1) {
$utilfile = 'util.php';
// Sanitize all input options, so they can be passed to util.
foreach ($options as $option => $value) {
$commandoptions .= behat_get_command_flags($option, $value);
}
} else {
// Only sanitize options for single run.
$cmdoptionsforsinglerun = [
'add-core-features-to-theme',
'axe',
'scss-deprecations',
];
foreach ($cmdoptionsforsinglerun as $option) {
$commandoptions .= behat_get_command_flags($option, $options[$option]);
}
}
// Changing the cwd to admin/tool/behat/cli.
$cwd = getcwd();
$output = null;
if ($options['disable-composer']) {
// Disable self-update and upgrade easily.
// Note: Installation will still occur regardless of this setting.
$options['composer-self-update'] = false;
$options['composer-upgrade'] = false;
}
// Install and update composer and dependencies as required.
testing_update_composer_dependencies($options['composer-self-update'], $options['composer-upgrade']);
// Check whether the behat test environment needs to be updated.
chdir(__DIR__);
exec("php $utilfile --diag $commandoptions", $output, $code);
if ($code == 0) {
echo "Behat test environment already installed\n";
} else if ($code == BEHAT_EXITCODE_INSTALL) {
// Behat and dependencies are installed and we need to install the test site.
chdir(__DIR__);
passthru("php $utilfile --install $commandoptions", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
} else if ($code == BEHAT_EXITCODE_REINSTALL) {
// Test site data is outdated.
chdir(__DIR__);
passthru("php $utilfile --drop $commandoptions", $code);
if ($code != 0) {
chdir($cwd);
exit($code);
}
chdir(__DIR__);
passthru("php $utilfile --install $commandoptions", $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.
chdir(__DIR__);
passthru("php $utilfile --enable $commandoptions", $code);
if ($code != 0) {
echo "Error enabling site" . PHP_EOL;
chdir($cwd);
exit($code);
}
exit(0);
+524
View File
@@ -0,0 +1,524 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Wrapper to run previously set-up behat tests in parallel.
*
* @package tool_behat
* @copyright 2014 NetSpot Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (isset($_SERVER['REMOTE_ADDR'])) {
die(); // No access from web!
}
define('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');
list($options, $unrecognised) = cli_get_params(
array(
'stop-on-failure' => 0,
'verbose' => false,
'replace' => '',
'help' => false,
'tags' => '',
'profile' => '',
'feature' => '',
'suite' => '',
'fromrun' => 1,
'torun' => 0,
'single-run' => false,
'rerun' => 0,
'auto-rerun' => 0,
),
array(
'h' => 'help',
't' => 'tags',
'p' => 'profile',
's' => 'single-run',
)
);
// Checking run.php CLI script usage.
$help = "
Behat utilities to run behat tests in parallel
Usage:
php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace=\"{run}\"] [--fromrun=value --torun=value] [--help]
Options:
--BEHAT_OPTION Any combination of behat option specified in http://behat.readthedocs.org/en/v2.5/guides/6.cli.html
--feature Only execute specified feature file (Absolute path of feature file).
--suite Specified theme scenarios will be executed.
--replace Replace args string with run process number, useful for output.
--fromrun Execute run starting from (Used for parallel runs on different vms)
--torun Execute run till (Used for parallel runs on different vms)
--rerun Re-run scenarios that failed during last execution.
--auto-rerun Automatically re-run scenarios that failed during last execution.
-h, --help Print out this help
Example from Moodle root directory:
\$ php admin/tool/behat/cli/run.php --tags=\"@javascript\"
More info in https://moodledev.io/general/development/tools/behat/running
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
$parallelrun = behat_config_manager::get_behat_run_config_value('parallel');
// Check if the options provided are valid to run behat.
if ($parallelrun === false) {
// Parallel run should not have fromrun or torun options greater than 1.
if (($options['fromrun'] > 1) || ($options['torun'] > 1)) {
echo "Test site is not initialized for parallel run." . PHP_EOL;
exit(1);
}
} else {
// Ensure fromrun is within limits of initialized test site.
if (!empty($options['fromrun']) && ($options['fromrun'] > $parallelrun)) {
echo "From run (" . $options['fromrun'] . ") is more than site with parallel runs (" . $parallelrun . ")" . PHP_EOL;
exit(1);
}
// Default torun is maximum parallel runs and should be less than equal to parallelruns.
if (empty($options['torun'])) {
$options['torun'] = $parallelrun;
} else {
if ($options['torun'] > $parallelrun) {
echo "To run (" . $options['torun'] . ") is more than site with parallel runs (" . $parallelrun . ")" . PHP_EOL;
exit(1);
}
}
}
// Capture signals and ensure we clean symlinks.
if (extension_loaded('pcntl')) {
$disabled = explode(',', ini_get('disable_functions'));
if (!in_array('pcntl_signal', $disabled)) {
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGINT, "signal_handler");
}
}
$time = microtime(true);
array_walk($unrecognised, function (&$v) {
if ($x = preg_filter("#^(-+\w+)=(.+)#", "\$1=\"\$2\"", $v)) {
$v = $x;
} else if (!preg_match("#^-#", $v)) {
$v = escapeshellarg($v);
}
});
$extraopts = $unrecognised;
if ($options['profile']) {
$profile = $options['profile'];
// If profile passed is not set, then exit (note we skip if the 'replace' option is found within the 'profile' value).
if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
!($options['replace'] && (strpos($profile, (string) $options['replace']) !== false))) {
echo "Invalid profile passed: " . $profile . PHP_EOL;
exit(1);
}
$extraopts['profile'] = '--profile="' . $profile . '"';
// By default, profile tags will be used.
if (!empty($CFG->behat_config[$profile]['filters']['tags'])) {
$tags = $CFG->behat_config[$profile]['filters']['tags'];
}
}
// Command line tags have precedence (std behat behavior).
if ($options['tags']) {
$tags = $options['tags'];
$extraopts['tags'] = '--tags="' . $tags . '"';
}
// Add suite option if specified.
if ($options['suite']) {
$extraopts['suite'] = '--suite="' . $options['suite'] . '"';
}
// Feature should be added to last, for behat command.
if ($options['feature']) {
$extraopts['feature'] = $options['feature'];
// Only run 1 process as process.
// Feature file is picked from absolute path provided, so no need to check for behat.yml.
$options['torun'] = $options['fromrun'];
}
// Set of options to pass to behat.
$extraoptstr = implode(' ', $extraopts);
// If rerun is passed then ensure we just run the failed processes.
$lastfailedstatus = 0;
$lasttorun = $options['torun'];
$lastfromrun = $options['fromrun'];
if ($options['rerun']) {
// Get last combined failed status.
$lastfailedstatus = behat_config_manager::get_behat_run_config_value('lastcombinedfailedstatus');
$lasttorun = behat_config_manager::get_behat_run_config_value('lasttorun');
$lastfromrun = behat_config_manager::get_behat_run_config_value('lastfromrun');
if ($lastfailedstatus !== false) {
$extraoptstr .= ' --rerun';
}
// If torun is less than last torun, then just set this to min last to run and similar for fromrun.
if ($options['torun'] < $lasttorun) {
$options['torun'];
}
if ($options['fromrun'] > $lastfromrun) {
$options['fromrun'];
}
unset($options['rerun']);
}
$cmds = array();
$exitcodes = array();
$status = 0;
$verbose = empty($options['verbose']) ? false : true;
// Execute behat run commands.
if (empty($parallelrun)) {
$cwd = getcwd();
chdir(__DIR__);
$runtestscommand = behat_command::get_behat_command(false, false, true);
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
$runtestscommand .= ' ' . $extraoptstr;
$cmds['singlerun'] = $runtestscommand;
echo "Running single behat site:" . PHP_EOL;
passthru("php $runtestscommand", $status);
$exitcodes['singlerun'] = $status;
chdir($cwd);
} else {
echo "Running " . ($options['torun'] - $options['fromrun'] + 1) . " parallel behat sites:" . PHP_EOL;
for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
$lastfailed = 1 & $lastfailedstatus >> ($i - 1);
// Bypass if not failed in last run.
if ($lastfailedstatus && !$lastfailed && ($i <= $lasttorun) && ($i >= $lastfromrun)) {
continue;
}
$CFG->behatrunprocess = $i;
// Options parameters to be added to each run.
$myopts = !empty($options['replace']) ? str_replace($options['replace'], $i, $extraoptstr) : $extraoptstr;
$behatcommand = behat_command::get_behat_command(false, false, true);
$behatconfigpath = behat_config_manager::get_behat_cli_config_filepath($i);
// Command to execute behat run.
$cmds[BEHAT_PARALLEL_SITE_NAME . $i] = $behatcommand . ' --config ' . $behatconfigpath . " " . $myopts;
echo "[" . BEHAT_PARALLEL_SITE_NAME . $i . "] " . $cmds[BEHAT_PARALLEL_SITE_NAME . $i] . PHP_EOL;
}
if (empty($cmds)) {
echo "No commands to execute " . PHP_EOL;
exit(1);
}
// Create site symlink if necessary.
if (!behat_config_manager::create_parallel_site_links($options['fromrun'], $options['torun'])) {
echo "Check permissions. If on windows, make sure you are running this command as admin" . PHP_EOL;
exit(1);
}
// Save torun and from run, so it can be used to detect if it was executed in last run.
behat_config_manager::set_behat_run_config_value('lasttorun', $options['torun']);
behat_config_manager::set_behat_run_config_value('lastfromrun', $options['fromrun']);
// Keep no delay by default, between each parallel, let user decide.
if (!defined('BEHAT_PARALLEL_START_DELAY')) {
define('BEHAT_PARALLEL_START_DELAY', 0);
}
// Execute all commands, relative to moodle root directory.
$processes = cli_execute_parallel($cmds, __DIR__ . "/../../../../", BEHAT_PARALLEL_START_DELAY);
$stoponfail = empty($options['stop-on-failure']) ? false : true;
// Print header.
print_process_start_info($processes);
// Print combined run o/p from processes.
$exitcodes = print_combined_run_output($processes, $stoponfail);
// Time to finish run.
$time = round(microtime(true) - $time, 0);
echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
ksort($exitcodes);
// Print exit info from each run.
// Status bits contains pass/fail status of parallel runs.
foreach ($exitcodes as $name => $exitcode) {
if ($exitcode) {
$runno = str_replace(BEHAT_PARALLEL_SITE_NAME, '', $name);
$status |= (1 << ($runno - 1));
}
}
// Print each process information.
print_each_process_info($processes, $verbose, $status);
}
// Save final exit code containing which run failed.
behat_config_manager::set_behat_run_config_value('lastcombinedfailedstatus', $status);
// Show exit code from each process, if any process failed and how to rerun failed process.
if ($verbose || $status) {
// Check if status of last run is failure and rerun is suggested.
if (!empty($options['auto-rerun']) && $status) {
// Rerun for the number of tries passed.
for ($i = 0; $i < $options['auto-rerun']; $i++) {
// Run individual commands, to avoid parallel failures.
foreach ($exitcodes as $behatrunname => $exitcode) {
// If not failed in last run, then skip.
if ($exitcode == 0) {
continue;
}
// This was a failure.
echo "*** Re-running behat run: $behatrunname ***" . PHP_EOL;
if ($verbose) {
echo "Executing: " . $cmds[$behatrunname] . " --rerun" . PHP_EOL;
}
passthru("php $cmds[$behatrunname] --rerun", $rerunstatus);
// Update exit code.
$exitcodes[$behatrunname] = $rerunstatus;
}
}
// Update status after auto-rerun finished.
$status = 0;
foreach ($exitcodes as $name => $exitcode) {
if ($exitcode) {
if (!empty($parallelrun)) {
$runno = str_replace(BEHAT_PARALLEL_SITE_NAME, '', $name);
} else {
$runno = 1;
}
$status |= (1 << ($runno - 1));
}
}
}
// Show final o/p with re-run commands.
if ($status) {
if (!empty($parallelrun)) {
// Echo exit codes.
echo "Exit codes for each behat run: " . PHP_EOL;
foreach ($exitcodes as $run => $exitcode) {
echo $run . ": " . $exitcode . PHP_EOL;
}
unset($extraopts['fromrun']);
unset($extraopts['torun']);
if (!empty($options['replace'])) {
$extraopts['replace'] = '--replace="' . $options['replace'] . '"';
}
}
echo "To re-run failed processes, you can use following command:" . PHP_EOL;
$extraopts['rerun'] = '--rerun';
$extraoptstr = implode(' ', $extraopts);
echo behat_command::get_behat_command(true, true, true) . " " . $extraoptstr . PHP_EOL;
}
echo PHP_EOL;
}
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
exit($status);
/**
* Signal handler for terminal exit.
*
* @param int $signal signal number.
*/
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);
}
}
/**
* Prints header from the first process.
*
* @param array $processes list of processes to loop though.
*/
function print_process_start_info($processes) {
$printed = false;
// Keep looping though processes, till we get first process o/p.
while (!$printed) {
usleep(10000);
foreach ($processes as $name => $process) {
// Exit if any process has stopped.
if (!$process->isRunning()) {
$printed = true;
break;
}
$op = explode(PHP_EOL, $process->getOutput());
if (count($op) >= 3) {
foreach ($op as $line) {
if (trim($line) && (strpos($line, '.') !== 0)) {
echo $line . PHP_EOL;
}
}
$printed = true;
}
}
}
}
/**
* Loop though all processes and print combined o/p
*
* @param array $processes list of processes to loop though.
* @param bool $stoponfail Stop all processes and exit if failed.
* @return array list of exit codes from all processes.
*/
function print_combined_run_output($processes, $stoponfail = false) {
$exitcodes = array();
$maxdotsonline = 70;
$remainingprintlen = $maxdotsonline;
$progresscount = 0;
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);
// Exit process if anything fails.
if ($stoponfail && (strpos($update, 'F') !== false)) {
$process->stop(0);
}
$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 {
$exitcodes[$name] = $process->getExitCode();
if ($stoponfail && ($exitcodes[$name] != 0)) {
foreach ($processes as $l => $p) {
$exitcodes[$l] = -1;
$process->stop(0);
}
}
}
}
}
echo PHP_EOL;
return $exitcodes;
}
/**
* Loop though all processes and print combined o/p
*
* @param array $processes list of processes to loop though.
* @param bool $verbose Show verbose output for each process.
*/
function print_each_process_info($processes, $verbose = false, $status = 0) {
foreach ($processes as $name => $process) {
echo "**************** [" . $name . "] ****************" . PHP_EOL;
if ($verbose) {
echo $process->getOutput();
echo $process->getErrorOutput();
} else if ($status) {
// Only show failed o/p.
$runno = str_replace(BEHAT_PARALLEL_SITE_NAME, '', $name);
if ((1 << ($runno - 1)) & $status) {
echo $process->getOutput();
echo $process->getErrorOutput();
} else {
echo get_status_lines_from_run_op($process);
}
} else {
echo get_status_lines_from_run_op($process);
}
echo PHP_EOL;
}
}
/**
* Extract status information from behat o/p and return.
* @param Symfony\Component\Process\Process $process
* @return string
*/
function get_status_lines_from_run_op(Symfony\Component\Process\Process $process) {
$statusstr = '';
$op = explode(PHP_EOL, $process->getOutput());
foreach ($op as $line) {
// Don't print progress .
if (trim($line) && (strpos($line, '.') !== 0) && (strpos($line, 'Moodle ') !== 0) &&
(strpos($line, 'Server OS ') !== 0) && (strpos($line, 'Started at ') !== 0) &&
(strpos($line, 'Browser specific fixes ') !== 0)) {
$statusstr .= $line . PHP_EOL;
}
}
return $statusstr;
}
+501
View File
@@ -0,0 +1,501 @@
<?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
* Same applies for $CFG->behat_dbname, $CFG->behat_dbuser, $CFG->behat_dbpass
* and $CFG->behat_dbhost. But if any of those is not defined $CFG->dbname,
* $CFG->dbuser, $CFG->dbpass and/or $CFG->dbhost will be used.
*
* @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,
'axe' => true,
'scss-deprecations' => 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|--no-axe|--scss-deprecations|--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.
--no-axe Disable axe accessibility tests.
--scss-deprecations Enable SCSS deprecation checks.
-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 https://moodledev.io/general/development/tools/behat/running
";
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) {
$extra .= behat_get_command_flags($option, $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 = $update ? strlen($update) : 0;
// 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;
}
+315
View File
@@ -0,0 +1,315 @@
<?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 Behat integration in Moodle
*
* All CLI utilities uses $CFG->behat_dataroot and $CFG->prefix_dataroot as
* $CFG->dataroot and $CFG->prefix
* Same applies for $CFG->behat_dbname, $CFG->behat_dbuser, $CFG->behat_dbpass
* and $CFG->behat_dbhost. But if any of those is not defined $CFG->dbname,
* $CFG->dbuser, $CFG->dbpass and/or $CFG->dbhost will be used.
*
* @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!.
}
// Basic functions.
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,
'run' => 0,
'drop' => false,
'enable' => false,
'disable' => false,
'diag' => false,
'tags' => '',
'updatesteps' => false,
'optimize-runs' => '',
'add-core-features-to-theme' => false,
'axe' => true,
'scss-deprecations' => false,
),
array(
'h' => 'help',
'o' => 'optimize-runs',
'a' => 'add-core-features-to-theme',
)
);
if ($options['install'] or $options['drop']) {
define('CACHE_DISABLE_ALL', true);
}
// Checking util_single_run.php CLI script usage.
$help = "
Behat utilities to manage the test environment
Usage:
php util_single_run.php [--install|--drop|--enable|--disable|--diag|--updatesteps|--help]
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.
--no-axe Disable axe accessibility tests.
--scss-deprecations Enable SCSS deprecation checks.
-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_single_run.php --enable
More info in https://moodledev.io/general/development/tools/behat/running
";
if (!empty($options['help'])) {
echo $help;
exit(0);
}
// Describe this script.
define('BEHAT_UTIL', true);
define('CLI_SCRIPT', true);
define('NO_OUTPUT_BUFFERING', true);
define('IGNORE_COMPONENT_CACHE', true);
// 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);
require_once(__DIR__ . '/../../../../config.php');
// Remove error handling overrides done in config.php.
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;
error_reporting($CFG->debug);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
// Finish moodle init.
define('ABORT_AFTER_CONFIG_CANCEL', true);
require("$CFG->dirroot/lib/setup.php");
raise_memory_limit(MEMORY_HUGE);
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/upgradelib.php');
require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/installlib.php');
require_once($CFG->libdir.'/testing/classes/test_lock.php');
if ($unrecognized) {
$unrecognized = implode(PHP_EOL . " ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $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.
$run = 0;
$parallel = 0;
if ($options['run']) {
$run = $options['run'];
// If parallel option is not passed, then try get it form config.
if (!$options['parallel']) {
$parallel = behat_config_manager::get_behat_run_config_value('parallel');
} else {
$parallel = $options['parallel'];
}
if (empty($parallel) || $run > $parallel) {
echo "Parallel runs can't be more then ".$parallel.PHP_EOL;
exit(1);
}
$CFG->behatrunprocess = $run;
}
// Run command (only one per time).
if ($options['install']) {
behat_util::install_site();
// This is only displayed once for parallel install.
if (empty($run)) {
mtrace("Acceptance tests site installed");
}
// Note: Do not build the themes here. This is done during the 'enable' stage.
} else if ($options['drop']) {
// Ensure no tests are running.
test_lock::acquire('behat');
behat_util::drop_site();
// This is only displayed once for parallel install.
if (empty($run)) {
mtrace("Acceptance tests site dropped");
}
} else if ($options['enable']) {
if (!empty($parallel)) {
// Save parallel site info for enable and install options.
behat_config_manager::set_behat_run_config_value('behatsiteenabled', 1);
}
// Configure axe according to option.
behat_config_manager::set_behat_run_config_value('axe', $options['axe']);
// Define whether to run Behat with SCSS deprecation checks.
behat_config_manager::set_behat_run_config_value('scss-deprecations', $options['scss-deprecations']);
// Enable test mode.
$timestart = microtime(true);
mtrace('Creating Behat configuration ...', '');
behat_util::start_test_mode($options['add-core-features-to-theme'], $options['optimize-runs'], $parallel, $run);
mtrace(' done in ' . round(microtime(true) - $timestart, 2) . ' seconds.');
// Themes are only built in the 'enable' command.
behat_util::build_themes(true);
mtrace("Testing environment themes built");
// This is only displayed once for parallel install.
if (empty($run)) {
// Notify user that 2.5 profile has been converted to 3.5.
if (behat_config_manager::$autoprofileconversion) {
mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
}
$runtestscommand = behat_command::get_behat_command(true, !empty($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: " . PHP_EOL .
$runtestscommand);
}
} else if ($options['disable']) {
behat_util::stop_test_mode($run);
// This is only displayed once for parallel install.
if (empty($run)) {
mtrace("Acceptance tests environment disabled");
}
} else if ($options['diag']) {
$code = behat_util::get_behat_status();
exit($code);
} else if ($options['updatesteps']) {
if (defined('BEHAT_FEATURE_STEP_FILE') && BEHAT_FEATURE_STEP_FILE) {
$behatstepfile = BEHAT_FEATURE_STEP_FILE;
} else {
echo "BEHAT_FEATURE_STEP_FILE is not set, please ensure you set this to writable file" . PHP_EOL;
exit(1);
}
// Run behat command to get steps in feature files.
$featurestepscmd = behat_command::get_behat_command(true);
$featurestepscmd .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
$featurestepscmd .= ' --dry-run --format=moodle_stepcount';
$processes = cli_execute_parallel(array($featurestepscmd), __DIR__ . "/../../../../");
$status = print_update_step_output(array_pop($processes), $behatstepfile);
exit($status);
} else {
echo $help;
exit(1);
}
exit(0);
/**
* Print update progress as dots for updating feature file step list.
*
* @param Process $process process executing update step command.
* @param string $featurestepfile feature step file in which steps will be saved.
* @return int exitcode.
*/
function print_update_step_output($process, $featurestepfile) {
$printedlength = 0;
echo "Updating steps feature file for parallel behat runs" . PHP_EOL;
// Show progress while running command.
while ($process->isRunning()) {
usleep(10000);
$op = $process->getIncrementalOutput();
if (trim($op)) {
echo ".";
$printedlength++;
if ($printedlength > 70) {
$printedlength = 0;
echo PHP_EOL;
}
}
}
// If any error then exit.
$exitcode = $process->getExitCode();
// Output err.
if ($exitcode != 0) {
echo $process->getErrorOutput();
exit($exitcode);
}
// Extract features with step info and save it in file.
$featuresteps = $process->getOutput();
$featuresteps = explode(PHP_EOL, $featuresteps);
$realroot = realpath(__DIR__.'/../../../../').'/';
foreach ($featuresteps as $featurestep) {
if (trim($featurestep)) {
$step = explode("::", $featurestep);
$step[0] = str_replace($realroot, '', $step[0]);
$steps[$step[0]] = $step[1];
}
}
if ($existing = @json_decode(file_get_contents($featurestepfile), true)) {
$steps = array_merge($existing, $steps);
}
arsort($steps);
if (!@file_put_contents($featurestepfile, json_encode($steps, JSON_PRETTY_PRINT))) {
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $featurestepfile . ' can not be created');
$exitcode = -1;
}
echo PHP_EOL. "Updated step count in " . $featurestepfile . PHP_EOL;
return $exitcode;
}