MediaWiki REL1_37
preprocessorFuzzTest.php
Go to the documentation of this file.
1<?php
25
26use Wikimedia\TestingAccessWrapper;
27
28$optionsWithoutArgs = [ 'verbose' ];
29require_once __DIR__ . '/CommandLineInc.php';
30
32 public $hairs = [
33 '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
34 '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
35 '<!--', '-->',
36 "\n==", "==\n",
37 '|', '=', "\n", ' ', "\t", "\x7f",
38 '~~', '~~~', '~~~~', 'subst:',
39 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
40 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
41
42 // extensions
43 // '<ref>', '</ref>', '<references/>',
44 ];
45 public $minLength = 0;
46 public $maxLength = 20;
47 public $maxTemplates = 5;
48 // public $outputTypes = [ 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ];
49 public $entryPoints = [ 'fuzzTestSrvus', 'fuzzTestPst', 'fuzzTestPreprocess' ];
50 public $verbose = false;
51
55 private static $currentTest = false;
56
60 public function execute() {
61 if ( !file_exists( 'results' ) ) {
62 mkdir( 'results' );
63 }
64 if ( !is_dir( 'results' ) ) {
65 echo "Unable to create 'results' directory\n";
66 exit( 1 );
67 }
68 $overallStart = microtime( true );
69 $reportInterval = 1000;
70 // @phan-suppress-next-line PhanInfiniteLoop
71 for ( $i = 1; true; $i++ ) {
72 $t = -microtime( true );
73 try {
74 self::$currentTest = new PPFuzzTest( $this );
75 self::$currentTest->execute();
76 $passed = 'passed';
77 } catch ( Exception $e ) {
78 $testReport = self::$currentTest->getReport();
79 $exceptionReport = $e instanceof MWException ? $e->getText() : (string)$e;
80 $hash = md5( $testReport );
81 file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
82 file_put_contents( "results/ppft-$hash.fail",
83 "Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
84 print "Test $hash failed\n";
85 $passed = 'failed';
86 }
87 $t += microtime( true );
88
89 if ( $this->verbose ) {
90 printf( "Test $passed in %.3f seconds\n", $t );
91 print self::$currentTest->getReport();
92 }
93
94 $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
95 if ( $reportMetric > 25 ) {
96 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
97 $reportInterval /= 2;
98 } else {
99 $reportInterval /= 5;
100 }
101 } elseif ( $reportMetric < 4 ) {
102 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
103 $reportInterval *= 5;
104 } else {
105 $reportInterval *= 2;
106 }
107 }
108 if ( $i % $reportInterval == 0 ) {
109 print "$i tests done\n";
110 /*
111 $testReport = self::$currentTest->getReport();
112 $filename = 'results/ppft-' . md5( $testReport ) . '.pass';
113 file_put_contents( $filename, "Input:\n$testReport\n" );*/
114 }
115 }
116 }
117
118 public function makeInputText( $max = false ) {
119 if ( $max === false ) {
120 $max = $this->maxLength;
121 }
122 $length = mt_rand( $this->minLength, $max );
123 $s = '';
124 for ( $i = 0; $i < $length; $i++ ) {
125 $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
126 $s .= $this->hairs[$hairIndex];
127 }
128 // Send through the UTF-8 normaliser
129 // This resolves a few differences between the old preprocessor and the
130 // XML-based one, which doesn't like illegals and converts line endings.
131 // It's done by the MW UI, so it's a reasonably legitimate thing to do.
132 $s = MediaWikiServices::getInstance()->getContentLanguage()->normalize( $s );
133
134 return $s;
135 }
136
137 public function makeTitle() {
138 return Title::newFromText( mt_rand( 0, 1000000 ), mt_rand( 0, 10 ) );
139 }
140
141 /*
142 public function pickOutputType() {
143 $count = count( $this->outputTypes );
144 return $this->outputTypes[ mt_rand( 0, $count - 1 ) ];
145 }*/
146
147 public function pickEntryPoint() {
148 $count = count( $this->entryPoints );
149
150 return $this->entryPoints[mt_rand( 0, $count - 1 )];
151 }
152}
153
161
163 private $parent;
165 public $nickname;
167 public $fancySig;
168
172 public function __construct( $tester ) {
173 global $wgMaxSigChars;
174 $this->parent = $tester;
175 $this->mainText = $tester->makeInputText();
176 $this->title = $tester->makeTitle();
177 // $this->outputType = $tester->pickOutputType();
178 $this->entryPoint = $tester->pickEntryPoint();
179 $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 );
180 $this->fancySig = (bool)mt_rand( 0, 1 );
181 $this->templates = [];
182 }
183
188 public function templateHook( $title ) {
189 $titleText = $title->getPrefixedDBkey();
190
191 if ( !isset( $this->templates[$titleText] ) ) {
192 $finalTitle = $title;
193 if ( count( $this->templates ) >= $this->parent->maxTemplates ) {
194 // Too many templates
195 $text = false;
196 } else {
197 if ( !mt_rand( 0, 1 ) ) {
198 // Redirect
199 $finalTitle = $this->parent->makeTitle();
200 }
201 if ( !mt_rand( 0, 5 ) ) {
202 // Doesn't exist
203 $text = false;
204 } else {
205 $text = $this->parent->makeInputText();
206 }
207 }
208 $this->templates[$titleText] = [
209 'text' => $text,
210 'finalTitle' => $finalTitle ];
211 }
212
213 return $this->templates[$titleText];
214 }
215
216 public function execute() {
217 $user = new PPFuzzUser;
218 $user->mName = 'Fuzz';
219 $user->mFrom = 'name';
220 $user->ppfz_test = $this;
221
223
224 $options = ParserOptions::newFromUser( $user );
225 $options->setTemplateCallback( [ $this, 'templateHook' ] );
226 $options->setTimestamp( wfTimestampNow() );
227 $this->output = call_user_func(
228 [ TestingAccessWrapper::newFromObject(
229 MediaWikiServices::getInstance()->getParser()
230 ), $this->entryPoint ],
231 $this->mainText,
232 $this->title,
233 $options
234 );
235
236 return $this->output;
237 }
238
239 public function getReport() {
240 $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" .
241 // "Output type: {$this->outputType}\n" .
242 "Entry point: {$this->entryPoint}\n" .
243 "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) .
244 ' ' . var_export( $this->nickname, true ) . "\n" .
245 "Main text: " . var_export( $this->mainText, true ) . "\n";
246 foreach ( $this->templates as $titleText => $template ) {
247 $finalTitle = $template['finalTitle'];
248 if ( $finalTitle != $titleText ) {
249 $s .= "[[$titleText]] -> [[$finalTitle]]: " . var_export( $template['text'], true ) . "\n";
250 } else {
251 $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
252 }
253 }
254 $s .= "Output: " . var_export( $this->output, true ) . "\n";
255
256 return $s;
257 }
258}
259
260class PPFuzzUser extends User {
262
263 public function load( $flags = null ) {
264 if ( $this->mDataLoaded ) {
265 return;
266 }
267 $this->mDataLoaded = true;
268 $this->loadDefaults( $this->mName );
269 }
270
271 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
272 if ( $oname === 'fancysig' ) {
273 return $this->ppfz_test->fancySig;
274 } elseif ( $oname === 'nickname' ) {
275 return $this->ppfz_test->nickname;
276 } else {
277 return parent::getOption( $oname, $defaultOverride, $ignoreHidden );
278 }
279 }
280}
281
282ini_set( 'memory_limit', '50M' );
283if ( isset( $args[0] ) ) {
284 $testText = file_get_contents( $args[0] );
285 if ( !$testText ) {
286 print "File not found\n";
287 exit( 1 );
288 }
289 $test = unserialize( $testText );
290 $result = $test->execute();
291 print "Test passed.\n";
292} else {
293 $tester = new PPFuzzTester;
294 $tester->verbose = isset( $options['verbose'] );
295 $tester->execute();
296}
serialize()
unserialize( $serialized)
$wgMaxSigChars
Maximum number of Unicode characters in signature.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
MediaWiki exception.
getText()
Get the text to display when reporting the error on the command line.
MediaWikiServices is the service locator for the application scope of MediaWiki.
PPFuzzTester $parent
static bool PPFuzzTest $currentTest
makeInputText( $max=false)
load( $flags=null)
Load the user table data for this object from the source given by mFrom.
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
static setUser( $user)
Reset the stub global user to a different "real" user object, while ensuring that any method calls on...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:69
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition User.php:1206
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:69
if( $line===false) $args
Definition mcc.php:124
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
$tester verbose