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 self
::requireTestsAutoloader();
26 TestSetup
::applyInitialConfig();
30 $files = $this->getOption( 'file', [ __DIR__
. '/parserTests.txt' ] );
31 $this->seed
= intval( $this->getOption( 'seed', 1 ) ) - 1;
32 $this->parserTest
= new ParserTestRunner(
33 new MultiTestRecorder
,
35 $this->fuzzTest( $files );
39 * Run a fuzz test series
40 * Draw input from a set of test files
41 * @param array $filenames
43 function fuzzTest( $filenames ) {
44 $dict = $this->getFuzzInput( $filenames );
45 $dictSize = strlen( $dict );
46 $logMaxLength = log( $this->maxFuzzTestLength
);
48 $teardown = $this->parserTest
->staticSetup();
49 $teardown = $this->parserTest
->setupDatabase( $teardown );
50 $teardown = $this->parserTest
->setupUploads( $teardown );
61 ini_set( 'memory_limit', $this->memoryLimit
* 1048576 * 2 );
66 $opts = ParserOptions
::newFromUser( $user );
67 $title = Title
::makeTitle( NS_MAIN
, 'Parser_test' );
70 // Generate test input
71 mt_srand( ++
$this->seed
);
72 $totalLength = mt_rand( 1, $this->maxFuzzTestLength
);
75 while ( strlen( $input ) < $totalLength ) {
76 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
77 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
78 $offset = mt_rand( 0, $dictSize - $hairLength );
79 $input .= substr( $dict, $offset, $hairLength );
82 $perTestTeardown = $this->parserTest
->perTestSetup( $fakeTest );
83 $parser = $this->parserTest
->getParser();
87 $parser->parse( $input, $title, $opts );
89 } catch ( Exception
$exception ) {
94 echo "Test failed with seed {$this->seed}\n";
96 printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
103 ScopedCallback
::consume( $perTestTeardown );
105 if ( $numTotal %
100 == 0 ) {
106 $usage = intval( memory_get_usage( true ) / $this->memoryLimit
/ 1048576 * 100 );
107 echo "{$this->seed}: $numSuccess/$numTotal (mem: $usage%)\n";
108 if ( $usage >= 100 ) {
109 echo "Out of memory:\n";
110 $memStats = $this->getMemoryBreakdown();
112 foreach ( $memStats as $name => $usage ) {
113 echo "$name: $usage\n";
115 if ( function_exists( 'hphpd_break' ) ) {
125 * Get a memory usage breakdown
128 function getMemoryBreakdown() {
131 foreach ( $GLOBALS as $name => $value ) {
132 $memStats['$' . $name] = $this->guessVarSize( $value );
135 $classes = get_declared_classes();
137 foreach ( $classes as $class ) {
138 $rc = new ReflectionClass( $class );
139 $props = $rc->getStaticProperties();
140 $memStats[$class] = $this->guessVarSize( $props );
141 $methods = $rc->getMethods();
143 foreach ( $methods as $method ) {
144 $memStats[$class] +
= $this->guessVarSize( $method->getStaticVariables() );
148 $functions = get_defined_functions();
150 foreach ( $functions['user'] as $function ) {
151 $rf = new ReflectionFunction( $function );
152 $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
161 * Estimate the size of the input variable
163 function guessVarSize( $var ) {
166 MediaWiki\
suppressWarnings();
167 $length = strlen( serialize( $var ) );
168 MediaWiki\restoreWarnings
();
169 } catch ( Exception
$e ) {
175 * Get an input dictionary from a set of parser test files
176 * @param array $filenames
179 function getFuzzInput( $filenames ) {
182 foreach ( $filenames as $filename ) {
183 $contents = file_get_contents( $filename );
185 '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
190 foreach ( $matches[1] as $match ) {
191 $dict .= $match . "\n";
199 $maintClass = 'ParserFuzzTest';
200 require RUN_MAINTENANCE_IF_MAIN
;