Proyectos de Subversion Moodle

Rev

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