RunnerTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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\Runner;
  13. use PhpCsFixer\AccessibleObject\AccessibleObject;
  14. use PhpCsFixer\Cache\Directory;
  15. use PhpCsFixer\Cache\NullCacheManager;
  16. use PhpCsFixer\Console\Command\FixCommand;
  17. use PhpCsFixer\Differ\DifferInterface;
  18. use PhpCsFixer\Differ\NullDiffer;
  19. use PhpCsFixer\Error\Error;
  20. use PhpCsFixer\Error\ErrorsManager;
  21. use PhpCsFixer\Fixer;
  22. use PhpCsFixer\Linter\Linter;
  23. use PhpCsFixer\Linter\LinterInterface;
  24. use PhpCsFixer\Linter\LintingException;
  25. use PhpCsFixer\Linter\LintingResultInterface;
  26. use PhpCsFixer\Runner\Event\AnalysisStarted;
  27. use PhpCsFixer\Runner\Parallel\ParallelConfig;
  28. use PhpCsFixer\Runner\Runner;
  29. use PhpCsFixer\Tests\TestCase;
  30. use PhpCsFixer\ToolInfo;
  31. use Symfony\Component\Console\Input\ArrayInput;
  32. use Symfony\Component\EventDispatcher\EventDispatcher;
  33. use Symfony\Component\Finder\Finder;
  34. /**
  35. * @internal
  36. *
  37. * @covers \PhpCsFixer\Runner\Runner
  38. */
  39. final class RunnerTest extends TestCase
  40. {
  41. /**
  42. * @covers \PhpCsFixer\Runner\Runner::fix
  43. * @covers \PhpCsFixer\Runner\Runner::fixFile
  44. */
  45. public function testThatFixSuccessfully(): void
  46. {
  47. $linter = $this->createLinterDouble();
  48. $fixers = [
  49. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  50. new Fixer\Import\NoUnusedImportsFixer(), // will be ignored cause of test keyword in namespace
  51. ];
  52. $expectedChangedInfo = [
  53. 'appliedFixers' => ['visibility_required'],
  54. 'diff' => '',
  55. ];
  56. $path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'fix';
  57. $runner = new Runner(
  58. Finder::create()->in($path),
  59. $fixers,
  60. new NullDiffer(),
  61. null,
  62. new ErrorsManager(),
  63. $linter,
  64. true,
  65. new NullCacheManager(),
  66. new Directory($path),
  67. false
  68. );
  69. $changed = $runner->fix();
  70. self::assertCount(2, $changed);
  71. self::assertSame($expectedChangedInfo, array_pop($changed));
  72. self::assertSame($expectedChangedInfo, array_pop($changed));
  73. $path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'fix';
  74. $runner = new Runner(
  75. Finder::create()->in($path),
  76. $fixers,
  77. new NullDiffer(),
  78. null,
  79. new ErrorsManager(),
  80. $linter,
  81. true,
  82. new NullCacheManager(),
  83. new Directory($path),
  84. true
  85. );
  86. $changed = $runner->fix();
  87. self::assertCount(1, $changed);
  88. self::assertSame($expectedChangedInfo, array_pop($changed));
  89. }
  90. /**
  91. * @covers \PhpCsFixer\Runner\Runner::fix
  92. * @covers \PhpCsFixer\Runner\Runner::fixFile
  93. * @covers \PhpCsFixer\Runner\Runner::fixSequential
  94. */
  95. public function testThatSequentialFixOfInvalidFileReportsToErrorManager(): void
  96. {
  97. $errorsManager = new ErrorsManager();
  98. $path = realpath(__DIR__.\DIRECTORY_SEPARATOR.'..').\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'invalid';
  99. $runner = new Runner(
  100. Finder::create()->in($path),
  101. [
  102. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  103. new Fixer\Import\NoUnusedImportsFixer(), // will be ignored cause of test keyword in namespace
  104. ],
  105. new NullDiffer(),
  106. null,
  107. $errorsManager,
  108. new Linter(),
  109. true,
  110. new NullCacheManager()
  111. );
  112. $changed = $runner->fix();
  113. $pathToInvalidFile = $path.\DIRECTORY_SEPARATOR.'somefile.php';
  114. self::assertCount(0, $changed);
  115. $errors = $errorsManager->getInvalidErrors();
  116. self::assertCount(1, $errors);
  117. $error = $errors[0];
  118. self::assertSame(Error::TYPE_INVALID, $error->getType());
  119. self::assertSame($pathToInvalidFile, $error->getFilePath());
  120. }
  121. /**
  122. * @covers \PhpCsFixer\Runner\Runner::fix
  123. * @covers \PhpCsFixer\Runner\Runner::fixFile
  124. * @covers \PhpCsFixer\Runner\Runner::fixParallel
  125. */
  126. public function testThatParallelFixOfInvalidFileReportsToErrorManager(): void
  127. {
  128. $errorsManager = new ErrorsManager();
  129. $path = realpath(__DIR__.\DIRECTORY_SEPARATOR.'..').\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'invalid';
  130. $runner = new Runner(
  131. Finder::create()->in($path),
  132. [
  133. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  134. new Fixer\Import\NoUnusedImportsFixer(), // will be ignored cause of test keyword in namespace
  135. ],
  136. new NullDiffer(),
  137. null,
  138. $errorsManager,
  139. new Linter(),
  140. true,
  141. new NullCacheManager(),
  142. null,
  143. false,
  144. new ParallelConfig(2, 1, 50),
  145. new ArrayInput([], (new FixCommand(new ToolInfo()))->getDefinition())
  146. );
  147. $changed = $runner->fix();
  148. $pathToInvalidFile = $path.\DIRECTORY_SEPARATOR.'somefile.php';
  149. self::assertCount(0, $changed);
  150. $errors = $errorsManager->getInvalidErrors();
  151. self::assertCount(1, $errors);
  152. $error = $errors[0];
  153. self::assertInstanceOf(LintingException::class, $error->getSource());
  154. self::assertSame(Error::TYPE_INVALID, $error->getType());
  155. self::assertSame($pathToInvalidFile, $error->getFilePath());
  156. }
  157. /**
  158. * @param list<string> $paths
  159. *
  160. * @covers \PhpCsFixer\Runner\Runner::fix
  161. * @covers \PhpCsFixer\Runner\Runner::fixParallel
  162. * @covers \PhpCsFixer\Runner\Runner::fixSequential
  163. *
  164. * @dataProvider provideRunnerUsesProperAnalysisModeCases
  165. */
  166. public function testRunnerUsesProperAnalysisMode(
  167. ParallelConfig $parallelConfig,
  168. array $paths,
  169. string $expectedMode
  170. ): void {
  171. $runner = new Runner(
  172. Finder::create()->in($paths),
  173. [
  174. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  175. new Fixer\Import\NoUnusedImportsFixer(), // will be ignored cause of test keyword in namespace
  176. ],
  177. new NullDiffer(),
  178. $eventDispatcher = new EventDispatcher(),
  179. new ErrorsManager(),
  180. new Linter(),
  181. true,
  182. new NullCacheManager(),
  183. null,
  184. false,
  185. $parallelConfig,
  186. new ArrayInput([], (new FixCommand(new ToolInfo()))->getDefinition())
  187. );
  188. $eventDispatcher->addListener(AnalysisStarted::NAME, static function (AnalysisStarted $event) use ($expectedMode): void {
  189. self::assertSame($expectedMode, $event->getMode());
  190. });
  191. $runner->fix();
  192. }
  193. /**
  194. * @return iterable<string, array{0: ParallelConfig, 1: list<string>}>
  195. */
  196. public static function provideRunnerUsesProperAnalysisModeCases(): iterable
  197. {
  198. $fixturesBasePath = realpath(__DIR__.\DIRECTORY_SEPARATOR.'..').\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR;
  199. yield 'single CPU = sequential even though file chunk is lower than actual files count' => [
  200. new ParallelConfig(1, 1),
  201. [$fixturesBasePath.'fix'],
  202. 'sequential',
  203. ];
  204. yield 'less files to fix than configured file chunk = sequential even though multiple CPUs enabled' => [
  205. new ParallelConfig(5, 10),
  206. [$fixturesBasePath.'fix'],
  207. 'sequential',
  208. ];
  209. yield 'multiple CPUs, more files to fix than file chunk size = parallel' => [
  210. new ParallelConfig(2, 1),
  211. [$fixturesBasePath.'fix'],
  212. 'parallel',
  213. ];
  214. }
  215. /**
  216. * @requires OS Darwin|Windows
  217. *
  218. * @TODO v4 do not switch on parallel execution by default while this test is not passing on Linux.
  219. *
  220. * @covers \PhpCsFixer\Runner\Runner::fix
  221. * @covers \PhpCsFixer\Runner\Runner::fixFile
  222. * @covers \PhpCsFixer\Runner\Runner::fixParallel
  223. *
  224. * @dataProvider provideParallelFixStopsOnFirstViolationIfSuchOptionIsEnabledCases
  225. */
  226. public function testParallelFixStopsOnFirstViolationIfSuchOptionIsEnabled(bool $stopOnViolation, int $expectedChanges): void
  227. {
  228. $errorsManager = new ErrorsManager();
  229. $path = realpath(__DIR__.\DIRECTORY_SEPARATOR.'..').\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'fix';
  230. $runner = new Runner(
  231. Finder::create()->in($path),
  232. [
  233. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  234. new Fixer\Import\NoUnusedImportsFixer(), // will be ignored cause of test keyword in namespace
  235. ],
  236. new NullDiffer(),
  237. null,
  238. $errorsManager,
  239. new Linter(),
  240. true,
  241. new NullCacheManager(),
  242. null,
  243. $stopOnViolation,
  244. new ParallelConfig(2, 1, 3),
  245. new ArrayInput([], (new FixCommand(new ToolInfo()))->getDefinition())
  246. );
  247. self::assertCount($expectedChanges, $runner->fix());
  248. }
  249. /**
  250. * @return iterable<array{0: bool, 1: int}>
  251. */
  252. public static function provideParallelFixStopsOnFirstViolationIfSuchOptionIsEnabledCases(): iterable
  253. {
  254. yield 'do NOT stop on violation' => [false, 2];
  255. yield 'stop on violation' => [true, 1];
  256. }
  257. /**
  258. * @covers \PhpCsFixer\Runner\Runner::fix
  259. * @covers \PhpCsFixer\Runner\Runner::fixFile
  260. */
  261. public function testThatDiffedFileIsPassedToDiffer(): void
  262. {
  263. $differ = $this->createDifferDouble();
  264. $path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'fix';
  265. $fixers = [
  266. new Fixer\ClassNotation\VisibilityRequiredFixer(),
  267. ];
  268. $runner = new Runner(
  269. Finder::create()->in($path),
  270. $fixers,
  271. $differ,
  272. null,
  273. new ErrorsManager(),
  274. new Linter(),
  275. true,
  276. new NullCacheManager(),
  277. new Directory($path),
  278. true
  279. );
  280. $runner->fix();
  281. self::assertSame($path, AccessibleObject::create($differ)->passedFile->getPath());
  282. }
  283. private function createDifferDouble(): DifferInterface
  284. {
  285. return new class implements DifferInterface {
  286. public ?\SplFileInfo $passedFile = null;
  287. public function diff(string $old, string $new, ?\SplFileInfo $file = null): string
  288. {
  289. $this->passedFile = $file;
  290. return 'some-diff';
  291. }
  292. };
  293. }
  294. private function createLinterDouble(): LinterInterface
  295. {
  296. return new class implements LinterInterface {
  297. public function isAsync(): bool
  298. {
  299. return false;
  300. }
  301. public function lintFile(string $path): LintingResultInterface
  302. {
  303. return new class implements LintingResultInterface {
  304. public function check(): void {}
  305. };
  306. }
  307. public function lintSource(string $source): LintingResultInterface
  308. {
  309. return new class implements LintingResultInterface {
  310. public function check(): void {}
  311. };
  312. }
  313. };
  314. }
  315. }