CiIntegrationTest.php 10 KB

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