PageRenderTime 145ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/maintenance/preprocessorFuzzTest.php

https://bitbucket.org/ghostfreeman/freeside-wiki
PHP | 257 lines | 189 code | 23 blank | 45 comment | 28 complexity | 0fdce510c3b5c85dfd8855339c2d6460 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * Performs fuzz-style testing of MediaWiki's preprocessor.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Maintenance
  22. */
  23. require_once( __DIR__ . '/commandLine.inc' );
  24. $wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PPFuzzTester::templateHook';
  25. class PPFuzzTester {
  26. var $hairs = array(
  27. '[[', ']]', '{{', '{{', '}}', '}}', '{{{', '}}}',
  28. '<', '>', '<nowiki', '<gallery', '</nowiki>', '</gallery>', '<nOwIkI>', '</NoWiKi>',
  29. '<!--' , '-->',
  30. "\n==", "==\n",
  31. '|', '=', "\n", ' ', "\t", "\x7f",
  32. '~~', '~~~', '~~~~', 'subst:',
  33. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
  34. 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
  35. // extensions
  36. // '<ref>', '</ref>', '<references/>',
  37. );
  38. var $minLength = 0;
  39. var $maxLength = 20;
  40. var $maxTemplates = 5;
  41. // var $outputTypes = array( 'OT_HTML', 'OT_WIKI', 'OT_PREPROCESS' );
  42. var $entryPoints = array( 'testSrvus', 'testPst', 'testPreprocess' );
  43. var $verbose = false;
  44. static $currentTest = false;
  45. function execute() {
  46. if ( !file_exists( 'results' ) ) {
  47. mkdir( 'results' );
  48. }
  49. if ( !is_dir( 'results' ) ) {
  50. echo "Unable to create 'results' directory\n";
  51. exit( 1 );
  52. }
  53. $overallStart = microtime( true );
  54. $reportInterval = 1000;
  55. for ( $i = 1; true; $i++ ) {
  56. $t = -microtime( true );
  57. try {
  58. self::$currentTest = new PPFuzzTest( $this );
  59. self::$currentTest->execute();
  60. $passed = 'passed';
  61. } catch ( MWException $e ) {
  62. $testReport = self::$currentTest->getReport();
  63. $exceptionReport = $e->getText();
  64. $hash = md5( $testReport );
  65. file_put_contents( "results/ppft-$hash.in", serialize( self::$currentTest ) );
  66. file_put_contents( "results/ppft-$hash.fail",
  67. "Input:\n$testReport\n\nException report:\n$exceptionReport\n" );
  68. print "Test $hash failed\n";
  69. $passed = 'failed';
  70. }
  71. $t += microtime( true );
  72. if ( $this->verbose ) {
  73. printf( "Test $passed in %.3f seconds\n", $t );
  74. print self::$currentTest->getReport();
  75. }
  76. $reportMetric = ( microtime( true ) - $overallStart ) / $i * $reportInterval;
  77. if ( $reportMetric > 25 ) {
  78. if ( substr( $reportInterval, 0, 1 ) === '1' ) {
  79. $reportInterval /= 2;
  80. } else {
  81. $reportInterval /= 5;
  82. }
  83. } elseif ( $reportMetric < 4 ) {
  84. if ( substr( $reportInterval, 0, 1 ) === '1' ) {
  85. $reportInterval *= 5;
  86. } else {
  87. $reportInterval *= 2;
  88. }
  89. }
  90. if ( $i % $reportInterval == 0 ) {
  91. print "$i tests done\n";
  92. /*
  93. $testReport = self::$currentTest->getReport();
  94. $filename = 'results/ppft-' . md5( $testReport ) . '.pass';
  95. file_put_contents( $filename, "Input:\n$testReport\n" );*/
  96. }
  97. }
  98. }
  99. function makeInputText( $max = false ) {
  100. if ( $max === false ) {
  101. $max = $this->maxLength;
  102. }
  103. $length = mt_rand( $this->minLength, $max );
  104. $s = '';
  105. for ( $i = 0; $i < $length; $i++ ) {
  106. $hairIndex = mt_rand( 0, count( $this->hairs ) - 1 );
  107. $s .= $this->hairs[$hairIndex];
  108. }
  109. // Send through the UTF-8 normaliser
  110. // This resolves a few differences between the old preprocessor and the
  111. // XML-based one, which doesn't like illegals and converts line endings.
  112. // It's done by the MW UI, so it's a reasonably legitimate thing to do.
  113. global $wgContLang;
  114. $s = $wgContLang->normalize( $s );
  115. return $s;
  116. }
  117. function makeTitle() {
  118. return Title::newFromText( mt_rand( 0, 1000000 ), mt_rand( 0, 10 ) );
  119. }
  120. /*
  121. function pickOutputType() {
  122. $count = count( $this->outputTypes );
  123. return $this->outputTypes[ mt_rand( 0, $count - 1 ) ];
  124. }*/
  125. function pickEntryPoint() {
  126. $count = count( $this->entryPoints );
  127. return $this->entryPoints[ mt_rand( 0, $count - 1 ) ];
  128. }
  129. }
  130. class PPFuzzTest {
  131. var $templates, $mainText, $title, $entryPoint, $output;
  132. function __construct( $tester ) {
  133. global $wgMaxSigChars;
  134. $this->parent = $tester;
  135. $this->mainText = $tester->makeInputText();
  136. $this->title = $tester->makeTitle();
  137. // $this->outputType = $tester->pickOutputType();
  138. $this->entryPoint = $tester->pickEntryPoint();
  139. $this->nickname = $tester->makeInputText( $wgMaxSigChars + 10 );
  140. $this->fancySig = (bool)mt_rand( 0, 1 );
  141. $this->templates = array();
  142. }
  143. /**
  144. * @param $title Title
  145. */
  146. function templateHook( $title ) {
  147. $titleText = $title->getPrefixedDBkey();
  148. if ( !isset( $this->templates[$titleText] ) ) {
  149. $finalTitle = $title;
  150. if ( count( $this->templates ) >= $this->parent->maxTemplates ) {
  151. // Too many templates
  152. $text = false;
  153. } else {
  154. if ( !mt_rand( 0, 1 ) ) {
  155. // Redirect
  156. $finalTitle = $this->parent->makeTitle();
  157. }
  158. if ( !mt_rand( 0, 5 ) ) {
  159. // Doesn't exist
  160. $text = false;
  161. } else {
  162. $text = $this->parent->makeInputText();
  163. }
  164. }
  165. $this->templates[$titleText] = array(
  166. 'text' => $text,
  167. 'finalTitle' => $finalTitle );
  168. }
  169. return $this->templates[$titleText];
  170. }
  171. function execute() {
  172. global $wgParser, $wgUser;
  173. $wgUser = new PPFuzzUser;
  174. $wgUser->mName = 'Fuzz';
  175. $wgUser->mFrom = 'name';
  176. $wgUser->ppfz_test = $this;
  177. $options = ParserOptions::newFromUser( $wgUser );
  178. $options->setTemplateCallback( array( $this, 'templateHook' ) );
  179. $options->setTimestamp( wfTimestampNow() );
  180. $this->output = call_user_func( array( $wgParser, $this->entryPoint ), $this->mainText, $this->title, $options );
  181. return $this->output;
  182. }
  183. function getReport() {
  184. $s = "Title: " . $this->title->getPrefixedDBkey() . "\n" .
  185. // "Output type: {$this->outputType}\n" .
  186. "Entry point: {$this->entryPoint}\n" .
  187. "User: " . ( $this->fancySig ? 'fancy' : 'no-fancy' ) . ' ' . var_export( $this->nickname, true ) . "\n" .
  188. "Main text: " . var_export( $this->mainText, true ) . "\n";
  189. foreach ( $this->templates as $titleText => $template ) {
  190. $finalTitle = $template['finalTitle'];
  191. if ( $finalTitle != $titleText ) {
  192. $s .= "[[$titleText]] -> [[$finalTitle]]: " . var_export( $template['text'], true ) . "\n";
  193. } else {
  194. $s .= "[[$titleText]]: " . var_export( $template['text'], true ) . "\n";
  195. }
  196. }
  197. $s .= "Output: " . var_export( $this->output, true ) . "\n";
  198. return $s;
  199. }
  200. }
  201. class PPFuzzUser extends User {
  202. var $ppfz_test, $mDataLoaded;
  203. function load() {
  204. if ( $this->mDataLoaded ) {
  205. return;
  206. }
  207. $this->mDataLoaded = true;
  208. $this->loadDefaults( $this->mName );
  209. }
  210. function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
  211. if ( $oname === 'fancysig' ) {
  212. return $this->ppfz_test->fancySig;
  213. } elseif ( $oname === 'nickname' ) {
  214. return $this->ppfz_test->nickname;
  215. } else {
  216. return parent::getOption( $oname, $defaultOverride, $ignoreHidden );
  217. }
  218. }
  219. }
  220. ini_set( 'memory_limit', '50M' );
  221. if ( isset( $args[0] ) ) {
  222. $testText = file_get_contents( $args[0] );
  223. if ( !$testText ) {
  224. print "File not found\n";
  225. exit( 1 );
  226. }
  227. $test = unserialize( $testText );
  228. $result = $test->execute();
  229. print "Test passed.\n";
  230. } else {
  231. $tester = new PPFuzzTester;
  232. $tester->verbose = isset( $options['verbose'] );
  233. $tester->execute();
  234. }