MediaWiki
REL1_24
|
00001 <?php 00024 require_once __DIR__ . '/commandLine.inc'; 00025 00026 $wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook'; 00027 00028 class PPFuzzTester { 00029 public $hairs = array( 00030 '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}', 00031 '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>', 00032 '<!--', '-->', 00033 "\n==", "==\n", 00034 '|', '=', "\n", ' ', "\t", "\x7f", 00035 '~~', '~~~', '~~~~', 'subst:', 00036 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 00037 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 00038 00039 // extensions 00040 // '<ref>', '</ref>', '<references/>', 00041 ); 00042 public $minLength = 0; 00043 public $maxLength = 20; 00044 public $maxTemplates = 5; 00045 // public $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' ); 00046 public $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' ); 00047 public $verbose = false; 00048 00049 private static $currentTest = false; 00050 00051 function execute() { 00052 if ( !file_exists( 'results' ) ) { 00053 mkdir( 'results' ); 00054 } 00055 if ( !is_dir( 'results' ) ) { 00056 echo "Unable to create 'results' directory\n"; 00057 exit( 1 ); 00058 } 00059 $overallStart = microtime( true ); 00060 $reportInterval = 1000; 00061 for ( $i = 1; true; $i++ ) { 00062 $t = -microtime( true ); 00063 try { 00064 self::$currentTest = new PPFuzzTest( $this ); 00065 self::$currentTest->execute(); 00066 $passed = 'passed'; 00067 } catch ( MWException $e ) { 00068 $testReport = self::$currentTest->getReport(); 00069 $exceptionReport = $e->getText(); 00070 $hash = md5( $testReport ); 00071 file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) ); 00072 file_put_contents( "results/ppft-$hash.fail", 00073 "Input:\n$testReport\n\nException report:\n$exceptionReport\n" ); 00074 print "Test $hash failed\n"; 00075 $passed = 'failed'; 00076 } 00077 $t += microtime( true ); 00078 00079 if ( $this->verbose ) { 00080 printf( "Test $passed in %.3f seconds\n", $t ); 00081 print self::$currentTest->getReport(); 00082 } 00083 00084 $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval; 00085 if ( $reportMetric > 25 ) { 00086 if ( substr( $reportInterval, 0, 1 ) === '1' ) { 00087 $reportInterval /= 2; 00088 } else { 00089 $reportInterval /= 5; 00090 } 00091 } elseif ( $reportMetric < 4 ) { 00092 if ( substr( $reportInterval, 0, 1 ) === '1' ) { 00093 $reportInterval *= 5; 00094 } else { 00095 $reportInterval *= 2; 00096 } 00097 } 00098 if ( $i % $reportInterval == 0 ) { 00099 print "$i tests done\n"; 00100 /* 00101 $testReport = self::$currentTest->getReport(); 00102 $filename = 'results/ppft-' . md5( $testReport ) . '.pass'; 00103 file_put_contents( $filename, "Input:\n$testReport\n" );*/ 00104 } 00105 } 00106 } 00107 00108 function makeInputText( $max = false ) { 00109 if ( $max === false ) { 00110 $max = $this->maxLength; 00111 } 00112 $length = mt_rand( $this->minLength, $max ); 00113 $s = ''; 00114 for ( $i = 0; $i < $length; $i++ ) { 00115 $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 ); 00116 $s .= $this->hairs[$hairIndex]; 00117 } 00118 // Send through the UTF-8 normaliser 00119 // This resolves a few differences between the old preprocessor and the 00120 // XML-based one, which doesn't like illegals and converts line endings. 00121 // It's done by the MW UI, so it's a reasonably legitimate thing to do. 00122 global $wgContLang; 00123 $s = $wgContLang->normalize( $s ); 00124 00125 return $s; 00126 } 00127 00128 function makeTitle() { 00129 return Title::newFromText( mt_rand( 0, 1000000 ), mt_rand( 0, 10 ) ); 00130 } 00131 00132 /* 00133 function pickOutputType() { 00134 $count = count( $this->outputTypes ); 00135 return $this->outputTypes[ mt_rand( 0, $count - 1 ) ]; 00136 }*/ 00137 00138 function pickEntryPoint() { 00139 $count = count( $this->entryPoints ); 00140 00141 return $this->entryPoints[mt_rand( 0, $count - 1 )]; 00142 } 00143 } 00144 00145 class PPFuzzTest { 00146 public $templates, $mainText, $title, $entryPoint, $output; 00147 00148 function __construct( $tester ) { 00149 global $wgMaxSigChars; 00150 $this->parent = $tester; 00151 $this->mainText = $tester->makeInputText(); 00152 $this->title = $tester->makeTitle(); 00153 // $this->outputType = $tester->pickOutputType(); 00154 $this->entryPoint = $tester->pickEntryPoint(); 00155 $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 ); 00156 $this->fancySig = (bool)mt_rand( 0, 1 ); 00157 $this->templates = array(); 00158 } 00159 00164 function templateHook( $title ) { 00165 $titleText = $title->getPrefixedDBkey(); 00166 00167 if ( !isset( $this->templates[$titleText] ) ) { 00168 $finalTitle = $title; 00169 if ( count( $this->templates ) >= $this->parent->maxTemplates ) { 00170 // Too many templates 00171 $text = false; 00172 } else { 00173 if ( !mt_rand( 0, 1 ) ) { 00174 // Redirect 00175 $finalTitle = $this->parent->makeTitle(); 00176 } 00177 if ( !mt_rand( 0, 5 ) ) { 00178 // Doesn't exist 00179 $text = false; 00180 } else { 00181 $text = $this->parent->makeInputText(); 00182 } 00183 } 00184 $this->templates[$titleText] = array( 00185 'text' => $text, 00186 'finalTitle' => $finalTitle ); 00187 } 00188 00189 return $this->templates[$titleText]; 00190 } 00191 00192 function execute() { 00193 global $wgParser, $wgUser; 00194 00195 $wgUser = new PPFuzzUser; 00196 $wgUser->mName = 'Fuzz'; 00197 $wgUser->mFrom = 'name'; 00198 $wgUser->ppfz_test = $this; 00199 00200 $options = ParserOptions::newFromUser( $wgUser ); 00201 $options->setTemplateCallback( array( $this, 'templateHook' ) ); 00202 $options->setTimestamp( wfTimestampNow() ); 00203 $this->output = call_user_func( 00204 array( $wgParser, $this->entryPoint ), 00205 $this->mainText, 00206 $this->title, 00207 $options 00208 ); 00209 00210 return $this->output; 00211 } 00212 00213 function getReport() { 00214 $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" . 00215 // "Output type: {$this->outputType}\n" . 00216 "Entry point: {$this->entryPoint}\n" . 00217 "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . 00218 ' ' . var_export( $this->nickname, true ) . "\n" . 00219 "Main text: " . var_export( $this->mainText, true ) . "\n"; 00220 foreach ( $this->templates as $titleText => $template ) { 00221 $finalTitle = $template['finalTitle']; 00222 if ( $finalTitle != $titleText ) { 00223 $s .= "[[$titleText]] -> [[$finalTitle]]: " . var_export( $template['text'], true ) . "\n"; 00224 } else { 00225 $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n"; 00226 } 00227 } 00228 $s .= "Output: " . var_export( $this->output, true ) . "\n"; 00229 00230 return $s; 00231 } 00232 } 00233 00234 class PPFuzzUser extends User { 00235 public $ppfz_test, $mDataLoaded; 00236 00237 function load() { 00238 if ( $this->mDataLoaded ) { 00239 return; 00240 } 00241 $this->mDataLoaded = true; 00242 $this->loadDefaults( $this->mName ); 00243 } 00244 00245 function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) { 00246 if ( $oname === 'fancysig' ) { 00247 return $this->ppfz_test->fancySig; 00248 } elseif ( $oname === 'nickname' ) { 00249 return $this->ppfz_test->nickname; 00250 } else { 00251 return parent::getOption( $oname, $defaultOverride, $ignoreHidden ); 00252 } 00253 } 00254 } 00255 00256 ini_set( 'memory_limit', '50M' ); 00257 if ( isset( $args[0] ) ) { 00258 $testText = file_get_contents( $args[0] ); 00259 if ( !$testText ) { 00260 print "File not found\n"; 00261 exit( 1 ); 00262 } 00263 $test = unserialize( $testText ); 00264 $result = $test->execute(); 00265 print "Test passed.\n"; 00266 } else { 00267 $tester = new PPFuzzTester; 00268 $tester->verbose = isset( $options['verbose'] ); 00269 $tester->execute(); 00270 }