]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - tests/parser/fuzzTest.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / tests / parser / fuzzTest.php
1 <?php
2
3 use Wikimedia\ScopedCallback;
4
5 require __DIR__ . '/../../maintenance/Maintenance.php';
6
7 // Make RequestContext::resetMain() happy
8 define( 'MW_PARSER_TEST', 1 );
9
10 class ParserFuzzTest extends Maintenance {
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
26         function finalSetup() {
27                 self::requireTestsAutoloader();
28                 TestSetup::applyInitialConfig();
29         }
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(
35                         new MultiTestRecorder,
36                         [] );
37                 $this->fuzzTest( $files );
38         }
39
40         /**
41          * Run a fuzz test series
42          * Draw input from a set of test files
43          * @param array $filenames
44          */
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
126         /**
127          * Get a memory usage breakdown
128          * @return array
129          */
130         function getMemoryBreakdown() {
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
162         /**
163          * Estimate the size of the input variable
164          */
165         function guessVarSize( $var ) {
166                 $length = 0;
167                 try {
168                         MediaWiki\suppressWarnings();
169                         $length = strlen( serialize( $var ) );
170                         MediaWiki\restoreWarnings();
171                 } catch ( Exception $e ) {
172                 }
173                 return $length;
174         }
175
176         /**
177          * Get an input dictionary from a set of parser test files
178          * @param array $filenames
179          * @return string
180          */
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,
189                                 $matches
190                         );
191
192                         foreach ( $matches[1] as $match ) {
193                                 $dict .= $match . "\n";
194                         }
195                 }
196
197                 return $dict;
198         }
199 }
200
201 $maintClass = 'ParserFuzzTest';
202 require RUN_MAINTENANCE_IF_MAIN;