CiIntegrationTest.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of PHP CS Fixer.
  5. *
  6. * (c) Fabien Potencier <fabien@symfony.com>
  7. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  8. *
  9. * This source file is subject to the MIT license that is bundled
  10. * with this source code in the file LICENSE.
  11. */
  12. namespace PhpCsFixer\Tests\Smoke;
  13. use Keradus\CliExecutor\CliResult;
  14. use Keradus\CliExecutor\CommandExecutor;
  15. use Keradus\CliExecutor\ScriptExecutor;
  16. use PhpCsFixer\Console\Application;
  17. /**
  18. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  19. *
  20. * @internal
  21. *
  22. * @requires OS Linux|Darwin
  23. *
  24. * @coversNothing
  25. *
  26. * @group covers-nothing
  27. *
  28. * @large
  29. */
  30. final class CiIntegrationTest extends AbstractSmokeTestCase
  31. {
  32. public static string $fixtureDir;
  33. public static function setUpBeforeClass(): void
  34. {
  35. parent::setUpBeforeClass();
  36. self::$fixtureDir = __DIR__.'/../Fixtures/ci-integration';
  37. try {
  38. CommandExecutor::create('composer --version', __DIR__)->getResult();
  39. } catch (\RuntimeException $e) {
  40. self::fail('Missing `composer` env script. Details:'."\n".$e->getMessage());
  41. }
  42. try {
  43. CommandExecutor::create('composer check', __DIR__.'/../..')->getResult();
  44. } catch (\RuntimeException $e) {
  45. self::fail('Composer check failed. Details:'."\n".$e->getMessage());
  46. }
  47. try {
  48. self::executeScript([
  49. 'rm -rf .git',
  50. 'git init --initial-branch=master -q',
  51. 'git config user.name test',
  52. 'git config user.email test',
  53. 'git add .',
  54. 'git commit -m "init" -q',
  55. ]);
  56. } catch (\RuntimeException $e) {
  57. self::fail($e->getMessage());
  58. }
  59. }
  60. public static function tearDownAfterClass(): void
  61. {
  62. parent::tearDownAfterClass();
  63. self::executeCommand('rm -rf .git');
  64. }
  65. protected function tearDown(): void
  66. {
  67. parent::tearDown();
  68. self::executeScript([
  69. 'git reset . -q',
  70. 'git checkout . -q',
  71. 'git clean -fdq',
  72. 'git checkout master -q',
  73. ]);
  74. }
  75. /**
  76. * @param list<string> $caseCommands
  77. * @param list<string> $expectedResult1Lines
  78. * @param list<string> $expectedResult2Lines
  79. *
  80. * @dataProvider provideIntegrationCases
  81. */
  82. public function testIntegration(
  83. string $branchName,
  84. array $caseCommands,
  85. array $expectedResult1Lines,
  86. array $expectedResult2Lines,
  87. string $expectedResult3FilesLine
  88. ): void {
  89. self::executeScript(array_merge(
  90. [
  91. "git checkout -b {$branchName} -q",
  92. ],
  93. $caseCommands
  94. ));
  95. $integrationScript = explode("\n", str_replace('vendor/bin/', './../../../', file_get_contents(__DIR__.'/../../ci-integration.sh')));
  96. $steps = [
  97. "COMMIT_RANGE=\"master..{$branchName}\"",
  98. "{$integrationScript[3]}\n{$integrationScript[4]}",
  99. $integrationScript[5],
  100. $integrationScript[6],
  101. $integrationScript[7],
  102. ];
  103. $result1 = self::executeScript([
  104. $steps[0],
  105. $steps[1],
  106. $steps[2],
  107. 'echo "$CHANGED_FILES"',
  108. ]);
  109. self::assertSame(implode("\n", $expectedResult1Lines)."\n", $result1->getOutput());
  110. $result2 = self::executeScript([
  111. $steps[0],
  112. $steps[1],
  113. $steps[2],
  114. $steps[3],
  115. 'echo "${EXTRA_ARGS}"',
  116. ]);
  117. self::assertSame(implode("\n", $expectedResult2Lines), $result2->getOutput());
  118. $result3 = self::executeScript([
  119. $steps[0],
  120. $steps[1],
  121. $steps[2],
  122. $steps[3],
  123. $steps[4],
  124. ]);
  125. $optionalDeprecatedVersionWarning = 'You are running PHP CS Fixer v3, which is not maintained anymore. Please update to v4.
  126. You may find an UPGRADE guide at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/v4.0.0/UPGRADE-v4.md .
  127. ';
  128. $optionalIncompatibilityWarning = 'PHP needs to be a minimum version of PHP 7.4.0 and maximum version of PHP 8.2.*.
  129. Current PHP version: '.PHP_VERSION.'.
  130. Ignoring environment requirements because `PHP_CS_FIXER_IGNORE_ENV` is set. Execution may be unstable.
  131. ';
  132. $optionalXdebugWarning = 'You are running PHP CS Fixer with xdebug enabled. This has a major impact on runtime performance.
  133. ';
  134. $optionalWarningsHelp = 'If you need help while solving warnings, ask at https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/discussions/, we will help you!
  135. ';
  136. $expectedResult3FilesLineAfterDotsIndex = strpos($expectedResult3FilesLine, ' ');
  137. $expectedResult3FilesDots = substr($expectedResult3FilesLine, 0, $expectedResult3FilesLineAfterDotsIndex);
  138. $expectedResult3FilesPercentage = substr($expectedResult3FilesLine, $expectedResult3FilesLineAfterDotsIndex);
  139. /** @phpstan-ignore-next-line to avoid `Ternary operator condition is always true|false.` */
  140. $aboutSubpattern = Application::VERSION_CODENAME
  141. ? 'PHP CS Fixer '.preg_quote(Application::VERSION, '/').' '.preg_quote(Application::VERSION_CODENAME, '/')." by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: ".PHP_VERSION
  142. : 'PHP CS Fixer '.preg_quote(Application::VERSION, '/')." by Fabien Potencier, Dariusz Ruminski and contributors.\nPHP runtime: ".PHP_VERSION;
  143. $pattern = \sprintf(
  144. '/^(?:%s)?(?:%s)?(?:%s)?(?:%s)?%s\n%s\n%s\n%s\n([\.S]{%d})%s\n%s$/',
  145. preg_quote($optionalDeprecatedVersionWarning, '/'),
  146. preg_quote($optionalIncompatibilityWarning, '/'),
  147. preg_quote($optionalXdebugWarning, '/'),
  148. preg_quote($optionalWarningsHelp, '/'),
  149. $aboutSubpattern,
  150. 'Running analysis on \d+ core(?: sequentially|s with \d+ files? per process)+\.',
  151. preg_quote('You can enable parallel runner and speed up the analysis! Please see https://cs.symfony.com/doc/usage.html for more information.', '/'),
  152. preg_quote('Loaded config default from ".php-cs-fixer.dist.php".', '/'),
  153. \strlen($expectedResult3FilesDots),
  154. preg_quote($expectedResult3FilesPercentage, '/'),
  155. preg_quote('Legend: .-no changes, F-fixed, S-skipped (cached or empty file), I-invalid file syntax (file ignored), E-error', '/')
  156. );
  157. self::assertMatchesRegularExpression($pattern, $result3->getError());
  158. preg_match($pattern, $result3->getError(), $matches);
  159. self::assertArrayHasKey(1, $matches);
  160. self::assertSame(substr_count($expectedResult3FilesDots, '.'), substr_count($matches[1], '.'));
  161. self::assertSame(substr_count($expectedResult3FilesDots, 'S'), substr_count($matches[1], 'S'));
  162. self::assertMatchesRegularExpression(
  163. '/^\s*Found \d+ of \d+ files that can be fixed in \d+\.\d+ seconds, \d+\.\d+ MB memory used\s*$/',
  164. $result3->getOutput()
  165. );
  166. }
  167. public static function provideIntegrationCases(): iterable
  168. {
  169. yield 'random-changes' => [
  170. 'random-changes',
  171. [
  172. 'touch dir\ a/file.php',
  173. 'rm -r dir\ c',
  174. 'echo "" >> dir\ b/file\ b.php',
  175. 'echo "echo 1;" >> dir\ b/file\ b.php',
  176. 'git add .',
  177. 'git commit -m "Random changes" -q',
  178. ],
  179. [
  180. 'dir a/file.php',
  181. 'dir b/file b.php',
  182. ],
  183. [
  184. '--path-mode=intersection',
  185. '--',
  186. 'dir a/file.php',
  187. 'dir b/file b.php',
  188. '',
  189. ],
  190. 'S. 2 / 2 (100%)',
  191. ];
  192. yield 'changes-including-dist-config-file' => [
  193. 'changes-including-dist-config-file',
  194. [
  195. 'echo "" >> dir\ b/file\ b.php',
  196. 'echo "echo 1;" >> dir\ b/file\ b.php',
  197. // `sed -i ...` is not handled the same on Linux and macOS
  198. 'sed -e \'s/@Symfony/@PSR2/\' .php-cs-fixer.dist.php > .php-cs-fixer.dist.php.new',
  199. 'mv .php-cs-fixer.dist.php.new .php-cs-fixer.dist.php',
  200. 'git add .',
  201. 'git commit -m "Random changes including config file" -q',
  202. ],
  203. [
  204. '.php-cs-fixer.dist.php',
  205. 'dir b/file b.php',
  206. ],
  207. [
  208. '',
  209. '',
  210. ],
  211. '... 3 / 3 (100%)',
  212. ];
  213. yield 'changes-including-custom-config-file-creation' => [
  214. 'changes-including-custom-config-file-creation',
  215. [
  216. 'echo "" >> dir\ b/file\ b.php',
  217. 'echo "echo 1;" >> dir\ b/file\ b.php',
  218. 'sed -e \'s/@Symfony/@PSR2/\' .php-cs-fixer.dist.php > .php-cs-fixer.php',
  219. 'git add .',
  220. 'git commit -m "Random changes including custom config file creation" -q',
  221. ],
  222. [
  223. '.php-cs-fixer.php',
  224. 'dir b/file b.php',
  225. ],
  226. [
  227. '',
  228. '',
  229. ],
  230. '... 3 / 3 (100%)',
  231. ];
  232. yield 'changes-including-composer-lock' => [
  233. 'changes-including-composer-lock',
  234. [
  235. 'echo "" >> dir\ b/file\ b.php',
  236. 'echo "echo 1;" >> dir\ b/file\ b.php',
  237. 'touch composer.lock',
  238. 'git add .',
  239. 'git commit -m "Random changes including composer.lock" -q',
  240. ],
  241. [
  242. 'composer.lock',
  243. 'dir b/file b.php',
  244. ],
  245. [
  246. '',
  247. '',
  248. ],
  249. '... 3 / 3 (100%)',
  250. ];
  251. }
  252. private static function executeCommand(string $command): CliResult
  253. {
  254. return CommandExecutor::create($command, self::$fixtureDir)->getResult();
  255. }
  256. /**
  257. * @param list<string> $scriptParts
  258. */
  259. private static function executeScript(array $scriptParts): CliResult
  260. {
  261. return ScriptExecutor::create($scriptParts, self::$fixtureDir)->getResult();
  262. }
  263. }