AbstractFixerTestCase.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. /*
  3. * This file is part of PHP CS Fixer.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace PhpCsFixer\Tests\Test;
  12. use PhpCsFixer\Fixer\ConfigurableFixerInterface;
  13. use PhpCsFixer\Fixer\FixerInterface;
  14. use PhpCsFixer\Linter\CachingLinter;
  15. use PhpCsFixer\Linter\Linter;
  16. use PhpCsFixer\Linter\LinterInterface;
  17. use PhpCsFixer\Tests\Test\Assert\AssertTokensTrait;
  18. use PhpCsFixer\Tests\TestCase;
  19. use PhpCsFixer\Tokenizer\Token;
  20. use PhpCsFixer\Tokenizer\Tokens;
  21. use Prophecy\Argument;
  22. /**
  23. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  24. *
  25. * @internal
  26. */
  27. abstract class AbstractFixerTestCase extends TestCase
  28. {
  29. use AssertTokensTrait;
  30. /**
  31. * @var LinterInterface
  32. */
  33. protected $linter;
  34. /**
  35. * @var null|ConfigurableFixerInterface|FixerInterface
  36. */
  37. protected $fixer;
  38. protected function setUp()
  39. {
  40. parent::setUp();
  41. $this->linter = $this->getLinter();
  42. $this->fixer = $this->createFixer();
  43. // @todo remove at 3.0 together with env var itself
  44. if (getenv('PHP_CS_FIXER_TEST_USE_LEGACY_TOKENIZER')) {
  45. Tokens::setLegacyMode(true);
  46. }
  47. }
  48. protected function tearDown()
  49. {
  50. parent::tearDown();
  51. $this->linter = null;
  52. $this->fixer = null;
  53. // @todo remove at 3.0
  54. Tokens::setLegacyMode(false);
  55. }
  56. /**
  57. * @return FixerInterface
  58. */
  59. protected function createFixer()
  60. {
  61. $fixerClassName = preg_replace('/^(PhpCsFixer)\\\\Tests(\\\\.+)Test$/', '$1$2', static::class);
  62. return new $fixerClassName();
  63. }
  64. /**
  65. * @param string $filename
  66. *
  67. * @return \SplFileInfo
  68. */
  69. protected function getTestFile($filename = __FILE__)
  70. {
  71. static $files = [];
  72. if (!isset($files[$filename])) {
  73. $files[$filename] = new \SplFileInfo($filename);
  74. }
  75. return $files[$filename];
  76. }
  77. /**
  78. * Tests if a fixer fixes a given string to match the expected result.
  79. *
  80. * It is used both if you want to test if something is fixed or if it is not touched by the fixer.
  81. * It also makes sure that the expected output does not change when run through the fixer. That means that you
  82. * do not need two test cases like [$expected] and [$expected, $input] (where $expected is the same in both cases)
  83. * as the latter covers both of them.
  84. * This method throws an exception if $expected and $input are equal to prevent test cases that accidentally do
  85. * not test anything.
  86. *
  87. * @param string $expected The expected fixer output
  88. * @param null|string $input The fixer input, or null if it should intentionally be equal to the output
  89. * @param null|\SplFileInfo $file The file to fix, or null if unneeded
  90. */
  91. protected function doTest($expected, $input = null, \SplFileInfo $file = null)
  92. {
  93. if ($expected === $input) {
  94. throw new \InvalidArgumentException('Input parameter must not be equal to expected parameter.');
  95. }
  96. $file = $file ?: $this->getTestFile();
  97. $fileIsSupported = $this->fixer->supports($file);
  98. if (null !== $input) {
  99. $this->assertNull($this->lintSource($input));
  100. Tokens::clearCache();
  101. $tokens = Tokens::fromCode($input);
  102. if ($fileIsSupported) {
  103. $this->assertTrue($this->fixer->isCandidate($tokens), 'Fixer must be a candidate for input code.');
  104. $this->assertFalse($tokens->isChanged(), 'Fixer must not touch Tokens on candidate check.');
  105. $fixResult = $this->fixer->fix($file, $tokens);
  106. $this->assertNull($fixResult, '->fix method must return null.');
  107. }
  108. $this->assertThat(
  109. $tokens->generateCode(),
  110. self::createIsIdenticalStringConstraint($expected),
  111. 'Code build on input code must match expected code.'
  112. );
  113. $this->assertTrue($tokens->isChanged(), 'Tokens collection built on input code must be marked as changed after fixing.');
  114. $tokens->clearEmptyTokens();
  115. $this->assertSame(
  116. \count($tokens),
  117. \count(array_unique(array_map(static function (Token $token) {
  118. return spl_object_hash($token);
  119. }, $tokens->toArray()))),
  120. 'Token items inside Tokens collection must be unique.'
  121. );
  122. Tokens::clearCache();
  123. $expectedTokens = Tokens::fromCode($expected);
  124. $this->assertTokens($expectedTokens, $tokens);
  125. }
  126. $this->assertNull($this->lintSource($expected));
  127. Tokens::clearCache();
  128. $tokens = Tokens::fromCode($expected);
  129. if ($fileIsSupported) {
  130. $fixResult = $this->fixer->fix($file, $tokens);
  131. $this->assertNull($fixResult, '->fix method must return null.');
  132. }
  133. $this->assertThat(
  134. $tokens->generateCode(),
  135. self::createIsIdenticalStringConstraint($expected),
  136. 'Code build on expected code must not change.'
  137. );
  138. $this->assertFalse($tokens->isChanged(), 'Tokens collection built on expected code must not be marked as changed after fixing.');
  139. }
  140. /**
  141. * @param string $source
  142. *
  143. * @return null|string
  144. */
  145. protected function lintSource($source)
  146. {
  147. try {
  148. $this->linter->lintSource($source)->check();
  149. } catch (\Exception $e) {
  150. return $e->getMessage()."\n\nSource:\n${source}";
  151. }
  152. }
  153. /**
  154. * @return LinterInterface
  155. */
  156. private function getLinter()
  157. {
  158. static $linter = null;
  159. if (null === $linter) {
  160. if (getenv('SKIP_LINT_TEST_CASES')) {
  161. $linterProphecy = $this->prophesize(\PhpCsFixer\Linter\LinterInterface::class);
  162. $linterProphecy
  163. ->lintSource(Argument::type('string'))
  164. ->willReturn($this->prophesize(\PhpCsFixer\Linter\LintingResultInterface::class)->reveal())
  165. ;
  166. $linter = $linterProphecy->reveal();
  167. } else {
  168. $linter = new CachingLinter(new Linter());
  169. }
  170. }
  171. return $linter;
  172. }
  173. /**
  174. * @todo Remove me when this class will end up in dedicated package.
  175. *
  176. * @param string $expected
  177. */
  178. private static function createIsIdenticalStringConstraint($expected)
  179. {
  180. $candidates = array_filter([
  181. 'PhpCsFixer\PhpunitConstraintIsIdenticalString\Constraint\IsIdenticalString',
  182. 'PHPUnit\Framework\Constraint\IsIdentical',
  183. 'PHPUnit_Framework_Constraint_IsIdentical',
  184. ], function ($className) { return class_exists($className); });
  185. if (empty($candidates)) {
  186. throw new \RuntimeException('PHPUnit not installed?!');
  187. }
  188. $candidate = array_shift($candidates);
  189. return new $candidate($expected);
  190. }
  191. }