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 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 *
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
33// Basic functions.
34require_once(__DIR__ . '/../../../../lib/clilib.php');
35require_once(__DIR__ . '/../../../../lib/behat/lib.php');
36
37// CLI options.
38list($options, $unrecognized) = cli_get_params(
39    array(
40        'help'        => false,
41        'install'     => false,
42        'parallel'    => 0,
43        'run'         => 0,
44        'drop'        => false,
45        'enable'      => false,
46        'disable'     => false,
47        'diag'        => false,
48        'tags'        => '',
49        'updatesteps' => false,
50        'optimize-runs' => '',
51        'add-core-features-to-theme' => false,
52        'axe'         => false,
53    ),
54    array(
55        'h' => 'help',
56        'o' => 'optimize-runs',
57        'a' => 'add-core-features-to-theme',
58    )
59);
60
61if ($options['install'] or $options['drop']) {
62    define('CACHE_DISABLE_ALL', true);
63}
64
65// Checking util_single_run.php CLI script usage.
66$help = "
67Behat utilities to manage the test environment
68
69Usage:
70  php util_single_run.php [--install|--drop|--enable|--disable|--diag|--updatesteps|--help]
71
72Options:
73--install        Installs the test environment for acceptance tests
74--drop           Drops the database tables and the dataroot contents
75--enable         Enables test environment and updates tests list
76--disable        Disables test environment
77--diag           Get behat test environment status code
78--updatesteps    Update feature step file.
79--axe            Include axe accessibility tests
80
81-o, --optimize-runs Split features with specified tags in all parallel runs.
82-a, --add-core-features-to-theme Add all core features to specified theme's
83
84-h, --help Print out this help
85
86Example from Moodle root directory:
87\$ php admin/tool/behat/cli/util_single_run.php --enable
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
97// Describe this script.
98define('BEHAT_UTIL', true);
99define('CLI_SCRIPT', true);
100define('NO_OUTPUT_BUFFERING', true);
101define('IGNORE_COMPONENT_CACHE', true);
102
103// Set run value, to be used by setup for configuring proper CFG variables.
104if ($options['run']) {
105    define('BEHAT_CURRENT_RUN', $options['run']);
106}
107
108// Only load CFG from config.php, stop ASAP in lib/setup.php.
109define('ABORT_AFTER_CONFIG', true);
110require_once(__DIR__ . '/../../../../config.php');
111
112// Remove error handling overrides done in config.php.
113$CFG->debug = (E_ALL | E_STRICT);
114$CFG->debugdisplay = 1;
115error_reporting($CFG->debug);
116ini_set('display_errors', '1');
117ini_set('log_errors', '1');
118
119// Finish moodle init.
120define('ABORT_AFTER_CONFIG_CANCEL', true);
121require("$CFG->dirroot/lib/setup.php");
122
123raise_memory_limit(MEMORY_HUGE);
124
125require_once($CFG->libdir.'/adminlib.php');
126require_once($CFG->libdir.'/upgradelib.php');
127require_once($CFG->libdir.'/clilib.php');
128require_once($CFG->libdir.'/installlib.php');
129require_once($CFG->libdir.'/testing/classes/test_lock.php');
130
131if ($unrecognized) {
132    $unrecognized = implode(PHP_EOL . "  ", $unrecognized);
133    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
134}
135
136// Behat utilities.
137require_once($CFG->libdir . '/behat/classes/util.php');
138require_once($CFG->libdir . '/behat/classes/behat_command.php');
139require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
140
141// Ensure run option is <= parallel run installed.
142$run = 0;
143$parallel = 0;
144if ($options['run']) {
145    $run = $options['run'];
146    // If parallel option is not passed, then try get it form config.
147    if (!$options['parallel']) {
148        $parallel = behat_config_manager::get_behat_run_config_value('parallel');
149    } else {
150        $parallel = $options['parallel'];
151    }
152
153    if (empty($parallel) || $run > $parallel) {
154        echo "Parallel runs can't be more then ".$parallel.PHP_EOL;
155        exit(1);
156    }
157    $CFG->behatrunprocess = $run;
158}
159
160// Run command (only one per time).
161if ($options['install']) {
162    behat_util::install_site();
163
164    // This is only displayed once for parallel install.
165    if (empty($run)) {
166        mtrace("Acceptance tests site installed");
167    }
168
169    // Note: Do not build the themes here. This is done during the 'enable' stage.
170
171} else if ($options['drop']) {
172    // Ensure no tests are running.
173    test_lock::acquire('behat');
174    behat_util::drop_site();
175    // This is only displayed once for parallel install.
176    if (empty($run)) {
177        mtrace("Acceptance tests site dropped");
178    }
179
180} else if ($options['enable']) {
181    if (!empty($parallel)) {
182        // Save parallel site info for enable and install options.
183        behat_config_manager::set_behat_run_config_value('behatsiteenabled', 1);
184    }
185
186    // Define whether to run Behat with axe tests.
187    behat_config_manager::set_behat_run_config_value('axe', $options['axe']);
188
189    // Enable test mode.
190    $timestart = microtime(true);
191    mtrace('Creating Behat configuration ...', '');
192    behat_util::start_test_mode($options['add-core-features-to-theme'], $options['optimize-runs'], $parallel, $run);
193    mtrace(' done in ' . round(microtime(true) - $timestart, 2) . ' seconds.');
194
195    // Themes are only built in the 'enable' command.
196    behat_util::build_themes(true);
197    mtrace("Testing environment themes built");
198
199    // This is only displayed once for parallel install.
200    if (empty($run)) {
201        // Notify user that 2.5 profile has been converted to 3.5.
202        if (behat_config_manager::$autoprofileconversion) {
203            mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
204        }
205
206        $runtestscommand = behat_command::get_behat_command(true, !empty($run));
207
208        $runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
209        mtrace("Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use: " . PHP_EOL .
210            $runtestscommand);
211    }
212
213} else if ($options['disable']) {
214    behat_util::stop_test_mode($run);
215    // This is only displayed once for parallel install.
216    if (empty($run)) {
217        mtrace("Acceptance tests environment disabled");
218    }
219
220} else if ($options['diag']) {
221    $code = behat_util::get_behat_status();
222    exit($code);
223
224} else if ($options['updatesteps']) {
225    if (defined('BEHAT_FEATURE_STEP_FILE') && BEHAT_FEATURE_STEP_FILE) {
226        $behatstepfile = BEHAT_FEATURE_STEP_FILE;
227    } else {
228        echo "BEHAT_FEATURE_STEP_FILE is not set, please ensure you set this to writable file" . PHP_EOL;
229        exit(1);
230    }
231
232    // Run behat command to get steps in feature files.
233    $featurestepscmd = behat_command::get_behat_command(true);
234    $featurestepscmd .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
235    $featurestepscmd .= ' --dry-run --format=moodle_stepcount';
236    $processes = cli_execute_parallel(array($featurestepscmd), __DIR__ . "/../../../../");
237    $status = print_update_step_output(array_pop($processes), $behatstepfile);
238
239    exit($status);
240} else {
241    echo $help;
242    exit(1);
243}
244
245exit(0);
246
247/**
248 * Print update progress as dots for updating feature file step list.
249 *
250 * @param Process $process process executing update step command.
251 * @param string $featurestepfile feature step file in which steps will be saved.
252 * @return int exitcode.
253 */
254function print_update_step_output($process, $featurestepfile) {
255    $printedlength = 0;
256
257    echo "Updating steps feature file for parallel behat runs" . PHP_EOL;
258
259    // Show progress while running command.
260    while ($process->isRunning()) {
261        usleep(10000);
262        $op = $process->getIncrementalOutput();
263        if (trim($op)) {
264            echo ".";
265            $printedlength++;
266            if ($printedlength > 70) {
267                $printedlength = 0;
268                echo PHP_EOL;
269            }
270        }
271    }
272
273    // If any error then exit.
274    $exitcode = $process->getExitCode();
275    // Output err.
276    if ($exitcode != 0) {
277        echo $process->getErrorOutput();
278        exit($exitcode);
279    }
280
281    // Extract features with step info and save it in file.
282    $featuresteps = $process->getOutput();
283    $featuresteps = explode(PHP_EOL, $featuresteps);
284
285    $realroot = realpath(__DIR__.'/../../../../').'/';
286    foreach ($featuresteps as $featurestep) {
287        if (trim($featurestep)) {
288            $step = explode("::", $featurestep);
289            $step[0] = str_replace($realroot, '', $step[0]);
290            $steps[$step[0]] = $step[1];
291        }
292    }
293
294    if ($existing = @json_decode(file_get_contents($featurestepfile), true)) {
295        $steps = array_merge($existing, $steps);
296    }
297    arsort($steps);
298
299    if (!@file_put_contents($featurestepfile, json_encode($steps, JSON_PRETTY_PRINT))) {
300        behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $featurestepfile . ' can not be created');
301        $exitcode = -1;
302    }
303
304    echo PHP_EOL. "Updated step count in " . $featurestepfile . PHP_EOL;
305
306    return $exitcode;
307}
308