See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * CLI tool with utilities to manage parallel Behat integration in Moodle 19 * 20 * All CLI utilities uses $CFG->behat_dataroot and $CFG->prefix_dataroot as 21 * $CFG->dataroot and $CFG->prefix 22 * Same applies for $CFG->behat_dbname, $CFG->behat_dbuser, $CFG->behat_dbpass 23 * and $CFG->behat_dbhost. But if any of those is not defined $CFG->dbname, 24 * $CFG->dbuser, $CFG->dbpass and/or $CFG->dbhost will be used. 25 * 26 * @package tool_behat 27 * @copyright 2012 David MonllaĆ³ 28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 29 */ 30 31 32 if (isset($_SERVER['REMOTE_ADDR'])) { 33 die(); // No access from web!. 34 } 35 36 define('BEHAT_UTIL', true); 37 define('CLI_SCRIPT', true); 38 define('NO_OUTPUT_BUFFERING', true); 39 define('IGNORE_COMPONENT_CACHE', true); 40 define('ABORT_AFTER_CONFIG', true); 41 42 require_once (__DIR__ . '/../../../../lib/clilib.php'); 43 44 // CLI options. 45 list($options, $unrecognized) = cli_get_params( 46 array( 47 'help' => false, 48 'install' => false, 49 'drop' => false, 50 'enable' => false, 51 'disable' => false, 52 'diag' => false, 53 'parallel' => 0, 54 'maxruns' => false, 55 'updatesteps' => false, 56 'fromrun' => 1, 57 'torun' => 0, 58 'optimize-runs' => '', 59 'add-core-features-to-theme' => false, 60 'axe' => true, 61 ), 62 array( 63 'h' => 'help', 64 'j' => 'parallel', 65 'm' => 'maxruns', 66 'o' => 'optimize-runs', 67 'a' => 'add-core-features-to-theme', 68 ) 69 ); 70 71 // Checking util.php CLI script usage. 72 $help = " 73 Behat utilities to manage the test environment 74 75 Usage: 76 php util.php [--install|--drop|--enable|--disable|--diag|--updatesteps|--no-axe|--help] [--parallel=value [--maxruns=value]] 77 78 Options: 79 --install Installs the test environment for acceptance tests 80 --drop Drops the database tables and the dataroot contents 81 --enable Enables test environment and updates tests list 82 --disable Disables test environment 83 --diag Get behat test environment status code 84 --updatesteps Update feature step file. 85 --no-axe Disable axe accessibility tests. 86 87 -j, --parallel Number of parallel behat run operation 88 -m, --maxruns Max parallel processes to be executed at one time. 89 -o, --optimize-runs Split features with specified tags in all parallel runs. 90 -a, --add-core-features-to-theme Add all core features to specified theme's 91 92 -h, --help Print out this help 93 94 Example from Moodle root directory: 95 \$ php admin/tool/behat/cli/util.php --enable --parallel=4 96 97 More info in https://moodledev.io/general/development/tools/behat/running 98 "; 99 100 if (!empty($options['help'])) { 101 echo $help; 102 exit(0); 103 } 104 105 $cwd = getcwd(); 106 107 // If Behat parallel site is being initiliased, then define a param to be used to ignore single run install. 108 if (!empty($options['parallel'])) { 109 define('BEHAT_PARALLEL_UTIL', true); 110 } 111 112 require_once(__DIR__ . '/../../../../config.php'); 113 require_once (__DIR__ . '/../../../../lib/behat/lib.php'); 114 require_once (__DIR__ . '/../../../../lib/behat/classes/behat_command.php'); 115 require_once (__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php'); 116 117 // Remove error handling overrides done in config.php. This is consistent with admin/tool/behat/cli/util_single_run.php. 118 $CFG->debug = (E_ALL | E_STRICT); 119 $CFG->debugdisplay = 1; 120 error_reporting($CFG->debug); 121 ini_set('display_errors', '1'); 122 ini_set('log_errors', '1'); 123 124 // Import the necessary libraries. 125 require_once($CFG->libdir . '/setuplib.php'); 126 require_once($CFG->libdir . '/behat/classes/util.php'); 127 128 // For drop option check if parallel site. 129 if ((empty($options['parallel'])) && ($options['drop']) || $options['updatesteps']) { 130 $options['parallel'] = behat_config_manager::get_behat_run_config_value('parallel'); 131 } 132 133 // If not a parallel site then open single run. 134 if (empty($options['parallel'])) { 135 // Set run config value for single run. 136 behat_config_manager::set_behat_run_config_value('singlerun', 1); 137 138 chdir(__DIR__); 139 // Check if behat is initialised, if not exit. 140 passthru("php util_single_run.php --diag", $status); 141 if ($status) { 142 exit ($status); 143 } 144 $cmd = commands_to_execute($options); 145 $processes = cli_execute_parallel(array($cmd), __DIR__); 146 $status = print_sequential_output($processes, false); 147 chdir($cwd); 148 exit($status); 149 } 150 151 // Default torun is maximum parallel runs. 152 if (empty($options['torun'])) { 153 $options['torun'] = $options['parallel']; 154 } 155 156 $status = false; 157 $cmds = commands_to_execute($options); 158 159 // Start executing commands either sequential/parallel for options provided. 160 if ($options['diag'] || $options['enable'] || $options['disable']) { 161 // Do it sequentially as it's fast and need to be displayed nicely. 162 foreach (array_chunk($cmds, 1, true) as $cmd) { 163 $processes = cli_execute_parallel($cmd, __DIR__); 164 print_sequential_output($processes); 165 } 166 167 } else if ($options['drop']) { 168 $processes = cli_execute_parallel($cmds, __DIR__); 169 $exitcodes = print_combined_drop_output($processes); 170 foreach ($exitcodes as $exitcode) { 171 $status = (bool)$status || (bool)$exitcode; 172 } 173 174 // Remove run config file. 175 $behatrunconfigfile = behat_config_manager::get_behat_run_config_file_path(); 176 if (file_exists($behatrunconfigfile)) { 177 if (!unlink($behatrunconfigfile)) { 178 behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete behat run config file'); 179 } 180 } 181 182 // Remove test file path. 183 if (file_exists(behat_util::get_test_file_path())) { 184 if (!unlink(behat_util::get_test_file_path())) { 185 behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete test file enable info'); 186 } 187 } 188 189 } else if ($options['install']) { 190 // This is intensive compared to behat itself so run them in chunk if option maxruns not set. 191 if ($options['maxruns']) { 192 foreach (array_chunk($cmds, $options['maxruns'], true) as $chunk) { 193 $processes = cli_execute_parallel($chunk, __DIR__); 194 $exitcodes = print_combined_install_output($processes); 195 foreach ($exitcodes as $name => $exitcode) { 196 if ($exitcode != 0) { 197 echo "Failed process [[$name]]" . PHP_EOL; 198 echo $processes[$name]->getOutput(); 199 echo PHP_EOL; 200 echo $processes[$name]->getErrorOutput(); 201 echo PHP_EOL . PHP_EOL; 202 } 203 $status = (bool)$status || (bool)$exitcode; 204 } 205 } 206 } else { 207 $processes = cli_execute_parallel($cmds, __DIR__); 208 $exitcodes = print_combined_install_output($processes); 209 foreach ($exitcodes as $name => $exitcode) { 210 if ($exitcode != 0) { 211 echo "Failed process [[$name]]" . PHP_EOL; 212 echo $processes[$name]->getOutput(); 213 echo PHP_EOL; 214 echo $processes[$name]->getErrorOutput(); 215 echo PHP_EOL . PHP_EOL; 216 } 217 $status = (bool)$status || (bool)$exitcode; 218 } 219 } 220 221 } else if ($options['updatesteps']) { 222 // Rewrite config file to ensure we have all the features covered. 223 if (empty($options['parallel'])) { 224 behat_config_manager::update_config_file('', true, '', $options['add-core-features-to-theme'], false, false); 225 } else { 226 // Update config file, ensuring we have up-to-date behat.yml. 227 for ($i = $options['fromrun']; $i <= $options['torun']; $i++) { 228 $CFG->behatrunprocess = $i; 229 230 // Update config file for each run. 231 behat_config_manager::update_config_file('', true, $options['optimize-runs'], $options['add-core-features-to-theme'], 232 $options['parallel'], $i); 233 } 234 unset($CFG->behatrunprocess); 235 } 236 237 // Do it sequentially as it's fast and need to be displayed nicely. 238 foreach (array_chunk($cmds, 1, true) as $cmd) { 239 $processes = cli_execute_parallel($cmd, __DIR__); 240 print_sequential_output($processes); 241 } 242 exit(0); 243 244 } else { 245 // We should never reach here. 246 echo $help; 247 exit(1); 248 } 249 250 // Ensure we have success status to show following information. 251 if ($status) { 252 echo "Unknown failure $status" . PHP_EOL; 253 exit((int)$status); 254 } 255 256 // Show command o/p (only one per time). 257 if ($options['install']) { 258 echo "Acceptance tests site installed for sites:".PHP_EOL; 259 260 // Display all sites which are installed/drop/diabled. 261 for ($i = $options['fromrun']; $i <= $options['torun']; $i++) { 262 if (empty($CFG->behat_parallel_run[$i - 1]['behat_wwwroot'])) { 263 echo $CFG->behat_wwwroot . "/" . BEHAT_PARALLEL_SITE_NAME . $i . PHP_EOL; 264 } else { 265 echo $CFG->behat_parallel_run[$i - 1]['behat_wwwroot'] . PHP_EOL; 266 } 267 268 } 269 } else if ($options['drop']) { 270 echo "Acceptance tests site dropped for " . $options['parallel'] . " parallel sites" . PHP_EOL; 271 272 } else if ($options['enable']) { 273 echo "Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:" . PHP_EOL; 274 echo behat_command::get_behat_command(true, true); 275 276 // Save fromrun and to run information. 277 if (isset($options['fromrun'])) { 278 behat_config_manager::set_behat_run_config_value('fromrun', $options['fromrun']); 279 } 280 281 if (isset($options['torun'])) { 282 behat_config_manager::set_behat_run_config_value('torun', $options['torun']); 283 } 284 if (isset($options['parallel'])) { 285 behat_config_manager::set_behat_run_config_value('parallel', $options['parallel']); 286 } 287 288 echo PHP_EOL; 289 290 } else if ($options['disable']) { 291 echo "Acceptance tests environment disabled for " . $options['parallel'] . " parallel sites" . PHP_EOL; 292 293 } else if ($options['diag']) { 294 // Valid option, so nothing to do. 295 } else { 296 echo $help; 297 chdir($cwd); 298 exit(1); 299 } 300 301 chdir($cwd); 302 exit(0); 303 304 /** 305 * Create commands to be executed for parallel run. 306 * 307 * @param array $options options provided by user. 308 * @return array commands to be executed. 309 */ 310 function commands_to_execute($options) { 311 $removeoptions = array('maxruns', 'fromrun', 'torun'); 312 $cmds = array(); 313 $extraoptions = $options; 314 $extra = ""; 315 316 // Remove extra options not in util_single_run.php. 317 foreach ($removeoptions as $ro) { 318 $extraoptions[$ro] = null; 319 unset($extraoptions[$ro]); 320 } 321 322 foreach ($extraoptions as $option => $value) { 323 $extra .= behat_get_command_flags($option, $value); 324 } 325 326 if (empty($options['parallel'])) { 327 $cmds = "php util_single_run.php " . $extra; 328 } else { 329 // Create commands which has to be executed for parallel site. 330 for ($i = $options['fromrun']; $i <= $options['torun']; $i++) { 331 $prefix = BEHAT_PARALLEL_SITE_NAME . $i; 332 $cmds[$prefix] = "php util_single_run.php " . $extra . " --run=" . $i . " 2>&1"; 333 } 334 } 335 return $cmds; 336 } 337 338 /** 339 * Print drop output merging each run. 340 * 341 * @param array $processes list of processes. 342 * @return array exit codes of each process. 343 */ 344 function print_combined_drop_output($processes) { 345 $exitcodes = array(); 346 $maxdotsonline = 70; 347 $remainingprintlen = $maxdotsonline; 348 $progresscount = 0; 349 echo "Dropping tables:" . PHP_EOL; 350 351 while (count($exitcodes) != count($processes)) { 352 usleep(10000); 353 foreach ($processes as $name => $process) { 354 if ($process->isRunning()) { 355 $op = $process->getIncrementalOutput(); 356 if (trim($op)) { 357 $update = preg_filter('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $op); 358 $strlentoprint = $update ? strlen($update) : 0; 359 360 // If not enough dots printed on line then just print. 361 if ($strlentoprint < $remainingprintlen) { 362 echo $update; 363 $remainingprintlen = $remainingprintlen - $strlentoprint; 364 } else if ($strlentoprint == $remainingprintlen) { 365 $progresscount += $maxdotsonline; 366 echo $update . " " . $progresscount . PHP_EOL; 367 $remainingprintlen = $maxdotsonline; 368 } else { 369 while ($part = substr($update, 0, $remainingprintlen) > 0) { 370 $progresscount += $maxdotsonline; 371 echo $part . " " . $progresscount . PHP_EOL; 372 $update = substr($update, $remainingprintlen); 373 $remainingprintlen = $maxdotsonline; 374 } 375 } 376 } 377 } else { 378 // Process exited. 379 $process->clearOutput(); 380 $exitcodes[$name] = $process->getExitCode(); 381 } 382 } 383 } 384 385 echo PHP_EOL; 386 return $exitcodes; 387 } 388 389 /** 390 * Print install output merging each run. 391 * 392 * @param array $processes list of processes. 393 * @return array exit codes of each process. 394 */ 395 function print_combined_install_output($processes) { 396 $exitcodes = array(); 397 $line = array(); 398 399 // Check what best we can do to accommodate all parallel run o/p on single line. 400 // Windows command line has length of 80 chars, so default we will try fit o/p in 80 chars. 401 if (defined('BEHAT_MAX_CMD_LINE_OUTPUT') && BEHAT_MAX_CMD_LINE_OUTPUT) { 402 $lengthofprocessline = (int)max(10, BEHAT_MAX_CMD_LINE_OUTPUT / count($processes)); 403 } else { 404 $lengthofprocessline = (int)max(10, 80 / count($processes)); 405 } 406 407 echo "Installing behat site for " . count($processes) . " parallel behat run" . PHP_EOL; 408 409 // Show process name in first row. 410 foreach ($processes as $name => $process) { 411 // If we don't have enough space to show full run name then show runX. 412 if ($lengthofprocessline < strlen($name) + 2) { 413 $name = substr($name, -5); 414 } 415 // One extra padding as we are adding | separator for rest of the data. 416 $line[$name] = str_pad('[' . $name . '] ', $lengthofprocessline + 1); 417 } 418 ksort($line); 419 $tableheader = array_keys($line); 420 echo implode("", $line) . PHP_EOL; 421 422 // Now print o/p from each process. 423 while (count($exitcodes) != count($processes)) { 424 usleep(50000); 425 $poutput = array(); 426 // Create child process. 427 foreach ($processes as $name => $process) { 428 if ($process->isRunning()) { 429 $output = $process->getIncrementalOutput(); 430 if (trim($output)) { 431 $poutput[$name] = explode(PHP_EOL, $output); 432 } 433 } else { 434 // Process exited. 435 $exitcodes[$name] = $process->getExitCode(); 436 } 437 } 438 ksort($poutput); 439 440 // Get max depth of o/p before displaying. 441 $maxdepth = 0; 442 foreach ($poutput as $pout) { 443 $pdepth = count($pout); 444 $maxdepth = $pdepth >= $maxdepth ? $pdepth : $maxdepth; 445 } 446 447 // Iterate over each process to get line to print. 448 for ($i = 0; $i <= $maxdepth; $i++) { 449 $pline = ""; 450 foreach ($tableheader as $name) { 451 $po = empty($poutput[$name][$i]) ? "" : substr($poutput[$name][$i], 0, $lengthofprocessline - 1); 452 $po = str_pad($po, $lengthofprocessline); 453 $pline .= "|". $po; 454 } 455 if (trim(str_replace("|", "", $pline))) { 456 echo $pline . PHP_EOL; 457 } 458 } 459 unset($poutput); 460 $poutput = null; 461 462 } 463 echo PHP_EOL; 464 return $exitcodes; 465 } 466 467 /** 468 * Print install output merging showing one run at a time. 469 * If any process fail then exit. 470 * 471 * @param array $processes list of processes. 472 * @param bool $showprefix show prefix. 473 * @return bool exitcode. 474 */ 475 function print_sequential_output($processes, $showprefix = true) { 476 $status = false; 477 foreach ($processes as $name => $process) { 478 $shownname = false; 479 while ($process->isRunning()) { 480 $op = $process->getIncrementalOutput(); 481 if (trim($op)) { 482 // Show name of the run once for sequential. 483 if ($showprefix && !$shownname) { 484 echo '[' . $name . '] '; 485 $shownname = true; 486 } 487 echo $op; 488 } 489 } 490 // If any error then exit. 491 $exitcode = $process->getExitCode(); 492 if ($exitcode != 0) { 493 exit($exitcode); 494 } 495 $status = $status || (bool)$exitcode; 496 } 497 return $status; 498 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body