3 use Wikimedia\ScopedCallback
;
5 require __DIR__
. '/../../maintenance/Maintenance.php';
7 // Make RequestContext::resetMain() happy
8 define( 'MW_PARSER_TEST', 1 );
10 class ParserFuzzTest
extends Maintenance
{
12 private $maxFuzzTestLength = 300;
13 private $memoryLimit = 100;
16 function __construct() {
17 parent
::__construct();
18 $this->addDescription( 'Run a fuzz test on the parser, until it segfaults ' .
19 'or throws an exception' );
20 $this->addOption( 'file', 'Use the specified file as a dictionary, ' .
21 ' or leave blank to use parserTests.txt', false, true, true );
23 $this->addOption( 'seed', 'Start the fuzz test from the specified seed', false, true );
26 function finalSetup() {
27 self
::requireTestsAutoloader();
28 TestSetup
::applyInitialConfig();
32 $files = $this->getOption( 'file', [ __DIR__
. '/parserTests.txt' ] );
33 $this->seed
= intval( $this->getOption( 'seed', 1 ) ) - 1;
34 $this->parserTest
= new ParserTestRunner(
35 new MultiTestRecorder
,
37 $this->fuzzTest( $files );
41 * Run a fuzz test series
42 * Draw input from a set of test files
43 * @param array $filenames
45 function fuzzTest( $filenames ) {
46 $dict = $this->getFuzzInput( $filenames );
47 $dictSize = strlen( $dict );
48 $logMaxLength = log( $this->maxFuzzTestLength
);
50 $teardown = $this->parserTest
->staticSetup();
51 $teardown = $this->parserTest
->setupDatabase( $teardown );
52 $teardown = $this->parserTest
->setupUploads( $teardown );
63 ini_set( 'memory_limit', $this->memoryLimit
* 1048576 * 2 );
68 $opts = ParserOptions
::newFromUser( $user );
69 $title = Title
::makeTitle( NS_MAIN
, 'Parser_test' );
72 // Generate test input
73 mt_srand( ++
$this->seed
);
74 $totalLength = mt_rand( 1, $this->maxFuzzTestLength
);
77 while ( strlen( $input ) < $totalLength ) {
78 $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
79 $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
80 $offset = mt_rand( 0, $dictSize - $hairLength );
81 $input .= substr( $dict, $offset, $hairLength );
84 $perTestTeardown = $this->parserTest
->perTestSetup( $fakeTest );
85 $parser = $this->parserTest
->getParser();
89 $parser->parse( $input, $title, $opts );
91 } catch ( Exception
$exception ) {
96 echo "Test failed with seed {$this->seed}\n";
98 printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
105 ScopedCallback
::consume( $perTestTeardown );
107 if ( $numTotal %
100 == 0 ) {
108 $usage = intval( memory_get_usage( true ) / $this->memoryLimit
/ 1048576 * 100 );
109 echo "{$this->seed}: $numSuccess/$numTotal (mem: $usage%)\n";
110 if ( $usage >= 100 ) {
111 echo "Out of memory:\n";
112 $memStats = $this->getMemoryBreakdown();
114 foreach ( $memStats as $name => $usage ) {
115 echo "$name: $usage\n";
117 if ( function_exists( 'hphpd_break' ) ) {
127 * Get a memory usage breakdown
130 function getMemoryBreakdown() {
133 foreach ( $GLOBALS as $name => $value ) {
134 $memStats['$' . $name] = $this->guessVarSize( $value );
137 $classes = get_declared_classes();
139 foreach ( $classes as $class ) {
140 $rc = new ReflectionClass( $class );
141 $props = $rc->getStaticProperties();
142 $memStats[$class] = $this->guessVarSize( $props );
143 $methods = $rc->getMethods();
145 foreach ( $methods as $method ) {
146 $memStats[$class] +
= $this->guessVarSize( $method->getStaticVariables() );
150 $functions = get_defined_functions();
152 foreach ( $functions['user'] as $function ) {
153 $rf = new ReflectionFunction( $function );
154 $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
163 * Estimate the size of the input variable
165 function guessVarSize( $var ) {
168 MediaWiki\
suppressWarnings();
169 $length = strlen( serialize( $var ) );
170 MediaWiki\restoreWarnings
();
171 } catch ( Exception
$e ) {
177 * Get an input dictionary from a set of parser test files
178 * @param array $filenames
181 function getFuzzInput( $filenames ) {
184 foreach ( $filenames as $filename ) {
185 $contents = file_get_contents( $filename );
187 '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
192 foreach ( $matches[1] as $match ) {
193 $dict .= $match . "\n";
201 $maintClass = 'ParserFuzzTest';
202 require RUN_MAINTENANCE_IF_MAIN
;