MediaWiki REL1_31
TestFileReader.php
Go to the documentation of this file.
1<?php
23 private $file;
24 private $fh;
25 private $section = null;
27 private $sectionData = [];
28 private $sectionLineNum = [];
29 private $lineNum = 0;
30 private $runDisabled;
31 private $runParsoid;
32 private $regex;
33
34 private $articles = [];
35 private $requirements = [];
36 private $tests = [];
37
38 public static function read( $file, array $options = [] ) {
39 $reader = new self( $file, $options );
40 $reader->execute();
41
42 $requirements = [];
43 foreach ( $reader->requirements as $type => $reqsOfType ) {
44 foreach ( $reqsOfType as $name => $unused ) {
45 $requirements[] = [
46 'type' => $type,
47 'name' => $name
48 ];
49 }
50 }
51
52 return [
53 'requirements' => $requirements,
54 'tests' => $reader->tests,
55 'articles' => $reader->articles
56 ];
57 }
58
59 private function __construct( $file, $options ) {
60 $this->file = $file;
61 $this->fh = fopen( $this->file, "rt" );
62
63 if ( !$this->fh ) {
64 throw new MWException( "Couldn't open file '$file'\n" );
65 }
66
67 $options = $options + [
68 'runDisabled' => false,
69 'runParsoid' => false,
70 'regex' => '//',
71 ];
72 $this->runDisabled = $options['runDisabled'];
73 $this->runParsoid = $options['runParsoid'];
74 $this->regex = $options['regex'];
75 }
76
77 private function addCurrentTest() {
78 // "input" and "result" are old section names allowed
79 // for backwards-compatibility.
80 $input = $this->checkSection( [ 'wikitext', 'input' ], false );
81 $nonTidySection = $this->checkSection(
82 [ 'html/php', 'html/*', 'html', 'result' ], false );
83 // Some tests have "with tidy" and "without tidy" variants
84 $tidySection = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
85
86 // Remove trailing newline
87 $data = array_map( 'ParserTestRunner::chomp', $this->sectionData );
88
89 // Apply defaults
90 $data += [
91 'options' => '',
92 'config' => ''
93 ];
94
95 if ( $input === false ) {
96 throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
97 "lacks input section" );
98 }
99
100 if ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->runDisabled ) {
101 // Disabled
102 return;
103 }
104
105 if ( $tidySection === false && $nonTidySection === false ) {
106 if ( isset( $data['html/parsoid'] ) || isset( $data['wikitext/edited'] ) ) {
107 // Parsoid only
108 return;
109 } else {
110 throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
111 "lacks result section" );
112 }
113 }
114
115 if ( preg_match( '/\\bparsoid\\b/i', $data['options'] ) && $nonTidySection === 'html'
116 && !$this->runParsoid
117 ) {
118 // A test which normally runs on Parsoid but can optionally be run with MW
119 return;
120 }
121
122 if ( !preg_match( $this->regex, $data['test'] ) ) {
123 // Filtered test
124 return;
125 }
126
127 $commonInfo = [
128 'test' => $data['test'],
129 'desc' => $data['test'],
130 'input' => $data[$input],
131 'options' => $data['options'],
132 'config' => $data['config'],
133 'line' => $this->sectionLineNum['test'],
134 'file' => $this->file
135 ];
136
137 if ( $nonTidySection !== false ) {
138 // Add non-tidy test
139 $this->tests[] = [
140 'result' => $data[$nonTidySection],
141 'resultSection' => $nonTidySection
142 ] + $commonInfo;
143
144 if ( $tidySection !== false ) {
145 // Add tidy subtest
146 $this->tests[] = [
147 'desc' => $data['test'] . ' (with tidy)',
148 'result' => $data[$tidySection],
149 'resultSection' => $tidySection,
150 'options' => $data['options'] . ' tidy',
151 'isSubtest' => true,
152 ] + $commonInfo;
153 }
154 } elseif ( $tidySection !== false ) {
155 // No need to override desc when there is no subtest
156 $this->tests[] = [
157 'result' => $data[$tidySection],
158 'resultSection' => $tidySection,
159 'options' => $data['options'] . ' tidy'
160 ] + $commonInfo;
161 } else {
162 throw new MWException( "Test at {$this->file}:{$this->sectionLineNum['test']} " .
163 "lacks result section" );
164 }
165 }
166
167 private function execute() {
168 while ( false !== ( $line = fgets( $this->fh ) ) ) {
169 $this->lineNum++;
170 $matches = [];
171
172 if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
173 $this->section = strtolower( $matches[1] );
174
175 if ( $this->section == 'endarticle' ) {
176 $this->checkSection( 'text' );
177 $this->checkSection( 'article' );
178
179 $this->addArticle(
180 ParserTestRunner::chomp( $this->sectionData['article'] ),
181 $this->sectionData['text'], $this->lineNum );
182
183 $this->clearSection();
184
185 continue;
186 }
187
188 if ( $this->section == 'endhooks' ) {
189 $this->checkSection( 'hooks' );
190
191 foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
192 $line = trim( $line );
193
194 if ( $line ) {
195 $this->addRequirement( 'hook', $line );
196 }
197 }
198
199 $this->clearSection();
200
201 continue;
202 }
203
204 if ( $this->section == 'endfunctionhooks' ) {
205 $this->checkSection( 'functionhooks' );
206
207 foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
208 $line = trim( $line );
209
210 if ( $line ) {
211 $this->addRequirement( 'functionHook', $line );
212 }
213 }
214
215 $this->clearSection();
216
217 continue;
218 }
219
220 if ( $this->section == 'endtransparenthooks' ) {
221 $this->checkSection( 'transparenthooks' );
222
223 foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
224 $line = trim( $line );
225
226 if ( $line ) {
227 $this->addRequirement( 'transparentHook', $line );
228 }
229 }
230
231 $this->clearSection();
232
233 continue;
234 }
235
236 if ( $this->section == 'end' ) {
237 $this->checkSection( 'test' );
238 $this->addCurrentTest();
239 $this->clearSection();
240 continue;
241 }
242
243 if ( isset( $this->sectionData[$this->section] ) ) {
244 throw new MWException( "duplicate section '$this->section' "
245 . "at line {$this->lineNum} of $this->file\n" );
246 }
247
248 $this->sectionLineNum[$this->section] = $this->lineNum;
249 $this->sectionData[$this->section] = '';
250
251 continue;
252 }
253
254 if ( $this->section ) {
255 $this->sectionData[$this->section] .= $line;
256 }
257 }
258 }
259
263 private function clearSection() {
264 $this->sectionLineNum = [];
265 $this->sectionData = [];
266 $this->section = null;
267 }
268
282 private function checkSection( $tokens, $fatal = true ) {
283 if ( is_null( $this->section ) ) {
284 throw new MWException( __METHOD__ . " can not verify a null section!\n" );
285 }
286 if ( !is_array( $tokens ) ) {
287 $tokens = [ $tokens ];
288 }
289 if ( count( $tokens ) == 0 ) {
290 throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
291 }
292
293 $data = $this->sectionData;
294 $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
295 return isset( $data[$token] );
296 } );
297
298 if ( count( $tokens ) == 0 ) {
299 if ( !$fatal ) {
300 return false;
301 }
302 throw new MWException( sprintf(
303 "'%s' without '%s' at line %s of %s\n",
304 $this->section,
305 implode( ',', $tokens ),
306 $this->lineNum,
307 $this->file
308 ) );
309 }
310 if ( count( $tokens ) > 1 ) {
311 throw new MWException( sprintf(
312 "'%s' with unexpected tokens '%s' at line %s of %s\n",
313 $this->section,
314 implode( ',', $tokens ),
315 $this->lineNum,
316 $this->file
317 ) );
318 }
319
320 return array_values( $tokens )[0];
321 }
322
323 private function addArticle( $name, $text, $line ) {
324 $this->articles[] = [
325 'name' => $name,
326 'text' => $text,
327 'line' => $line,
328 'file' => $this->file
329 ];
330 }
331
332 private function addRequirement( $type, $name ) {
333 $this->requirements[$type][$name] = true;
334 }
335}
$line
Definition cdb.php:59
MediaWiki exception.
static chomp( $s)
Remove last character if it is a newline.
addArticle( $name, $text, $line)
clearSection()
Clear section name and its data.
__construct( $file, $options)
$sectionData
String|null: current test section being analyzed.
checkSection( $tokens, $fatal=true)
Verify the current section data has some value for the given token name(s) (first parameter).
addRequirement( $type, $name)
static read( $file, array $options=[])
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2001
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
$tokens
if(is_array($mode)) switch( $mode) $input