3 require __DIR__
. '/../../maintenance/Maintenance.php';
5 // Make RequestContext::resetMain() happy
6 define( 'MW_PARSER_TEST', 1 );
8 class ParserFuzzTest
extends Maintenance
{
10 private $maxFuzzTestLength = 300;
11 private $memoryLimit = 100;
14 function __construct() {
15 parent
::__construct();
16 $this->addDescription( 'Run a fuzz test on the parser, until it segfaults ' .
17 'or throws an exception' );
18 $this->addOption( 'file', 'Use the specified file as a dictionary, ' .
19 ' or leave blank to use parserTests.txt', false, true, true );
21 $this->addOption( 'seed', 'Start the fuzz test from the specified seed', false, true );
24 function finalSetup() {
25 require_once __DIR__
. '/../TestsAutoLoader.php';
29 $files = $this->getOption( 'file', [ __DIR__
. '/parserTests.txt' ] );
30 $this->seed
= intval( $this->getOption( 'seed', 1 ) ) - 1;
31 $this->parserTest
= new ParserTest
;
32 $this->fuzzTest( $files );
36 * Run a fuzz test series
37 * Draw input from a set of test files
38 * @param array $filenames
40 function fuzzTest( $filenames ) {
41 $GLOBALS['wgContLang'] = Language
::factory( 'en' );
42 $dict = $this->getFuzzInput( $filenames );
43 $dictSize = strlen( $dict );
44 $logMaxLength = log( $this->maxFuzzTestLength
);
45 $this->parserTest
->setupDatabase();
46 ini_set( 'memory_limit', $this->memoryLimit
* 1048576 * 2 );
51 $opts = ParserOptions
::newFromUser( $user );
52 $title = Title
::makeTitle( NS_MAIN
, 'Parser_test' );
55 // Generate test input
56 mt_srand( ++
$this->seed
);
57 $totalLength = mt_rand( 1, $this->maxFuzzTestLength
);
60 while ( strlen( $input ) < $totalLength ) {
61 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
62 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
63 $offset = mt_rand( 0, $dictSize - $hairLength );
64 $input .= substr( $dict, $offset, $hairLength );
67 $this->parserTest
->setupGlobals();
68 $parser = $this->parserTest
->getParser();
72 $parser->parse( $input, $title, $opts );
74 } catch ( Exception
$exception ) {
79 echo "Test failed with seed {$this->seed}\n";
81 printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
88 $this->parserTest
->teardownGlobals();
89 $parser->__destruct();
91 if ( $numTotal %
100 == 0 ) {
92 $usage = intval( memory_get_usage( true ) / $this->memoryLimit
/ 1048576 * 100 );
93 echo "{$this->seed}: $numSuccess/$numTotal (mem: $usage%)\n";
94 if ( $usage >= 100 ) {
95 echo "Out of memory:\n";
96 $memStats = $this->getMemoryBreakdown();
98 foreach ( $memStats as $name => $usage ) {
99 echo "$name: $usage\n";
101 if ( function_exists( 'hphpd_break' ) ) {
111 * Get a memory usage breakdown
114 function getMemoryBreakdown() {
117 foreach ( $GLOBALS as $name => $value ) {
118 $memStats['$' . $name] = $this->guessVarSize( $value );
121 $classes = get_declared_classes();
123 foreach ( $classes as $class ) {
124 $rc = new ReflectionClass( $class );
125 $props = $rc->getStaticProperties();
126 $memStats[$class] = $this->guessVarSize( $props );
127 $methods = $rc->getMethods();
129 foreach ( $methods as $method ) {
130 $memStats[$class] +
= $this->guessVarSize( $method->getStaticVariables() );
134 $functions = get_defined_functions();
136 foreach ( $functions['user'] as $function ) {
137 $rf = new ReflectionFunction( $function );
138 $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
147 * Estimate the size of the input variable
149 function guessVarSize( $var ) {
152 MediaWiki\
suppressWarnings();
153 $length = strlen( serialize( $var ) );
154 MediaWiki\restoreWarnings
();
155 } catch ( Exception
$e ) {
161 * Get an input dictionary from a set of parser test files
162 * @param array $filenames
165 function getFuzzInput( $filenames ) {
168 foreach ( $filenames as $filename ) {
169 $contents = file_get_contents( $filename );
171 '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
176 foreach ( $matches[1] as $match ) {
177 $dict .= $match . "\n";
185 $maintClass = 'ParserFuzzTest';
186 require RUN_MAINTENANCE_IF_MAIN
;