SelfUpdateCommandTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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\Console\Command;
  13. use org\bovigo\vfs\vfsStream;
  14. use org\bovigo\vfs\vfsStreamDirectory;
  15. use org\bovigo\vfs\vfsStreamException;
  16. use org\bovigo\vfs\vfsStreamWrapper;
  17. use PhpCsFixer\Console\Application;
  18. use PhpCsFixer\Console\Command\SelfUpdateCommand;
  19. use PhpCsFixer\Console\SelfUpdate\NewVersionChecker;
  20. use PhpCsFixer\Tests\TestCase;
  21. use PhpCsFixer\ToolInfoInterface;
  22. use Prophecy\Argument;
  23. use Symfony\Component\Console\Command\Command;
  24. use Symfony\Component\Console\Tester\CommandTester;
  25. /**
  26. * @internal
  27. *
  28. * @covers \PhpCsFixer\Console\Command\SelfUpdateCommand
  29. */
  30. final class SelfUpdateCommandTest extends TestCase
  31. {
  32. /**
  33. * @var null|vfsStreamDirectory
  34. */
  35. private $root;
  36. protected function setUp(): void
  37. {
  38. parent::setUp();
  39. $this->root = vfsStream::setup();
  40. file_put_contents($this->getToolPath(), 'Current PHP CS Fixer.');
  41. file_put_contents("{$this->root->url()}/{$this->getNewMinorReleaseVersion()}.phar", 'New minor version of PHP CS Fixer.');
  42. file_put_contents("{$this->root->url()}/{$this->getNewMajorReleaseVersion()}.phar", 'New major version of PHP CS Fixer.');
  43. }
  44. protected function tearDown(): void
  45. {
  46. parent::tearDown();
  47. $this->root = null;
  48. try {
  49. vfsStreamWrapper::unregister();
  50. } catch (vfsStreamException $exception) {
  51. // ignored
  52. }
  53. }
  54. /**
  55. * @dataProvider provideCommandNameCases
  56. */
  57. public function testCommandName(string $name): void
  58. {
  59. $command = new SelfUpdateCommand(
  60. $this->prophesize(\PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface::class)->reveal(),
  61. $this->createToolInfo(),
  62. $this->prophesize(\PhpCsFixer\PharCheckerInterface::class)->reveal()
  63. );
  64. $application = new Application();
  65. $application->add($command);
  66. static::assertSame($command, $application->find($name));
  67. }
  68. public function provideCommandNameCases(): array
  69. {
  70. return [
  71. ['self-update'],
  72. ['selfupdate'],
  73. ];
  74. }
  75. /**
  76. * @param ?string $latestMinorVersion
  77. *
  78. * @dataProvider provideExecuteCases
  79. */
  80. public function testExecute(
  81. string $latestVersion,
  82. ?string $latestMinorVersion,
  83. array $input,
  84. bool $decorated,
  85. string $expectedFileContents,
  86. string $expectedDisplay
  87. ): void {
  88. $versionChecker = $this->prophesize(\PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface::class);
  89. $versionChecker->getLatestVersion()->willReturn($latestVersion);
  90. $versionChecker
  91. ->getLatestVersionOfMajor($this->getCurrentMajorVersion())
  92. ->willReturn($latestMinorVersion)
  93. ;
  94. $actualVersionCheck = new NewVersionChecker(
  95. $this->prophesize(\PhpCsFixer\Console\SelfUpdate\GithubClientInterface::class)->reveal()
  96. );
  97. $versionChecker
  98. ->compareVersions(Argument::type('string'), Argument::type('string'))
  99. ->will(function (array $arguments) use ($actualVersionCheck): int {
  100. return $actualVersionCheck->compareVersions($arguments[0], $arguments[1]);
  101. })
  102. ;
  103. $command = new SelfUpdateCommand(
  104. $versionChecker->reveal(),
  105. $this->createToolInfo(),
  106. $this->prophesize(\PhpCsFixer\PharCheckerInterface::class)->reveal()
  107. );
  108. $commandTester = $this->execute($command, $input, $decorated);
  109. static::assertSame($expectedFileContents, file_get_contents($this->getToolPath()));
  110. static::assertDisplay($expectedDisplay, $commandTester);
  111. static::assertSame(0, $commandTester->getStatusCode());
  112. }
  113. public function provideExecuteCases(): array
  114. {
  115. $currentVersion = Application::VERSION;
  116. $minorRelease = $this->getNewMinorReleaseVersion();
  117. $majorRelease = $this->getNewMajorReleaseVersion();
  118. $major = $this->getNewMajorVersion();
  119. $currentContents = 'Current PHP CS Fixer.';
  120. $minorContents = 'New minor version of PHP CS Fixer.';
  121. $majorContents = 'New major version of PHP CS Fixer.';
  122. $upToDateDisplay = "\033[32mPHP CS Fixer is already up-to-date.\033[39m\n";
  123. $newMinorDisplay = "\033[32mPHP CS Fixer updated\033[39m (\033[33m{$currentVersion}\033[39m -> \033[33m{$minorRelease}\033[39m)\n";
  124. $newMajorDisplay = "\033[32mPHP CS Fixer updated\033[39m (\033[33m{$currentVersion}\033[39m -> \033[33m{$majorRelease}\033[39m)\n";
  125. $majorInfoNoMinorDisplay = <<<OUTPUT
  126. \033[32mA new major version of PHP CS Fixer is available\033[39m (\033[33m{$majorRelease}\033[39m)
  127. \033[32mBefore upgrading please read\033[39m https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/{$majorRelease}/UPGRADE-v{$major}.md
  128. \033[32mIf you are ready to upgrade run this command with\033[39m \033[33m-f\033[39m
  129. \033[32mChecking for new minor/patch version...\033[39m
  130. \033[32mNo minor update for PHP CS Fixer.\033[39m
  131. OUTPUT;
  132. $majorInfoNewMinorDisplay = <<<OUTPUT
  133. \033[32mA new major version of PHP CS Fixer is available\033[39m (\033[33m{$majorRelease}\033[39m)
  134. \033[32mBefore upgrading please read\033[39m https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/{$majorRelease}/UPGRADE-v{$major}.md
  135. \033[32mIf you are ready to upgrade run this command with\033[39m \033[33m-f\033[39m
  136. \033[32mChecking for new minor/patch version...\033[39m
  137. \033[32mPHP CS Fixer updated\033[39m (\033[33m{$currentVersion}\033[39m -> \033[33m{$minorRelease}\033[39m)
  138. OUTPUT;
  139. return [
  140. // no new version available
  141. [Application::VERSION, Application::VERSION, [], true, $currentContents, $upToDateDisplay],
  142. [Application::VERSION, Application::VERSION, [], false, $currentContents, $upToDateDisplay],
  143. [Application::VERSION, Application::VERSION, ['--force' => true], true, $currentContents, $upToDateDisplay],
  144. [Application::VERSION, Application::VERSION, ['-f' => true], false, $currentContents, $upToDateDisplay],
  145. [Application::VERSION, Application::VERSION, ['--force' => true], true, $currentContents, $upToDateDisplay],
  146. [Application::VERSION, Application::VERSION, ['-f' => true], false, $currentContents, $upToDateDisplay],
  147. // new minor version available
  148. [$minorRelease, $minorRelease, [], true, $minorContents, $newMinorDisplay],
  149. [$minorRelease, $minorRelease, ['--force' => true], true, $minorContents, $newMinorDisplay],
  150. [$minorRelease, $minorRelease, ['-f' => true], true, $minorContents, $newMinorDisplay],
  151. [$minorRelease, $minorRelease, [], false, $minorContents, $newMinorDisplay],
  152. [$minorRelease, $minorRelease, ['--force' => true], false, $minorContents, $newMinorDisplay],
  153. [$minorRelease, $minorRelease, ['-f' => true], false, $minorContents, $newMinorDisplay],
  154. // new major version available
  155. [$majorRelease, Application::VERSION, [], true, $currentContents, $majorInfoNoMinorDisplay],
  156. [$majorRelease, Application::VERSION, [], false, $currentContents, $majorInfoNoMinorDisplay],
  157. [$majorRelease, Application::VERSION, ['--force' => true], true, $majorContents, $newMajorDisplay],
  158. [$majorRelease, Application::VERSION, ['-f' => true], false, $majorContents, $newMajorDisplay],
  159. [$majorRelease, Application::VERSION, ['--force' => true], true, $majorContents, $newMajorDisplay],
  160. [$majorRelease, Application::VERSION, ['-f' => true], false, $majorContents, $newMajorDisplay],
  161. // new minor version and new major version available
  162. [$majorRelease, $minorRelease, [], true, $minorContents, $majorInfoNewMinorDisplay],
  163. [$majorRelease, $minorRelease, [], false, $minorContents, $majorInfoNewMinorDisplay],
  164. [$majorRelease, $minorRelease, ['--force' => true], true, $majorContents, $newMajorDisplay],
  165. [$majorRelease, $minorRelease, ['-f' => true], false, $majorContents, $newMajorDisplay],
  166. [$majorRelease, $minorRelease, ['--force' => true], true, $majorContents, $newMajorDisplay],
  167. [$majorRelease, $minorRelease, ['-f' => true], false, $majorContents, $newMajorDisplay],
  168. // weird/unexpected versions
  169. ['v0.1.0', 'v0.1.0', [], true, $currentContents, $upToDateDisplay],
  170. ['v0.1.0', 'v0.1.0', [], false, $currentContents, $upToDateDisplay],
  171. ['v0.1.0', 'v0.1.0', ['--force' => true], true, $currentContents, $upToDateDisplay],
  172. ['v0.1.0', 'v0.1.0', ['-f' => true], false, $currentContents, $upToDateDisplay],
  173. ['v0.1.0', 'v0.1.0', ['--force' => true], true, $currentContents, $upToDateDisplay],
  174. ['v0.1.0', 'v0.1.0', ['-f' => true], false, $currentContents, $upToDateDisplay],
  175. ['v0.1.0', null, [], true, $currentContents, $upToDateDisplay],
  176. ['v0.1.0', null, [], false, $currentContents, $upToDateDisplay],
  177. ['v0.1.0', null, ['--force' => true], true, $currentContents, $upToDateDisplay],
  178. ['v0.1.0', null, ['-f' => true], false, $currentContents, $upToDateDisplay],
  179. ['v0.1.0', null, ['--force' => true], true, $currentContents, $upToDateDisplay],
  180. ['v0.1.0', null, ['-f' => true], false, $currentContents, $upToDateDisplay],
  181. ['v0.1.0', Application::VERSION, [], true, $currentContents, $upToDateDisplay],
  182. ['v0.1.0', Application::VERSION, [], false, $currentContents, $upToDateDisplay],
  183. ['v0.1.0', Application::VERSION, ['--force' => true], true, $currentContents, $upToDateDisplay],
  184. ['v0.1.0', Application::VERSION, ['-f' => true], false, $currentContents, $upToDateDisplay],
  185. ['v0.1.0', Application::VERSION, ['--force' => true], true, $currentContents, $upToDateDisplay],
  186. ['v0.1.0', Application::VERSION, ['-f' => true], false, $currentContents, $upToDateDisplay],
  187. [Application::VERSION, 'v0.1.0', [], true, $currentContents, $upToDateDisplay],
  188. [Application::VERSION, 'v0.1.0', [], false, $currentContents, $upToDateDisplay],
  189. [Application::VERSION, 'v0.1.0', ['--force' => true], true, $currentContents, $upToDateDisplay],
  190. [Application::VERSION, 'v0.1.0', ['-f' => true], false, $currentContents, $upToDateDisplay],
  191. [Application::VERSION, 'v0.1.0', ['--force' => true], true, $currentContents, $upToDateDisplay],
  192. [Application::VERSION, 'v0.1.0', ['-f' => true], false, $currentContents, $upToDateDisplay],
  193. ];
  194. }
  195. /**
  196. * @dataProvider provideExecuteWhenNotAbleToGetLatestVersionsCases
  197. */
  198. public function testExecuteWhenNotAbleToGetLatestVersions(
  199. bool $latestVersionSuccess,
  200. bool $latestMinorVersionSuccess,
  201. array $input,
  202. bool $decorated
  203. ): void {
  204. $versionChecker = $this->prophesize(\PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface::class);
  205. $newMajorVersion = $this->getNewMajorReleaseVersion();
  206. $versionChecker->getLatestVersion()->will(function () use ($latestVersionSuccess, $newMajorVersion): string {
  207. if ($latestVersionSuccess) {
  208. return $newMajorVersion;
  209. }
  210. throw new \RuntimeException('Foo.');
  211. });
  212. $newMinorVersion = $this->getNewMinorReleaseVersion();
  213. $versionChecker
  214. ->getLatestVersionOfMajor($this->getCurrentMajorVersion())
  215. ->will(function () use ($latestMinorVersionSuccess, $newMinorVersion): string {
  216. if ($latestMinorVersionSuccess) {
  217. return $newMinorVersion;
  218. }
  219. throw new \RuntimeException('Foo.');
  220. })
  221. ;
  222. $command = new SelfUpdateCommand(
  223. $versionChecker->reveal(),
  224. $this->createToolInfo(),
  225. $this->prophesize(\PhpCsFixer\PharCheckerInterface::class)->reveal()
  226. );
  227. $commandTester = $this->execute($command, $input, $decorated);
  228. static::assertDisplay(
  229. "\033[37;41mUnable to determine newest version: Foo.\033[39;49m\n",
  230. $commandTester
  231. );
  232. static::assertSame(1, $commandTester->getStatusCode());
  233. }
  234. public function provideExecuteWhenNotAbleToGetLatestVersionsCases(): array
  235. {
  236. return [
  237. [false, false, [], true],
  238. [false, false, ['--force' => true], true],
  239. [false, false, ['-f' => true], true],
  240. [false, false, [], false],
  241. [false, false, ['--force' => true], false],
  242. [false, false, ['-f' => true], false],
  243. [true, false, [], true],
  244. [true, false, ['--force' => true], true],
  245. [true, false, ['-f' => true], true],
  246. [true, false, [], false],
  247. [true, false, ['--force' => true], false],
  248. [true, false, ['-f' => true], false],
  249. [false, true, [], true],
  250. [false, true, ['--force' => true], true],
  251. [false, true, ['-f' => true], true],
  252. [false, true, [], false],
  253. [false, true, ['--force' => true], false],
  254. [false, true, ['-f' => true], false],
  255. ];
  256. }
  257. /**
  258. * @dataProvider provideExecuteWhenNotInstalledAsPharCases
  259. */
  260. public function testExecuteWhenNotInstalledAsPhar(array $input, bool $decorated): void
  261. {
  262. $command = new SelfUpdateCommand(
  263. $this->prophesize(\PhpCsFixer\Console\SelfUpdate\NewVersionCheckerInterface::class)->reveal(),
  264. $this->createToolInfo(false),
  265. $this->prophesize(\PhpCsFixer\PharCheckerInterface::class)->reveal()
  266. );
  267. $commandTester = $this->execute($command, $input, $decorated);
  268. static::assertDisplay(
  269. "\033[37;41mSelf-update is available only for PHAR version.\033[39;49m\n",
  270. $commandTester
  271. );
  272. static::assertSame(1, $commandTester->getStatusCode());
  273. }
  274. public function provideExecuteWhenNotInstalledAsPharCases(): array
  275. {
  276. return [
  277. [[], true],
  278. [['--force' => true], true],
  279. [['-f' => true], true],
  280. [[], false],
  281. [['--force' => true], false],
  282. [['-f' => true], false],
  283. ];
  284. }
  285. private function execute(Command $command, array $input, bool $decorated): CommandTester
  286. {
  287. $application = new Application();
  288. $application->add($command);
  289. $input = ['command' => $command->getName()] + $input;
  290. $commandTester = new CommandTester($command);
  291. $realPath = $_SERVER['argv'][0];
  292. $_SERVER['argv'][0] = $this->getToolPath();
  293. $commandTester->execute($input, ['decorated' => $decorated]);
  294. $_SERVER['argv'][0] = $realPath;
  295. return $commandTester;
  296. }
  297. private static function assertDisplay(string $expectedDisplay, CommandTester $commandTester): void
  298. {
  299. if (!$commandTester->getOutput()->isDecorated()) {
  300. $expectedDisplay = preg_replace("/\033\\[(\\d+;)*\\d+m/", '', $expectedDisplay);
  301. }
  302. static::assertSame(
  303. $expectedDisplay,
  304. $commandTester->getDisplay(true)
  305. );
  306. }
  307. private function createToolInfo(bool $isInstalledAsPhar = true): ToolInfoInterface
  308. {
  309. $root = $this->root;
  310. $toolInfo = $this->prophesize(ToolInfoInterface::class);
  311. $toolInfo->isInstalledAsPhar()->willReturn($isInstalledAsPhar);
  312. $toolInfo
  313. ->getPharDownloadUri(Argument::type('string'))
  314. ->will(function (array $arguments) use ($root): string {
  315. return "{$root->url()}/{$arguments[0]}.phar";
  316. })
  317. ;
  318. return $toolInfo->reveal();
  319. }
  320. private function getToolPath(): string
  321. {
  322. return "{$this->root->url()}/php-cs-fixer";
  323. }
  324. private function getCurrentMajorVersion(): int
  325. {
  326. return (int) preg_replace('/^v?(\d+).*$/', '$1', Application::VERSION);
  327. }
  328. private function getNewMinorReleaseVersion(): string
  329. {
  330. return "{$this->getCurrentMajorVersion()}.999.0";
  331. }
  332. private function getNewMajorVersion(): int
  333. {
  334. return $this->getCurrentMajorVersion() + 1;
  335. }
  336. private function getNewMajorReleaseVersion(): string
  337. {
  338. return $this->getNewMajorVersion().'.0.0';
  339. }
  340. }