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