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 - https://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 <https://www.gnu.org/licenses/>.
16
 
17
namespace tool_generator\local\testscenario;
18
 
19
use behat_data_generators;
20
use Behat\Gherkin\Parser;
21
use Behat\Gherkin\Lexer;
22
use Behat\Gherkin\Keywords\ArrayKeywords;
23
use ReflectionClass;
24
use ReflectionMethod;
25
 
26
/**
27
 * Class to process a scenario generator file.
28
 *
29
 * @package    tool_generator
30
 * @copyright  2023 Ferran Recio <ferran@moodle.com>
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32
 */
33
class runner {
34
 
35
    /** @var behat_data_generators the behat data generator instance. */
36
    private behat_data_generators $generator;
37
 
38
    /** @var array of valid steps indexed by given expression tag. */
39
    private array $validsteps;
40
 
41
    /**
42
     * Initi all composer, behat libraries and load the valid steps.
43
     */
44
    public function init() {
45
        $this->include_composer_libraries();
46
        $this->include_behat_libraries();
47
        $this->load_generator();
48
    }
49
 
50
    /**
51
     * Include composer autload.
52
     */
53
    public function include_composer_libraries() {
54
        global $CFG;
55
        if (!file_exists($CFG->dirroot . '/vendor/autoload.php')) {
56
            throw new \moodle_exception('Missing composer.');
57
        }
58
        require_once($CFG->dirroot . '/vendor/autoload.php');
59
        return true;
60
    }
61
 
62
    /**
63
     * Include all necessary behat libraries.
64
     */
65
    public function include_behat_libraries() {
66
        global $CFG;
67
        if (!class_exists('Behat\Gherkin\Lexer')) {
68
            throw new \moodle_exception('Missing behat classes.');
69
        }
70
        // Behat utilities.
71
        require_once($CFG->libdir . '/behat/classes/util.php');
72
        require_once($CFG->libdir . '/behat/classes/behat_command.php');
73
        require_once($CFG->libdir . '/behat/behat_base.php');
74
        require_once("{$CFG->libdir}/tests/behat/behat_data_generators.php");
75
        return true;
76
    }
77
 
78
    /**
79
     * Load all generators.
80
     */
81
    private function load_generator() {
82
        $this->generator = new behat_data_generators();
83
        $this->validsteps = $this->scan_generator($this->generator);
84
    }
85
 
86
    /**
87
     * Scan a generator to get all valid steps.
88
     * @param behat_data_generators $generator the generator to scan.
89
     * @return array the valid steps.
90
     */
91
    private function scan_generator(behat_data_generators $generator): array {
92
        $result = [];
93
        $class = new ReflectionClass($generator);
94
        $methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
95
        foreach ($methods as $method) {
96
            $given = $this->get_method_given($method);
97
            if ($given) {
98
                $result[$given] = $method->getName();
99
            }
100
        }
101
        return $result;
102
    }
103
 
104
    /**
105
     * Get the given expression tag of a method.
106
     *
107
     * @param ReflectionMethod $method the method to get the given expression tag.
108
     * @return string|null the given expression tag or null if not found.
109
     */
110
    private function get_method_given(ReflectionMethod $method): ?string {
111
        $doccomment = $method->getDocComment();
112
        $doccomment = str_replace("\r\n", "\n", $doccomment);
113
        $doccomment = str_replace("\r", "\n", $doccomment);
114
        $doccomment = explode("\n", $doccomment);
115
        foreach ($doccomment as $line) {
116
            $matches = [];
117
            if (preg_match('/.*\@(given|when|then)\s+(.+)$/i', $line, $matches)) {
118
                return $matches[2];
119
            }
120
        }
121
        return null;
122
    }
123
 
124
    /**
125
     * Parse a feature file.
126
     * @param string $content the feature file content.
127
     * @return parsedfeature
128
     */
129
    public function parse_feature(string $content): parsedfeature {
130
        $result = new parsedfeature();
131
 
132
        $parser = $this->get_parser();
133
        $feature = $parser->parse($content);
134
 
135
        // No need for background in testing scenarios because scenarios can only contain generators.
136
        // In the future the background can be used to define clean up steps (when clean up methods
137
        // are implemented).
138
        if ($feature->hasScenarios()) {
139
            $scenarios = $feature->getScenarios();
140
            foreach ($scenarios as $scenario) {
141
                if ($scenario->getNodeType() == 'Outline') {
142
                    $result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
143
                    $result->add_error(get_string('testscenario_outline', 'tool_generator'));
144
                    continue;
145
                }
146
                $result->add_scenario($scenario->getNodeType(), $scenario->getTitle());
147
                $steps = $scenario->getSteps();
148
                foreach ($steps as $step) {
149
                    $result->add_step(new steprunner($this->generator, $this->validsteps, $step));
150
                }
151
            }
152
        }
153
        return $result;
154
    }
155
 
156
    /**
157
     * Get the parser.
158
     * @return Parser
159
     */
160
    private function get_parser(): Parser {
161
        $keywords = new ArrayKeywords([
162
            'en' => [
163
                'feature' => 'Feature',
164
                // If in the future we have clean up steps, background will be renamed to "Clean up".
165
                'background' => 'Background',
166
                'scenario' => 'Scenario',
167
                'scenario_outline' => 'Scenario Outline|Scenario Template',
168
                'examples' => 'Examples|Scenarios',
169
                'given' => 'Given',
170
                'when' => 'When',
171
                'then' => 'Then',
172
                'and' => 'And',
173
                'but' => 'But',
174
            ],
175
        ]);
176
        $lexer = new Lexer($keywords);
177
        $parser = new Parser($lexer);
178
        return $parser;
179
    }
180
 
181
    /**
182
     * Execute a parsed feature.
183
     * @param parsedfeature $parsedfeature the parsed feature to execute.
184
     * @return bool true if all steps were executed successfully.
185
     */
186
    public function execute(parsedfeature $parsedfeature): bool {
187
        if (!$parsedfeature->is_valid()) {
188
            return false;
189
        }
190
        $result = true;
191
        $steps = $parsedfeature->get_all_steps();
192
        foreach ($steps as $step) {
193
            $result = $step->execute() && $result;
194
        }
195
        return $result;
196
    }
197
}