MediaWiki REL1_35
preprocessorFuzzTest.php
Go to the documentation of this file.
1<?php
25
26use Wikimedia\TestingAccessWrapper;
27
28$optionsWithoutArgs = [ 'verbose' ];
29require_once __DIR__ . '/commandLine.inc';
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
57 public function execute() {
58 if ( !file_exists( 'results' ) ) {
59 mkdir( 'results' );
60 }
61 if ( !is_dir( 'results' ) ) {
62 echo "Unable to create 'results' directory\n";
63 exit( 1 );
64 }
65 $overallStart = microtime( true );
66 $reportInterval = 1000;
67 // @phan-suppress-next-line PhanInfiniteLoop
68 for ( $i = 1; true; $i++ ) {
69 $t = -microtime( true );
70 try {
71 self::$currentTest = new PPFuzzTest( $this );
72 self::$currentTest->execute();
73 $passed = 'passed';
74 } catch ( Exception $e ) {
75 $testReport = self::$currentTest->getReport();
76 $exceptionReport = $e instanceof MWException ? $e->getText() : (string)$e;
77 $hash = md5( $testReport );
78 file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
79 file_put_contents( "results/ppft-$hash.fail",
80 "Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
81 print "Test $hash failed\n";
82 $passed = 'failed';
83 }
84 $t += microtime( true );
85
86 if ( $this->verbose ) {
87 printf( "Test $passed in %.3f seconds\n", $t );
88 print self::$currentTest->getReport();
89 }
90
91 $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
92 if ( $reportMetric > 25 ) {
93 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
94 $reportInterval /= 2;
95 } else {
96 $reportInterval /= 5;
97 }
98 } elseif ( $reportMetric < 4 ) {
99 if ( substr( $reportInterval, 0, 1 ) === '1' ) {
100 $reportInterval *= 5;
101 } else {
102 $reportInterval *= 2;
103 }
104 }
105 if ( $i % $reportInterval == 0 ) {
106 print "$i tests done\n";
107 /*
108 $testReport = self::$currentTest->getReport();
109 $filename = 'results/ppft-' . md5( $testReport ) . '.pass';
110 file_put_contents( $filename, "Input:\n$testReport\n" );*/
111 }
112 }
113 }
114
115 public function makeInputText( $max = false ) {
116 if ( $max === false ) {
117 $max = $this->maxLength;
118 }
119 $length = mt_rand( $this->minLength, $max );
120 $s = '';
121 for ( $i = 0; $i < $length; $i++ ) {
122 $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
123 $s .= $this->hairs[$hairIndex];
124 }
125 // Send through the UTF-8 normaliser
126 // This resolves a few differences between the old preprocessor and the
127 // XML-based one, which doesn't like illegals and converts line endings.
128 // It's done by the MW UI, so it's a reasonably legitimate thing to do.
129 $s = MediaWikiServices::getInstance()->getContentLanguage()->normalize( $s );
130
131 return $s;
132 }
133
134 public function makeTitle() {
135 return Title::newFromText( mt_rand( 0, 1000000 ), mt_rand( 0, 10 ) );
136 }
137
138 /*
139 public function pickOutputType() {
140 $count = count( $this->outputTypes );
141 return $this->outputTypes[ mt_rand( 0, $count - 1 ) ];
142 }*/
143
144 public function pickEntryPoint() {
145 $count = count( $this->entryPoints );
146
147 return $this->entryPoints[mt_rand( 0, $count - 1 )];
148 }
149}
150
158
160 private $parent;
162 public $nickname;
164 public $fancySig;
165
169 public function __construct( $tester ) {
170 global $wgMaxSigChars;
171 $this->parent = $tester;
172 $this->mainText = $tester->makeInputText();
173 $this->title = $tester->makeTitle();
174 // $this->outputType = $tester->pickOutputType();
175 $this->entryPoint = $tester->pickEntryPoint();
176 $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 );
177 $this->fancySig = (bool)mt_rand( 0, 1 );
178 $this->templates = [];
179 }
180
185 public function templateHook( $title ) {
186 $titleText = $title->getPrefixedDBkey();
187
188 if ( !isset( $this->templates[$titleText] ) ) {
189 $finalTitle = $title;
190 if ( count( $this->templates ) >= $this->parent->maxTemplates ) {
191 // Too many templates
192 $text = false;
193 } else {
194 if ( !mt_rand( 0, 1 ) ) {
195 // Redirect
196 $finalTitle = $this->parent->makeTitle();
197 }
198 if ( !mt_rand( 0, 5 ) ) {
199 // Doesn't exist
200 $text = false;
201 } else {
202 $text = $this->parent->makeInputText();
203 }
204 }
205 $this->templates[$titleText] = [
206 'text' => $text,
207 'finalTitle' => $finalTitle ];
208 }
209
210 return $this->templates[$titleText];
211 }
212
213 public function execute() {
214 global $wgUser;
215
216 $user = new PPFuzzUser;
217 $user->mName = 'Fuzz';
218 $user->mFrom = 'name';
219 $user->ppfz_test = $this;
220
221 $wgUser = $user;
222
223 $options = ParserOptions::newFromUser( $user );
224 $options->setTemplateCallback( [ $this, 'templateHook' ] );
225 $options->setTimestamp( wfTimestampNow() );
226 $this->output = call_user_func(
227 [ TestingAccessWrapper::newFromObject(
228 MediaWikiServices::getInstance()->getParser()
229 ), $this->entryPoint ],
230 $this->mainText,
231 $this->title,
232 $options
233 );
234
235 return $this->output;
236 }
237
238 public function getReport() {
239 $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" .
240 // "Output type: {$this->outputType}\n" .
241 "Entry point: {$this->entryPoint}\n" .
242 "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) .
243 ' ' . var_export( $this->nickname, true ) . "\n" .
244 "Main text: " . var_export( $this->mainText, true ) . "\n";
245 foreach ( $this->templates as $titleText => $template ) {
246 $finalTitle = $template['finalTitle'];
247 if ( $finalTitle != $titleText ) {
248 $s .= "[[$titleText]] -> [[$finalTitle]]: " . var_export( $template['text'], true ) . "\n";
249 } else {
250 $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
251 }
252 }
253 $s .= "Output: " . var_export( $this->output, true ) . "\n";
254
255 return $s;
256 }
257}
258
259class PPFuzzUser extends User {
261
262 public function load( $flags = null ) {
263 if ( $this->mDataLoaded ) {
264 return;
265 }
266 $this->mDataLoaded = true;
267 $this->loadDefaults( $this->mName );
268 }
269
270 public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
271 if ( $oname === 'fancysig' ) {
272 return $this->ppfz_test->fancySig;
273 } elseif ( $oname === 'nickname' ) {
274 return $this->ppfz_test->nickname;
275 } else {
276 return parent::getOption( $oname, $defaultOverride, $ignoreHidden );
277 }
278 }
279}
280
281ini_set( 'memory_limit', '50M' );
282if ( isset( $args[0] ) ) {
283 $testText = file_get_contents( $args[0] );
284 if ( !$testText ) {
285 print "File not found\n";
286 exit( 1 );
287 }
288 $test = unserialize( $testText );
289 $result = $test->execute();
290 print "Test passed.\n";
291} else {
292 $tester = new PPFuzzTester;
293 $tester->verbose = isset( $options['verbose'] );
294 $tester->execute();
295}
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.
array[] $templates
-var array<string,array{text:string|false,finalTitle:Title}>
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
loadDefaults( $name=false, $actorId=null)
Set cached properties to default.
Definition User.php:1162
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
if( $line===false) $args
Definition mcc.php:124
$tester verbose