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