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 * Utils to set Behat config
19 *
20 * @package    core
21 * @category   test
22 * @copyright  2012 David Monllaó
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28require_once(__DIR__ . '/behat_config_util.php');
29
30/**
31 * Behat configuration manager
32 *
33 * Creates/updates Behat config files getting tests
34 * and steps from Moodle codebase
35 *
36 * @package    core
37 * @category   test
38 * @copyright  2012 David Monllaó
39 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 */
41class behat_config_manager {
42
43    /**
44     * @var bool Keep track of the automatic profile conversion. So we can notify user.
45     */
46    public static $autoprofileconversion = false;
47
48    /**
49     * @var behat_config_util keep object of behat_config_util for use.
50     */
51    public static $behatconfigutil = null;
52
53    /**
54     * Returns behat_config_util.
55     *
56     * @return behat_config_util
57     */
58    private static function get_behat_config_util() {
59        if (!self::$behatconfigutil) {
60            self::$behatconfigutil = new behat_config_util();
61        }
62
63        return self::$behatconfigutil;
64    }
65
66    /**
67     * Updates a config file
68     *
69     * The tests runner and the steps definitions list uses different
70     * config files to avoid problems with concurrent executions.
71     *
72     * The steps definitions list can be filtered by component so it's
73     * behat.yml is different from the $CFG->dirroot one.
74     *
75     * @param  string $component Restricts the obtained steps definitions to the specified component
76     * @param  string $testsrunner If the config file will be used to run tests
77     * @param  string $tags features files including tags.
78     * @param  bool   $themesuitewithallfeatures if only theme specific features need to be included in the suite.
79     * @param  int    $parallelruns number of parallel runs.
80     * @param  int    $run current run for which config needs to be updated.
81     * @return void
82     */
83    public static function update_config_file($component = '', $testsrunner = true, $tags = '',
84        $themesuitewithallfeatures = false, $parallelruns = 0, $run = 0) {
85
86        global $CFG;
87
88        // Behat must have a separate behat.yml to have access to the whole set of features and steps definitions.
89        if ($testsrunner === true) {
90            $configfilepath = behat_command::get_behat_dir($run) . '/behat.yml';
91        } else {
92            // Alternative for steps definitions filtering, one for each user.
93            $configfilepath = self::get_steps_list_config_filepath();
94        }
95
96        $behatconfigutil = self::get_behat_config_util();
97        $behatconfigutil->set_theme_suite_to_include_core_features($themesuitewithallfeatures);
98        $behatconfigutil->set_tag_for_feature_filter($tags);
99
100        // Gets all the components with features, if running the tests otherwise not required.
101        $features = array();
102        if ($testsrunner) {
103            $features = $behatconfigutil->get_components_features();
104        }
105
106        // Gets all the components with steps definitions.
107        $stepsdefinitions = $behatconfigutil->get_components_contexts($component);
108        // We don't want the deprecated steps definitions here.
109        if (!$testsrunner) {
110            unset($stepsdefinitions['behat_deprecated']);
111        }
112
113        // Get current run.
114        if (empty($run) && ($run !== false) && !empty($CFG->behatrunprocess)) {
115            $run = $CFG->behatrunprocess;
116        }
117
118        // Get number of parallel runs if not passed.
119        if (empty($parallelruns) && ($parallelruns !== false)) {
120            $parallelruns = self::get_behat_run_config_value('parallel');
121        }
122
123        // Behat config file specifing the main context class,
124        // the required Behat extensions and Moodle test wwwroot.
125        $contents = $behatconfigutil->get_config_file_contents($features, $stepsdefinitions, $tags, $parallelruns, $run);
126
127        // Stores the file.
128        if (!file_put_contents($configfilepath, $contents)) {
129            behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $configfilepath . ' can not be created');
130        }
131
132    }
133
134    /**
135     * @deprecated since 3.2 - please use behat_config_util.php
136     */
137    public static function get_features_with_tags() {
138        throw new coding_exception('get_features_with_tags() can not be used anymore. ' .
139            'Please use behat_config_util instead.');
140    }
141
142    /**
143     * @deprecated since 3.2 - please use behat_config_util.php
144     */
145    public static function get_components_steps_definitions() {
146        throw new coding_exception('get_components_steps_definitions() can not be used anymore. ' .
147            'Please use behat_config_util instead.');
148    }
149
150    /**
151     * Returns the behat config file path used by the steps definition list
152     *
153     * @return string
154     */
155    public static function get_steps_list_config_filepath() {
156        global $USER;
157
158        // We don't cygwin-it as it is called using exec() which uses cmd.exe.
159        $userdir = behat_command::get_behat_dir() . '/users/' . $USER->id;
160        make_writable_directory($userdir);
161
162        return $userdir . '/behat.yml';
163    }
164
165    /**
166     * Returns the behat config file path used by the behat cli command.
167     *
168     * @param int $runprocess Runprocess.
169     * @return string
170     */
171    public static function get_behat_cli_config_filepath($runprocess = 0) {
172        global $CFG;
173
174        if ($runprocess) {
175            if (isset($CFG->behat_parallel_run[$runprocess - 1 ]['behat_dataroot'])) {
176                $command = $CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'];
177            } else {
178                $command = $CFG->behat_dataroot . $runprocess;
179            }
180        } else {
181            $command = $CFG->behat_dataroot;
182        }
183        $command .= DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
184
185        // Cygwin uses linux-style directory separators.
186        if (testing_is_cygwin()) {
187            $command = str_replace('\\', '/', $command);
188        }
189
190        return $command;
191    }
192
193    /**
194     * Returns the path to the parallel run file which specifies if parallel test environment is enabled
195     * and how many parallel runs to execute.
196     *
197     * @return string
198     */
199    public final static function get_behat_run_config_file_path() {
200        return behat_command::get_parent_behat_dir() . '/run_environment.json';
201    }
202
203    /**
204     * Get config for parallel run.
205     *
206     * @param string $key Key to store
207     * @return string|int|array value which is stored.
208     */
209    public final static function get_behat_run_config_value($key) {
210        $parallelrunconfigfile = self::get_behat_run_config_file_path();
211
212        if (file_exists($parallelrunconfigfile)) {
213            if ($parallelrunconfigs = @json_decode(file_get_contents($parallelrunconfigfile), true)) {
214                if (isset($parallelrunconfigs[$key])) {
215                    return $parallelrunconfigs[$key];
216                }
217            }
218        }
219
220        return false;
221    }
222
223    /**
224     * Save/update config for parallel run.
225     *
226     * @param string $key Key to store
227     * @param string|int|array $value to store.
228     */
229    public final static function set_behat_run_config_value($key, $value) {
230        $parallelrunconfigs = array();
231        $parallelrunconfigfile = self::get_behat_run_config_file_path();
232
233        // Get any existing config first.
234        if (file_exists($parallelrunconfigfile)) {
235            $parallelrunconfigs = @json_decode(file_get_contents($parallelrunconfigfile), true);
236        }
237        $parallelrunconfigs[$key] = $value;
238
239        @file_put_contents($parallelrunconfigfile, json_encode($parallelrunconfigs, JSON_PRETTY_PRINT));
240    }
241
242    /**
243     * Drops parallel site links.
244     *
245     * @return bool true on success else false.
246     */
247    public final static function drop_parallel_site_links() {
248        global $CFG;
249
250        // Get parallel test runs.
251        $parallelrun = self::get_behat_run_config_value('parallel');
252
253        if (empty($parallelrun)) {
254            return false;
255        }
256
257        // If parallel run then remove links and original file.
258        clearstatcache();
259        for ($i = 1; $i <= $parallelrun; $i++) {
260            // Don't delete links for specified sites, as they should be accessible.
261            if (!empty($CFG->behat_parallel_run['behat_wwwroot'][$i - 1]['behat_wwwroot'])) {
262                continue;
263            }
264            $link = $CFG->dirroot . '/' . BEHAT_PARALLEL_SITE_NAME . $i;
265            if (file_exists($link) && is_link($link)) {
266                @unlink($link);
267            }
268        }
269        return true;
270    }
271
272    /**
273     * Create parallel site links.
274     *
275     * @param int $fromrun first run
276     * @param int $torun last run.
277     * @return bool true for sucess, else false.
278     */
279    public final static function create_parallel_site_links($fromrun, $torun) {
280        global $CFG;
281
282        // Create site symlink if necessary.
283        clearstatcache();
284        for ($i = $fromrun; $i <= $torun; $i++) {
285            // Don't create links for specified sites, as they should be accessible.
286            if (!empty($CFG->behat_parallel_run['behat_wwwroot'][$i - 1]['behat_wwwroot'])) {
287                continue;
288            }
289            $link = $CFG->dirroot.'/'.BEHAT_PARALLEL_SITE_NAME.$i;
290            clearstatcache();
291            if (file_exists($link)) {
292                if (!is_link($link) || !is_dir($link)) {
293                    echo "File exists at link location ($link) but is not a link or directory!" . PHP_EOL;
294                    return false;
295                }
296            } else if (!symlink($CFG->dirroot, $link)) {
297                // Try create link in case it's not already present.
298                echo "Unable to create behat site symlink ($link)" . PHP_EOL;
299                return false;
300            }
301        }
302        return true;
303    }
304
305    /**
306     * @deprecated since 3.2 - please use behat_config_util.php
307     */
308    protected static function get_config_file_contents() {
309        throw new coding_exception('get_config_file_contents() can not be used anymore. ' .
310            'Please use behat_config_util instead.');
311    }
312
313    /**
314     * @deprecated since 3.2 - please use behat_config_util.php
315     */
316    protected static function merge_behat_config() {
317        throw new coding_exception('merge_behat_config() can not be used anymore. ' .
318            'Please use behat_config_util instead.');
319    }
320
321    /**
322     * @deprecated since 3.2 - please use behat_config_util.php
323     */
324    protected static function get_behat_profile() {
325        throw new coding_exception('get_behat_profile() can not be used anymore. ' .
326            'Please use behat_config_util instead.');
327    }
328
329    /**
330     * @deprecated since 3.2 - please use behat_config_util.php
331     */
332    protected static function profile_guided_allocate() {
333        throw new coding_exception('profile_guided_allocate() can not be used anymore. ' .
334            'Please use behat_config_util instead.');
335    }
336
337    /**
338     * @deprecated since 3.2 - please use behat_config_util.php
339     */
340    protected static function merge_config() {
341        throw new coding_exception('merge_config() can not be used anymore. ' .
342            'Please use behat_config_util instead.');
343    }
344
345    /**
346     * @deprecated since 3.2 - please use behat_config_util.php
347     */
348    protected final static function clean_path() {
349        throw new coding_exception('clean_path() can not be used anymore. ' .
350            'Please use behat_config_util instead.');
351    }
352
353    /**
354     * @deprecated since 3.2 - please use behat_config_util.php
355     */
356    protected final static function get_behat_tests_path() {
357        throw new coding_exception('get_behat_tests_path() can not be used anymore. ' .
358            'Please use behat_config_util instead.');
359    }
360
361}
362