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