Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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