xref: /PHP-Parser/test_old/run.php (revision b11fca03)
1<?php
2
3error_reporting(E_ALL | E_STRICT);
4ini_set('short_open_tag', false);
5
6if ('cli' !== php_sapi_name()) {
7    die('This script is designed for running on the command line.');
8}
9
10function showHelp($error) {
11    die($error . "\n\n" .
12<<<OUTPUT
13This script has to be called with the following signature:
14
15    php run.php [--no-progress] testType pathToTestFiles
16
17The test type must be one of: PHP, Symfony
18
19The following options are available:
20
21    --no-progress            Disables showing which file is currently tested.
22    --verbose                Print more information for failures.
23    --php-version=VERSION    PHP version to use for lexing/parsing.
24
25OUTPUT
26    );
27}
28
29$options = array();
30$arguments = array();
31
32// remove script name from argv
33array_shift($argv);
34
35foreach ($argv as $arg) {
36    if ('-' === $arg[0]) {
37        $parts = explode('=', $arg);
38        $options[$parts[0]] = $parts[1] ?? true;
39    } else {
40        $arguments[] = $arg;
41    }
42}
43
44if (count($arguments) !== 2) {
45    showHelp('Too few arguments passed!');
46}
47
48$showProgress = !isset($options['--no-progress']);
49$verbose = isset($options['--verbose']);
50$phpVersion = $options['--php-version'] ?? '8.0';
51$testType = $arguments[0];
52$dir = $arguments[1];
53
54require_once __DIR__ . '/../vendor/autoload.php';
55
56switch ($testType) {
57    case 'Symfony':
58        $fileFilter = function($path) {
59            if (!preg_match('~\.php$~', $path)) {
60                return false;
61            }
62
63            if (preg_match('~(?:
64# invalid php code
65  dependency-injection.Tests.Fixtures.xml.xml_with_wrong_ext
66# difference in nop statement
67| framework-bundle.Resources.views.Form.choice_widget_options\.html
68# difference due to INF
69| yaml.Tests.InlineTest
70)\.php$~x', $path)) {
71                return false;
72            }
73
74            return true;
75        };
76        $codeExtractor = function($file, $code) {
77            return $code;
78        };
79        break;
80    case 'PHP':
81        $fileFilter = function($path) {
82            return preg_match('~\.phpt$~', $path);
83        };
84        $codeExtractor = function($file, $code) {
85            if (preg_match('~(?:
86# skeleton files
87  ext.gmp.tests.001
88| ext.skeleton.tests.00\d
89# multibyte encoded files
90| ext.mbstring.tests.zend_multibyte-01
91| Zend.tests.multibyte.multibyte_encoding_001
92| Zend.tests.multibyte.multibyte_encoding_004
93| Zend.tests.multibyte.multibyte_encoding_005
94# invalid code due to missing WS after opening tag
95| tests.run-test.bug75042-3
96# contains invalid chars, which we treat as parse error
97| Zend.tests.warning_during_heredoc_scan_ahead
98# pretty print differences due to negative LNumbers
99| Zend.tests.neg_num_string
100| Zend.tests.numeric_strings.neg_num_string
101| Zend.tests.bug72918
102# pretty print difference due to nop statements
103| ext.mbstring.tests.htmlent
104| ext.standard.tests.file.fread_basic
105# its too hard to emulate these on old PHP versions
106| Zend.tests.flexible-heredoc-complex-test[1-4]
107# whitespace in namespaced name
108| Zend.tests.bug55086
109| Zend.tests.grammar.regression_010
110# not worth emulating on old PHP versions
111| Zend.tests.type_declarations.intersection_types.parsing_comment
112)\.phpt$~x', $file)) {
113                return null;
114            }
115
116            if (!preg_match('~--FILE--\s*(.*?)\n--[A-Z]+--~s', $code, $matches)) {
117                return null;
118            }
119            if (preg_match('~--EXPECT(?:F|REGEX)?--\s*(?:Parse|Fatal) error~', $code)) {
120                return null;
121            }
122
123            return $matches[1];
124        };
125        break;
126    default:
127        showHelp('Test type must be one of: PHP or Symfony');
128}
129
130$lexer = new PhpParser\Lexer\Emulative(\PhpParser\PhpVersion::fromString($phpVersion));
131if (version_compare($phpVersion, '7.0', '>=')) {
132    $parser = new PhpParser\Parser\Php7($lexer);
133} else {
134    $parser = new PhpParser\Parser\Php5($lexer);
135}
136$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
137$nodeDumper = new PhpParser\NodeDumper;
138
139$cloningTraverser = new PhpParser\NodeTraverser;
140$cloningTraverser->addVisitor(new PhpParser\NodeVisitor\CloningVisitor);
141
142$parseFail = $fpppFail = $ppFail = $compareFail = $count = 0;
143
144$readTime = $parseTime = $cloneTime = 0;
145$fpppTime = $ppTime = $reparseTime = $compareTime = 0;
146$totalStartTime = microtime(true);
147
148foreach (new RecursiveIteratorIterator(
149             new RecursiveDirectoryIterator($dir),
150             RecursiveIteratorIterator::LEAVES_ONLY)
151         as $file) {
152    if (!$fileFilter($file)) {
153        continue;
154    }
155
156    $startTime = microtime(true);
157    $origCode = file_get_contents($file);
158    $readTime += microtime(true) - $startTime;
159
160    if (null === $origCode = $codeExtractor($file, $origCode)) {
161        continue;
162    }
163
164    set_time_limit(10);
165
166    ++$count;
167
168    if ($showProgress) {
169        echo substr(str_pad('Testing file ' . $count . ': ' . substr($file, strlen($dir)), 79), 0, 79), "\r";
170    }
171
172    try {
173        $startTime = microtime(true);
174        $origStmts = $parser->parse($origCode);
175        $parseTime += microtime(true) - $startTime;
176
177        $origTokens = $parser->getTokens();
178
179        $startTime = microtime(true);
180        $stmts = $cloningTraverser->traverse($origStmts);
181        $cloneTime += microtime(true) - $startTime;
182
183        $startTime = microtime(true);
184        $code = $prettyPrinter->printFormatPreserving($stmts, $origStmts, $origTokens);
185        $fpppTime += microtime(true) - $startTime;
186
187        if ($code !== $origCode) {
188            echo $file, ":\n Result of format-preserving pretty-print differs\n";
189            if ($verbose) {
190                echo "FPPP output:\n=====\n$code\n=====\n\n";
191            }
192
193            ++$fpppFail;
194        }
195
196        $startTime = microtime(true);
197        $code = "<?php\n" . $prettyPrinter->prettyPrint($stmts);
198        $ppTime += microtime(true) - $startTime;
199
200        try {
201            $startTime = microtime(true);
202            $ppStmts = $parser->parse($code);
203            $reparseTime += microtime(true) - $startTime;
204
205            $startTime = microtime(true);
206            $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts);
207            $compareTime += microtime(true) - $startTime;
208
209            if (!$same) {
210                echo $file, ":\n    Result of initial parse and parse after pretty print differ\n";
211                if ($verbose) {
212                    echo "Pretty printer output:\n=====\n$code\n=====\n\n";
213                }
214
215                ++$compareFail;
216            }
217        } catch (PhpParser\Error $e) {
218            echo $file, ":\n    Parse of pretty print failed with message: {$e->getMessage()}\n";
219            if ($verbose) {
220                echo "Pretty printer output:\n=====\n$code\n=====\n\n";
221            }
222
223            ++$ppFail;
224        }
225    } catch (PhpParser\Error $e) {
226        echo $file, ":\n    Parse failed with message: {$e->getMessage()}\n";
227
228        ++$parseFail;
229    } catch (Throwable $e) {
230        echo $file, ":\n    Unknown error occurred: $e\n";
231    }
232}
233
234if (0 === $parseFail && 0 === $ppFail && 0 === $compareFail) {
235    $exit = 0;
236    echo "\n\n", 'All tests passed.', "\n";
237} else {
238    $exit = 1;
239    echo "\n\n", '==========', "\n\n", 'There were: ', "\n";
240    if (0 !== $parseFail) {
241        echo '    ', $parseFail,   ' parse failures.',        "\n";
242    }
243    if (0 !== $ppFail) {
244        echo '    ', $ppFail,      ' pretty print failures.', "\n";
245    }
246    if (0 !== $fpppFail) {
247        echo '    ', $fpppFail,      ' FPPP failures.', "\n";
248    }
249    if (0 !== $compareFail) {
250        echo '    ', $compareFail, ' compare failures.',      "\n";
251    }
252}
253
254echo "\n",
255     'Tested files:         ', $count,        "\n",
256     "\n",
257     'Reading files took:   ', $readTime,    "\n",
258     'Parsing took:         ', $parseTime,   "\n",
259     'Cloning took:         ', $cloneTime,   "\n",
260     'FPPP took:            ', $fpppTime,    "\n",
261     'Pretty printing took: ', $ppTime,      "\n",
262     'Reparsing took:       ', $reparseTime, "\n",
263     'Comparing took:       ', $compareTime, "\n",
264     "\n",
265     'Total time:           ', microtime(true) - $totalStartTime, "\n",
266     'Maximum memory usage: ', memory_get_peak_usage(true), "\n";
267
268exit($exit);
269