* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; use PHPUnit\Framework\TestCase; use SebastianBergmann\Diff\Differ; use SebastianBergmann\Diff\Utils\FileUtils; use SebastianBergmann\Diff\Utils\UnifiedDiffAssertTrait; use Symfony\Component\Process\Process; /** * @covers SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\Differ * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator * * @requires OS Linux */ final class StrictUnifiedDiffOutputBuilderIntegrationTest extends TestCase { use UnifiedDiffAssertTrait; private $dir; private $fileFrom; private $fileTo; private $filePatch; protected function setUp(): void { $this->dir = \realpath(__DIR__ . '/../../fixtures/out') . '/'; $this->fileFrom = $this->dir . 'from.txt'; $this->fileTo = $this->dir . 'to.txt'; $this->filePatch = $this->dir . 'diff.patch'; if (!\is_dir($this->dir)) { throw new \RuntimeException('Integration test working directory not found.'); } $this->cleanUpTempFiles(); } protected function tearDown(): void { $this->cleanUpTempFiles(); } /** * Integration test * * - get a file pair * - create a `diff` between the files * - test applying the diff using `git apply` * - test applying the diff using `patch` * * @param string $fileFrom * @param string $fileTo * * @dataProvider provideFilePairs */ public function testIntegrationUsingPHPFileInVendorGitApply(string $fileFrom, string $fileTo): void { $from = FileUtils::getFileContent($fileFrom); $to = FileUtils::getFileContent($fileTo); $diff = (new Differ(new StrictUnifiedDiffOutputBuilder(['fromFile' => 'Original', 'toFile' => 'New'])))->diff($from, $to); if ('' === $diff && $from === $to) { // odd case: test after executing as it is more efficient than to read the files and check the contents every time $this->addToAssertionCount(1); return; } $this->doIntegrationTestGitApply($diff, $from, $to); } /** * Integration test * * - get a file pair * - create a `diff` between the files * - test applying the diff using `git apply` * - test applying the diff using `patch` * * @param string $fileFrom * @param string $fileTo * * @dataProvider provideFilePairs */ public function testIntegrationUsingPHPFileInVendorPatch(string $fileFrom, string $fileTo): void { $from = FileUtils::getFileContent($fileFrom); $to = FileUtils::getFileContent($fileTo); $diff = (new Differ(new StrictUnifiedDiffOutputBuilder(['fromFile' => 'Original', 'toFile' => 'New'])))->diff($from, $to); if ('' === $diff && $from === $to) { // odd case: test after executing as it is more efficient than to read the files and check the contents every time $this->addToAssertionCount(1); return; } $this->doIntegrationTestPatch($diff, $from, $to); } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider provideOutputBuildingCases * @dataProvider provideSample * @dataProvider provideBasicDiffGeneration */ public function testIntegrationOfUnitTestCasesGitApply(string $expected, string $from, string $to): void { $this->doIntegrationTestGitApply($expected, $from, $to); } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider provideOutputBuildingCases * @dataProvider provideSample * @dataProvider provideBasicDiffGeneration */ public function testIntegrationOfUnitTestCasesPatch(string $expected, string $from, string $to): void { $this->doIntegrationTestPatch($expected, $from, $to); } public function provideOutputBuildingCases(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideOutputBuildingCases(); } public function provideSample(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideSample(); } public function provideBasicDiffGeneration(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideBasicDiffGeneration(); } public function provideFilePairs(): array { $cases = []; $fromFile = __FILE__; $vendorDir = \realpath(__DIR__ . '/../../../vendor'); $fileIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($vendorDir, \RecursiveDirectoryIterator::SKIP_DOTS)); /** @var \SplFileInfo $file */ foreach ($fileIterator as $file) { if ('php' !== $file->getExtension()) { continue; } $toFile = $file->getPathname(); $cases[\sprintf("Diff file:\n\"%s\"\nvs.\n\"%s\"\n", \realpath($fromFile), \realpath($toFile))] = [$fromFile, $toFile]; $fromFile = $toFile; } return $cases; } /** * Compare diff create by builder and against one create by `diff` command. * * @param string $diff * @param string $from * @param string $to * * @dataProvider provideBasicDiffGeneration */ public function testIntegrationDiffOutputBuilderVersusDiffCommand(string $diff, string $from, string $to): void { $this->assertNotSame('', $diff); $this->assertValidUnifiedDiffFormat($diff); $this->assertNotFalse(\file_put_contents($this->fileFrom, $from)); $this->assertNotFalse(\file_put_contents($this->fileTo, $to)); $p = new Process(\sprintf('diff -u %s %s', \escapeshellarg($this->fileFrom), \escapeshellarg($this->fileTo))); $p->run(); $this->assertSame(1, $p->getExitCode()); // note: Process assumes exit code 0 for `isSuccessful`, however `diff` uses the exit code `1` for success with diff $output = $p->getOutput(); $diffLines = \preg_split('/(.*\R)/', $diff, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $diffLines[0] = \preg_replace('#^\-\-\- .*#', '--- /' . $this->fileFrom, $diffLines[0], 1); $diffLines[1] = \preg_replace('#^\+\+\+ .*#', '+++ /' . $this->fileFrom, $diffLines[1], 1); $diff = \implode('', $diffLines); $outputLines = \preg_split('/(.*\R)/', $output, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $outputLines[0] = \preg_replace('#^\-\-\- .*#', '--- /' . $this->fileFrom, $outputLines[0], 1); $outputLines[1] = \preg_replace('#^\+\+\+ .*#', '+++ /' . $this->fileFrom, $outputLines[1], 1); $output = \implode('', $outputLines); $this->assertSame($diff, $output); } private function doIntegrationTestGitApply(string $diff, string $from, string $to): void { $this->assertNotSame('', $diff); $this->assertValidUnifiedDiffFormat($diff); $diff = self::setDiffFileHeader($diff, $this->fileFrom); $this->assertNotFalse(\file_put_contents($this->fileFrom, $from)); $this->assertNotFalse(\file_put_contents($this->filePatch, $diff)); $p = new Process(\sprintf( 'git --git-dir %s apply --check -v --unsafe-paths --ignore-whitespace %s', \escapeshellarg($this->dir), \escapeshellarg($this->filePatch) )); $p->run(); $this->assertProcessSuccessful($p); } private function doIntegrationTestPatch(string $diff, string $from, string $to): void { $this->assertNotSame('', $diff); $this->assertValidUnifiedDiffFormat($diff); $diff = self::setDiffFileHeader($diff, $this->fileFrom); $this->assertNotFalse(\file_put_contents($this->fileFrom, $from)); $this->assertNotFalse(\file_put_contents($this->filePatch, $diff)); $command = \sprintf( 'patch -u --verbose --posix %s < %s', \escapeshellarg($this->fileFrom), \escapeshellarg($this->filePatch) ); $p = new Process($command); $p->run(); $this->assertProcessSuccessful($p); $this->assertStringEqualsFile( $this->fileFrom, $to, \sprintf('Patch command "%s".', $command) ); } private function assertProcessSuccessful(Process $p): void { $this->assertTrue( $p->isSuccessful(), \sprintf( "Command exec. was not successful:\n\"%s\"\nOutput:\n\"%s\"\nStdErr:\n\"%s\"\nExit code %d.\n", $p->getCommandLine(), $p->getOutput(), $p->getErrorOutput(), $p->getExitCode() ) ); } private function cleanUpTempFiles(): void { @\unlink($this->fileFrom . '.orig'); @\unlink($this->fileFrom . '.rej'); @\unlink($this->fileFrom); @\unlink($this->fileTo); @\unlink($this->filePatch); } private static function setDiffFileHeader(string $diff, string $file): string { $diffLines = \preg_split('/(.*\R)/', $diff, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $diffLines[0] = \preg_replace('#^\-\-\- .*#', '--- /' . $file, $diffLines[0], 1); $diffLines[1] = \preg_replace('#^\+\+\+ .*#', '+++ /' . $file, $diffLines[1], 1); return \implode('', $diffLines); } }