run.php 8.2 KB

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