diff --git a/src/Task/BuiltIn/Composer/SelfUpdateTask.php b/src/Task/BuiltIn/Composer/SelfUpdateTask.php new file mode 100644 index 0000000..41dbac7 --- /dev/null +++ b/src/Task/BuiltIn/Composer/SelfUpdateTask.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Mage\Task\BuiltIn\Composer; + +use Symfony\Component\Process\Process; +use Mage\Task\AbstractTask; + +/** + * Composer Task - Self update + * + * @author Yanick Witschi + */ +class SelfUpdateTask extends AbstractTask +{ + /** + * Only used for unit tests. + * + * @var \DateTime + */ + private $dateToCompare; + + /** + * @return string + */ + public function getName() + { + return 'composer/selfupdate'; + } + + /** + * @return string + */ + public function getDescription() + { + return '[Composer] Selfupdate'; + } + + /** + * @return bool + */ + public function execute() + { + $options = $this->getOptions(); + $days = $options['days']; + $versionCommand = sprintf('%s --version', $options['path']); + + /** @var Process $process */ + $process = $this->runtime->runCommand(trim($versionCommand)); + + if (!$process->isSuccessful()) { + return false; + } + + $dt = $this->extractDate($process->getOutput()); + + // Date could not be extracted, always run update + if (false === $dt) { + return $this->selfUpdate($options); + } + + // Check age + if (!$this->isOlderThan($dt, $days)) { + return true; + } + + return $this->selfUpdate($options); + } + + /** + * This tasks obviously always takes the current date to compare the age + * of the composer.phar. This method is used for unit test purposes + * only. + * + * @param \DateTime $dateToCompare + */ + public function setDateToCompare(\DateTime $dateToCompare) + { + $this->dateToCompare = $dateToCompare; + } + + /** + * @param \DateTime $dt + * @param int $days + * + * @return bool + */ + protected function isOlderThan(\DateTime $dt, $days) + { + $dtComp = new \DateTime($days . ' days ago'); + + if (null !== $this->dateToCompare) { + $dtComp = $this->dateToCompare; + } + + return $dt < $dtComp; + } + + /** + * @param array $options + * + * @return bool + */ + protected function selfUpdate(array $options) + { + $selfupdateCommand = sprintf('%s selfupdate %s', $options['path'], $options['release']); + + /** @var Process $process */ + $process = $this->runtime->runCommand(trim($selfupdateCommand)); + + return $process->isSuccessful(); + } + + /** + * @param string $output + * + * @return \DateTime|false + */ + protected function extractDate($output) + { + $date = substr($output, -19); + + return \DateTime::createFromFormat('Y-m-d H:i:s', $date); + } + + /** + * @return array + */ + protected function getOptions() + { + $options = array_merge( + ['path' => 'composer', 'release' => '', 'days' => 30], + $this->runtime->getMergedOption('composer'), + $this->options + ); + + return $options; + } +} diff --git a/tests/Command/BuiltIn/Composer/SelfUpdateTaskTest.php b/tests/Command/BuiltIn/Composer/SelfUpdateTaskTest.php new file mode 100644 index 0000000..c394c27 --- /dev/null +++ b/tests/Command/BuiltIn/Composer/SelfUpdateTaskTest.php @@ -0,0 +1,157 @@ +assertSame('composer/selfupdate', $task->getName()); + $this->assertSame('[Composer] Selfupdate', $task->getDescription()); + } + + public function testExecuteWithFailingVersionDoesNotCallSelfupdate() + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setMethods(['runCommand']) + ->getMock(); + + $runtime + ->expects($this->once()) + ->method('runCommand') + ->with('composer --version') + ->willReturn($this->mockProcess(false)); + + $task = $this->getTask($runtime); + $this->assertFalse($task->execute()); + } + + public function testExecuteWithNoDateVersionDoesCallSelfupdate() + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setMethods(['runCommand']) + ->getMock(); + + $runtime + ->expects($this->exactly(2)) + ->method('runCommand') + ->withConsecutive( + ['composer --version'], + ['composer selfupdate'] + ) + ->willReturnOnConsecutiveCalls( + $this->mockProcess(true, 'whatever-without-valid-date'), + $this->mockProcess(true) + ); + + $task = $this->getTask($runtime); + $this->assertTrue($task->execute()); + } + + public function testExecuteShouldUpdate() + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setMethods(['runCommand']) + ->getMock(); + + $runtime + ->expects($this->exactly(2)) + ->method('runCommand') + ->withConsecutive( + ['composer --version'], + ['composer selfupdate'] + ) + ->willReturnOnConsecutiveCalls( + $this->mockProcess(true, 'Composer version 1.3.2 2017-01-01 18:23:41'), + $this->mockProcess(true) + ); + + $task = $this->getTask($runtime); + $task->setOptions(['days' => 30]); + $this->assertTrue($task->execute()); + } + + public function testExecuteShouldNotUpdate() + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setMethods(['runCommand']) + ->getMock(); + + $runtime + ->expects($this->exactly(1)) + ->method('runCommand') + ->with('composer --version') + ->willReturn($this->mockProcess(true, 'Composer version 1.3.2 2017-01-01 18:23:41')); + + $task = $this->getTask($runtime); + $task->setDateToCompare(\DateTime::createFromFormat('Y-m-d H:i:s', '2016-12-10 18:23:41')); + $task->setOptions(['days' => 30]); + $this->assertTrue($task->execute()); + } + + public function testWithRelease() + { + $runtime = $this->getMockBuilder(Runtime::class) + ->setMethods(['runCommand']) + ->getMock(); + + $runtime + ->expects($this->exactly(2)) + ->method('runCommand') + ->withConsecutive( + ['composer --version'], + ['composer selfupdate 1.3.1'] + ) + ->willReturnOnConsecutiveCalls( + $this->mockProcess(true, 'Composer version 1.3.2 2017-01-01 18:23:41'), + $this->mockProcess(true) + ); + + $task = $this->getTask($runtime); + $task->setOptions(['days' => 30, 'release' => '1.3.1']); + $this->assertTrue($task->execute()); + } + + private function getTask($runtime) + { + $config = [ + 'magephp' => [ + 'composer' => [ + 'path' => 'composer.phar' + ] + ] + ]; + + /** @var Runtime $runtime */ + $runtime->setConfiguration($config); + + $task = new SelfUpdateTask(); + $task->setRuntime($runtime); + + return $task; + } + + private function mockProcess($successful, $output = '') + { + $process = $this->getMockBuilder(Process::class) + ->disableOriginalConstructor() + ->getMock(); + $process + ->expects($this->any()) + ->method('isSuccessful') + ->willReturn($successful); + + $process + ->expects($this->any()) + ->method('getOutput') + ->willReturn($output); + + return $process; + } +}