1<?php
2/*
3 * This file is part of sebastian/diff.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace SebastianBergmann\Diff;
12
13use SebastianBergmann\Diff\LCS\MemoryEfficientImplementation;
14use SebastianBergmann\Diff\LCS\TimeEfficientImplementation;
15use PHPUnit\Framework\TestCase;
16
17/**
18 * @covers SebastianBergmann\Diff\Differ
19 *
20 * @uses SebastianBergmann\Diff\LCS\MemoryEfficientImplementation
21 * @uses SebastianBergmann\Diff\LCS\TimeEfficientImplementation
22 * @uses SebastianBergmann\Diff\Chunk
23 * @uses SebastianBergmann\Diff\Diff
24 * @uses SebastianBergmann\Diff\Line
25 * @uses SebastianBergmann\Diff\Parser
26 */
27class DifferTest extends TestCase
28{
29    const REMOVED = 2;
30    const ADDED   = 1;
31    const OLD     = 0;
32
33    /**
34     * @var Differ
35     */
36    private $differ;
37
38    protected function setUp()
39    {
40        $this->differ = new Differ;
41    }
42
43    /**
44     * @param array        $expected
45     * @param string|array $from
46     * @param string|array $to
47     * @dataProvider arrayProvider
48     */
49    public function testArrayRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation(array $expected, $from, $to)
50    {
51        $this->assertEquals($expected, $this->differ->diffToArray($from, $to, new TimeEfficientImplementation));
52    }
53
54    /**
55     * @param string $expected
56     * @param string $from
57     * @param string $to
58     * @dataProvider textProvider
59     */
60    public function testTextRepresentationOfDiffCanBeRenderedUsingTimeEfficientLcsImplementation($expected, $from, $to)
61    {
62        $this->assertEquals($expected, $this->differ->diff($from, $to, new TimeEfficientImplementation));
63    }
64
65    /**
66     * @param array        $expected
67     * @param string|array $from
68     * @param string|array $to
69     * @dataProvider arrayProvider
70     */
71    public function testArrayRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation(array $expected, $from, $to)
72    {
73        $this->assertEquals($expected, $this->differ->diffToArray($from, $to, new MemoryEfficientImplementation));
74    }
75
76    /**
77     * @param string $expected
78     * @param string $from
79     * @param string $to
80     * @dataProvider textProvider
81     */
82    public function testTextRepresentationOfDiffCanBeRenderedUsingMemoryEfficientLcsImplementation($expected, $from, $to)
83    {
84        $this->assertEquals($expected, $this->differ->diff($from, $to, new MemoryEfficientImplementation));
85    }
86
87    public function testCustomHeaderCanBeUsed()
88    {
89        $differ = new Differ('CUSTOM HEADER');
90
91        $this->assertEquals(
92            "CUSTOM HEADER@@ @@\n-a\n+b\n",
93            $differ->diff('a', 'b')
94        );
95    }
96
97    public function testTypesOtherThanArrayAndStringCanBePassed()
98    {
99        $this->assertEquals(
100            "--- Original\n+++ New\n@@ @@\n-1\n+2\n",
101            $this->differ->diff(1, 2)
102        );
103    }
104
105    /**
106     * @param string $diff
107     * @param Diff[] $expected
108     * @dataProvider diffProvider
109     */
110    public function testParser($diff, array $expected)
111    {
112        $parser = new Parser;
113        $result = $parser->parse($diff);
114
115        $this->assertEquals($expected, $result);
116    }
117
118    public function arrayProvider()
119    {
120        return array(
121            array(
122                array(
123                    array('a', self::REMOVED),
124                    array('b', self::ADDED)
125                ),
126                'a',
127                'b'
128            ),
129            array(
130                array(
131                    array('ba', self::REMOVED),
132                    array('bc', self::ADDED)
133                ),
134                'ba',
135                'bc'
136            ),
137            array(
138                array(
139                    array('ab', self::REMOVED),
140                    array('cb', self::ADDED)
141                ),
142                'ab',
143                'cb'
144            ),
145            array(
146                array(
147                    array('abc', self::REMOVED),
148                    array('adc', self::ADDED)
149                ),
150                'abc',
151                'adc'
152            ),
153            array(
154                array(
155                    array('ab', self::REMOVED),
156                    array('abc', self::ADDED)
157                ),
158                'ab',
159                'abc'
160            ),
161            array(
162                array(
163                    array('bc', self::REMOVED),
164                    array('abc', self::ADDED)
165                ),
166                'bc',
167                'abc'
168            ),
169            array(
170                array(
171                    array('abc', self::REMOVED),
172                    array('abbc', self::ADDED)
173                ),
174                'abc',
175                'abbc'
176            ),
177            array(
178                array(
179                    array('abcdde', self::REMOVED),
180                    array('abcde', self::ADDED)
181                ),
182                'abcdde',
183                'abcde'
184            ),
185            'same start' => array(
186                array(
187                    array(17, self::OLD),
188                    array('b', self::REMOVED),
189                    array('d', self::ADDED),
190                ),
191                array(30 => 17, 'a' => 'b'),
192                array(30 => 17, 'c' => 'd'),
193            ),
194            'same end' => array(
195                array(
196                    array(1, self::REMOVED),
197                    array(2, self::ADDED),
198                    array('b', self::OLD),
199                ),
200                array(1 => 1, 'a' => 'b'),
201                array(1 => 2, 'a' => 'b'),
202            ),
203            'same start (2), same end (1)' => array(
204                array(
205                    array(17, self::OLD),
206                    array(2, self::OLD),
207                    array(4, self::REMOVED),
208                    array('a', self::ADDED),
209                    array(5, self::ADDED),
210                    array('x', self::OLD),
211                ),
212                array(30 => 17, 1 => 2, 2 => 4, 'z' => 'x'),
213                array(30 => 17, 1 => 2, 3 => 'a', 2 => 5, 'z' => 'x'),
214            ),
215            'same' => array(
216                array(
217                    array('x', self::OLD),
218                ),
219                array('z' => 'x'),
220                array('z' => 'x'),
221            ),
222            'diff' => array(
223                array(
224                    array('y', self::REMOVED),
225                    array('x', self::ADDED),
226                ),
227                array('x' => 'y'),
228                array('z' => 'x'),
229            ),
230            'diff 2' => array(
231                array(
232                    array('y', self::REMOVED),
233                    array('b', self::REMOVED),
234                    array('x', self::ADDED),
235                    array('d', self::ADDED),
236                ),
237                array('x' => 'y', 'a' => 'b'),
238                array('z' => 'x', 'c' => 'd'),
239            ),
240            'test line diff detection' => array(
241                array(
242                    array(
243                        '#Warning: Strings contain different line endings!',
244                        self::OLD,
245                    ),
246                    array(
247                        '<?php',
248                        self::OLD,
249                    ),
250                    array(
251                        '',
252                        self::OLD,
253                    ),
254                ),
255                "<?php\r\n",
256                "<?php\n"
257            )
258        );
259    }
260
261    public function textProvider()
262    {
263        return array(
264            array(
265                "--- Original\n+++ New\n@@ @@\n-a\n+b\n",
266                'a',
267                'b'
268            ),
269            array(
270                "--- Original\n+++ New\n@@ @@\n-ba\n+bc\n",
271                'ba',
272                'bc'
273            ),
274            array(
275                "--- Original\n+++ New\n@@ @@\n-ab\n+cb\n",
276                'ab',
277                'cb'
278            ),
279            array(
280                "--- Original\n+++ New\n@@ @@\n-abc\n+adc\n",
281                'abc',
282                'adc'
283            ),
284            array(
285                "--- Original\n+++ New\n@@ @@\n-ab\n+abc\n",
286                'ab',
287                'abc'
288            ),
289            array(
290                "--- Original\n+++ New\n@@ @@\n-bc\n+abc\n",
291                'bc',
292                'abc'
293            ),
294            array(
295                "--- Original\n+++ New\n@@ @@\n-abc\n+abbc\n",
296                'abc',
297                'abbc'
298            ),
299            array(
300                "--- Original\n+++ New\n@@ @@\n-abcdde\n+abcde\n",
301                'abcdde',
302                'abcde'
303            ),
304            array(
305                "--- Original\n+++ New\n@@ @@\n-A\n+A1\n B\n",
306                "A\nB",
307                "A1\nB",
308            ),
309            array(
310                <<<EOF
311--- Original
312+++ New
313@@ @@
314 a
315-b
316+p
317@@ @@
318 i
319-j
320+w
321 k
322
323EOF
324            ,
325                "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
326                "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
327            ),
328            array(
329                <<<EOF
330--- Original
331+++ New
332@@ @@
333 a
334-b
335+p
336@@ @@
337 i
338-j
339+w
340 k
341
342EOF
343                ,
344                "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk",
345                "a\np\nc\nd\ne\nf\ng\nh\ni\nw\nk",
346            ),
347        );
348    }
349
350    public function diffProvider()
351    {
352        $serialized_arr = <<<EOL
353a:1:{i:0;O:27:"SebastianBergmann\Diff\Diff":3:{s:33:"�SebastianBergmann\Diff\Diff�from";s:7:"old.txt";s:31:"�SebastianBergmann\Diff\Diff�to";s:7:"new.txt";s:35:"�SebastianBergmann\Diff\Diff�chunks";a:3:{i:0;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"�SebastianBergmann\Diff\Chunk�start";i:1;s:40:"�SebastianBergmann\Diff\Chunk�startRange";i:3;s:33:"�SebastianBergmann\Diff\Chunk�end";i:1;s:38:"�SebastianBergmann\Diff\Chunk�endRange";i:4;s:35:"�SebastianBergmann\Diff\Chunk�lines";a:4:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:1;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}}}i:1;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"�SebastianBergmann\Diff\Chunk�start";i:5;s:40:"�SebastianBergmann\Diff\Chunk�startRange";i:10;s:33:"�SebastianBergmann\Diff\Chunk�end";i:6;s:38:"�SebastianBergmann\Diff\Chunk�endRange";i:8;s:35:"�SebastianBergmann\Diff\Chunk�lines";a:11:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:8:"+1121211";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"1111111";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:8:"-1111111";}i:6;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:8:"-1111111";}i:7;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:8:"-2222222";}i:8;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:9;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:10;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}}}i:2;O:28:"SebastianBergmann\Diff\Chunk":5:{s:35:"�SebastianBergmann\Diff\Chunk�start";i:17;s:40:"�SebastianBergmann\Diff\Chunk�startRange";i:5;s:33:"�SebastianBergmann\Diff\Chunk�end";i:16;s:38:"�SebastianBergmann\Diff\Chunk�endRange";i:6;s:35:"�SebastianBergmann\Diff\Chunk�lines";a:6:{i:0;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:1;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:2;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:3;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:8:"+2122212";}i:4;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}i:5;O:27:"SebastianBergmann\Diff\Line":2:{s:33:"�SebastianBergmann\Diff\Line�type";i:3;s:36:"�SebastianBergmann\Diff\Line�content";s:7:"2222222";}}}}}}
354EOL;
355
356        return array(
357            array(
358                "--- 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",
359                \unserialize($serialized_arr)
360            )
361        );
362    }
363
364    /**
365     * @param string $expected
366     * @param string $from
367     * @param string $to
368     * @dataProvider textForNoNonDiffLinesProvider
369     */
370    public function testDiffDoNotShowNonDiffLines($expected, $from, $to)
371    {
372        $differ = new Differ('', false);
373        $this->assertSame($expected, $differ->diff($from, $to));
374    }
375
376    public function textForNoNonDiffLinesProvider()
377    {
378        return array(
379            array(
380                '', 'a', 'a'
381            ),
382            array(
383                "-A\n+C\n",
384                "A\n\n\nB",
385                "C\n\n\nB",
386            ),
387        );
388    }
389
390    /**
391     * @requires PHPUnit 5.7
392     */
393    public function testDiffToArrayInvalidFromType()
394    {
395        $differ = new Differ;
396
397        $this->expectException('\InvalidArgumentException');
398        $this->expectExceptionMessageRegExp('#^"from" must be an array or string\.$#');
399
400        $differ->diffToArray(null, '');
401    }
402
403    /**
404     * @requires PHPUnit 5.7
405     */
406    public function testDiffInvalidToType()
407    {
408        $differ = new Differ;
409
410        $this->expectException('\InvalidArgumentException');
411        $this->expectExceptionMessageRegExp('#^"to" must be an array or string\.$#');
412
413        $differ->diffToArray('', new \stdClass);
414    }
415}
416