]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - maintenance/preprocessorFuzzTest.php
MediaWiki 1.30.2-scripts2
[autoinstallsdev/mediawiki.git] / maintenance / preprocessorFuzzTest.php
1 <?php
2 /**
3  * Performs fuzz-style testing of MediaWiki's preprocessor.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  * @ingroup Maintenance
22  */
23
24 $optionsWithoutArgs = [ 'verbose' ];
25 require_once __DIR__ . '/commandLine.inc';
26
27 $wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook';
28
29 class PPFuzzTester {
30         public $hairs = [
31                 '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
32                 '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
33                 '<!--', '-->',
34                 "\n==", "==\n",
35                 '|', '=', "\n", ' ', "\t", "\x7f",
36                 '~~', '~~~', '~~~~', 'subst:',
37                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
38                 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
39
40                 // extensions
41                 // '<ref>', '</ref>', '<references/>',
42         ];
43         public $minLength = 0;
44         public $maxLength = 20;
45         public $maxTemplates = 5;
46         // public $outputTypes = [ 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ];
47         public $entryPoints = [ 'testSrvus', 'testPst', 'testPreprocess' ];
48         public $verbose = false;
49
50         /**
51          * @var bool|PPFuzzTest
52          */
53         private static $currentTest = false;
54
55         function execute() {
56                 if ( !file_exists( 'results' ) ) {
57                         mkdir( 'results' );
58                 }
59                 if ( !is_dir( 'results' ) ) {
60                         echo "Unable to create 'results' directory\n";
61                         exit( 1 );
62                 }
63                 $overallStart = microtime( true );
64                 $reportInterval = 1000;
65                 for ( $i = 1; true; $i++ ) {
66                         $t = -microtime( true );
67                         try {
68                                 self::$currentTest = new PPFuzzTest( $this );
69                                 self::$currentTest->execute();
70                                 $passed = 'passed';
71                         } catch ( Exception $e ) {
72                                 $testReport = self::$currentTest->getReport();
73                                 $exceptionReport = $e->getText();
74                                 $hash = md5( $testReport );
75                                 file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
76                                 file_put_contents( "results/ppft-$hash.fail",
77                                         "Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
78                                 print "Test $hash failed\n";
79                                 $passed = 'failed';
80                         }
81                         $t += microtime( true );
82
83                         if ( $this->verbose ) {
84                                 printf( "Test $passed in %.3f seconds\n", $t );
85                                 print self::$currentTest->getReport();
86                         }
87
88                         $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
89                         if ( $reportMetric > 25 ) {
90                                 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
91                                         $reportInterval /= 2;
92                                 } else {
93                                         $reportInterval /= 5;
94                                 }
95                         } elseif ( $reportMetric < 4 ) {
96                                 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
97                                         $reportInterval *= 5;
98                                 } else {
99                                         $reportInterval *= 2;
100                                 }
101                         }
102                         if ( $i % $reportInterval == 0 ) {
103                                 print "$i tests done\n";
104                                 /*
105                                 $testReport = self::$currentTest->getReport();
106                                 $filename = 'results/ppft-' . md5( $testReport ) . '.pass';
107                                 file_put_contents( $filename, "Input:\n$testReport\n" );*/
108                         }
109                 }
110         }
111
112         function makeInputText( $max = false ) {
113                 if ( $max === false ) {
114                         $max = $this->maxLength;
115                 }
116                 $length = mt_rand( $this->minLength, $max );
117                 $s = '';
118                 for ( $i = 0; $i < $length; $i++ ) {
119                         $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
120                         $s .= $this->hairs[$hairIndex];
121                 }
122                 // Send through the UTF-8 normaliser
123                 // This resolves a few differences between the old preprocessor and the
124                 // XML-based one, which doesn't like illegals and converts line endings.
125                 // It's done by the MW UI, so it's a reasonably legitimate thing to do.
126                 global $wgContLang;
127                 $s = $wgContLang->normalize( $s );
128
129                 return $s;
130         }
131
132         function makeTitle() {
133                 return Title::newFromText( mt_rand( 0, 1000000 ), mt_rand( 0, 10 ) );
134         }
135
136         /*
137         function pickOutputType() {
138                 $count = count( $this->outputTypes );
139                 return $this->outputTypes[ mt_rand( 0, $count - 1 ) ];
140         }*/
141
142         function pickEntryPoint() {
143                 $count = count( $this->entryPoints );
144
145                 return $this->entryPoints[mt_rand( 0, $count - 1 )];
146         }
147 }
148
149 class PPFuzzTest {
150         public $templates, $mainText, $title, $entryPoint, $output;
151
152         function __construct( $tester ) {
153                 global $wgMaxSigChars;
154                 $this->parent = $tester;
155                 $this->mainText = $tester->makeInputText();
156                 $this->title = $tester->makeTitle();
157                 // $this->outputType = $tester->pickOutputType();
158                 $this->entryPoint = $tester->pickEntryPoint();
159                 $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 );
160                 $this->fancySig = (bool)mt_rand( 0, 1 );
161                 $this->templates = [];
162         }
163
164         /**
165          * @param Title $title
166          * @return array
167          */
168         function templateHook( $title ) {
169                 $titleText = $title->getPrefixedDBkey();
170
171                 if ( !isset( $this->templates[$titleText] ) ) {
172                         $finalTitle = $title;
173                         if ( count( $this->templates ) >= $this->parent->maxTemplates ) {
174                                 // Too many templates
175                                 $text = false;
176                         } else {
177                                 if ( !mt_rand( 0, 1 ) ) {
178                                         // Redirect
179                                         $finalTitle = $this->parent->makeTitle();
180                                 }
181                                 if ( !mt_rand( 0, 5 ) ) {
182                                         // Doesn't exist
183                                         $text = false;
184                                 } else {
185                                         $text = $this->parent->makeInputText();
186                                 }
187                         }
188                         $this->templates[$titleText] = [
189                                 'text' => $text,
190                                 'finalTitle' => $finalTitle ];
191                 }
192
193                 return $this->templates[$titleText];
194         }
195
196         function execute() {
197                 global $wgParser, $wgUser;
198
199                 $wgUser = new PPFuzzUser;
200                 $wgUser->mName = 'Fuzz';
201                 $wgUser->mFrom = 'name';
202                 $wgUser->ppfz_test = $this;
203
204                 $options = ParserOptions::newFromUser( $wgUser );
205                 $options->setTemplateCallback( [ $this, 'templateHook' ] );
206                 $options->setTimestamp( wfTimestampNow() );
207                 $this->output = call_user_func(
208                         [ $wgParser, $this->entryPoint ],
209                         $this->mainText,
210                         $this->title,
211                         $options
212                 );
213
214                 return $this->output;
215         }
216
217         function getReport() {
218                 $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" .
219 //                      "Output type: {$this->outputType}\n" .
220                         "Entry point: {$this->entryPoint}\n" .
221                         "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) .
222                         ' ' . var_export( $this->nickname, true ) . "\n" .
223                         "Main text: " . var_export( $this->mainText, true ) . "\n";
224                 foreach ( $this->templates as $titleText => $template ) {
225                         $finalTitle = $template['finalTitle'];
226                         if ( $finalTitle != $titleText ) {
227                                 $s .= "[[$titleText]] -> [[$finalTitle]]: " . var_export( $template['text'], true ) . "\n";
228                         } else {
229                                 $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
230                         }
231                 }
232                 $s .= "Output: " . var_export( $this->output, true ) . "\n";
233
234                 return $s;
235         }
236 }
237
238 class PPFuzzUser extends User {
239         public $ppfz_test, $mDataLoaded;
240
241         function load() {
242                 if ( $this->mDataLoaded ) {
243                         return;
244                 }
245                 $this->mDataLoaded = true;
246                 $this->loadDefaults( $this->mName );
247         }
248
249         function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
250                 if ( $oname === 'fancysig' ) {
251                         return $this->ppfz_test->fancySig;
252                 } elseif ( $oname === 'nickname' ) {
253                         return $this->ppfz_test->nickname;
254                 } else {
255                         return parent::getOption( $oname, $defaultOverride, $ignoreHidden );
256                 }
257         }
258 }
259
260 ini_set( 'memory_limit', '50M' );
261 if ( isset( $args[0] ) ) {
262         $testText = file_get_contents( $args[0] );
263         if ( !$testText ) {
264                 print "File not found\n";
265                 exit( 1 );
266         }
267         $test = unserialize( $testText );
268         $result = $test->execute();
269         print "Test passed.\n";
270 } else {
271         $tester = new PPFuzzTester;
272         $tester->verbose = isset( $options['verbose'] );
273         $tester->execute();
274 }