MediaWiki REL1_31
fuzzTest.php
Go to the documentation of this file.
1<?php
2
3use Wikimedia\ScopedCallback;
4
5require __DIR__ . '/../../maintenance/Maintenance.php';
6
7// Make RequestContext::resetMain() happy
8define( 'MW_PARSER_TEST', 1 );
9
11 private $parserTest;
12 private $maxFuzzTestLength = 300;
13 private $memoryLimit = 100;
14 private $seed;
15
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 );
22
23 $this->addOption( 'seed', 'Start the fuzz test from the specified seed', false, true );
24 }
25
30
31 function execute() {
32 $files = $this->getOption( 'file', [ __DIR__ . '/parserTests.txt' ] );
33 $this->seed = intval( $this->getOption( 'seed', 1 ) ) - 1;
34 $this->parserTest = new ParserTestRunner(
36 [] );
37 $this->fuzzTest( $files );
38 }
39
45 function fuzzTest( $filenames ) {
46 $dict = $this->getFuzzInput( $filenames );
47 $dictSize = strlen( $dict );
48 $logMaxLength = log( $this->maxFuzzTestLength );
49
50 $teardown = $this->parserTest->staticSetup();
51 $teardown = $this->parserTest->setupDatabase( $teardown );
52 $teardown = $this->parserTest->setupUploads( $teardown );
53
54 $fakeTest = [
55 'test' => '',
56 'desc' => '',
57 'input' => '',
58 'result' => '',
59 'options' => '',
60 'config' => ''
61 ];
62
63 ini_set( 'memory_limit', $this->memoryLimit * 1048576 * 2 );
64
65 $numTotal = 0;
66 $numSuccess = 0;
67 $user = new User;
68 $opts = ParserOptions::newFromUser( $user );
69 $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
70
71 while ( true ) {
72 // Generate test input
73 mt_srand( ++$this->seed );
74 $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
75 $input = '';
76
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 );
82 }
83
84 $perTestTeardown = $this->parserTest->perTestSetup( $fakeTest );
85 $parser = $this->parserTest->getParser();
86
87 // Run the test
88 try {
89 $parser->parse( $input, $title, $opts );
90 $fail = false;
91 } catch ( Exception $exception ) {
92 $fail = true;
93 }
94
95 if ( $fail ) {
96 echo "Test failed with seed {$this->seed}\n";
97 echo "Input:\n";
98 printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
99 echo "$exception\n";
100 } else {
101 $numSuccess++;
102 }
103
104 $numTotal++;
105 ScopedCallback::consume( $perTestTeardown );
106
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();
113
114 foreach ( $memStats as $name => $usage ) {
115 echo "$name: $usage\n";
116 }
117 if ( function_exists( 'hphpd_break' ) ) {
118 hphpd_break();
119 }
120 return;
121 }
122 }
123 }
124 }
125
131 $memStats = [];
132
133 foreach ( $GLOBALS as $name => $value ) {
134 $memStats['$' . $name] = $this->guessVarSize( $value );
135 }
136
137 $classes = get_declared_classes();
138
139 foreach ( $classes as $class ) {
140 $rc = new ReflectionClass( $class );
141 $props = $rc->getStaticProperties();
142 $memStats[$class] = $this->guessVarSize( $props );
143 $methods = $rc->getMethods();
144
145 foreach ( $methods as $method ) {
146 $memStats[$class] += $this->guessVarSize( $method->getStaticVariables() );
147 }
148 }
149
150 $functions = get_defined_functions();
151
152 foreach ( $functions['user'] as $function ) {
153 $rf = new ReflectionFunction( $function );
154 $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
155 }
156
157 asort( $memStats );
158
159 return $memStats;
160 }
161
165 function guessVarSize( $var ) {
166 $length = 0;
167 try {
168 Wikimedia\suppressWarnings();
169 $length = strlen( serialize( $var ) );
170 Wikimedia\restoreWarnings();
171 } catch ( Exception $e ) {
172 }
173 return $length;
174 }
175
181 function getFuzzInput( $filenames ) {
182 $dict = '';
183
184 foreach ( $filenames as $filename ) {
185 $contents = file_get_contents( $filename );
186 preg_match_all(
187 '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
188 $contents,
190 );
191
192 foreach ( $matches[1] as $match ) {
193 $dict .= $match . "\n";
194 }
195 }
196
197 return $dict;
198 }
199}
200
201$maintClass = 'ParserFuzzTest';
serialize()
$GLOBALS['IP']
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
memoryLimit()
Normally we disable the memory_limit when running admin scripts.
static requireTestsAutoloader()
Call this to set up the autoloader to allow classes to be used from the tests directory.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
This is a TestRecorder representing a collection of other TestRecorders.
guessVarSize( $var)
Estimate the size of the input variable.
Definition fuzzTest.php:165
getFuzzInput( $filenames)
Get an input dictionary from a set of parser test files.
Definition fuzzTest.php:181
__construct()
Default constructor.
Definition fuzzTest.php:16
finalSetup()
Handle some last-minute setup here.
Definition fuzzTest.php:26
fuzzTest( $filenames)
Run a fuzz test series Draw input from a set of test files.
Definition fuzzTest.php:45
execute()
Do the actual work.
Definition fuzzTest.php:31
getMemoryBreakdown()
Get a memory usage breakdown.
Definition fuzzTest.php:130
static applyInitialConfig()
This should be called before Setup.php, e.g.
Definition TestSetup.php:11
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:53
do that in ParserLimitReportFormat instead $parser
Definition hooks.txt:2603
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
returning false will NOT prevent logging $e
Definition hooks.txt:2176
$maintClass
Definition fuzzTest.php:201
const NS_MAIN
Definition Defines.php:74
require_once RUN_MAINTENANCE_IF_MAIN
if(is_array($mode)) switch( $mode) $input