]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - tests/parser/TestFileReader.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / tests / parser / TestFileReader.php
1 <?php
2 /**
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16  * http://www.gnu.org/copyleft/gpl.html
17  *
18  * @file
19  * @ingroup Testing
20  */
21
22 class TestFileReader {
23         private $file;
24         private $fh;
25         private $section = null;
26         /** String|null: current test section being analyzed */
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
260         /**
261          * Clear section name and its data
262          */
263         private function clearSection() {
264                 $this->sectionLineNum = [];
265                 $this->sectionData = [];
266                 $this->section = null;
267         }
268
269         /**
270          * Verify the current section data has some value for the given token
271          * name(s) (first parameter).
272          * Throw an exception if it is not set, referencing current section
273          * and adding the current file name and line number
274          *
275          * @param string|array $tokens Expected token(s) that should have been
276          * mentioned before closing this section
277          * @param bool $fatal True iff an exception should be thrown if
278          * the section is not found.
279          * @return bool|string
280          * @throws MWException
281          */
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 }