diff --git a/config/services.xml b/config/services.xml
index 52ca97c..6c668fb 100644
--- a/config/services.xml
+++ b/config/services.xml
@@ -27,6 +27,11 @@
+
+
+
+
+
@@ -76,5 +81,11 @@
+
+
+
+
+
+
diff --git a/src/Command/Process/Action/RevertAction.php b/src/Command/Process/Action/RevertAction.php
index 5c47259..3ebd64e 100644
--- a/src/Command/Process/Action/RevertAction.php
+++ b/src/Command/Process/Action/RevertAction.php
@@ -179,7 +179,7 @@ private function printPatchRevertingFailed(OutputInterface $output, PatchInterfa
'Reverting patch %s (%s) failed.%s',
$patch->getId(),
$patch->getPath(),
- $this->renderer->formatErrorOutput($errorOutput)
+ PHP_EOL . $errorOutput
);
$this->logger->error($errorMessage);
diff --git a/src/Command/Process/ApplyLocal.php b/src/Command/Process/ApplyLocal.php
index 3ba8ebd..6316fab 100644
--- a/src/Command/Process/ApplyLocal.php
+++ b/src/Command/Process/ApplyLocal.php
@@ -89,13 +89,13 @@ public function run(InputInterface $input, OutputInterface $output)
$this->printInfo($output, $message);
array_push($appliedPatches, $patch);
} catch (ApplierException $exception) {
- $this->printError($output, 'Error: patch conflict happened');
+ $this->printError($output, 'Error: patch can\'t be applied');
$messages = $this->rollbackProcessor->process($appliedPatches);
$output->writeln($messages);
$errorMessage = sprintf(
'Applying patch %s failed.%s',
$patch->getPath(),
- $this->renderer->formatErrorOutput($exception->getMessage())
+ PHP_EOL . $exception->getMessage()
);
throw new RuntimeException($errorMessage, $exception->getCode());
diff --git a/src/Command/Process/Ece/Revert.php b/src/Command/Process/Ece/Revert.php
index b2b93b4..876267f 100644
--- a/src/Command/Process/Ece/Revert.php
+++ b/src/Command/Process/Ece/Revert.php
@@ -121,7 +121,7 @@ function ($patch) {
$errorMessage = sprintf(
'Reverting patch %s failed.%s',
$patch->getPath(),
- $this->renderer->formatErrorOutput($exception->getMessage())
+ PHP_EOL . $exception->getMessage()
);
$this->printError($output, $errorMessage);
}
diff --git a/src/Command/Process/Renderer.php b/src/Command/Process/Renderer.php
index 319ce7b..8c39bc9 100644
--- a/src/Command/Process/Renderer.php
+++ b/src/Command/Process/Renderer.php
@@ -140,21 +140,6 @@ public function printPatchInfo(
$output->writeln('');
}
- /**
- * Format error output.
- *
- * @param string $errorOutput
- * @return string
- */
- public function formatErrorOutput(string $errorOutput): string
- {
- if (preg_match('#^.*?Error Output:(?.*?)$#is', $errorOutput, $matches)) {
- $errorOutput = PHP_EOL . 'Error Output:' . $matches['errors'];
- }
-
- return $errorOutput;
- }
-
/**
* Asks a confirmation question to the user.
*
diff --git a/src/Command/Process/ShowStatus.php b/src/Command/Process/ShowStatus.php
index 181366e..0ae5294 100644
--- a/src/Command/Process/ShowStatus.php
+++ b/src/Command/Process/ShowStatus.php
@@ -111,9 +111,14 @@ function ($patch) {
*/
private function printDetailsInfo(OutputInterface $output)
{
+ $supportUrl = 'https://support.magento.com';
+ $releaseNotesUrl = 'https://devdocs.magento.com/quality-patches/release-notes.html';
+
$output->writeln(
- 'More detailed information about patches you can find on ' .
- 'https://support.magento.com>'
+ 'Patch details you can find on ' .
+ sprintf('%1$s> (search for patch id, ex. MDVA-30265)', $supportUrl) .
+ PHP_EOL .
+ sprintf('Release notes %1$s>', $releaseNotesUrl)
);
}
diff --git a/src/Patch/Applier.php b/src/Patch/Applier.php
index e5f2d1e..93ecfb6 100644
--- a/src/Patch/Applier.php
+++ b/src/Patch/Applier.php
@@ -10,8 +10,6 @@
use Magento\CloudPatches\Composer\MagentoVersion;
use Magento\CloudPatches\Filesystem\Filesystem;
use Magento\CloudPatches\Patch\Status\StatusPool;
-use Magento\CloudPatches\Shell\ProcessFactory;
-use Symfony\Component\Process\Exception\ProcessFailedException;
/**
* Applies and reverts patches.
@@ -19,9 +17,9 @@
class Applier
{
/**
- * @var ProcessFactory
+ * @var PatchCommandInterface
*/
- private $processFactory;
+ private $patchCommand;
/**
* @var GitConverter
@@ -39,18 +37,18 @@ class Applier
private $filesystem;
/**
- * @param ProcessFactory $processFactory
+ * @param PatchCommandInterface $patchCommand
* @param GitConverter $gitConverter
* @param MagentoVersion $magentoVersion
* @param Filesystem $filesystem
*/
public function __construct(
- ProcessFactory $processFactory,
+ PatchCommandInterface $patchCommand,
GitConverter $gitConverter,
MagentoVersion $magentoVersion,
Filesystem $filesystem
) {
- $this->processFactory = $processFactory;
+ $this->patchCommand = $patchCommand;
$this->gitConverter = $gitConverter;
$this->magentoVersion = $magentoVersion;
$this->filesystem = $filesystem;
@@ -69,13 +67,11 @@ public function apply(string $path, string $id): string
{
$content = $this->readContent($path);
try {
- $this->processFactory->create(['git', 'apply'], $content)
- ->mustRun();
- } catch (ProcessFailedException $exception) {
+ $this->patchCommand->apply($content);
+ } catch (PatchCommandException $exception) {
try {
- $this->processFactory->create(['git', 'apply', '--check', '--reverse'], $content)
- ->mustRun();
- } catch (ProcessFailedException $reverseException) {
+ $this->patchCommand->revertCheck($content);
+ } catch (PatchCommandException $reverseException) {
throw new ApplierException($exception->getMessage(), $exception->getCode());
}
@@ -98,13 +94,11 @@ public function revert(string $path, string $id): string
{
$content = $this->readContent($path);
try {
- $this->processFactory->create(['git', 'apply', '--reverse'], $content)
- ->mustRun();
- } catch (ProcessFailedException $exception) {
+ $this->patchCommand->revert($content);
+ } catch (PatchCommandException $exception) {
try {
- $this->processFactory->create(['git', 'apply', '--check'], $content)
- ->mustRun();
- } catch (ProcessFailedException $applyException) {
+ $this->patchCommand->applyCheck($content);
+ } catch (PatchCommandException $applyException) {
throw new ApplierException($exception->getMessage(), $exception->getCode());
}
@@ -124,13 +118,11 @@ public function status(string $patchContent): string
{
$patchContent = $this->prepareContent($patchContent);
try {
- $this->processFactory->create(['git', 'apply', '--check'], $patchContent)
- ->mustRun();
- } catch (ProcessFailedException $exception) {
+ $this->patchCommand->applyCheck($patchContent);
+ } catch (PatchCommandException $exception) {
try {
- $this->processFactory->create(['git', 'apply', '--check', '--reverse'], $patchContent)
- ->mustRun();
- } catch (ProcessFailedException $reverseException) {
+ $this->patchCommand->revertCheck($patchContent);
+ } catch (PatchCommandException $reverseException) {
return StatusPool::NA;
}
@@ -150,9 +142,8 @@ public function checkApply(string $patchContent): bool
{
$patchContent = $this->prepareContent($patchContent);
try {
- $this->processFactory->create(['git', 'apply', '--check'], $patchContent)
- ->mustRun();
- } catch (ProcessFailedException $exception) {
+ $this->patchCommand->applyCheck($patchContent);
+ } catch (PatchCommandException $exception) {
return false;
}
diff --git a/src/Patch/Conflict/Processor.php b/src/Patch/Conflict/Processor.php
index 2927899..68b99de 100644
--- a/src/Patch/Conflict/Processor.php
+++ b/src/Patch/Conflict/Processor.php
@@ -73,7 +73,7 @@ public function process(
array $appliedPatches,
string $exceptionMessage
) {
- $errorMessage = 'Error: patch conflict happened';
+ $errorMessage = 'Error: patch can\'t be applied';
$this->logger->error($errorMessage);
$output->writeln('' . $errorMessage . '');
@@ -84,7 +84,7 @@ public function process(
'Applying patch %s (%s) failed.%s%s',
$patch->getId(),
$patch->getPath(),
- $this->renderer->formatErrorOutput($exceptionMessage),
+ PHP_EOL . $exceptionMessage,
$conflictDetails ? PHP_EOL . $conflictDetails : ''
);
diff --git a/src/Patch/PatchCommand.php b/src/Patch/PatchCommand.php
new file mode 100644
index 0000000..a96baf4
--- /dev/null
+++ b/src/Patch/PatchCommand.php
@@ -0,0 +1,89 @@
+drivers = $drivers;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function apply(string $patch)
+ {
+ $this->getDriver()->apply($patch);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revert(string $patch)
+ {
+ $this->getDriver()->revert($patch);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applyCheck(string $patch)
+ {
+ $this->getDriver()->applyCheck($patch);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revertCheck(string $patch)
+ {
+ $this->getDriver()->revertCheck($patch);
+ }
+
+ /**
+ * Returns first available driver
+ *
+ * @return DriverInterface
+ * @throws PatchCommandNotFound
+ */
+ private function getDriver(): DriverInterface
+ {
+ if ($this->driver === null) {
+ foreach ($this->drivers as $driver) {
+ if ($driver->isInstalled()) {
+ $this->driver = $driver;
+ break;
+ }
+ }
+ if ($this->driver === null) {
+ throw new PatchCommandNotFound();
+ }
+ }
+ return $this->driver;
+ }
+}
diff --git a/src/Patch/PatchCommandException.php b/src/Patch/PatchCommandException.php
new file mode 100644
index 0000000..3bcda3d
--- /dev/null
+++ b/src/Patch/PatchCommandException.php
@@ -0,0 +1,17 @@
+getMessage();
+ if ($previous instanceof ProcessFailedException) {
+ $message = $previous->getProcess()->getErrorOutput() ?: ($previous->getProcess()->getOutput() ?: $message);
+ }
+ parent::__construct($message, $previous->getCode(), $previous);
+ }
+}
diff --git a/src/Shell/Command/DriverInterface.php b/src/Shell/Command/DriverInterface.php
new file mode 100644
index 0000000..9ac8d86
--- /dev/null
+++ b/src/Shell/Command/DriverInterface.php
@@ -0,0 +1,23 @@
+processFactory = $processFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function apply(string $patch)
+ {
+ try {
+ $this->processFactory->create(['git', 'apply'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revert(string $patch)
+ {
+ try {
+ $this->processFactory->create(['git', 'apply', '--reverse'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applyCheck(string $patch)
+ {
+ try {
+ $this->processFactory->create(['git', 'apply', '--check'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revertCheck(string $patch)
+ {
+ try {
+ $this->processFactory->create(['git', 'apply', '--reverse', '--check'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isInstalled(): bool
+ {
+ try {
+ $this->processFactory->create(['git', '--version'])->mustRun();
+ $result = true;
+ } catch (ProcessFailedException $exception) {
+ $result = false;
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Shell/Command/PatchDriver.php b/src/Shell/Command/PatchDriver.php
new file mode 100644
index 0000000..b22c4cc
--- /dev/null
+++ b/src/Shell/Command/PatchDriver.php
@@ -0,0 +1,100 @@
+processFactory = $processFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function apply(string $patch)
+ {
+ try {
+ $this->applyCheck($patch);
+ $this->processFactory->create(['patch', '--silent', '-p1', '--no-backup-if-mismatch'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revert(string $patch)
+ {
+ try {
+ $this->revertCheck($patch);
+ $this->processFactory->create(['patch', '--silent', '-p1', '--no-backup-if-mismatch', '--reverse'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function applyCheck(string $patch)
+ {
+ try {
+ $this->processFactory->create(['patch', '--silent', '-p1', '--dry-run'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function revertCheck(string $patch)
+ {
+ try {
+ $this->processFactory->create(['patch', '--silent', '-p1', '--reverse', '--dry-run'], $patch)
+ ->mustRun();
+ } catch (ProcessFailedException $exception) {
+ throw new DriverException($exception);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isInstalled(): bool
+ {
+ try {
+ $this->processFactory->create(['patch', '--version'])->mustRun();
+ $result = true;
+ } catch (ProcessFailedException $exception) {
+ $result = false;
+ }
+
+ return $result;
+ }
+}
diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php
index 4c0e3c5..9ef6b34 100644
--- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php
+++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php
@@ -184,9 +184,6 @@ public function testRevertWithException()
$this->applier->method('revert')
->willThrowException(new ApplierException('Error'));
- $this->renderer->expects($this->once())
- ->method('formatErrorOutput')
- ->with('Error');
$outputMock->expects($this->once())
->method('writeln')
->withConsecutive(
diff --git a/src/Test/Unit/Command/Process/ApplyLocalTest.php b/src/Test/Unit/Command/Process/ApplyLocalTest.php
index cc6490c..b9f7e85 100644
--- a/src/Test/Unit/Command/Process/ApplyLocalTest.php
+++ b/src/Test/Unit/Command/Process/ApplyLocalTest.php
@@ -171,9 +171,6 @@ function ($path, $title) {
->method('process')
->withConsecutive([[$patch1]])
->willReturn($rollbackMessages);
- $this->renderer->expects($this->once())
- ->method('formatErrorOutput')
- ->with('Applier error message');
$this->expectException(RuntimeException::class);
$this->manager->run($inputMock, $outputMock);
diff --git a/src/Test/Unit/Command/Process/Ece/RevertTest.php b/src/Test/Unit/Command/Process/Ece/RevertTest.php
index 82dacb0..1197ba2 100644
--- a/src/Test/Unit/Command/Process/Ece/RevertTest.php
+++ b/src/Test/Unit/Command/Process/Ece/RevertTest.php
@@ -165,10 +165,6 @@ function ($path, $title) {
}
);
- $this->renderer->expects($this->once())
- ->method('formatErrorOutput')
- ->with('Applier error message');
-
$this->revertAction->expects($this->once())
->method('execute')
->withConsecutive([$inputMock, $outputMock, []]);
diff --git a/src/Test/Unit/Command/Process/RendererTest.php b/src/Test/Unit/Command/Process/RendererTest.php
index f78e161..6a944b4 100644
--- a/src/Test/Unit/Command/Process/RendererTest.php
+++ b/src/Test/Unit/Command/Process/RendererTest.php
@@ -122,53 +122,6 @@ public function printPatchInfoDataProvider(): array
];
}
- /**
- * Tests error output formatting.
- *
- * @param string $errorOutput
- * @param string $expectedOutput
- * @dataProvider formatErrorOutputDataProvider
- */
- public function testFormatErrorOutput(string $errorOutput, string $expectedOutput)
- {
- $this->assertEquals($expectedOutput, $this->renderer->formatErrorOutput($errorOutput));
- }
-
- /**
- * @return array
- */
- public function formatErrorOutputDataProvider(): array
- {
- return [
- [
- 'error' => 'The command "\'git\' \'apply\' \'/path/to/patch/MC-1111_test_patch_1.1.1_ce.patch\'" failed.
-
-Exit Code: 1(General error)
-
-Working directory: /path/to/patch
-
-Output:
-================
-
-
-Error Output:
-================
-error: patch failed: vendor/magento/module-admin-analytics/Controller/Adminhtml/Config/DisableAdminUsage.php:23
-error: vendor/magento/module-admin-analytics/Controller/Adminhtml/Config/DisableAdminUsage.php: patch does not apply',
-
- 'expectedOutput' => '
-Error Output:
-================
-error: patch failed: vendor/magento/module-admin-analytics/Controller/Adminhtml/Config/DisableAdminUsage.php:23
-error: vendor/magento/module-admin-analytics/Controller/Adminhtml/Config/DisableAdminUsage.php: patch does not apply'
- ],
- [
- 'error' => 'Some other output',
- 'expectedOutput' => 'Some other output'
- ],
- ];
- }
-
/**
* Creates patch mock.
*
diff --git a/src/Test/Unit/Patch/ApplierTest.php b/src/Test/Unit/Patch/ApplierTest.php
index d62d0c0..cd08b37 100644
--- a/src/Test/Unit/Patch/ApplierTest.php
+++ b/src/Test/Unit/Patch/ApplierTest.php
@@ -12,12 +12,11 @@
use Magento\CloudPatches\Patch\Applier;
use Magento\CloudPatches\Patch\ApplierException;
use Magento\CloudPatches\Patch\GitConverter;
+use Magento\CloudPatches\Patch\PatchCommandException;
+use Magento\CloudPatches\Patch\PatchCommandInterface;
use Magento\CloudPatches\Patch\Status\StatusPool;
-use Magento\CloudPatches\Shell\ProcessFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
-use Symfony\Component\Process\Exception\ProcessFailedException;
-use Symfony\Component\Process\Process;
/**
* @inheritDoc
@@ -30,9 +29,9 @@ class ApplierTest extends TestCase
private $applier;
/**
- * @var ProcessFactory|MockObject
+ * @var PatchCommandInterface|MockObject
*/
- private $processFactory;
+ private $patchCommand;
/**
* @var GitConverter|MockObject
@@ -54,13 +53,13 @@ class ApplierTest extends TestCase
*/
protected function setUp()
{
- $this->processFactory = $this->createMock(ProcessFactory::class);
+ $this->patchCommand = $this->createMock(PatchCommandInterface::class);
$this->gitConverter = $this->createMock(GitConverter::class);
$this->magentoVersion = $this->createMock(MagentoVersion::class);
$this->filesystem = $this->createMock(Filesystem::class);
$this->applier = new Applier(
- $this->processFactory,
+ $this->patchCommand,
$this->gitConverter,
$this->magentoVersion,
$this->filesystem
@@ -86,14 +85,10 @@ public function testApply()
$this->gitConverter->expects($this->once())
->method('convert')
->willReturn('gitContent');
- $processMock = $this->createMock(Process::class);
- $this->processFactory->expects($this->once())
- ->method('create')
- ->withConsecutive([['git', 'apply'], 'gitContent'])
- ->willReturn($processMock);
- $processMock->expects($this->once())
- ->method('mustRun');
+ $this->patchCommand->expects($this->once())
+ ->method('apply')
+ ->with('gitContent');
$this->assertSame($expectedMessage, $this->applier->apply($path, $patchId));
}
@@ -106,14 +101,13 @@ public function testApplyFailed()
$path = 'path/to/patch';
$patchId = 'MC-11111';
- /** @var Process|MockObject $result */
- $processMock = $this->createMock(Process::class);
- $processMock->method('mustRun')
- ->willThrowException(new ProcessFailedException($processMock));
+ $this->patchCommand->expects($this->once())
+ ->method('apply')
+ ->willThrowException(new PatchCommandException('Patch cannot be applied'));
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturn($processMock);
+ $this->patchCommand->expects($this->once())
+ ->method('revertCheck')
+ ->willThrowException(new PatchCommandException('Patch cannot be reverted'));
$this->expectException(ApplierException::class);
$this->applier->apply($path, $patchId);
@@ -139,45 +133,16 @@ public function testApplyPatchAlreadyApplied()
$this->gitConverter->expects($this->never())
->method('convert');
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturnMap([
- [['git', 'apply'], 'patchContent'],
- [['git', 'apply', '--check', '--reverse'], 'patchContent']
- ])->willReturnCallback([$this, 'shellApplyRevertCallback']);
+ $this->patchCommand->expects($this->once())
+ ->method('apply')
+ ->with('patchContent')
+ ->willThrowException(new PatchCommandException('Patch cannot be applied'));
- $this->assertSame($expectedMessage, $this->applier->apply($path, $patchId));
- }
+ $this->patchCommand->expects($this->once())
+ ->method('revertCheck')
+ ->with('patchContent');
- /**
- * Callback for 'apply' and 'revert' operations.
- *
- * @param array $command
- * @return Process
- *
- * @throws ProcessFailedException when the command isn't a reverse
- */
- public function shellApplyRevertCallback(array $command): Process
- {
- if (in_array('--reverse', $command, true) && in_array('--check', $command, true) ||
- !in_array('--reverse', $command, true) && in_array('--check', $command, true)
- ) {
- // Command was the reverse check, it's all good.
- /** @var Process|MockObject $result */
- $result = $this->createMock(Process::class);
- $result->expects($this->once())
- ->method('mustRun');
-
- return $result;
- }
-
- /** @var Process|MockObject $result */
- $result = $this->createMock(Process::class);
- $result->expects($this->once())
- ->method('mustRun')
- ->willThrowException(new ProcessFailedException($result));
-
- return $result;
+ $this->assertSame($expectedMessage, $this->applier->apply($path, $patchId));
}
/**
@@ -201,14 +166,9 @@ public function testRevert()
->method('convert')
->willReturn('gitContent');
- $processMock = $this->createMock(Process::class);
-
- $this->processFactory->expects($this->once())
- ->method('create')
- ->withConsecutive([['git', 'apply', '--reverse'], 'gitContent'])
- ->willReturn($processMock);
- $processMock->expects($this->once())
- ->method('mustRun');
+ $this->patchCommand->expects($this->once())
+ ->method('revert')
+ ->with('gitContent');
$this->assertSame($expectedMessage, $this->applier->revert($path, $patchId));
}
@@ -221,14 +181,13 @@ public function testRevertFailed()
$path = 'path/to/patch';
$patchId = 'MC-11111';
- /** @var Process|MockObject $result */
- $processMock = $this->createMock(Process::class);
- $processMock->method('mustRun')
- ->willThrowException(new ProcessFailedException($processMock));
+ $this->patchCommand->expects($this->once())
+ ->method('revert')
+ ->willThrowException(new PatchCommandException('Patch cannot be reverted'));
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturn($processMock);
+ $this->patchCommand->expects($this->once())
+ ->method('applyCheck')
+ ->willThrowException(new PatchCommandException('Patch cannot be applied'));
$this->expectException(ApplierException::class);
$this->applier->revert($path, $patchId);
@@ -255,12 +214,14 @@ public function testRevertPatchWasntApplied()
$this->gitConverter->expects($this->never())
->method('convert');
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturnMap([
- [['git', 'apply'], $patchContent],
- [['git', 'apply', '--check'], $patchContent]
- ])->willReturnCallback([$this, 'shellApplyRevertCallback']);
+ $this->patchCommand->expects($this->once())
+ ->method('revert')
+ ->with($patchContent)
+ ->willThrowException(new PatchCommandException('Patch cannot be reverted'));
+
+ $this->patchCommand->expects($this->once())
+ ->method('applyCheck')
+ ->with($patchContent);
$this->assertSame($expectedMessage, $this->applier->revert($path, $patchId));
}
@@ -271,14 +232,10 @@ public function testRevertPatchWasntApplied()
public function testStatusNotApplied()
{
$patchContent = 'patch content';
- $processMock = $this->createMock(Process::class);
- $this->processFactory->expects($this->once())
- ->method('create')
- ->withConsecutive([['git', 'apply', '--check'], $patchContent])
- ->willReturn($processMock);
- $processMock->expects($this->once())
- ->method('mustRun');
+ $this->patchCommand->expects($this->once())
+ ->method('applyCheck')
+ ->with($patchContent);
$this->assertSame(StatusPool::NOT_APPLIED, $this->applier->status($patchContent));
}
@@ -290,14 +247,15 @@ public function testStatusNotAvailable()
{
$patchContent = 'patch content';
- /** @var Process|MockObject $result */
- $processMock = $this->createMock(Process::class);
- $processMock->method('mustRun')
- ->willThrowException(new ProcessFailedException($processMock));
+ $this->patchCommand->expects($this->once())
+ ->method('applyCheck')
+ ->with($patchContent)
+ ->willThrowException(new PatchCommandException('Patch cannot be applied'));
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturn($processMock);
+ $this->patchCommand->expects($this->once())
+ ->method('revertCheck')
+ ->with($patchContent)
+ ->willThrowException(new PatchCommandException('Patch cannot be reverted'));
$this->assertSame(StatusPool::NA, $this->applier->status($patchContent));
}
@@ -309,42 +267,15 @@ public function testStatusApplied()
{
$patchContent = 'patch content';
- $this->processFactory->expects($this->exactly(2))
- ->method('create')
- ->willReturnMap([
- [['git', 'apply', '--check']],
- [['git', 'apply', '--check', '--reverse']]
- ])->willReturnCallback([$this, 'shellStatusCallback']);
+ $this->patchCommand->expects($this->once())
+ ->method('applyCheck')
+ ->with($patchContent)
+ ->willThrowException(new PatchCommandException('Patch cannot be applied'));
- $this->assertSame(StatusPool::APPLIED, $this->applier->status($patchContent));
- }
+ $this->patchCommand->expects($this->once())
+ ->method('revertCheck')
+ ->with($patchContent);
- /**
- * Callback for 'status' operations.
- *
- * @param array $command
- * @return Process
- *
- * @throws ProcessFailedException when the command isn't a reverse
- */
- public function shellStatusCallback(array $command): Process
- {
- if (in_array('--reverse', $command, true) && in_array('--check', $command, true)) {
- // Command was the reverse check, it's all good.
- /** @var Process|MockObject $result */
- $result = $this->createMock(Process::class);
- $result->expects($this->once())
- ->method('mustRun');
-
- return $result;
- }
-
- /** @var Process|MockObject $result */
- $result = $this->createMock(Process::class);
- $result->expects($this->once())
- ->method('mustRun')
- ->willThrowException(new ProcessFailedException($result));
-
- return $result;
+ $this->assertSame(StatusPool::APPLIED, $this->applier->status($patchContent));
}
}
diff --git a/src/Test/Unit/Patch/Conflict/ProcessorTest.php b/src/Test/Unit/Patch/Conflict/ProcessorTest.php
index 00569d6..8f09459 100644
--- a/src/Test/Unit/Patch/Conflict/ProcessorTest.php
+++ b/src/Test/Unit/Patch/Conflict/ProcessorTest.php
@@ -76,7 +76,6 @@ public function testProcess()
$failedPatch = $this->createPatch('MC-3', 'path3');
$exceptionMessage = 'exceptionMessage';
$conflictDetails = 'Conflict details';
- $formattedOutput = 'formattedOutput';
$rollbackMessages = ['Patch 1 has been reverted', 'Patch 2 has been reverted'];
/** @var OutputInterface|MockObject $outputMock */
@@ -90,14 +89,10 @@ public function testProcess()
->method('analyze')
->withConsecutive([$failedPatch])
->willReturn($conflictDetails);
- $this->renderer->expects($this->once())
- ->method('formatErrorOutput')
- ->withConsecutive([$exceptionMessage])
- ->willReturn($formattedOutput);
$outputMock->expects($this->exactly(2))
->method('writeln')
->withConsecutive(
- [$this->stringContains('Error: patch conflict happened')],
+ [$this->stringContains('Error: patch can\'t be applied')],
[$rollbackMessages]
);
@@ -105,7 +100,7 @@ public function testProcess()
'Applying patch %s (%s) failed.%s%s',
$failedPatch->getId(),
$failedPatch->getPath(),
- $formattedOutput,
+ PHP_EOL .$exceptionMessage,
PHP_EOL . $conflictDetails
);
diff --git a/src/Test/Unit/Shell/Command/PatchDriverTest.php b/src/Test/Unit/Shell/Command/PatchDriverTest.php
new file mode 100644
index 0000000..d1462d5
--- /dev/null
+++ b/src/Test/Unit/Shell/Command/PatchDriverTest.php
@@ -0,0 +1,187 @@
+baseDir = dirname(__DIR__, 5) . '/tests/unit/';
+ $this->cwd = $this->baseDir . 'var/';
+ $processFactory = $this->createMock(ProcessFactory::class);
+ $processFactory->method('create')
+ ->willReturnCallback(
+ function (array $cmd, string $input = null) {
+ return new Process(
+ $cmd,
+ $this->cwd,
+ null,
+ $input
+ );
+ }
+ );
+ $this->command = new PatchDriver(
+ $processFactory
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function tearDown()
+ {
+ foreach (glob($this->cwd . '*') as $file) {
+ if (is_file($file)) {
+ unlink($file);
+ }
+ }
+ parent::tearDown();
+ }
+
+ /**
+ * Tests that patch is applied
+ */
+ public function testApply()
+ {
+ $this->copyFileToWorkingDir($this->getFixtureFile('file1.md'));
+ $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch'));
+ $this->command->apply($patchContent);
+ $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md'));
+ $actual = $this->getFileContent($this->getVarFile('file1.md'));
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that patch is not applied to any target files if an error occurs
+ */
+ public function testApplyFailure()
+ {
+ $this->copyFileToWorkingDir($this->getFixtureFile('file1.md'));
+ $this->copyFileToWorkingDir($this->getFixtureFile('file2_applied_patch.md'), 'file2.md');
+ $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch'));
+ $exception = null;
+ try {
+ $this->command->apply($patchContent);
+ } catch (PatchCommandException $e) {
+ $exception = $e;
+ }
+ $this->assertNotNull($exception);
+ $expected = $this->getFileContent($this->getFixtureFile('file1.md'));
+ $actual = $this->getFileContent($this->getVarFile('file1.md'));
+ $this->assertEquals($expected, $actual);
+ $expected = $this->getFileContent($this->getFixtureFile('file2_applied_patch.md'));
+ $actual = $this->getFileContent($this->getVarFile('file2.md'));
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that patch is reverted
+ */
+ public function testRevert()
+ {
+ $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md');
+ $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch'));
+ $this->command->revert($patchContent);
+ $expected = $this->getFileContent($this->getFixtureFile('file1.md'));
+ $actual = $this->getFileContent($this->getVarFile('file1.md'));
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Tests that patch is not reverted in any target files if an error occurs
+ */
+ public function testRevertFailure()
+ {
+ $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md');
+ $this->copyFileToWorkingDir($this->getFixtureFile('file2.md'));
+ $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch'));
+ $exception = null;
+ try {
+ $this->command->revert($patchContent);
+ } catch (PatchCommandException $e) {
+ $exception = $e;
+ }
+ $this->assertNotNull($exception);
+ $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md'));
+ $actual = $this->getFileContent($this->getVarFile('file1.md'));
+ $this->assertEquals($expected, $actual);
+ $expected = $this->getFileContent($this->getFixtureFile('file2.md'));
+ $actual = $this->getFileContent($this->getVarFile('file2.md'));
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Get file path in var directory
+ *
+ * @param string $name
+ * @return string
+ */
+ private function getVarFile(string $name): string
+ {
+ return $this->cwd . $name;
+ }
+
+ /**
+ * Get file path in files directory
+ *
+ * @param string $name
+ * @return string
+ */
+ private function getFixtureFile(string $name): string
+ {
+ return $this->baseDir . '_data/files/' . $name;
+ }
+
+ /**
+ * Get the file content
+ *
+ * @param string $path
+ * @return string
+ */
+ private function getFileContent(string $path): string
+ {
+ return file_get_contents($path);
+ }
+
+ /**
+ * Copy file to working directory
+ *
+ * @param string $path
+ * @param string|null $name
+ */
+ private function copyFileToWorkingDir(string $path, string $name = null)
+ {
+ $name = $name ?? basename($path);
+ copy($path, $this->getVarFile($name));
+ }
+}
diff --git a/tests/unit/_data/files/file1.md b/tests/unit/_data/files/file1.md
new file mode 100644
index 0000000..abe4d18
--- /dev/null
+++ b/tests/unit/_data/files/file1.md
@@ -0,0 +1 @@
+## File 1
diff --git a/tests/unit/_data/files/file1.patch b/tests/unit/_data/files/file1.patch
new file mode 100644
index 0000000..9c259fe
--- /dev/null
+++ b/tests/unit/_data/files/file1.patch
@@ -0,0 +1,9 @@
+diff --git a/file1.md b/file1.md
+index abe4d18..2f8298f 100644
+--- a/file1.md
++++ b/file1.md
+@@ -1 +1,3 @@
+-## File 1
++# File One
++
++## Description
diff --git a/tests/unit/_data/files/file1_and_file2.patch b/tests/unit/_data/files/file1_and_file2.patch
new file mode 100644
index 0000000..f2dcb41
--- /dev/null
+++ b/tests/unit/_data/files/file1_and_file2.patch
@@ -0,0 +1,18 @@
+diff --git a/file1.md b/file1.md
+index abe4d18..2f8298f 100644
+--- a/file1.md
++++ b/file1.md
+@@ -1 +1,3 @@
+-## File 1
++# File One
++
++## Description
+diff --git a/file2.md b/file2.md
+index 37ae4d5..c49d210 100644
+--- a/file2.md
++++ b/file2.md
+@@ -1 +1,3 @@
+-## File 2
++# File Two
++
++## Description
diff --git a/tests/unit/_data/files/file1_applied_patch.md b/tests/unit/_data/files/file1_applied_patch.md
new file mode 100644
index 0000000..2f8298f
--- /dev/null
+++ b/tests/unit/_data/files/file1_applied_patch.md
@@ -0,0 +1,3 @@
+# File One
+
+## Description
diff --git a/tests/unit/_data/files/file2.md b/tests/unit/_data/files/file2.md
new file mode 100644
index 0000000..37ae4d5
--- /dev/null
+++ b/tests/unit/_data/files/file2.md
@@ -0,0 +1 @@
+## File 2
diff --git a/tests/unit/_data/files/file2_applied_patch.md b/tests/unit/_data/files/file2_applied_patch.md
new file mode 100644
index 0000000..c49d210
--- /dev/null
+++ b/tests/unit/_data/files/file2_applied_patch.md
@@ -0,0 +1,3 @@
+# File Two
+
+## Description
diff --git a/tests/unit/var/.gitignore b/tests/unit/var/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/tests/unit/var/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore