source: moodle/trunk/fuentes/admin/tool/behat/cli/util.php @ 1331

Last change on this file since 1331 was 1331, checked in by jrpelegrina, 3 years ago

Updated to moodle 3.0.3

File size: 14.7 KB
RevLine 
[136]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/**
[1331]18 * CLI tool with utilities to manage parallel Behat integration in Moodle
[136]19 *
20 * All CLI utilities uses $CFG->behat_dataroot and $CFG->prefix_dataroot as
21 * $CFG->dataroot and $CFG->prefix
22 *
23 * @package    tool_behat
24 * @copyright  2012 David Monllaó
25 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
28
29if (isset($_SERVER['REMOTE_ADDR'])) {
30    die(); // No access from web!.
31}
32
[1331]33define('BEHAT_UTIL', true);
34define('CLI_SCRIPT', true);
35define('NO_OUTPUT_BUFFERING', true);
36define('IGNORE_COMPONENT_CACHE', true);
37define('ABORT_AFTER_CONFIG', true);
38
39require_once(__DIR__ . '/../../../../config.php');
[136]40require_once(__DIR__ . '/../../../../lib/clilib.php');
41require_once(__DIR__ . '/../../../../lib/behat/lib.php');
[1331]42require_once(__DIR__ . '/../../../../lib/behat/classes/behat_command.php');
43require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
[136]44
45// CLI options.
46list($options, $unrecognized) = cli_get_params(
47    array(
[1331]48        'help'        => false,
49        'install'     => false,
50        'drop'        => false,
51        'enable'      => false,
52        'disable'     => false,
53        'diag'        => false,
54        'parallel'    => 0,
55        'maxruns'     => false,
56        'updatesteps' => false,
57        'fromrun'     => 1,
58        'torun'       => 0,
[136]59    ),
60    array(
[1331]61        'h' => 'help',
62        'j' => 'parallel',
63        'm' => 'maxruns'
[136]64    )
65);
66
67// Checking util.php CLI script usage.
68$help = "
69Behat utilities to manage the test environment
70
[1331]71Usage:
72  php util.php [--install|--drop|--enable|--disable|--diag|--updatesteps|--help] [--parallel=value [--maxruns=value]]
73
[136]74Options:
[1331]75--install      Installs the test environment for acceptance tests
76--drop         Drops the database tables and the dataroot contents
77--enable       Enables test environment and updates tests list
78--disable      Disables test environment
79--diag         Get behat test environment status code
80--updatesteps  Update feature step file.
81-j, --parallel Number of parallel behat run operation
82-m, --maxruns  Max parallel processes to be executed at one time.
[136]83
84-h, --help     Print out this help
85
86Example from Moodle root directory:
[1331]87\$ php admin/tool/behat/cli/util.php --enable --parallel=4
[136]88
89More info in http://docs.moodle.org/dev/Acceptance_testing#Running_tests
90";
91
92if (!empty($options['help'])) {
93    echo $help;
94    exit(0);
95}
96
[1331]97$cwd = getcwd();
[136]98
[1331]99// For drop option check if parallel site.
100if ((empty($options['parallel'])) && ($options['drop']) || $options['updatesteps']) {
101    // Get parallel run info from first run.
102    $options['parallel'] = behat_config_manager::get_parallel_test_runs($options['fromrun']);
103}
[136]104
[1331]105// If not a parallel site then open single run.
106if (empty($options['parallel'])) {
107    chdir(__DIR__);
108    // Check if behat is initialised, if not exit.
109    passthru("php util_single_run.php --diag", $status);
110    if ($status) {
111        exit ($status);
112    }
113    $cmd = commands_to_execute($options);
114    $processes = cli_execute_parallel(array($cmd), __DIR__);
115    $status = print_sequential_output($processes, false);
116    chdir($cwd);
117    exit($status);
118}
[136]119
[1331]120// Default torun is maximum parallel runs.
121if (empty($options['torun'])) {
122    $options['torun'] = $options['parallel'];
123}
[136]124
[1331]125$status = false;
126$cmds = commands_to_execute($options);
[136]127
[1331]128// Start executing commands either sequential/parallel for options provided.
129if ($options['diag'] || $options['enable'] || $options['disable']) {
130    // Do it sequentially as it's fast and need to be displayed nicely.
131    foreach (array_chunk($cmds, 1, true) as $cmd) {
132        $processes = cli_execute_parallel($cmd, __DIR__);
133        print_sequential_output($processes);
134    }
[136]135
[1331]136} else if ($options['drop']) {
137    $processes = cli_execute_parallel($cmds, __DIR__);
138    $exitcodes = print_combined_drop_output($processes);
139    foreach ($exitcodes as $exitcode) {
140        $status = (bool)$status || (bool)$exitcode;
141    }
142
143} else if ($options['install']) {
144    // This is intensive compared to behat itself so run them in chunk if option maxruns not set.
145    if ($options['maxruns']) {
146        foreach (array_chunk($cmds, $options['maxruns'], true) as $chunk) {
147            $processes = cli_execute_parallel($chunk, __DIR__);
148            $exitcodes = print_combined_install_output($processes);
149            foreach ($exitcodes as $name => $exitcode) {
150                if ($exitcode != 0) {
151                    echo "Failed process [[$name]]" . PHP_EOL;
152                    echo $processes[$name]->getOutput();
153                    echo PHP_EOL;
154                    echo $processes[$name]->getErrorOutput();
155                    echo PHP_EOL . PHP_EOL;
156                }
157                $status = (bool)$status || (bool)$exitcode;
158            }
159        }
160    } else {
161        $processes = cli_execute_parallel($cmds, __DIR__);
162        $exitcodes = print_combined_install_output($processes);
163        foreach ($exitcodes as $name => $exitcode) {
164            if ($exitcode != 0) {
165                echo "Failed process [[$name]]" . PHP_EOL;
166                echo $processes[$name]->getOutput();
167                echo PHP_EOL;
168                echo $processes[$name]->getErrorOutput();
169                echo PHP_EOL . PHP_EOL;
170            }
171            $status = (bool)$status || (bool)$exitcode;
172        }
173    }
174
175} else if ($options['updatesteps']) {
176    // Rewrite config file to ensure we have all the features covered.
177    if (empty($options['parallel'])) {
178        behat_config_manager::update_config_file();
179    } else {
180        // Update config file, ensuring we have up-to-date behat.yml.
181        for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
182            $CFG->behatrunprocess = $i;
183            behat_config_manager::update_config_file();
184        }
185        unset($CFG->behatrunprocess);
186    }
187
188    // Do it sequentially as it's fast and need to be displayed nicely.
189    foreach (array_chunk($cmds, 1, true) as $cmd) {
190        $processes = cli_execute_parallel($cmd, __DIR__);
191        print_sequential_output($processes);
192    }
193    exit(0);
194
195} else {
196    // We should never reach here.
197    echo $help;
198    exit(1);
[136]199}
200
[1331]201// Ensure we have success status to show following information.
202if ($status) {
203    echo "Unknown failure $status" . PHP_EOL;
204    exit((int)$status);
205}
[136]206
[1331]207// Show command o/p (only one per time).
[136]208if ($options['install']) {
[1331]209    echo "Acceptance tests site installed for sites:".PHP_EOL;
210
211    // Display all sites which are installed/drop/diabled.
212    for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
213        if (empty($CFG->behat_parallel_run[$i - 1]['behat_wwwroot'])) {
214            echo $CFG->behat_wwwroot . "/" . BEHAT_PARALLEL_SITE_NAME . $i . PHP_EOL;
215        } else {
216            echo $CFG->behat_parallel_run[$i - 1]['behat_wwwroot'] . PHP_EOL;
217        }
218
219    }
[136]220} else if ($options['drop']) {
[1331]221    echo "Acceptance tests site dropped for " . $options['parallel'] . " parallel sites" . PHP_EOL;
222
[136]223} else if ($options['enable']) {
[1331]224    echo "Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:" . PHP_EOL;
225    echo behat_command::get_behat_command(true, true);
226    echo PHP_EOL;
227
[136]228} else if ($options['disable']) {
[1331]229    echo "Acceptance tests environment disabled for " . $options['parallel'] . " parallel sites" . PHP_EOL;
230
[136]231} else if ($options['diag']) {
[1331]232    // Valid option, so nothing to do.
[136]233} else {
234    echo $help;
[1331]235    chdir($cwd);
236    exit(1);
[136]237}
238
[1331]239chdir($cwd);
[136]240exit(0);
[1331]241
242/**
243 * Create commands to be executed for parallel run.
244 *
245 * @param array $options options provided by user.
246 * @return array commands to be executed.
247 */
248function commands_to_execute($options) {
249    $removeoptions = array('maxruns', 'fromrun', 'torun');
250    $cmds = array();
251    $extraoptions = $options;
252    $extra = "";
253
254    // Remove extra options not in util_single_run.php.
255    foreach ($removeoptions as $ro) {
256        $extraoptions[$ro] = null;
257        unset($extraoptions[$ro]);
258    }
259
260    foreach ($extraoptions as $option => $value) {
261        if ($options[$option]) {
262            $extra .= " --$option";
263            if ($value) {
264                $extra .= "=$value";
265            }
266        }
267    }
268
269    if (empty($options['parallel'])) {
270        $cmds = "php util_single_run.php " . $extra;
271    } else {
272        // Create commands which has to be executed for parallel site.
273        for ($i = $options['fromrun']; $i <= $options['torun']; $i++) {
274            $prefix = BEHAT_PARALLEL_SITE_NAME . $i;
275            $cmds[$prefix] = "php util_single_run.php " . $extra . " --run=" . $i . " 2>&1";
276        }
277    }
278    return $cmds;
279}
280
281/**
282 * Print drop output merging each run.
283 *
284 * @param array $processes list of processes.
285 * @return array exit codes of each process.
286 */
287function print_combined_drop_output($processes) {
288    $exitcodes = array();
289    $maxdotsonline = 70;
290    $remainingprintlen = $maxdotsonline;
291    $progresscount = 0;
292    echo "Dropping tables:" . PHP_EOL;
293
294    while (count($exitcodes) != count($processes)) {
295        usleep(10000);
296        foreach ($processes as $name => $process) {
297            if ($process->isRunning()) {
298                $op = $process->getIncrementalOutput();
299                if (trim($op)) {
300                    $update = preg_filter('#^\s*([FS\.\-]+)(?:\s+\d+)?\s*$#', '$1', $op);
301                    $strlentoprint = strlen($update);
302
303                    // If not enough dots printed on line then just print.
304                    if ($strlentoprint < $remainingprintlen) {
305                        echo $update;
306                        $remainingprintlen = $remainingprintlen - $strlentoprint;
307                    } else if ($strlentoprint == $remainingprintlen) {
308                        $progresscount += $maxdotsonline;
309                        echo $update . " " . $progresscount . PHP_EOL;
310                        $remainingprintlen = $maxdotsonline;
311                    } else {
312                        while ($part = substr($update, 0, $remainingprintlen) > 0) {
313                            $progresscount += $maxdotsonline;
314                            echo $part . " " . $progresscount . PHP_EOL;
315                            $update = substr($update, $remainingprintlen);
316                            $remainingprintlen = $maxdotsonline;
317                        }
318                    }
319                }
320            } else {
321                // Process exited.
322                $process->clearOutput();
323                $exitcodes[$name] = $process->getExitCode();
324            }
325        }
326    }
327
328    echo PHP_EOL;
329    return $exitcodes;
330}
331
332/**
333 * Print install output merging each run.
334 *
335 * @param array $processes list of processes.
336 * @return array exit codes of each process.
337 */
338function print_combined_install_output($processes) {
339    $exitcodes = array();
340    $line = array();
341
342    // Check what best we can do to accommodate  all parallel run o/p on single line.
343    // Windows command line has length of 80 chars, so default we will try fit o/p in 80 chars.
344    if (defined('BEHAT_MAX_CMD_LINE_OUTPUT') && BEHAT_MAX_CMD_LINE_OUTPUT) {
345        $lengthofprocessline = (int)max(10, BEHAT_MAX_CMD_LINE_OUTPUT / count($processes));
346    } else {
347        $lengthofprocessline = (int)max(10, 80 / count($processes));
348    }
349
350    echo "Installing behat site for " . count($processes) . " parallel behat run" . PHP_EOL;
351
352    // Show process name in first row.
353    foreach ($processes as $name => $process) {
354        // If we don't have enough space to show full run name then show runX.
355        if ($lengthofprocessline < strlen($name + 2)) {
356            $name = substr($name, -5);
357        }
358        // One extra padding as we are adding | separator for rest of the data.
359        $line[$name] = str_pad('[' . $name . '] ', $lengthofprocessline + 1);
360    }
361    ksort($line);
362    $tableheader = array_keys($line);
363    echo implode("", $line) . PHP_EOL;
364
365    // Now print o/p from each process.
366    while (count($exitcodes) != count($processes)) {
367        usleep(50000);
368        $poutput = array();
369        // Create child process.
370        foreach ($processes as $name => $process) {
371            if ($process->isRunning()) {
372                $output = $process->getIncrementalOutput();
373                if (trim($output)) {
374                    $poutput[$name] = explode(PHP_EOL, $output);
375                }
376            } else {
377                // Process exited.
378                $exitcodes[$name] = $process->getExitCode();
379            }
380        }
381        ksort($poutput);
382
383        // Get max depth of o/p before displaying.
384        $maxdepth = 0;
385        foreach ($poutput as $pout) {
386            $pdepth = count($pout);
387            $maxdepth = $pdepth >= $maxdepth ? $pdepth : $maxdepth;
388        }
389
390        // Iterate over each process to get line to print.
391        for ($i = 0; $i <= $maxdepth; $i++) {
392            $pline = "";
393            foreach ($tableheader as $name) {
394                $po = empty($poutput[$name][$i]) ? "" : substr($poutput[$name][$i], 0, $lengthofprocessline - 1);
395                $po = str_pad($po, $lengthofprocessline);
396                $pline .= "|". $po;
397            }
398            if (trim(str_replace("|", "", $pline))) {
399                echo $pline . PHP_EOL;
400            }
401        }
402        unset($poutput);
403        $poutput = null;
404
405    }
406    echo PHP_EOL;
407    return $exitcodes;
408}
409
410/**
411 * Print install output merging showing one run at a time.
412 * If any process fail then exit.
413 *
414 * @param array $processes list of processes.
415 * @param bool $showprefix show prefix.
416 * @return bool exitcode.
417 */
418function print_sequential_output($processes, $showprefix = true) {
419    $status = false;
420    foreach ($processes as $name => $process) {
421        $shownname = false;
422        while ($process->isRunning()) {
423            $op = $process->getIncrementalOutput();
424            if (trim($op)) {
425                // Show name of the run once for sequential.
426                if ($showprefix && !$shownname) {
427                    echo '[' . $name . '] ';
428                    $shownname = true;
429                }
430                echo $op;
431            }
432        }
433        // If any error then exit.
434        $exitcode = $process->getExitCode();
435        if ($exitcode != 0) {
436            exit($exitcode);
437        }
438        $status = $status || (bool)$exitcode;
439    }
440    return $status;
441}
Note: See TracBrowser for help on using the repository browser.