pax_global_header00006660000000000000000000000064134257524430014523gustar00rootroot0000000000000052 comment=720fcc7e9b5cf384ea68d9d930d480907a0c1a29 diff-3.0.2/000077500000000000000000000000001342575244300124355ustar00rootroot00000000000000diff-3.0.2/.github/000077500000000000000000000000001342575244300137755ustar00rootroot00000000000000diff-3.0.2/.github/stale.yml000066400000000000000000000027011342575244300156300ustar00rootroot00000000000000# Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 # Number of days of inactivity before a stale Issue or Pull Request is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - enhancement # Set to true to ignore issues in a project (defaults to false) exemptProjects: false # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: false # Label to use when marking as stale staleLabel: wontfix # Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had activity within the last 60 days. It will be closed after 7 days if no further activity occurs. Thank you for your contributions. # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. closeComment: > This issue has been automatically closed because it has not had activity since it was marked as stale. Thank you for your contributions. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 30 # Limit to only `issues` or `pulls` only: issues diff-3.0.2/.gitignore000066400000000000000000000001221342575244300144200ustar00rootroot00000000000000/.idea /composer.lock /vendor /.php_cs.cache /.phpunit.result.cache /from.txt.origdiff-3.0.2/.php_cs.dist000066400000000000000000000150121342575244300146530ustar00rootroot00000000000000 For the full copyright and license information, please view the LICENSE file that was distributed with this source code. EOF; return PhpCsFixer\Config::create() ->setRiskyAllowed(true) ->setRules( [ 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => [ 'operators' => [ '=' => 'align', '=>' => 'align', ], ], 'blank_line_after_namespace' => true, 'blank_line_before_statement' => [ 'statements' => [ 'break', 'continue', 'declare', 'do', 'for', 'foreach', 'if', 'include', 'include_once', 'require', 'require_once', 'return', 'switch', 'throw', 'try', 'while', 'yield', ], ], 'braces' => true, 'cast_spaces' => true, 'class_attributes_separation' => ['elements' => ['method']], 'compact_nullable_typehint' => true, 'concat_space' => ['spacing' => 'one'], 'declare_equal_normalize' => ['space' => 'none'], 'declare_strict_types' => true, 'dir_constant' => true, 'elseif' => true, 'encoding' => true, 'full_opening_tag' => true, 'function_declaration' => true, 'header_comment' => ['header' => $header, 'separate' => 'none'], 'indentation_type' => true, 'line_ending' => true, 'list_syntax' => ['syntax' => 'short'], 'lowercase_cast' => true, 'lowercase_constants' => true, 'lowercase_keywords' => true, 'magic_constant_casing' => true, 'method_argument_space' => ['ensure_fully_multiline' => true], 'modernize_types_casting' => true, 'native_function_casing' => true, 'native_function_invocation' => true, 'no_alias_functions' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_closing_tag' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_blank_lines' => true, 'no_homoglyph_names' => true, 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => ['use' => 'print'], 'no_null_property_initialization' => true, 'no_short_bool_cast' => true, 'no_short_echo_tag' => true, 'no_singleline_whitespace_before_semicolons' => true, 'no_spaces_after_function_name' => true, 'no_spaces_inside_parenthesis' => true, 'no_superfluous_elseif' => true, 'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_trailing_whitespace' => true, 'no_trailing_whitespace_in_comment' => true, 'no_unneeded_control_parentheses' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, 'no_unreachable_default_argument_value' => true, 'no_unused_imports' => true, 'no_useless_else' => true, 'no_whitespace_before_comma_in_array' => true, 'no_whitespace_in_blank_line' => true, 'non_printable_character' => true, 'normalize_index_brace' => true, 'object_operator_without_whitespace' => true, 'ordered_class_elements' => [ 'order' => [ 'use_trait', 'constant_public', 'constant_protected', 'constant_private', 'property_public_static', 'property_protected_static', 'property_private_static', 'property_public', 'property_protected', 'property_private', 'method_public_static', 'construct', 'destruct', 'magic', 'phpunit', 'method_public', 'method_protected', 'method_private', 'method_protected_static', 'method_private_static', ], ], 'ordered_imports' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_align' => true, 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, 'phpdoc_order' => true, 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_separation' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'phpdoc_types' => true, 'phpdoc_types_order' => true, 'phpdoc_var_without_name' => true, 'pow_to_exponentiation' => true, 'protected_to_private' => true, 'return_type_declaration' => ['space_before' => 'none'], 'self_accessor' => true, 'short_scalar_cast' => true, 'simplified_null_return' => true, 'single_blank_line_at_eof' => true, 'single_import_per_statement' => true, 'single_line_after_imports' => true, 'single_quote' => true, 'standardize_not_equals' => true, 'ternary_to_null_coalescing' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'visibility_required' => true, 'void_return' => true, 'whitespace_after_comma_in_array' => true, ] ) ->setFinder( PhpCsFixer\Finder::create() ->files() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ); diff-3.0.2/.travis.yml000066400000000000000000000006531342575244300145520ustar00rootroot00000000000000language: php php: - 7.1 - 7.2 - 7.3 - master sudo: false before_install: - composer self-update - composer clear-cache install: - travis_retry composer update --no-interaction --no-ansi --no-progress --no-suggest --optimize-autoloader --prefer-stable script: - ./vendor/bin/phpunit --coverage-clover=coverage.xml after_success: - bash <(curl -s https://codecov.io/bash) notifications: email: false diff-3.0.2/ChangeLog.md000066400000000000000000000030741342575244300146120ustar00rootroot00000000000000# ChangeLog All notable changes are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## [3.0.2] - 2019-02-04 ### Changed * `Chunk::setLines()` now ensures that the `$lines` array only contains `Line` objects ## [3.0.1] - 2018-06-10 ### Fixed * Removed `"minimum-stability": "dev",` from `composer.json` ## [3.0.0] - 2018-02-01 * The `StrictUnifiedDiffOutputBuilder` implementation of the `DiffOutputBuilderInterface` was added ### Changed * The default `DiffOutputBuilderInterface` implementation now generates context lines (unchanged lines) ### Removed * Removed support for PHP 7.0 ### Fixed * Fixed [#70](https://github.com/sebastianbergmann/diff/issues/70): Diffing of arrays no longer works ## [2.0.1] - 2017-08-03 ### Fixed * Fixed [#66](https://github.com/sebastianbergmann/diff/pull/66): Restored backwards compatibility for PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 ## [2.0.0] - 2017-07-11 [YANKED] ### Added * Implemented [#64](https://github.com/sebastianbergmann/diff/pull/64): Show line numbers for chunks of a diff ### Removed * This component is no longer supported on PHP 5.6 [3.0.2]: https://github.com/sebastianbergmann/diff/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/sebastianbergmann/diff/compare/3.0.0...3.0.1 [3.0.0]: https://github.com/sebastianbergmann/diff/compare/2.0...3.0.0 [2.0.1]: https://github.com/sebastianbergmann/diff/compare/c341c98ce083db77f896a0aa64f5ee7652915970...2.0.1 [2.0.0]: https://github.com/sebastianbergmann/diff/compare/1.4...c341c98ce083db77f896a0aa64f5ee7652915970 diff-3.0.2/LICENSE000066400000000000000000000030151342575244300134410ustar00rootroot00000000000000sebastian/diff Copyright (c) 2002-2019, Sebastian Bergmann . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Sebastian Bergmann nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff-3.0.2/README.md000066400000000000000000000164201342575244300137170ustar00rootroot00000000000000# sebastian/diff Diff implementation for PHP, factored out of PHPUnit into a stand-alone component. ## Installation You can add this library as a local, per-project dependency to your project using [Composer](https://getcomposer.org/): composer require sebastian/diff If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency: composer require --dev sebastian/diff ### Usage #### Generating diff The `Differ` class can be used to generate a textual representation of the difference between two strings: ```php diff('foo', 'bar'); ``` The code above yields the output below: ```diff --- Original +++ New @@ @@ -foo +bar ``` There are three output builders available in this package: #### UnifiedDiffOutputBuilder This is default builder, which generates the output close to udiff and is used by PHPUnit. ```php diff('foo', 'bar'); ``` #### StrictUnifiedDiffOutputBuilder Generates (strict) Unified diff's (unidiffs) with hunks, similar to `diff -u` and compatible with `patch` and `git apply`. ```php true, // ranges of length one are rendered with the trailing `,1` 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 'fromFile' => null, 'fromFileDate' => null, 'toFile' => null, 'toFileDate' => null, ]); $differ = new Differ($builder); print $differ->diff('foo', 'bar'); ``` #### DiffOnlyOutputBuilder Output only the lines that differ. ```php diff('foo', 'bar'); ``` #### DiffOutputBuilderInterface You can pass any output builder to the `Differ` class as longs as it implements the `DiffOutputBuilderInterface`. #### Parsing diff The `Parser` class can be used to parse a unified diff into an object graph: ```php use SebastianBergmann\Diff\Parser; use SebastianBergmann\Git; $git = new Git('/usr/local/src/money'); $diff = $git->getDiff( '948a1a07768d8edd10dcefa8315c1cbeffb31833', 'c07a373d2399f3e686234c4f7f088d635eb9641b' ); $parser = new Parser; print_r($parser->parse($diff)); ``` The code above yields the output below: Array ( [0] => SebastianBergmann\Diff\Diff Object ( [from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php [to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php [chunks:SebastianBergmann\Diff\Diff:private] => Array ( [0] => SebastianBergmann\Diff\Chunk Object ( [start:SebastianBergmann\Diff\Chunk:private] => 87 [startRange:SebastianBergmann\Diff\Chunk:private] => 7 [end:SebastianBergmann\Diff\Chunk:private] => 87 [endRange:SebastianBergmann\Diff\Chunk:private] => 7 [lines:SebastianBergmann\Diff\Chunk:private] => Array ( [0] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::add ) [1] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => * @covers SebastianBergmann\Money\Money::newMoney ) [2] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => */ ) [3] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 2 [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded() ) [4] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 1 [content:SebastianBergmann\Diff\Line:private] => public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded() ) [5] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => { ) [6] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => $a = new Money(1, new Currency('EUR')); ) [7] => SebastianBergmann\Diff\Line Object ( [type:SebastianBergmann\Diff\Line:private] => 3 [content:SebastianBergmann\Diff\Line:private] => $b = new Money(2, new Currency('EUR')); ) ) ) ) ) ) diff-3.0.2/build.xml000066400000000000000000000014161342575244300142600ustar00rootroot00000000000000 diff-3.0.2/composer.json000066400000000000000000000015551342575244300151650ustar00rootroot00000000000000{ "name": "sebastian/diff", "description": "Diff implementation", "keywords": ["diff", "udiff", "unidiff", "unified diff"], "homepage": "https://github.com/sebastianbergmann/diff", "license": "BSD-3-Clause", "authors": [ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, { "name": "Kore Nordmann", "email": "mail@kore-nordmann.de" } ], "require": { "php": "^7.1" }, "require-dev": { "phpunit/phpunit": "^7.5 || ^8.0", "symfony/process": "^2 || ^3.3 || ^4" }, "autoload": { "classmap": [ "src/" ] }, "autoload-dev": { "classmap": [ "tests/" ] }, "extra": { "branch-alias": { "dev-master": "3.0-dev" } } } diff-3.0.2/phpunit.xml000066400000000000000000000013631342575244300146510ustar00rootroot00000000000000 tests src diff-3.0.2/src/000077500000000000000000000000001342575244300132245ustar00rootroot00000000000000diff-3.0.2/src/Chunk.php000066400000000000000000000031551342575244300150110ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Chunk { /** * @var int */ private $start; /** * @var int */ private $startRange; /** * @var int */ private $end; /** * @var int */ private $endRange; /** * @var Line[] */ private $lines; public function __construct(int $start = 0, int $startRange = 1, int $end = 0, int $endRange = 1, array $lines = []) { $this->start = $start; $this->startRange = $startRange; $this->end = $end; $this->endRange = $endRange; $this->lines = $lines; } public function getStart(): int { return $this->start; } public function getStartRange(): int { return $this->startRange; } public function getEnd(): int { return $this->end; } public function getEndRange(): int { return $this->endRange; } /** * @return Line[] */ public function getLines(): array { return $this->lines; } /** * @param Line[] $lines */ public function setLines(array $lines): void { foreach ($lines as $line) { if (!$line instanceof Line) { throw new InvalidArgumentException; } } $this->lines = $lines; } } diff-3.0.2/src/Diff.php000066400000000000000000000022401342575244300146030ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Diff { /** * @var string */ private $from; /** * @var string */ private $to; /** * @var Chunk[] */ private $chunks; /** * @param string $from * @param string $to * @param Chunk[] $chunks */ public function __construct(string $from, string $to, array $chunks = []) { $this->from = $from; $this->to = $to; $this->chunks = $chunks; } public function getFrom(): string { return $this->from; } public function getTo(): string { return $this->to; } /** * @return Chunk[] */ public function getChunks(): array { return $this->chunks; } /** * @param Chunk[] $chunks */ public function setChunks(array $chunks): void { $this->chunks = $chunks; } } diff-3.0.2/src/Differ.php000066400000000000000000000224711342575244300151420ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** * Diff implementation. */ final class Differ { public const OLD = 0; public const ADDED = 1; public const REMOVED = 2; public const DIFF_LINE_END_WARNING = 3; public const NO_LINE_END_EOF_WARNING = 4; /** * @var DiffOutputBuilderInterface */ private $outputBuilder; /** * @param DiffOutputBuilderInterface $outputBuilder * * @throws InvalidArgumentException */ public function __construct($outputBuilder = null) { if ($outputBuilder instanceof DiffOutputBuilderInterface) { $this->outputBuilder = $outputBuilder; } elseif (null === $outputBuilder) { $this->outputBuilder = new UnifiedDiffOutputBuilder; } elseif (\is_string($outputBuilder)) { // PHPUnit 6.1.4, 6.2.0, 6.2.1, 6.2.2, and 6.2.3 support // @see https://github.com/sebastianbergmann/phpunit/issues/2734#issuecomment-314514056 // @deprecated $this->outputBuilder = new UnifiedDiffOutputBuilder($outputBuilder); } else { throw new InvalidArgumentException( \sprintf( 'Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got %s.', \is_object($outputBuilder) ? 'instance of "' . \get_class($outputBuilder) . '"' : \gettype($outputBuilder) . ' "' . $outputBuilder . '"' ) ); } } /** * Returns the diff between two arrays or strings as string. * * @param array|string $from * @param array|string $to * @param null|LongestCommonSubsequenceCalculator $lcs * * @return string */ public function diff($from, $to, LongestCommonSubsequenceCalculator $lcs = null): string { $diff = $this->diffToArray( $this->normalizeDiffInput($from), $this->normalizeDiffInput($to), $lcs ); return $this->outputBuilder->getDiff($diff); } /** * Returns the diff between two arrays or strings as array. * * Each array element contains two elements: * - [0] => mixed $token * - [1] => 2|1|0 * * - 2: REMOVED: $token was removed from $from * - 1: ADDED: $token was added to $from * - 0: OLD: $token is not changed in $to * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequenceCalculator $lcs * * @return array */ public function diffToArray($from, $to, LongestCommonSubsequenceCalculator $lcs = null): array { if (\is_string($from)) { $from = $this->splitStringByLines($from); } elseif (!\is_array($from)) { throw new InvalidArgumentException('"from" must be an array or string.'); } if (\is_string($to)) { $to = $this->splitStringByLines($to); } elseif (!\is_array($to)) { throw new InvalidArgumentException('"to" must be an array or string.'); } [$from, $to, $start, $end] = self::getArrayDiffParted($from, $to); if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(\array_values($from), \array_values($to)); $diff = []; foreach ($start as $token) { $diff[] = [$token, self::OLD]; } \reset($from); \reset($to); foreach ($common as $token) { while (($fromToken = \reset($from)) !== $token) { $diff[] = [\array_shift($from), self::REMOVED]; } while (($toToken = \reset($to)) !== $token) { $diff[] = [\array_shift($to), self::ADDED]; } $diff[] = [$token, self::OLD]; \array_shift($from); \array_shift($to); } while (($token = \array_shift($from)) !== null) { $diff[] = [$token, self::REMOVED]; } while (($token = \array_shift($to)) !== null) { $diff[] = [$token, self::ADDED]; } foreach ($end as $token) { $diff[] = [$token, self::OLD]; } if ($this->detectUnmatchedLineEndings($diff)) { \array_unshift($diff, ["#Warning: Strings contain different line endings!\n", self::DIFF_LINE_END_WARNING]); } return $diff; } /** * Casts variable to string if it is not a string or array. * * @param mixed $input * * @return array|string */ private function normalizeDiffInput($input) { if (!\is_array($input) && !\is_string($input)) { return (string) $input; } return $input; } /** * Checks if input is string, if so it will split it line-by-line. * * @param string $input * * @return array */ private function splitStringByLines(string $input): array { return \preg_split('/(.*\R)/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); } /** * @param array $from * @param array $to * * @return LongestCommonSubsequenceCalculator */ private function selectLcsImplementation(array $from, array $to): LongestCommonSubsequenceCalculator { // We do not want to use the time-efficient implementation if its memory // footprint will probably exceed this value. Note that the footprint // calculation is only an estimation for the matrix and the LCS method // will typically allocate a bit more memory than this. $memoryLimit = 100 * 1024 * 1024; if ($this->calculateEstimatedFootprint($from, $to) > $memoryLimit) { return new MemoryEfficientLongestCommonSubsequenceCalculator; } return new TimeEfficientLongestCommonSubsequenceCalculator; } /** * Calculates the estimated memory footprint for the DP-based method. * * @param array $from * @param array $to * * @return float|int */ private function calculateEstimatedFootprint(array $from, array $to) { $itemSize = PHP_INT_SIZE === 4 ? 76 : 144; return $itemSize * \min(\count($from), \count($to)) ** 2; } /** * Returns true if line ends don't match in a diff. * * @param array $diff * * @return bool */ private function detectUnmatchedLineEndings(array $diff): bool { $newLineBreaks = ['' => true]; $oldLineBreaks = ['' => true]; foreach ($diff as $entry) { if (self::OLD === $entry[1]) { $ln = $this->getLinebreak($entry[0]); $oldLineBreaks[$ln] = true; $newLineBreaks[$ln] = true; } elseif (self::ADDED === $entry[1]) { $newLineBreaks[$this->getLinebreak($entry[0])] = true; } elseif (self::REMOVED === $entry[1]) { $oldLineBreaks[$this->getLinebreak($entry[0])] = true; } } // if either input or output is a single line without breaks than no warning should be raised if (['' => true] === $newLineBreaks || ['' => true] === $oldLineBreaks) { return false; } // two way compare foreach ($newLineBreaks as $break => $set) { if (!isset($oldLineBreaks[$break])) { return true; } } foreach ($oldLineBreaks as $break => $set) { if (!isset($newLineBreaks[$break])) { return true; } } return false; } private function getLinebreak($line): string { if (!\is_string($line)) { return ''; } $lc = \substr($line, -1); if ("\r" === $lc) { return "\r"; } if ("\n" !== $lc) { return ''; } if ("\r\n" === \substr($line, -2)) { return "\r\n"; } return "\n"; } private static function getArrayDiffParted(array &$from, array &$to): array { $start = []; $end = []; \reset($to); foreach ($from as $k => $v) { $toK = \key($to); if ($toK === $k && $v === $to[$k]) { $start[$k] = $v; unset($from[$k], $to[$k]); } else { break; } } \end($from); \end($to); do { $fromK = \key($from); $toK = \key($to); if (null === $fromK || null === $toK || \current($from) !== \current($to)) { break; } \prev($from); \prev($to); $end = [$fromK => $from[$fromK]] + $end; unset($from[$fromK], $to[$toK]); } while (true); return [$from, $to, $start, $end]; } } diff-3.0.2/src/Exception/000077500000000000000000000000001342575244300151625ustar00rootroot00000000000000diff-3.0.2/src/Exception/ConfigurationException.php000066400000000000000000000021031342575244300223550ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class ConfigurationException extends InvalidArgumentException { /** * @param string $option * @param string $expected * @param mixed $value * @param int $code * @param null|\Exception $previous */ public function __construct( string $option, string $expected, $value, int $code = 0, \Exception $previous = null ) { parent::__construct( \sprintf( 'Option "%s" must be %s, got "%s".', $option, $expected, \is_object($value) ? \get_class($value) : (null === $value ? '' : \gettype($value) . '#' . $value) ), $code, $previous ); } } diff-3.0.2/src/Exception/Exception.php000066400000000000000000000005001342575244300176240ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; interface Exception { } diff-3.0.2/src/Exception/InvalidArgumentException.php000066400000000000000000000006021342575244300226410ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; class InvalidArgumentException extends \InvalidArgumentException implements Exception { } diff-3.0.2/src/Line.php000066400000000000000000000015171342575244300146300ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class Line { public const ADDED = 1; public const REMOVED = 2; public const UNCHANGED = 3; /** * @var int */ private $type; /** * @var string */ private $content; public function __construct(int $type = self::UNCHANGED, string $content = '') { $this->type = $type; $this->content = $content; } public function getContent(): string { return $this->content; } public function getType(): int { return $this->type; } } diff-3.0.2/src/LongestCommonSubsequenceCalculator.php000066400000000000000000000010741342575244300227400ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; interface LongestCommonSubsequenceCalculator { /** * Calculates the longest common subsequence of two arrays. * * @param array $from * @param array $to * * @return array */ public function calculate(array $from, array $to): array; } diff-3.0.2/src/MemoryEfficientLongestCommonSubsequenceCalculator.php000066400000000000000000000041261342575244300257470ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** * {@inheritdoc} */ public function calculate(array $from, array $to): array { $cFrom = \count($from); $cTo = \count($to); if ($cFrom === 0) { return []; } if ($cFrom === 1) { if (\in_array($from[0], $to, true)) { return [$from[0]]; } return []; } $i = (int) ($cFrom / 2); $fromStart = \array_slice($from, 0, $i); $fromEnd = \array_slice($from, $i); $llB = $this->length($fromStart, $to); $llE = $this->length(\array_reverse($fromEnd), \array_reverse($to)); $jMax = 0; $max = 0; for ($j = 0; $j <= $cTo; $j++) { $m = $llB[$j] + $llE[$cTo - $j]; if ($m >= $max) { $max = $m; $jMax = $j; } } $toStart = \array_slice($to, 0, $jMax); $toEnd = \array_slice($to, $jMax); return \array_merge( $this->calculate($fromStart, $toStart), $this->calculate($fromEnd, $toEnd) ); } private function length(array $from, array $to): array { $current = \array_fill(0, \count($to) + 1, 0); $cFrom = \count($from); $cTo = \count($to); for ($i = 0; $i < $cFrom; $i++) { $prev = $current; for ($j = 0; $j < $cTo; $j++) { if ($from[$i] === $to[$j]) { $current[$j + 1] = $prev[$j] + 1; } else { $current[$j + 1] = \max($current[$j], $prev[$j + 1]); } } } return $current; } } diff-3.0.2/src/Output/000077500000000000000000000000001342575244300145245ustar00rootroot00000000000000diff-3.0.2/src/Output/AbstractChunkOutputBuilder.php000066400000000000000000000031171342575244300225230ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; abstract class AbstractChunkOutputBuilder implements DiffOutputBuilderInterface { /** * Takes input of the diff array and returns the common parts. * Iterates through diff line by line. * * @param array $diff * @param int $lineThreshold * * @return array */ protected function getCommonChunks(array $diff, int $lineThreshold = 5): array { $diffSize = \count($diff); $capturing = false; $chunkStart = 0; $chunkSize = 0; $commonChunks = []; for ($i = 0; $i < $diffSize; ++$i) { if ($diff[$i][1] === 0 /* OLD */) { if ($capturing === false) { $capturing = true; $chunkStart = $i; $chunkSize = 0; } else { ++$chunkSize; } } elseif ($capturing !== false) { if ($chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } $capturing = false; } } if ($capturing !== false && $chunkSize >= $lineThreshold) { $commonChunks[$chunkStart] = $chunkStart + $chunkSize; } return $commonChunks; } } diff-3.0.2/src/Output/DiffOnlyOutputBuilder.php000066400000000000000000000037111342575244300215010ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; use SebastianBergmann\Diff\Differ; /** * Builds a diff string representation in a loose unified diff format * listing only changes lines. Does not include line numbers. */ final class DiffOnlyOutputBuilder implements DiffOutputBuilderInterface { /** * @var string */ private $header; public function __construct(string $header = "--- Original\n+++ New\n") { $this->header = $header; } public function getDiff(array $diff): string { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } foreach ($diff as $diffEntry) { if ($diffEntry[1] === Differ::ADDED) { \fwrite($buffer, '+' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::REMOVED) { \fwrite($buffer, '-' . $diffEntry[0]); } elseif ($diffEntry[1] === Differ::DIFF_LINE_END_WARNING) { \fwrite($buffer, ' ' . $diffEntry[0]); continue; // Warnings should not be tested for line break, it will always be there } else { /* Not changed (old) 0 */ continue; // we didn't write the non changs line, so do not add a line break either } $lc = \substr($diffEntry[0], -1); if ($lc !== "\n" && $lc !== "\r") { \fwrite($buffer, "\n"); // \No newline at end of file } } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); return $diff; } } diff-3.0.2/src/Output/DiffOutputBuilderInterface.php000066400000000000000000000010121342575244300224500ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; /** * Defines how an output builder should take a generated * diff array and return a string representation of that diff. */ interface DiffOutputBuilderInterface { public function getDiff(array $diff): string; } diff-3.0.2/src/Output/StrictUnifiedDiffOutputBuilder.php000066400000000000000000000243151342575244300233370ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; use SebastianBergmann\Diff\ConfigurationException; use SebastianBergmann\Diff\Differ; /** * Strict Unified diff output builder. * * Generates (strict) Unified diff's (unidiffs) with hunks. */ final class StrictUnifiedDiffOutputBuilder implements DiffOutputBuilderInterface { private static $default = [ 'collapseRanges' => true, // ranges of length one are rendered with the trailing `,1` 'commonLineThreshold' => 6, // number of same lines before ending a new hunk and creating a new one (if needed) 'contextLines' => 3, // like `diff: -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3 'fromFile' => null, 'fromFileDate' => null, 'toFile' => null, 'toFileDate' => null, ]; /** * @var bool */ private $changed; /** * @var bool */ private $collapseRanges; /** * @var int >= 0 */ private $commonLineThreshold; /** * @var string */ private $header; /** * @var int >= 0 */ private $contextLines; public function __construct(array $options = []) { $options = \array_merge(self::$default, $options); if (!\is_bool($options['collapseRanges'])) { throw new ConfigurationException('collapseRanges', 'a bool', $options['collapseRanges']); } if (!\is_int($options['contextLines']) || $options['contextLines'] < 0) { throw new ConfigurationException('contextLines', 'an int >= 0', $options['contextLines']); } if (!\is_int($options['commonLineThreshold']) || $options['commonLineThreshold'] <= 0) { throw new ConfigurationException('commonLineThreshold', 'an int > 0', $options['commonLineThreshold']); } foreach (['fromFile', 'toFile'] as $option) { if (!\is_string($options[$option])) { throw new ConfigurationException($option, 'a string', $options[$option]); } } foreach (['fromFileDate', 'toFileDate'] as $option) { if (null !== $options[$option] && !\is_string($options[$option])) { throw new ConfigurationException($option, 'a string or ', $options[$option]); } } $this->header = \sprintf( "--- %s%s\n+++ %s%s\n", $options['fromFile'], null === $options['fromFileDate'] ? '' : "\t" . $options['fromFileDate'], $options['toFile'], null === $options['toFileDate'] ? '' : "\t" . $options['toFileDate'] ); $this->collapseRanges = $options['collapseRanges']; $this->commonLineThreshold = $options['commonLineThreshold']; $this->contextLines = $options['contextLines']; } public function getDiff(array $diff): string { if (0 === \count($diff)) { return ''; } $this->changed = false; $buffer = \fopen('php://memory', 'r+b'); \fwrite($buffer, $this->header); $this->writeDiffHunks($buffer, $diff); if (!$this->changed) { \fclose($buffer); return ''; } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); // If the last char is not a linebreak: add it. // This might happen when both the `from` and `to` do not have a trailing linebreak $last = \substr($diff, -1); return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff ; } private function writeDiffHunks($output, array $diff): void { // detect "No newline at end of file" and insert into `$diff` if needed $upperLimit = \count($diff); if (0 === $diff[$upperLimit - 1][1]) { $lc = \substr($diff[$upperLimit - 1][0], -1); if ("\n" !== $lc) { \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { // search back for the last `+` and `-` line, // check if has trailing linebreak, else add under it warning under it $toFind = [1 => true, 2 => true]; for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = \substr($diff[$i][0], -1); if ("\n" !== $lc) { \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } if (!\count($toFind)) { break; } } } } // write hunks to output buffer $cutOff = \max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { ++$fromStart; ++$toStart; continue; } ++$sameCount; ++$toRange; ++$fromRange; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture : $this->contextLines ; // note: $contextEndOffset = $this->contextLines; // // because we never go beyond the end of the diff. // with the cutoff/contextlines here the follow is never true; // // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { // $contextEndOffset = count($diff) - 1; // } // // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output ); $fromStart += $fromRange; $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; } continue; } $sameCount = 0; if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { continue; } $this->changed = true; if (false === $hunkCapture) { $hunkCapture = $i; } if (Differ::ADDED === $entry[1]) { // added ++$toRange; } if (Differ::REMOVED === $entry[1]) { // removed ++$fromRange; } } if (false === $hunkCapture) { return; } // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; // prevent trying to write out more common lines than there are in the diff _and_ // do not write more than configured through the context lines $contextEndOffset = \min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output ); } private function writeHunk( array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output ): void { \fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . $toStart); if (!$this->collapseRanges || 1 !== $toRange) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { $this->changed = true; \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { $this->changed = true; \fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { \fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { $this->changed = true; \fwrite($output, $diff[$i][0]); } //} elseif ($diff[$i][1] === Differ::DIFF_LINE_END_WARNING) { // custom comment inserted by PHPUnit/diff package // skip //} else { // unknown/invalid //} } } } diff-3.0.2/src/Output/UnifiedDiffOutputBuilder.php000066400000000000000000000200521342575244300221400ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; use SebastianBergmann\Diff\Differ; /** * Builds a diff string representation in unified diff format in chunks. */ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder { /** * @var bool */ private $collapseRanges = true; /** * @var int >= 0 */ private $commonLineThreshold = 6; /** * @var int >= 0 */ private $contextLines = 3; /** * @var string */ private $header; /** * @var bool */ private $addLineNumbers; public function __construct(string $header = "--- Original\n+++ New\n", bool $addLineNumbers = false) { $this->header = $header; $this->addLineNumbers = $addLineNumbers; } public function getDiff(array $diff): string { $buffer = \fopen('php://memory', 'r+b'); if ('' !== $this->header) { \fwrite($buffer, $this->header); if ("\n" !== \substr($this->header, -1, 1)) { \fwrite($buffer, "\n"); } } if (0 !== \count($diff)) { $this->writeDiffHunks($buffer, $diff); } $diff = \stream_get_contents($buffer, -1, 0); \fclose($buffer); // If the last char is not a linebreak: add it. // This might happen when both the `from` and `to` do not have a trailing linebreak $last = \substr($diff, -1); return "\n" !== $last && "\r" !== $last ? $diff . "\n" : $diff ; } private function writeDiffHunks($output, array $diff): void { // detect "No newline at end of file" and insert into `$diff` if needed $upperLimit = \count($diff); if (0 === $diff[$upperLimit - 1][1]) { $lc = \substr($diff[$upperLimit - 1][0], -1); if ("\n" !== $lc) { \array_splice($diff, $upperLimit, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } } else { // search back for the last `+` and `-` line, // check if has trailing linebreak, else add under it warning under it $toFind = [1 => true, 2 => true]; for ($i = $upperLimit - 1; $i >= 0; --$i) { if (isset($toFind[$diff[$i][1]])) { unset($toFind[$diff[$i][1]]); $lc = \substr($diff[$i][0], -1); if ("\n" !== $lc) { \array_splice($diff, $i + 1, 0, [["\n\\ No newline at end of file\n", Differ::NO_LINE_END_EOF_WARNING]]); } if (!\count($toFind)) { break; } } } } // write hunks to output buffer $cutOff = \max($this->commonLineThreshold, $this->contextLines); $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; $toStart = $fromStart = 1; foreach ($diff as $i => $entry) { if (0 === $entry[1]) { // same if (false === $hunkCapture) { ++$fromStart; ++$toStart; continue; } ++$sameCount; ++$toRange; ++$fromRange; if ($sameCount === $cutOff) { $contextStartOffset = ($hunkCapture - $this->contextLines) < 0 ? $hunkCapture : $this->contextLines ; // note: $contextEndOffset = $this->contextLines; // // because we never go beyond the end of the diff. // with the cutoff/contextlines here the follow is never true; // // if ($i - $cutOff + $this->contextLines + 1 > \count($diff)) { // $contextEndOffset = count($diff) - 1; // } // // ; that would be true for a trailing incomplete hunk case which is dealt with after this loop $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $cutOff + $this->contextLines + 1, $fromStart - $contextStartOffset, $fromRange - $cutOff + $contextStartOffset + $this->contextLines, $toStart - $contextStartOffset, $toRange - $cutOff + $contextStartOffset + $this->contextLines, $output ); $fromStart += $fromRange; $toStart += $toRange; $hunkCapture = false; $sameCount = $toRange = $fromRange = 0; } continue; } $sameCount = 0; if ($entry[1] === Differ::NO_LINE_END_EOF_WARNING) { continue; } if (false === $hunkCapture) { $hunkCapture = $i; } if (Differ::ADDED === $entry[1]) { ++$toRange; } if (Differ::REMOVED === $entry[1]) { ++$fromRange; } } if (false === $hunkCapture) { return; } // we end here when cutoff (commonLineThreshold) was not reached, but we where capturing a hunk, // do not render hunk till end automatically because the number of context lines might be less than the commonLineThreshold $contextStartOffset = $hunkCapture - $this->contextLines < 0 ? $hunkCapture : $this->contextLines ; // prevent trying to write out more common lines than there are in the diff _and_ // do not write more than configured through the context lines $contextEndOffset = \min($sameCount, $this->contextLines); $fromRange -= $sameCount; $toRange -= $sameCount; $this->writeHunk( $diff, $hunkCapture - $contextStartOffset, $i - $sameCount + $contextEndOffset + 1, $fromStart - $contextStartOffset, $fromRange + $contextStartOffset + $contextEndOffset, $toStart - $contextStartOffset, $toRange + $contextStartOffset + $contextEndOffset, $output ); } private function writeHunk( array $diff, int $diffStartIndex, int $diffEndIndex, int $fromStart, int $fromRange, int $toStart, int $toRange, $output ): void { if ($this->addLineNumbers) { \fwrite($output, '@@ -' . $fromStart); if (!$this->collapseRanges || 1 !== $fromRange) { \fwrite($output, ',' . $fromRange); } \fwrite($output, ' +' . $toStart); if (!$this->collapseRanges || 1 !== $toRange) { \fwrite($output, ',' . $toRange); } \fwrite($output, " @@\n"); } else { \fwrite($output, "@@ @@\n"); } for ($i = $diffStartIndex; $i < $diffEndIndex; ++$i) { if ($diff[$i][1] === Differ::ADDED) { \fwrite($output, '+' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::REMOVED) { \fwrite($output, '-' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::OLD) { \fwrite($output, ' ' . $diff[$i][0]); } elseif ($diff[$i][1] === Differ::NO_LINE_END_EOF_WARNING) { \fwrite($output, "\n"); // $diff[$i][0] } else { /* Not changed (old) Differ::OLD or Warning Differ::DIFF_LINE_END_WARNING */ \fwrite($output, ' ' . $diff[$i][0]); } } } } diff-3.0.2/src/Parser.php000066400000000000000000000056461342575244300152040ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * Unified diff parser. */ final class Parser { /** * @param string $string * * @return Diff[] */ public function parse(string $string): array { $lines = \preg_split('(\r\n|\r|\n)', $string); if (!empty($lines) && $lines[\count($lines) - 1] === '') { \array_pop($lines); } $lineCount = \count($lines); $diffs = []; $diff = null; $collected = []; for ($i = 0; $i < $lineCount; ++$i) { if (\preg_match('(^---\\s+(?P\\S+))', $lines[$i], $fromMatch) && \preg_match('(^\\+\\+\\+\\s+(?P\\S+))', $lines[$i + 1], $toMatch)) { if ($diff !== null) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; $collected = []; } $diff = new Diff($fromMatch['file'], $toMatch['file']); ++$i; } else { if (\preg_match('/^(?:diff --git |index [\da-f\.]+|[+-]{3} [ab])/', $lines[$i])) { continue; } $collected[] = $lines[$i]; } } if ($diff !== null && \count($collected)) { $this->parseFileDiff($diff, $collected); $diffs[] = $diff; } return $diffs; } private function parseFileDiff(Diff $diff, array $lines): void { $chunks = []; $chunk = null; foreach ($lines as $line) { if (\preg_match('/^@@\s+-(?P\d+)(?:,\s*(?P\d+))?\s+\+(?P\d+)(?:,\s*(?P\d+))?\s+@@/', $line, $match)) { $chunk = new Chunk( (int) $match['start'], isset($match['startrange']) ? \max(1, (int) $match['startrange']) : 1, (int) $match['end'], isset($match['endrange']) ? \max(1, (int) $match['endrange']) : 1 ); $chunks[] = $chunk; $diffLines = []; continue; } if (\preg_match('/^(?P[+ -])?(?P.*)/', $line, $match)) { $type = Line::UNCHANGED; if ($match['type'] === '+') { $type = Line::ADDED; } elseif ($match['type'] === '-') { $type = Line::REMOVED; } $diffLines[] = new Line($type, $match['line']); if (null !== $chunk) { $chunk->setLines($diffLines); } } } $diff->setChunks($chunks); } } diff-3.0.2/src/TimeEfficientLongestCommonSubsequenceCalculator.php000066400000000000000000000034141342575244300253740ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; final class TimeEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator { /** * {@inheritdoc} */ public function calculate(array $from, array $to): array { $common = []; $fromLength = \count($from); $toLength = \count($to); $width = $fromLength + 1; $matrix = new \SplFixedArray($width * ($toLength + 1)); for ($i = 0; $i <= $fromLength; ++$i) { $matrix[$i] = 0; } for ($j = 0; $j <= $toLength; ++$j) { $matrix[$j * $width] = 0; } for ($i = 1; $i <= $fromLength; ++$i) { for ($j = 1; $j <= $toLength; ++$j) { $o = ($j * $width) + $i; $matrix[$o] = \max( $matrix[$o - 1], $matrix[$o - $width], $from[$i - 1] === $to[$j - 1] ? $matrix[$o - $width - 1] + 1 : 0 ); } } $i = $fromLength; $j = $toLength; while ($i > 0 && $j > 0) { if ($from[$i - 1] === $to[$j - 1]) { $common[] = $from[$i - 1]; --$i; --$j; } else { $o = ($j * $width) + $i; if ($matrix[$o - $width] > $matrix[$o - 1]) { --$j; } else { --$i; } } } return \array_reverse($common); } } diff-3.0.2/tests/000077500000000000000000000000001342575244300135775ustar00rootroot00000000000000diff-3.0.2/tests/ChunkTest.php000066400000000000000000000032221342575244300162170ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Chunk */ final class ChunkTest extends TestCase { /** * @var Chunk */ private $chunk; protected function setUp(): void { $this->chunk = new Chunk; } public function testHasInitiallyNoLines(): void { $this->assertSame([], $this->chunk->getLines()); } public function testCanBeCreatedWithoutArguments(): void { $this->assertInstanceOf(Chunk::class, $this->chunk); } public function testStartCanBeRetrieved(): void { $this->assertSame(0, $this->chunk->getStart()); } public function testStartRangeCanBeRetrieved(): void { $this->assertSame(1, $this->chunk->getStartRange()); } public function testEndCanBeRetrieved(): void { $this->assertSame(0, $this->chunk->getEnd()); } public function testEndRangeCanBeRetrieved(): void { $this->assertSame(1, $this->chunk->getEndRange()); } public function testLinesCanBeRetrieved(): void { $this->assertSame([], $this->chunk->getLines()); } public function testLinesCanBeSet(): void { $lines = [new Line(Line::ADDED, 'added'), new Line(Line::REMOVED, 'removed')]; $this->chunk->setLines($lines); $this->assertSame($lines, $this->chunk->getLines()); } } diff-3.0.2/tests/DiffTest.php000066400000000000000000000031651342575244300160250ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Diff * * @uses SebastianBergmann\Diff\Chunk */ final class DiffTest extends TestCase { public function testGettersAfterConstructionWithDefault(): void { $from = 'line1a'; $to = 'line2a'; $diff = new Diff($from, $to); $this->assertSame($from, $diff->getFrom()); $this->assertSame($to, $diff->getTo()); $this->assertSame([], $diff->getChunks(), 'Expect chunks to be default value "array()".'); } public function testGettersAfterConstructionWithChunks(): void { $from = 'line1b'; $to = 'line2b'; $chunks = [new Chunk(), new Chunk(2, 3)]; $diff = new Diff($from, $to, $chunks); $this->assertSame($from, $diff->getFrom()); $this->assertSame($to, $diff->getTo()); $this->assertSame($chunks, $diff->getChunks(), 'Expect chunks to be passed value.'); } public function testSetChunksAfterConstruction(): void { $diff = new Diff('line1c', 'line2c'); $this->assertSame([], $diff->getChunks(), 'Expect chunks to be default value "array()".'); $chunks = [new Chunk(), new Chunk(2, 3)]; $diff->setChunks($chunks); $this->assertSame($chunks, $diff->getChunks(), 'Expect chunks to be passed value.'); } } diff-3.0.2/tests/DifferTest.php000066400000000000000000000273531342575244300163610ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; /** * @covers SebastianBergmann\Diff\Differ * @covers SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\MemoryEfficientLongestCommonSubsequenceCalculator * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator * @uses SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder */ final class DifferTest extends TestCase { /** * @var Differ */ private $differ; protected function setUp(): void { $this->differ = new Differ; } /** * @param array $expected * @param array|string $from * @param array|string $to * * @dataProvider arrayProvider */ public function testArrayRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(array $expected, $from, $to): void { $this->assertSame($expected, $this->differ->diffToArray($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator)); } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider textProvider */ public function testTextRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(string $expected, string $from, string $to): void { $this->assertSame($expected, $this->differ->diff($from, $to, new TimeEfficientLongestCommonSubsequenceCalculator)); } /** * @param array $expected * @param array|string $from * @param array|string $to * * @dataProvider arrayProvider */ public function testArrayRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(array $expected, $from, $to): void { $this->assertSame($expected, $this->differ->diffToArray($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator)); } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider textProvider */ public function testTextRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(string $expected, string $from, string $to): void { $this->assertSame($expected, $this->differ->diff($from, $to, new MemoryEfficientLongestCommonSubsequenceCalculator)); } public function testTypesOtherThanArrayAndStringCanBePassed(): void { $this->assertSame( "--- Original\n+++ New\n@@ @@\n-1\n+2\n", $this->differ->diff(1, 2) ); } public function testArrayDiffs(): void { $this->assertSame( '--- Original +++ New @@ @@ -one +two ', $this->differ->diff(['one'], ['two']) ); } public function arrayProvider(): array { return [ [ [ ['a', Differ::REMOVED], ['b', Differ::ADDED], ], 'a', 'b', ], [ [ ['ba', Differ::REMOVED], ['bc', Differ::ADDED], ], 'ba', 'bc', ], [ [ ['ab', Differ::REMOVED], ['cb', Differ::ADDED], ], 'ab', 'cb', ], [ [ ['abc', Differ::REMOVED], ['adc', Differ::ADDED], ], 'abc', 'adc', ], [ [ ['ab', Differ::REMOVED], ['abc', Differ::ADDED], ], 'ab', 'abc', ], [ [ ['bc', Differ::REMOVED], ['abc', Differ::ADDED], ], 'bc', 'abc', ], [ [ ['abc', Differ::REMOVED], ['abbc', Differ::ADDED], ], 'abc', 'abbc', ], [ [ ['abcdde', Differ::REMOVED], ['abcde', Differ::ADDED], ], 'abcdde', 'abcde', ], 'same start' => [ [ [17, Differ::OLD], ['b', Differ::REMOVED], ['d', Differ::ADDED], ], [30 => 17, 'a' => 'b'], [30 => 17, 'c' => 'd'], ], 'same end' => [ [ [1, Differ::REMOVED], [2, Differ::ADDED], ['b', Differ::OLD], ], [1 => 1, 'a' => 'b'], [1 => 2, 'a' => 'b'], ], 'same start (2), same end (1)' => [ [ [17, Differ::OLD], [2, Differ::OLD], [4, Differ::REMOVED], ['a', Differ::ADDED], [5, Differ::ADDED], ['x', Differ::OLD], ], [30 => 17, 1 => 2, 2 => 4, 'z' => 'x'], [30 => 17, 1 => 2, 3 => 'a', 2 => 5, 'z' => 'x'], ], 'same' => [ [ ['x', Differ::OLD], ], ['z' => 'x'], ['z' => 'x'], ], 'diff' => [ [ ['y', Differ::REMOVED], ['x', Differ::ADDED], ], ['x' => 'y'], ['z' => 'x'], ], 'diff 2' => [ [ ['y', Differ::REMOVED], ['b', Differ::REMOVED], ['x', Differ::ADDED], ['d', Differ::ADDED], ], ['x' => 'y', 'a' => 'b'], ['z' => 'x', 'c' => 'd'], ], 'test line diff detection' => [ [ [ "#Warning: Strings contain different line endings!\n", Differ::DIFF_LINE_END_WARNING, ], [ " [ [ [ "#Warning: Strings contain different line endings!\n", Differ::DIFF_LINE_END_WARNING, ], [ "expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('#^"from" must be an array or string\.$#'); $this->differ->diffToArray(null, ''); } public function testDiffInvalidToType(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('#^"to" must be an array or string\.$#'); $this->differ->diffToArray('', new \stdClass); } /** * @param array $expected * @param string $input * * @dataProvider provideSplitStringByLinesCases */ public function testSplitStringByLines(array $expected, string $input): void { $reflection = new \ReflectionObject($this->differ); $method = $reflection->getMethod('splitStringByLines'); $method->setAccessible(true); $this->assertSame($expected, $method->invoke($this->differ, $input)); } public function provideSplitStringByLinesCases(): array { return [ [ [], '', ], [ ['a'], 'a', ], [ ["a\n"], "a\n", ], [ ["a\r"], "a\r", ], [ ["a\r\n"], "a\r\n", ], [ ["\n"], "\n", ], [ ["\r"], "\r", ], [ ["\r\n"], "\r\n", ], [ [ "A\n", "B\n", "\n", "C\n", ], "A\nB\n\nC\n", ], [ [ "A\r\n", "B\n", "\n", "C\r", ], "A\r\nB\n\nC\r", ], [ [ "\n", "A\r\n", "B\n", "\n", 'C', ], "\nA\r\nB\n\nC", ], ]; } public function testConstructorInvalidArgInt(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got integer "1"\.$/'); new Differ(1); } public function testConstructorInvalidArgObject(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessageRegExp('/^Expected builder to be an instance of DiffOutputBuilderInterface, or a string, got instance of "SplFileInfo"\.$/'); new Differ(new \SplFileInfo(__FILE__)); } } diff-3.0.2/tests/Exception/000077500000000000000000000000001342575244300155355ustar00rootroot00000000000000diff-3.0.2/tests/Exception/ConfigurationExceptionTest.php000066400000000000000000000021361342575244300235760ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\ConfigurationException */ final class ConfigurationExceptionTest extends TestCase { public function testConstructWithDefaults(): void { $e = new ConfigurationException('test', 'A', 'B'); $this->assertSame(0, $e->getCode()); $this->assertNull($e->getPrevious()); $this->assertSame('Option "test" must be A, got "string#B".', $e->getMessage()); } public function testConstruct(): void { $e = new ConfigurationException( 'test', 'integer', new \SplFileInfo(__FILE__), 789, new \BadMethodCallException(__METHOD__) ); $this->assertSame('Option "test" must be integer, got "SplFileInfo".', $e->getMessage()); } } diff-3.0.2/tests/Exception/InvalidArgumentExceptionTest.php000066400000000000000000000017451342575244300240650ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\InvalidArgumentException */ final class InvalidArgumentExceptionTest extends TestCase { public function testInvalidArgumentException(): void { $previousException = new \LogicException(); $message = 'test'; $code = 123; $exception = new InvalidArgumentException($message, $code, $previousException); $this->assertInstanceOf(Exception::class, $exception); $this->assertSame($message, $exception->getMessage()); $this->assertSame($code, $exception->getCode()); $this->assertSame($previousException, $exception->getPrevious()); } } diff-3.0.2/tests/LineTest.php000066400000000000000000000016561342575244300160470ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Line */ final class LineTest extends TestCase { /** * @var Line */ private $line; protected function setUp(): void { $this->line = new Line; } public function testCanBeCreatedWithoutArguments(): void { $this->assertInstanceOf(Line::class, $this->line); } public function testTypeCanBeRetrieved(): void { $this->assertSame(Line::UNCHANGED, $this->line->getType()); } public function testContentCanBeRetrieved(): void { $this->assertSame('', $this->line->getContent()); } } diff-3.0.2/tests/LongestCommonSubsequenceTest.php000066400000000000000000000132101342575244300221340ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; /** * @coversNothing */ abstract class LongestCommonSubsequenceTest extends TestCase { /** * @var LongestCommonSubsequenceCalculator */ private $implementation; /** * @var string */ private $memoryLimit; /** * @var int[] */ private $stress_sizes = [1, 2, 3, 100, 500, 1000, 2000]; protected function setUp(): void { $this->memoryLimit = \ini_get('memory_limit'); \ini_set('memory_limit', '256M'); $this->implementation = $this->createImplementation(); } protected function tearDown(): void { \ini_set('memory_limit', $this->memoryLimit); } public function testBothEmpty(): void { $from = []; $to = []; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); } public function testIsStrictComparison(): void { $from = [ false, 0, 0.0, '', null, [], true, 1, 1.0, 'foo', ['foo', 'bar'], ['foo' => 'bar'], ]; $to = $from; $common = $this->implementation->calculate($from, $to); $this->assertSame($from, $common); $to = [ false, false, false, false, false, false, true, true, true, true, true, true, ]; $expected = [ false, true, ]; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); } public function testEqualSequences(): void { foreach ($this->stress_sizes as $size) { $range = \range(1, $size); $from = $range; $to = $range; $common = $this->implementation->calculate($from, $to); $this->assertSame($range, $common); } } public function testDistinctSequences(): void { $from = ['A']; $to = ['B']; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); $from = ['A', 'B', 'C']; $to = ['D', 'E', 'F']; $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \range($size + 1, $size * 2); $common = $this->implementation->calculate($from, $to); $this->assertSame([], $common); } } public function testCommonSubsequence(): void { $from = ['A', 'C', 'E', 'F', 'G']; $to = ['A', 'B', 'D', 'E', 'H']; $expected = ['A', 'E']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); $from = ['A', 'C', 'E', 'F', 'G']; $to = ['B', 'C', 'D', 'E', 'F', 'H']; $expected = ['C', 'E', 'F']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); foreach ($this->stress_sizes as $size) { $from = $size < 2 ? [1] : \range(1, $size + 1, 2); $to = $size < 3 ? [1] : \range(1, $size + 1, 3); $expected = $size < 6 ? [1] : \range(1, $size + 1, 6); $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); } } public function testSingleElementSubsequenceAtStart(): void { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, 0, 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testSingleElementSubsequenceAtMiddle(): void { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, (int) ($size / 2), 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testSingleElementSubsequenceAtEnd(): void { foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_slice($from, $size - 1, 1); $common = $this->implementation->calculate($from, $to); $this->assertSame($to, $common); } } public function testReversedSequences(): void { $from = ['A', 'B']; $to = ['B', 'A']; $expected = ['A']; $common = $this->implementation->calculate($from, $to); $this->assertSame($expected, $common); foreach ($this->stress_sizes as $size) { $from = \range(1, $size); $to = \array_reverse($from); $common = $this->implementation->calculate($from, $to); $this->assertSame([1], $common); } } public function testStrictTypeCalculate(): void { $diff = $this->implementation->calculate(['5'], ['05']); $this->assertIsArray($diff); $this->assertCount(0, $diff); } /** * @return LongestCommonSubsequenceCalculator */ abstract protected function createImplementation(): LongestCommonSubsequenceCalculator; } diff-3.0.2/tests/MemoryEfficientImplementationTest.php000066400000000000000000000011771342575244300231510ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @covers SebastianBergmann\Diff\MemoryEfficientLongestCommonSubsequenceCalculator */ final class MemoryEfficientImplementationTest extends LongestCommonSubsequenceTest { protected function createImplementation(): LongestCommonSubsequenceCalculator { return new MemoryEfficientLongestCommonSubsequenceCalculator; } } diff-3.0.2/tests/Output/000077500000000000000000000000001342575244300150775ustar00rootroot00000000000000diff-3.0.2/tests/Output/AbstractChunkOutputBuilderTest.php000066400000000000000000000104511342575244300237350ustar00rootroot00000000000000 * * 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; /** * @covers SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder * * @uses SebastianBergmann\Diff\Differ * @uses SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator */ final class AbstractChunkOutputBuilderTest extends TestCase { /** * @param array $expected * @param string $from * @param string $to * @param int $lineThreshold * * @dataProvider provideGetCommonChunks */ public function testGetCommonChunks(array $expected, string $from, string $to, int $lineThreshold = 5): void { $output = new class extends AbstractChunkOutputBuilder { public function getDiff(array $diff): string { return ''; } public function getChunks(array $diff, $lineThreshold) { return $this->getCommonChunks($diff, $lineThreshold); } }; $this->assertSame( $expected, $output->getChunks((new Differ)->diffToArray($from, $to), $lineThreshold) ); } public function provideGetCommonChunks(): array { return[ 'same (with default threshold)' => [ [], 'A', 'A', ], 'same (threshold 0)' => [ [0 => 0], 'A', 'A', 0, ], 'empty' => [ [], '', '', ], 'single line diff' => [ [], 'A', 'B', ], 'below threshold I' => [ [], "A\nX\nC", "A\nB\nC", ], 'below threshold II' => [ [], "A\n\n\n\nX\nC", "A\n\n\n\nB\nC", ], 'below threshold III' => [ [0 => 5], "A\n\n\n\n\n\nB", "A\n\n\n\n\n\nA", ], 'same start' => [ [0 => 5], "A\n\n\n\n\n\nX\nC", "A\n\n\n\n\n\nB\nC", ], 'same start long' => [ [0 => 13], "\n\n\n\n\n\n\n\n\n\n\n\n\n\nA", "\n\n\n\n\n\n\n\n\n\n\n\n\n\nB", ], 'same part in between' => [ [2 => 8], "A\n\n\n\n\n\n\nX\nY\nZ\n\n", "B\n\n\n\n\n\n\nX\nA\nZ\n\n", ], 'same trailing' => [ [2 => 14], "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "B\n\n\n\n\n\n\n\n\n\n\n\n\n\n", ], 'same part in between, same trailing' => [ [2 => 7, 10 => 15], "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\n", "B\n\n\n\n\n\n\nB\n\n\n\n\n\n\n", ], 'below custom threshold I' => [ [], "A\n\nB", "A\n\nD", 2, ], 'custom threshold I' => [ [0 => 1], "A\n\nB", "A\n\nD", 1, ], 'custom threshold II' => [ [], "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "A\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", 19, ], [ [3 => 9], "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk", "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk", ], [ [0 => 5, 8 => 13], "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC", "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC", ], [ [0 => 5, 8 => 13], "A\nA\nA\nA\nA\nA\nX\nC\nC\nC\nC\nC\nC\nX", "A\nA\nA\nA\nA\nA\nB\nC\nC\nC\nC\nC\nC\nY", ], ]; } } diff-3.0.2/tests/Output/DiffOnlyOutputBuilderTest.php000066400000000000000000000035441342575244300227200ustar00rootroot00000000000000 * * 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; /** * @covers SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder * * @uses SebastianBergmann\Diff\Differ * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator */ final class DiffOnlyOutputBuilderTest extends TestCase { /** * @param string $expected * @param string $from * @param string $to * @param string $header * * @dataProvider textForNoNonDiffLinesProvider */ public function testDiffDoNotShowNonDiffLines(string $expected, string $from, string $to, string $header = ''): void { $differ = new Differ(new DiffOnlyOutputBuilder($header)); $this->assertSame($expected, $differ->diff($from, $to)); } public function textForNoNonDiffLinesProvider(): array { return [ [ " #Warning: Strings contain different line endings!\n-A\r\n+B\n", "A\r\n", "B\n", ], [ "-A\n+B\n", "\nA", "\nB", ], [ '', 'a', 'a', ], [ "-A\n+C\n", "A\n\n\nB", "C\n\n\nB", ], [ "header\n", 'a', 'a', 'header', ], [ "header\n", 'a', 'a', "header\n", ], ]; } } diff-3.0.2/tests/Output/Integration/000077500000000000000000000000001342575244300173625ustar00rootroot00000000000000diff-3.0.2/tests/Output/Integration/StrictUnifiedDiffOutputBuilderIntegrationTest.php000066400000000000000000000234741342575244300312460ustar00rootroot00000000000000 * * 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); } } diff-3.0.2/tests/Output/Integration/UnifiedDiffOutputBuilderIntegrationTest.php000066400000000000000000000112601342575244300300430ustar00rootroot00000000000000 * * 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\Utils\UnifiedDiffAssertTrait; use Symfony\Component\Process\Process; /** * @covers SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\Differ * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator * * @requires OS Linux */ final class UnifiedDiffOutputBuilderIntegrationTest extends TestCase { use UnifiedDiffAssertTrait; private $dir; private $fileFrom; private $filePatch; protected function setUp(): void { $this->dir = \realpath(__DIR__ . '/../../fixtures/out/') . '/'; $this->fileFrom = $this->dir . 'from.txt'; $this->filePatch = $this->dir . 'patch.txt'; $this->cleanUpTempFiles(); } protected function tearDown(): void { $this->cleanUpTempFiles(); } /** * @dataProvider provideDiffWithLineNumbers * * @param mixed $expected * @param mixed $from * @param mixed $to */ public function testDiffWithLineNumbersPath($expected, $from, $to): void { $this->doIntegrationTestPatch($expected, $from, $to); } /** * @dataProvider provideDiffWithLineNumbers * * @param mixed $expected * @param mixed $from * @param mixed $to */ public function testDiffWithLineNumbersGitApply($expected, $from, $to): void { $this->doIntegrationTestGitApply($expected, $from, $to); } public function provideDiffWithLineNumbers() { return \array_filter( UnifiedDiffOutputBuilderDataProvider::provideDiffWithLineNumbers(), static function ($key) { return !\is_string($key) || false === \strpos($key, 'non_patch_compat'); }, ARRAY_FILTER_USE_KEY ); } 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', // --posix \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 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)); $command = \sprintf( 'git --git-dir %s apply --check -v --unsafe-paths --ignore-whitespace %s', \escapeshellarg($this->dir), \escapeshellarg($this->filePatch) ); $p = new Process($command); $p->run(); $this->assertProcessSuccessful($p); } 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); @\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); } } diff-3.0.2/tests/Output/StrictUnifiedDiffOutputBuilderDataProvider.php000066400000000000000000000064461342575244300262240ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; final class StrictUnifiedDiffOutputBuilderDataProvider { public static function provideOutputBuildingCases(): array { return [ [ '--- input.txt +++ output.txt @@ -1,3 +1,4 @@ +b ' . ' ' . ' ' . ' @@ -16,5 +17,4 @@ ' . ' ' . ' ' . ' - -B +A ', "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nB\n", "b\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nA\n", [ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ], ], [ '--- ' . __FILE__ . "\t2017-10-02 17:38:11.586413675 +0100 +++ output1.txt\t2017-10-03 12:09:43.086719482 +0100 @@ -1,1 +1,1 @@ -B +X ", "B\n", "X\n", [ 'fromFile' => __FILE__, 'fromFileDate' => '2017-10-02 17:38:11.586413675 +0100', 'toFile' => 'output1.txt', 'toFileDate' => '2017-10-03 12:09:43.086719482 +0100', 'collapseRanges' => false, ], ], [ '--- input.txt +++ output.txt @@ -1 +1 @@ -B +X ', "B\n", "X\n", [ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', 'collapseRanges' => true, ], ], ]; } public static function provideSample(): array { return [ [ '--- input.txt +++ output.txt @@ -1,6 +1,6 @@ 1 2 3 -4 +X 5 6 ', "1\n2\n3\n4\n5\n6\n", "1\n2\n3\nX\n5\n6\n", [ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ], ], ]; } public static function provideBasicDiffGeneration(): array { return [ [ "--- input.txt +++ output.txt @@ -1,2 +1 @@ -A -B +A\rB ", "A\nB\n", "A\rB\n", ], [ "--- input.txt +++ output.txt @@ -1 +1 @@ - +\r \\ No newline at end of file ", "\n", "\r", ], [ "--- input.txt +++ output.txt @@ -1 +1 @@ -\r \\ No newline at end of file + ", "\r", "\n", ], [ '--- input.txt +++ output.txt @@ -1,3 +1,3 @@ X A -A +B ', "X\nA\nA\n", "X\nA\nB\n", ], [ '--- input.txt +++ output.txt @@ -1,3 +1,3 @@ X A -A \ No newline at end of file +B ', "X\nA\nA", "X\nA\nB\n", ], [ '--- input.txt +++ output.txt @@ -1,3 +1,3 @@ A A -A +B \ No newline at end of file ', "A\nA\nA\n", "A\nA\nB", ], [ '--- input.txt +++ output.txt @@ -1 +1 @@ -A \ No newline at end of file +B \ No newline at end of file ', 'A', 'B', ], ]; } } diff-3.0.2/tests/Output/StrictUnifiedDiffOutputBuilderTest.php000066400000000000000000000421261342575244300245520ustar00rootroot00000000000000 * * 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\ConfigurationException; use SebastianBergmann\Diff\Differ; use SebastianBergmann\Diff\Utils\UnifiedDiffAssertTrait; /** * @covers SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\Differ */ final class StrictUnifiedDiffOutputBuilderTest extends TestCase { use UnifiedDiffAssertTrait; /** * @param string $expected * @param string $from * @param string $to * @param array $options * * @dataProvider provideOutputBuildingCases */ public function testOutputBuilding(string $expected, string $from, string $to, array $options): void { $diff = $this->getDiffer($options)->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } /** * @param string $expected * @param string $from * @param string $to * @param array $options * * @dataProvider provideSample */ public function testSample(string $expected, string $from, string $to, array $options): void { $diff = $this->getDiffer($options)->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } /** * {@inheritdoc} */ public function assertValidDiffFormat(string $diff): void { $this->assertValidUnifiedDiffFormat($diff); } /** * {@inheritdoc} */ public function provideOutputBuildingCases(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideOutputBuildingCases(); } /** * {@inheritdoc} */ public function provideSample(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideSample(); } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider provideBasicDiffGeneration */ public function testBasicDiffGeneration(string $expected, string $from, string $to): void { $diff = $this->getDiffer([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ])->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } public function provideBasicDiffGeneration(): array { return StrictUnifiedDiffOutputBuilderDataProvider::provideBasicDiffGeneration(); } /** * @param string $expected * @param string $from * @param string $to * @param array $config * * @dataProvider provideConfiguredDiffGeneration */ public function testConfiguredDiffGeneration(string $expected, string $from, string $to, array $config = []): void { $diff = $this->getDiffer(\array_merge([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ], $config))->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } public function provideConfiguredDiffGeneration(): array { return [ [ '--- input.txt +++ output.txt @@ -1 +1 @@ -a \ No newline at end of file +b \ No newline at end of file ', 'a', 'b', ], [ '', "1\n2", "1\n2", ], [ '', "1\n", "1\n", ], [ '--- input.txt +++ output.txt @@ -4 +4 @@ -X +4 ', "1\n2\n3\nX\n5\n6\n7\n8\n9\n0\n", "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", [ 'contextLines' => 0, ], ], [ '--- input.txt +++ output.txt @@ -3,3 +3,3 @@ 3 -X +4 5 ', "1\n2\n3\nX\n5\n6\n7\n8\n9\n0\n", "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", [ 'contextLines' => 1, ], ], [ '--- input.txt +++ output.txt @@ -1,10 +1,10 @@ 1 2 3 -X +4 5 6 7 8 9 0 ', "1\n2\n3\nX\n5\n6\n7\n8\n9\n0\n", "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", [ 'contextLines' => 999, ], ], [ '--- input.txt +++ output.txt @@ -1,0 +1,2 @@ + +A ', '', "\nA\n", ], [ '--- input.txt +++ output.txt @@ -1,2 +1,0 @@ - -A ', "\nA\n", '', ], [ '--- input.txt +++ output.txt @@ -1,5 +1,5 @@ 1 -X +2 3 -Y +4 5 @@ -8,3 +8,3 @@ 8 -X +9 0 ', "1\nX\n3\nY\n5\n6\n7\n8\nX\n0\n", "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", [ 'commonLineThreshold' => 2, 'contextLines' => 1, ], ], [ '--- input.txt +++ output.txt @@ -2 +2 @@ -X +2 @@ -4 +4 @@ -Y +4 @@ -9 +9 @@ -X +9 ', "1\nX\n3\nY\n5\n6\n7\n8\nX\n0\n", "1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n", [ 'commonLineThreshold' => 1, 'contextLines' => 0, ], ], ]; } public function testReUseBuilder(): void { $differ = $this->getDiffer([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ]); $diff = $differ->diff("A\nB\n", "A\nX\n"); $this->assertSame( '--- input.txt +++ output.txt @@ -1,2 +1,2 @@ A -B +X ', $diff ); $diff = $differ->diff("A\n", "A\n"); $this->assertSame( '', $diff ); } public function testEmptyDiff(): void { $builder = new StrictUnifiedDiffOutputBuilder([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', ]); $this->assertSame( '', $builder->getDiff([]) ); } /** * @param array $options * @param string $message * * @dataProvider provideInvalidConfiguration */ public function testInvalidConfiguration(array $options, string $message): void { $this->expectException(ConfigurationException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote($message, '#'))); new StrictUnifiedDiffOutputBuilder($options); } public function provideInvalidConfiguration(): array { $time = \time(); return [ [ ['collapseRanges' => 1], 'Option "collapseRanges" must be a bool, got "integer#1".', ], [ ['contextLines' => 'a'], 'Option "contextLines" must be an int >= 0, got "string#a".', ], [ ['commonLineThreshold' => -2], 'Option "commonLineThreshold" must be an int > 0, got "integer#-2".', ], [ ['commonLineThreshold' => 0], 'Option "commonLineThreshold" must be an int > 0, got "integer#0".', ], [ ['fromFile' => new \SplFileInfo(__FILE__)], 'Option "fromFile" must be a string, got "SplFileInfo".', ], [ ['fromFile' => null], 'Option "fromFile" must be a string, got "".', ], [ [ 'fromFile' => __FILE__, 'toFile' => 1, ], 'Option "toFile" must be a string, got "integer#1".', ], [ [ 'fromFile' => __FILE__, 'toFile' => __FILE__, 'toFileDate' => $time, ], 'Option "toFileDate" must be a string or , got "integer#' . $time . '".', ], [ [], 'Option "fromFile" must be a string, got "".', ], ]; } /** * @param string $expected * @param string $from * @param string $to * @param int $threshold * * @dataProvider provideCommonLineThresholdCases */ public function testCommonLineThreshold(string $expected, string $from, string $to, int $threshold): void { $diff = $this->getDiffer([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', 'commonLineThreshold' => $threshold, 'contextLines' => 0, ])->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } public function provideCommonLineThresholdCases(): array { return [ [ '--- input.txt +++ output.txt @@ -2,3 +2,3 @@ -X +B C12 -Y +D @@ -7 +7 @@ -X +Z ', "A\nX\nC12\nY\nA\nA\nX\n", "A\nB\nC12\nD\nA\nA\nZ\n", 2, ], [ '--- input.txt +++ output.txt @@ -2 +2 @@ -X +B @@ -4 +4 @@ -Y +D ', "A\nX\nV\nY\n", "A\nB\nV\nD\n", 1, ], ]; } /** * @param string $expected * @param string $from * @param string $to * @param int $contextLines * @param int $commonLineThreshold * * @dataProvider provideContextLineConfigurationCases */ public function testContextLineConfiguration(string $expected, string $from, string $to, int $contextLines, int $commonLineThreshold = 6): void { $diff = $this->getDiffer([ 'fromFile' => 'input.txt', 'toFile' => 'output.txt', 'contextLines' => $contextLines, 'commonLineThreshold' => $commonLineThreshold, ])->diff($from, $to); $this->assertValidDiffFormat($diff); $this->assertSame($expected, $diff); } public function provideContextLineConfigurationCases(): array { $from = "A\nB\nC\nD\nE\nF\nX\nG\nH\nI\nJ\nK\nL\nM\n"; $to = "A\nB\nC\nD\nE\nF\nY\nG\nH\nI\nJ\nK\nL\nM\n"; return [ 'EOF 0' => [ "--- input.txt\n+++ output.txt\n@@ -3 +3 @@ -X \\ No newline at end of file +Y \\ No newline at end of file ", "A\nB\nX", "A\nB\nY", 0, ], 'EOF 1' => [ "--- input.txt\n+++ output.txt\n@@ -2,2 +2,2 @@ B -X \\ No newline at end of file +Y \\ No newline at end of file ", "A\nB\nX", "A\nB\nY", 1, ], 'EOF 2' => [ "--- input.txt\n+++ output.txt\n@@ -1,3 +1,3 @@ A B -X \\ No newline at end of file +Y \\ No newline at end of file ", "A\nB\nX", "A\nB\nY", 2, ], 'EOF 200' => [ "--- input.txt\n+++ output.txt\n@@ -1,3 +1,3 @@ A B -X \\ No newline at end of file +Y \\ No newline at end of file ", "A\nB\nX", "A\nB\nY", 200, ], 'n/a 0' => [ "--- input.txt\n+++ output.txt\n@@ -7 +7 @@\n-X\n+Y\n", $from, $to, 0, ], 'G' => [ "--- input.txt\n+++ output.txt\n@@ -6,3 +6,3 @@\n F\n-X\n+Y\n G\n", $from, $to, 1, ], 'H' => [ "--- input.txt\n+++ output.txt\n@@ -5,5 +5,5 @@\n E\n F\n-X\n+Y\n G\n H\n", $from, $to, 2, ], 'I' => [ "--- input.txt\n+++ output.txt\n@@ -4,7 +4,7 @@\n D\n E\n F\n-X\n+Y\n G\n H\n I\n", $from, $to, 3, ], 'J' => [ "--- input.txt\n+++ output.txt\n@@ -3,9 +3,9 @@\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n", $from, $to, 4, ], 'K' => [ "--- input.txt\n+++ output.txt\n@@ -2,11 +2,11 @@\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n", $from, $to, 5, ], 'L' => [ "--- input.txt\n+++ output.txt\n@@ -1,13 +1,13 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n", $from, $to, 6, ], 'M' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n M\n", $from, $to, 7, ], 'M no linebreak EOF .1' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n-M\n+M\n\\ No newline at end of file\n", $from, \substr($to, 0, -1), 7, ], 'M no linebreak EOF .2' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n-M\n\\ No newline at end of file\n+M\n", \substr($from, 0, -1), $to, 7, ], 'M no linebreak EOF .3' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n M\n", \substr($from, 0, -1), \substr($to, 0, -1), 7, ], 'M no linebreak EOF .4' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n M\n\\ No newline at end of file\n", \substr($from, 0, -1), \substr($to, 0, -1), 10000, 10000, ], 'M+1' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n M\n", $from, $to, 8, ], 'M+100' => [ "--- input.txt\n+++ output.txt\n@@ -1,14 +1,14 @@\n A\n B\n C\n D\n E\n F\n-X\n+Y\n G\n H\n I\n J\n K\n L\n M\n", $from, $to, 107, ], '0 II' => [ "--- input.txt\n+++ output.txt\n@@ -12 +12 @@\n-X\n+Y\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nM\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nM\n", 0, 999, ], '0\' II' => [ "--- input.txt\n+++ output.txt\n@@ -12 +12 @@\n-X\n+Y\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nM\nA\nA\nA\nA\nA\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nM\nA\nA\nA\nA\nA\n", 0, 999, ], '0\'\' II' => [ "--- input.txt\n+++ output.txt\n@@ -12,2 +12,2 @@\n-X\n-M\n\\ No newline at end of file\n+Y\n+M\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nM", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nM\n", 0, ], '0\'\'\' II' => [ "--- input.txt\n+++ output.txt\n@@ -12,2 +12,2 @@\n-X\n-X1\n+Y\n+Y2\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nX1\nM\nA\nA\nA\nA\nA\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nY2\nM\nA\nA\nA\nA\nA\n", 0, 999, ], '1 II' => [ "--- input.txt\n+++ output.txt\n@@ -11,3 +11,3 @@\n K\n-X\n+Y\n M\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nM\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nM\n", 1, ], '5 II' => [ "--- input.txt\n+++ output.txt\n@@ -7,7 +7,7 @@\n G\n H\n I\n J\n K\n-X\n+Y\n M\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nX\nM\n", "A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nY\nM\n", 5, ], [ '--- input.txt +++ output.txt @@ -1,28 +1,28 @@ A -X +B V -Y +D 1 A 2 A 3 A 4 A 8 A 9 A 5 A A A A A A A A A A A ', "A\nX\nV\nY\n1\nA\n2\nA\n3\nA\n4\nA\n8\nA\n9\nA\n5\nA\nA\nA\nA\nA\nA\nA\nA\nA\nA\nA\n", "A\nB\nV\nD\n1\nA\n2\nA\n3\nA\n4\nA\n8\nA\n9\nA\n5\nA\nA\nA\nA\nA\nA\nA\nA\nA\nA\nA\n", 9999, 99999, ], ]; } /** * Returns a new instance of a Differ with a new instance of the class (DiffOutputBuilderInterface) under test. * * @param array $options * * @return Differ */ private function getDiffer(array $options = []): Differ { return new Differ(new StrictUnifiedDiffOutputBuilder($options)); } } diff-3.0.2/tests/Output/UnifiedDiffOutputBuilderDataProvider.php000066400000000000000000000126111342575244300250220ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Output; final class UnifiedDiffOutputBuilderDataProvider { public static function provideDiffWithLineNumbers(): array { return [ 'diff line 1 non_patch_compat' => [ '--- Original +++ New @@ -1 +1 @@ -AA +BA ', 'AA', 'BA', ], 'diff line +1 non_patch_compat' => [ '--- Original +++ New @@ -1 +1,2 @@ -AZ + +B ', 'AZ', "\nB", ], 'diff line -1 non_patch_compat' => [ '--- Original +++ New @@ -1,2 +1 @@ - -AF +B ', "\nAF", 'B', ], 'II non_patch_compat' => [ '--- Original +++ New @@ -1,4 +1,2 @@ - - A 1 ', "\n\nA\n1", "A\n1", ], 'diff last line II - no trailing linebreak non_patch_compat' => [ '--- Original +++ New @@ -5,4 +5,4 @@ ' . ' ' . ' ' . ' -E +B ', "A\n\n\n\n\n\n\nE", "A\n\n\n\n\n\n\nB", ], [ "--- Original\n+++ New\n@@ -1,2 +1 @@\n \n-\n", "\n\n", "\n", ], 'diff line endings non_patch_compat' => [ "--- Original\n+++ New\n@@ -1 +1 @@\n #Warning: Strings contain different line endings!\n- [ '--- Original +++ New ', "AT\n", "AT\n", ], [ '--- Original +++ New @@ -1,4 +1,4 @@ -b +a ' . ' ' . ' ' . ' ', "b\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", "a\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", ], 'diff line @1' => [ '--- Original +++ New @@ -1,2 +1,2 @@ ' . ' -AG +B ', "\nAG\n", "\nB\n", ], 'same multiple lines' => [ '--- Original +++ New @@ -1,4 +1,4 @@ ' . ' ' . ' -V +B C213 ', "\n\nV\nC213", "\n\nB\nC213", ], 'diff last line I' => [ '--- Original +++ New @@ -5,4 +5,4 @@ ' . ' ' . ' ' . ' -E +B ', "A\n\n\n\n\n\n\nE\n", "A\n\n\n\n\n\n\nB\n", ], 'diff line middle' => [ '--- Original +++ New @@ -5,7 +5,7 @@ ' . ' ' . ' ' . ' -X +Z ' . ' ' . ' ' . ' ', "A\n\n\n\n\n\n\nX\n\n\n\n\n\n\nAY", "A\n\n\n\n\n\n\nZ\n\n\n\n\n\n\nAY", ], 'diff last line III' => [ '--- Original +++ New @@ -12,4 +12,4 @@ ' . ' ' . ' ' . ' -A +B ', "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nA\n", "A\n\n\n\n\n\n\nA\n\n\n\n\n\n\nB\n", ], [ '--- Original +++ New @@ -1,8 +1,8 @@ A -B +B1 D E EE F -G +G1 H ', "A\nB\nD\nE\nEE\nF\nG\nH", "A\nB1\nD\nE\nEE\nF\nG1\nH", ], [ '--- Original +++ New @@ -1,4 +1,5 @@ Z + a b c @@ -7,5 +8,5 @@ f g h -i +x j ', 'Z a b c d e f g h i j ', 'Z a b c d e f g h x j ', ], [ '--- Original +++ New @@ -1,7 +1,5 @@ - -a +b A -X - +Y ' . ' A ', "\na\nA\nX\n\n\nA\n", "b\nA\nY\n\nA\n", ], [ << * * 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; /** * @covers SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder * * @uses SebastianBergmann\Diff\Differ * @uses SebastianBergmann\Diff\Output\AbstractChunkOutputBuilder * @uses SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator */ final class UnifiedDiffOutputBuilderTest extends TestCase { /** * @param string $expected * @param string $from * @param string $to * @param string $header * * @dataProvider headerProvider */ public function testCustomHeaderCanBeUsed(string $expected, string $from, string $to, string $header): void { $differ = new Differ(new UnifiedDiffOutputBuilder($header)); $this->assertSame( $expected, $differ->diff($from, $to) ); } public function headerProvider(): array { return [ [ "CUSTOM HEADER\n@@ @@\n-a\n+b\n", 'a', 'b', 'CUSTOM HEADER', ], [ "CUSTOM HEADER\n@@ @@\n-a\n+b\n", 'a', 'b', "CUSTOM HEADER\n", ], [ "CUSTOM HEADER\n\n@@ @@\n-a\n+b\n", 'a', 'b', "CUSTOM HEADER\n\n", ], [ "@@ @@\n-a\n+b\n", 'a', 'b', '', ], ]; } /** * @param string $expected * @param string $from * @param string $to * * @dataProvider provideDiffWithLineNumbers */ public function testDiffWithLineNumbers($expected, $from, $to): void { $differ = new Differ(new UnifiedDiffOutputBuilder("--- Original\n+++ New\n", true)); $this->assertSame($expected, $differ->diff($from, $to)); } public function provideDiffWithLineNumbers(): array { return UnifiedDiffOutputBuilderDataProvider::provideDiffWithLineNumbers(); } } diff-3.0.2/tests/ParserTest.php000066400000000000000000000114371342575244300164120ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; use PHPUnit\Framework\TestCase; use SebastianBergmann\Diff\Utils\FileUtils; /** * @covers SebastianBergmann\Diff\Parser * * @uses SebastianBergmann\Diff\Chunk * @uses SebastianBergmann\Diff\Diff * @uses SebastianBergmann\Diff\Line */ final class ParserTest extends TestCase { /** * @var Parser */ private $parser; protected function setUp(): void { $this->parser = new Parser; } public function testParse(): void { $content = FileUtils::getFileContent(__DIR__ . '/fixtures/patch.txt'); $diffs = $this->parser->parse($content); $this->assertContainsOnlyInstancesOf(Diff::class, $diffs); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertContainsOnlyInstancesOf(Chunk::class, $chunks); $this->assertCount(1, $chunks); $this->assertSame(20, $chunks[0]->getStart()); $this->assertCount(4, $chunks[0]->getLines()); } public function testParseWithMultipleChunks(): void { $content = FileUtils::getFileContent(__DIR__ . '/fixtures/patch2.txt'); $diffs = $this->parser->parse($content); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertCount(3, $chunks); $this->assertSame(20, $chunks[0]->getStart()); $this->assertSame(320, $chunks[1]->getStart()); $this->assertSame(600, $chunks[2]->getStart()); $this->assertCount(5, $chunks[0]->getLines()); $this->assertCount(5, $chunks[1]->getLines()); $this->assertCount(4, $chunks[2]->getLines()); } public function testParseWithRemovedLines(): void { $content = <<parser->parse($content); $this->assertContainsOnlyInstancesOf(Diff::class, $diffs); $this->assertCount(1, $diffs); $chunks = $diffs[0]->getChunks(); $this->assertContainsOnlyInstancesOf(Chunk::class, $chunks); $this->assertCount(1, $chunks); $chunk = $chunks[0]; $this->assertSame(49, $chunk->getStart()); $this->assertSame(49, $chunk->getEnd()); $this->assertSame(9, $chunk->getStartRange()); $this->assertSame(8, $chunk->getEndRange()); $lines = $chunk->getLines(); $this->assertContainsOnlyInstancesOf(Line::class, $lines); $this->assertCount(2, $lines); /** @var Line $line */ $line = $lines[0]; $this->assertSame('A', $line->getContent()); $this->assertSame(Line::UNCHANGED, $line->getType()); $line = $lines[1]; $this->assertSame('B', $line->getContent()); $this->assertSame(Line::REMOVED, $line->getType()); } public function testParseDiffForMulitpleFiles(): void { $content = <<parser->parse($content); $this->assertCount(2, $diffs); /** @var Diff $diff */ $diff = $diffs[0]; $this->assertSame('a/Test.txt', $diff->getFrom()); $this->assertSame('b/Test.txt', $diff->getTo()); $this->assertCount(1, $diff->getChunks()); $diff = $diffs[1]; $this->assertSame('a/Test2.txt', $diff->getFrom()); $this->assertSame('b/Test2.txt', $diff->getTo()); $this->assertCount(1, $diff->getChunks()); } /** * @param string $diff * @param Diff[] $expected * * @dataProvider diffProvider */ public function testParser(string $diff, array $expected): void { $result = $this->parser->parse($diff); $this->assertEquals($expected, $result); } public function diffProvider(): array { return [ [ "--- old.txt 2014-11-04 08:51:02.661868729 +0300\n+++ new.txt 2014-11-04 08:51:02.665868730 +0300\n@@ -1,3 +1,4 @@\n+2222111\n 1111111\n 1111111\n 1111111\n@@ -5,10 +6,8 @@\n 1111111\n 1111111\n 1111111\n +1121211\n 1111111\n -1111111\n -1111111\n -2222222\n 2222222\n 2222222\n 2222222\n@@ -17,5 +16,6 @@\n 2222222\n 2222222\n 2222222\n +2122212\n 2222222\n 2222222\n", \unserialize(FileUtils::getFileContent(__DIR__ . '/fixtures/serialized_diff.bin')), ], ]; } } diff-3.0.2/tests/TimeEfficientImplementationTest.php000066400000000000000000000011711342575244300225710ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff; /** * @covers SebastianBergmann\Diff\TimeEfficientLongestCommonSubsequenceCalculator */ final class TimeEfficientImplementationTest extends LongestCommonSubsequenceTest { protected function createImplementation(): LongestCommonSubsequenceCalculator { return new TimeEfficientLongestCommonSubsequenceCalculator; } } diff-3.0.2/tests/Utils/000077500000000000000000000000001342575244300146775ustar00rootroot00000000000000diff-3.0.2/tests/Utils/FileUtils.php000066400000000000000000000013741342575244300173150ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Utils; final class FileUtils { public static function getFileContent(string $file): string { $content = @\file_get_contents($file); if (false === $content) { $error = \error_get_last(); throw new \RuntimeException(\sprintf( 'Failed to read content of file "%s".%s', $file, $error ? ' ' . $error['message'] : '' )); } return $content; } } diff-3.0.2/tests/Utils/UnifiedDiffAssertTrait.php000066400000000000000000000256701342575244300217640ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Utils; trait UnifiedDiffAssertTrait { /** * @param string $diff * * @throws \UnexpectedValueException */ public function assertValidUnifiedDiffFormat(string $diff): void { if ('' === $diff) { $this->addToAssertionCount(1); return; } // test diff ends with a line break $last = \substr($diff, -1); if ("\n" !== $last && "\r" !== $last) { throw new \UnexpectedValueException(\sprintf('Expected diff to end with a line break, got "%s".', $last)); } $lines = \preg_split('/(.*\R)/', $diff, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $lineCount = \count($lines); $lineNumber = $diffLineFromNumber = $diffLineToNumber = 1; $fromStart = $fromTillOffset = $toStart = $toTillOffset = -1; $expectHunkHeader = true; // check for header if ($lineCount > 1) { $this->unifiedDiffAssertLinePrefix($lines[0], 'Line 1.'); $this->unifiedDiffAssertLinePrefix($lines[1], 'Line 2.'); if ('---' === \substr($lines[0], 0, 3)) { if ('+++' !== \substr($lines[1], 0, 3)) { throw new \UnexpectedValueException(\sprintf("Line 1 indicates a header, so line 2 must start with \"+++\".\nLine 1: \"%s\"\nLine 2: \"%s\".", $lines[0], $lines[1])); } $this->unifiedDiffAssertHeaderLine($lines[0], '--- ', 'Line 1.'); $this->unifiedDiffAssertHeaderLine($lines[1], '+++ ', 'Line 2.'); $lineNumber = 3; } } $endOfLineTypes = []; $diffClosed = false; // assert format of lines, get all hunks, test the line numbers for (; $lineNumber <= $lineCount; ++$lineNumber) { if ($diffClosed) { throw new \UnexpectedValueException(\sprintf('Unexpected line as 2 "No newline" markers have found, ". Line %d.', $lineNumber)); } $line = $lines[$lineNumber - 1]; // line numbers start by 1, array index at 0 $type = $this->unifiedDiffAssertLinePrefix($line, \sprintf('Line %d.', $lineNumber)); if ($expectHunkHeader && '@' !== $type && '\\' !== $type) { throw new \UnexpectedValueException(\sprintf('Expected hunk start (\'@\'), got "%s". Line %d.', $type, $lineNumber)); } if ('@' === $type) { if (!$expectHunkHeader) { throw new \UnexpectedValueException(\sprintf('Unexpected hunk start (\'@\'). Line %d.', $lineNumber)); } $previousHunkFromEnd = $fromStart + $fromTillOffset; $previousHunkTillEnd = $toStart + $toTillOffset; [$fromStart, $fromTillOffset, $toStart, $toTillOffset] = $this->unifiedDiffAssertHunkHeader($line, \sprintf('Line %d.', $lineNumber)); // detect overlapping hunks if ($fromStart < $previousHunkFromEnd) { throw new \UnexpectedValueException(\sprintf('Unexpected new hunk; "from" (\'-\') start overlaps previous hunk. Line %d.', $lineNumber)); } if ($toStart < $previousHunkTillEnd) { throw new \UnexpectedValueException(\sprintf('Unexpected new hunk; "to" (\'+\') start overlaps previous hunk. Line %d.', $lineNumber)); } /* valid states; hunks touches against each other: $fromStart === $previousHunkFromEnd $toStart === $previousHunkTillEnd */ $diffLineFromNumber = $fromStart; $diffLineToNumber = $toStart; $expectHunkHeader = false; continue; } if ('-' === $type) { if (isset($endOfLineTypes['-'])) { throw new \UnexpectedValueException(\sprintf('Not expected from (\'-\'), already closed by "\\ No newline at end of file". Line %d.', $lineNumber)); } ++$diffLineFromNumber; } elseif ('+' === $type) { if (isset($endOfLineTypes['+'])) { throw new \UnexpectedValueException(\sprintf('Not expected to (\'+\'), already closed by "\\ No newline at end of file". Line %d.', $lineNumber)); } ++$diffLineToNumber; } elseif (' ' === $type) { if (isset($endOfLineTypes['-'])) { throw new \UnexpectedValueException(\sprintf('Not expected same (\' \'), \'-\' already closed by "\\ No newline at end of file". Line %d.', $lineNumber)); } if (isset($endOfLineTypes['+'])) { throw new \UnexpectedValueException(\sprintf('Not expected same (\' \'), \'+\' already closed by "\\ No newline at end of file". Line %d.', $lineNumber)); } ++$diffLineFromNumber; ++$diffLineToNumber; } elseif ('\\' === $type) { if (!isset($lines[$lineNumber - 2])) { throw new \UnexpectedValueException(\sprintf('Unexpected "\\ No newline at end of file", it must be preceded by \'+\' or \'-\' line. Line %d.', $lineNumber)); } $previousType = $this->unifiedDiffAssertLinePrefix($lines[$lineNumber - 2], \sprintf('Preceding line of "\\ No newline at end of file" of unexpected format. Line %d.', $lineNumber)); if (isset($endOfLineTypes[$previousType])) { throw new \UnexpectedValueException(\sprintf('Unexpected "\\ No newline at end of file", "%s" was already closed. Line %d.', $type, $lineNumber)); } $endOfLineTypes[$previousType] = true; $diffClosed = \count($endOfLineTypes) > 1; } else { // internal state error throw new \RuntimeException(\sprintf('Unexpected line type "%s" Line %d.', $type, $lineNumber)); } $expectHunkHeader = $diffLineFromNumber === ($fromStart + $fromTillOffset) && $diffLineToNumber === ($toStart + $toTillOffset) ; } if ( $diffLineFromNumber !== ($fromStart + $fromTillOffset) && $diffLineToNumber !== ($toStart + $toTillOffset) ) { throw new \UnexpectedValueException(\sprintf('Unexpected EOF, number of lines in hunk "from" (\'-\')) and "to" (\'+\') mismatched. Line %d.', $lineNumber)); } if ($diffLineFromNumber !== ($fromStart + $fromTillOffset)) { throw new \UnexpectedValueException(\sprintf('Unexpected EOF, number of lines in hunk "from" (\'-\')) mismatched. Line %d.', $lineNumber)); } if ($diffLineToNumber !== ($toStart + $toTillOffset)) { throw new \UnexpectedValueException(\sprintf('Unexpected EOF, number of lines in hunk "to" (\'+\')) mismatched. Line %d.', $lineNumber)); } $this->addToAssertionCount(1); } /** * @param string $line * @param string $message * * @return string '+', '-', '@', ' ' or '\' */ private function unifiedDiffAssertLinePrefix(string $line, string $message): string { $this->unifiedDiffAssertStrLength($line, 2, $message); // 2: line type indicator ('+', '-', ' ' or '\') and a line break $firstChar = $line[0]; if ('+' === $firstChar || '-' === $firstChar || '@' === $firstChar || ' ' === $firstChar) { return $firstChar; } if ("\\ No newline at end of file\n" === $line) { return '\\'; } throw new \UnexpectedValueException(\sprintf('Expected line to start with \'@\', \'-\' or \'+\', got "%s". %s', $line, $message)); } private function unifiedDiffAssertStrLength(string $line, int $min, string $message): void { $length = \strlen($line); if ($length < $min) { throw new \UnexpectedValueException(\sprintf('Expected string length of minimal %d, got %d. %s', $min, $length, $message)); } } /** * Assert valid unified diff header line * * Samples: * - "+++ from1.txt\t2017-08-24 19:51:29.383985722 +0200" * - "+++ from1.txt" * * @param string $line * @param string $start * @param string $message */ private function unifiedDiffAssertHeaderLine(string $line, string $start, string $message): void { if (0 !== \strpos($line, $start)) { throw new \UnexpectedValueException(\sprintf('Expected header line to start with "%s", got "%s". %s', $start . ' ', $line, $message)); } // sample "+++ from1.txt\t2017-08-24 19:51:29.383985722 +0200\n" $match = \preg_match( "/^([^\t]*)(?:[\t]([\\S].*[\\S]))?\n$/", \substr($line, 4), // 4 === string length of "+++ " / "--- " $matches ); if (1 !== $match) { throw new \UnexpectedValueException(\sprintf('Header line does not match expected pattern, got "%s". %s', $line, $message)); } // $file = $matches[1]; if (\count($matches) > 2) { $this->unifiedDiffAssertHeaderDate($matches[2], $message); } } private function unifiedDiffAssertHeaderDate(string $date, string $message): void { // sample "2017-08-24 19:51:29.383985722 +0200" $match = \preg_match( '/^([\d]{4})-([01]?[\d])-([0123]?[\d])(:? [\d]{1,2}:[\d]{1,2}(?::[\d]{1,2}(:?\.[\d]+)?)?(?: ([\+\-][\d]{4}))?)?$/', $date, $matches ); if (1 !== $match || ($matchesCount = \count($matches)) < 4) { throw new \UnexpectedValueException(\sprintf('Date of header line does not match expected pattern, got "%s". %s', $date, $message)); } // [$full, $year, $month, $day, $time] = $matches; } /** * @param string $line * @param string $message * * @return int[] */ private function unifiedDiffAssertHunkHeader(string $line, string $message): array { if (1 !== \preg_match('#^@@ -([\d]+)((?:,[\d]+)?) \+([\d]+)((?:,[\d]+)?) @@\n$#', $line, $matches)) { throw new \UnexpectedValueException( \sprintf( 'Hunk header line does not match expected pattern, got "%s". %s', $line, $message ) ); } return [ (int) $matches[1], empty($matches[2]) ? 1 : (int) \substr($matches[2], 1), (int) $matches[3], empty($matches[4]) ? 1 : (int) \substr($matches[4], 1), ]; } } diff-3.0.2/tests/Utils/UnifiedDiffAssertTraitIntegrationTest.php000066400000000000000000000077551342575244300250340ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Utils; use PHPUnit\Framework\TestCase; use Symfony\Component\Process\Process; /** * @requires OS Linux * * @coversNothing */ final class UnifiedDiffAssertTraitIntegrationTest extends TestCase { use UnifiedDiffAssertTrait; private $filePatch; protected function setUp(): void { $this->filePatch = __DIR__ . '/../fixtures/out/patch.txt'; $this->cleanUpTempFiles(); } protected function tearDown(): void { $this->cleanUpTempFiles(); } /** * @param string $fileFrom * @param string $fileTo * * @dataProvider provideFilePairsCases */ public function testValidPatches(string $fileFrom, string $fileTo): void { $command = \sprintf( 'diff -u %s %s > %s', \escapeshellarg(\realpath($fileFrom)), \escapeshellarg(\realpath($fileTo)), \escapeshellarg($this->filePatch) ); $p = new Process($command); $p->run(); $exitCode = $p->getExitCode(); if (0 === $exitCode) { // odd case when two files have the same content. Test after executing as it is more efficient than to read the files and check the contents every time. $this->addToAssertionCount(1); return; } $this->assertSame( 1, // means `diff` found a diff between the files we gave it $exitCode, \sprintf( "Command exec. was not successful:\n\"%s\"\nOutput:\n\"%s\"\nStdErr:\n\"%s\"\nExit code %d.\n", $command, $p->getOutput(), $p->getErrorOutput(), $p->getExitCode() ) ); $this->assertValidUnifiedDiffFormat(FileUtils::getFileContent($this->filePatch)); } /** * @return array> */ public function provideFilePairsCases(): array { $cases = []; // created cases based on dedicated fixtures $dir = \realpath(__DIR__ . '/../fixtures/UnifiedDiffAssertTraitIntegrationTest'); $dirLength = \strlen($dir); for ($i = 1;; ++$i) { $fromFile = \sprintf('%s/%d_a.txt', $dir, $i); $toFile = \sprintf('%s/%d_b.txt', $dir, $i); if (!\file_exists($fromFile)) { break; } $this->assertFileExists($toFile); $cases[\sprintf("Diff file:\n\"%s\"\nvs.\n\"%s\"\n", \substr(\realpath($fromFile), $dirLength), \substr(\realpath($toFile), $dirLength))] = [$fromFile, $toFile]; } // create cases based on PHP files within the vendor directory for integration testing $dir = \realpath(__DIR__ . '/../../vendor'); $dirLength = \strlen($dir); $fileIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)); $fromFile = __FILE__; /** @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", \substr(\realpath($fromFile), $dirLength), \substr(\realpath($toFile), $dirLength))] = [$fromFile, $toFile]; $fromFile = $toFile; } return $cases; } private function cleanUpTempFiles(): void { @\unlink($this->filePatch); } } diff-3.0.2/tests/Utils/UnifiedDiffAssertTraitTest.php000066400000000000000000000261441342575244300226210ustar00rootroot00000000000000 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace SebastianBergmann\Diff\Utils; use PHPUnit\Framework\TestCase; /** * @covers SebastianBergmann\Diff\Utils\UnifiedDiffAssertTrait */ final class UnifiedDiffAssertTraitTest extends TestCase { use UnifiedDiffAssertTrait; /** * @param string $diff * * @dataProvider provideValidCases */ public function testValidCases(string $diff): void { $this->assertValidUnifiedDiffFormat($diff); } public function provideValidCases(): array { return [ [ '--- Original +++ New @@ -8 +8 @@ -Z +U ', ], [ '--- Original +++ New @@ -8 +8 @@ -Z +U @@ -15 +15 @@ -X +V ', ], 'empty diff. is valid' => [ '', ], ]; } public function testNoLinebreakEnd(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Expected diff to end with a line break, got "C".', '#'))); $this->assertValidUnifiedDiffFormat("A\nB\nC"); } public function testInvalidStartWithoutHeader(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Expected line to start with '@', '-' or '+', got \"A\n\". Line 1.", '#'))); $this->assertValidUnifiedDiffFormat("A\n"); } public function testInvalidStartHeader1(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Line 1 indicates a header, so line 2 must start with \"+++\".\nLine 1: \"--- A\n\"\nLine 2: \"+ 1\n\".", '#'))); $this->assertValidUnifiedDiffFormat("--- A\n+ 1\n"); } public function testInvalidStartHeader2(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Header line does not match expected pattern, got \"+++ file X\n\". Line 2.", '#'))); $this->assertValidUnifiedDiffFormat("--- A\n+++ file\tX\n"); } public function testInvalidStartHeader3(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Date of header line does not match expected pattern, got "[invalid date]". Line 1.', '#'))); $this->assertValidUnifiedDiffFormat( "--- Original\t[invalid date] +++ New @@ -1,2 +1,2 @@ -A +B " . ' ' ); } public function testInvalidStartHeader4(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Expected header line to start with \"+++ \", got \"+++INVALID\n\". Line 2.", '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++INVALID @@ -1,2 +1,2 @@ -A +B ' . ' ' ); } public function testInvalidLine1(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Expected line to start with '@', '-' or '+', got \"1\n\". Line 5.", '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ -Z 1 +U ' ); } public function testInvalidLine2(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Expected string length of minimal 2, got 1. Line 4.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ ' ); } public function testHunkInvalidFormat(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote("Hunk header line does not match expected pattern, got \"@@ INVALID -1,1 +1,1 @@\n\". Line 3.", '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ INVALID -1,1 +1,1 @@ -Z +U ' ); } public function testHunkOverlapFrom(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected new hunk; "from" (\'-\') start overlaps previous hunk. Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,1 +8,1 @@ -Z +U @@ -7,1 +9,1 @@ -Z +U ' ); } public function testHunkOverlapTo(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected new hunk; "to" (\'+\') start overlaps previous hunk. Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,1 +8,1 @@ -Z +U @@ -17,1 +7,1 @@ -Z +U ' ); } public function testExpectHunk1(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Expected hunk start (\'@\'), got "+". Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ -Z +U +O ' ); } public function testExpectHunk2(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected hunk start (\'@\'). Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ ' . ' ' . ' @@ -38,12 +48,12 @@ ' ); } public function testMisplacedLineAfterComments1(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected line as 2 "No newline" markers have found, ". Line 8.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ -Z \ No newline at end of file +U \ No newline at end of file +A ' ); } public function testMisplacedLineAfterComments2(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected line as 2 "No newline" markers have found, ". Line 7.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ +U \ No newline at end of file \ No newline at end of file \ No newline at end of file ' ); } public function testMisplacedLineAfterComments3(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected line as 2 "No newline" markers have found, ". Line 7.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8 +8 @@ +U \ No newline at end of file \ No newline at end of file +A ' ); } public function testMisplacedComment(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected "\ No newline at end of file", it must be preceded by \'+\' or \'-\' line. Line 1.', '#'))); $this->assertValidUnifiedDiffFormat( '\ No newline at end of file ' ); } public function testUnexpectedDuplicateNoNewLineEOF(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected "\\ No newline at end of file", "\\" was already closed. Line 8.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ ' . ' ' . ' \ No newline at end of file ' . ' \ No newline at end of file ' ); } public function testFromAfterClose(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Not expected from (\'-\'), already closed by "\ No newline at end of file". Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ -A \ No newline at end of file -A \ No newline at end of file ' ); } public function testSameAfterFromClose(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Not expected same (\' \'), \'-\' already closed by "\ No newline at end of file". Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ -A \ No newline at end of file A \ No newline at end of file ' ); } public function testToAfterClose(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Not expected to (\'+\'), already closed by "\ No newline at end of file". Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ +A \ No newline at end of file +A \ No newline at end of file ' ); } public function testSameAfterToClose(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Not expected same (\' \'), \'+\' already closed by "\ No newline at end of file". Line 6.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,12 +8,12 @@ +A \ No newline at end of file A \ No newline at end of file ' ); } public function testUnexpectedEOFFromMissingLines(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected EOF, number of lines in hunk "from" (\'-\')) mismatched. Line 7.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,19 +7,2 @@ -A +B ' . ' ' ); } public function testUnexpectedEOFToMissingLines(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected EOF, number of lines in hunk "to" (\'+\')) mismatched. Line 7.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -8,2 +7,3 @@ -A +B ' . ' ' ); } public function testUnexpectedEOFBothFromAndToMissingLines(): void { $this->expectException(\UnexpectedValueException::class); $this->expectExceptionMessageRegExp(\sprintf('#^%s$#', \preg_quote('Unexpected EOF, number of lines in hunk "from" (\'-\')) and "to" (\'+\') mismatched. Line 7.', '#'))); $this->assertValidUnifiedDiffFormat( '--- Original +++ New @@ -1,12 +1,14 @@ -A +B ' . ' ' ); } } diff-3.0.2/tests/fixtures/000077500000000000000000000000001342575244300154505ustar00rootroot00000000000000diff-3.0.2/tests/fixtures/.editorconfig000066400000000000000000000000141342575244300201200ustar00rootroot00000000000000root = true diff-3.0.2/tests/fixtures/UnifiedDiffAssertTraitIntegrationTest/000077500000000000000000000000001342575244300250565ustar00rootroot00000000000000diff-3.0.2/tests/fixtures/UnifiedDiffAssertTraitIntegrationTest/1_a.txt000066400000000000000000000000011342575244300262460ustar00rootroot00000000000000adiff-3.0.2/tests/fixtures/UnifiedDiffAssertTraitIntegrationTest/1_b.txt000066400000000000000000000000001342575244300262460ustar00rootroot00000000000000diff-3.0.2/tests/fixtures/UnifiedDiffAssertTraitIntegrationTest/2_a.txt000066400000000000000000000001051342575244300262540ustar00rootroot00000000000000a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a adiff-3.0.2/tests/fixtures/UnifiedDiffAssertTraitIntegrationTest/2_b.txt000066400000000000000000000000431342575244300262560ustar00rootroot00000000000000a a a a a a a a a a b a a a a a a cdiff-3.0.2/tests/fixtures/out/000077500000000000000000000000001342575244300162575ustar00rootroot00000000000000diff-3.0.2/tests/fixtures/out/.editorconfig000066400000000000000000000000141342575244300207270ustar00rootroot00000000000000root = true diff-3.0.2/tests/fixtures/out/.gitignore000066400000000000000000000001041342575244300202420ustar00rootroot00000000000000# reset all ignore rules to create sandbox for integration test !/**diff-3.0.2/tests/fixtures/patch.txt000066400000000000000000000003101342575244300173020ustar00rootroot00000000000000diff --git a/Foo.php b/Foo.php index abcdefg..abcdefh 100644 --- a/Foo.php +++ b/Foo.php @@ -20,4 +20,5 @@ class Foo const ONE = 1; const TWO = 2; + const THREE = 3; const FOUR = 4; diff-3.0.2/tests/fixtures/patch2.txt000066400000000000000000000006321342575244300173730ustar00rootroot00000000000000diff --git a/Foo.php b/Foo.php index abcdefg..abcdefh 100644 --- a/Foo.php +++ b/Foo.php @@ -20,4 +20,5 @@ class Foo const ONE = 1; const TWO = 2; + const THREE = 3; const FOUR = 4; @@ -320,4 +320,5 @@ class Foo const A = 'A'; const B = 'B'; + const C = 'C'; const D = 'D'; @@ -600,4 +600,5 @@ class Foo public function doSomething() { + return 'foo'; } diff-3.0.2/tests/fixtures/serialized_diff.bin000066400000000000000000000100571342575244300212700ustar00rootroot00000000000000a:1:{i:0;O:27:"SebastianBergmann\Diff\Diff":3:{s:33:"SebastianBergmann\Diff\Difffrom";s:7:"old.txt";s:31:"SebastianBergmann\Diff\Diffto";s:7:"new.txt";s:35:"SebastianBergmann\Diff\Diffchunks";a:3:{i:0;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:1;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:3;s:33:"SebastianBergmann\Diff\Chunkend";i:1;s:38:"SebastianBergmann\Diff\ChunkendRange";i:4;s:35:"SebastianBergmann\Diff\Chunklines";a:4:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:1;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}}}i:1;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:5;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:10;s:33:"SebastianBergmann\Diff\Chunkend";i:6;s:38:"SebastianBergmann\Diff\ChunkendRange";i:8;s:35:"SebastianBergmann\Diff\Chunklines";a:11:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+1121211";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"1111111";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:6;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-1111111";}i:7;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"-2222222";}i:8;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:9;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:10;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}i:2;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"SebastianBergmann\Diff\Chunkstart";i:17;s:40:"SebastianBergmann\Diff\ChunkstartRange";i:5;s:33:"SebastianBergmann\Diff\Chunkend";i:16;s:38:"SebastianBergmann\Diff\ChunkendRange";i:6;s:35:"SebastianBergmann\Diff\Chunklines";a:6:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:8:"+2122212";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"SebastianBergmann\Diff\Linetype";i:3;s:36:"SebastianBergmann\Diff\Linecontent";s:7:"2222222";}}}}}}