Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?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 environmentUsage: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 helpExample from Moodle root directory:\$ php admin/tool/behat/cli/util.php --enable --parallel=4More 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;}