1#!/usr/bin/perl -w
2#
3# Copyright (C) Research in Motion Limited 2010. All Rights Reserved.
4# Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10#     * Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#     * Redistributions in binary form must reproduce the above
13# copyright notice, this list of conditions and the following disclaimer
14# in the documentation and/or other materials provided with the
15# distribution.
16#     * Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17# its contributors may be used to endorse or promote products derived
18# from this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32# Unit tests of parseSvnProperty().
33
34use strict;
35use warnings;
36
37use Test::More;
38use VCSUtils;
39
40my @testCaseHashRefs = (
41####
42# Simple test cases
43##
44{
45    # New test
46    diffName => "simple: add svn:executable",
47    inputText => <<'END',
48Added: svn:executable
49   + *
50END
51    expectedReturn => [
52{
53    name => "svn:executable",
54    propertyChangeDelta => 1,
55    value => "*",
56},
57undef],
58    expectedNextLine => undef,
59},
60{
61    # New test
62    diffName => "simple: delete svn:executable",
63    inputText => <<'END',
64Deleted: svn:executable
65   - *
66END
67    expectedReturn => [
68{
69    name => "svn:executable",
70    propertyChangeDelta => -1,
71    value => "*",
72},
73undef],
74    expectedNextLine => undef,
75},
76{
77    # New test
78    diffName => "simple: add svn:mergeinfo",
79    inputText => <<'END',
80Added: svn:mergeinfo
81   Merged /trunk/Makefile:r33020
82END
83    expectedReturn => [
84{
85    name => "svn:mergeinfo",
86    propertyChangeDelta => 1,
87    value => "/trunk/Makefile:r33020",
88},
89undef],
90    expectedNextLine => undef,
91},
92{
93    # New test
94    diffName => "simple: delete svn:mergeinfo",
95    inputText => <<'END',
96Deleted: svn:mergeinfo
97   Reverse-merged /trunk/Makefile:r33020
98END
99    expectedReturn => [
100{
101    name => "svn:mergeinfo",
102    propertyChangeDelta => -1,
103    value => "/trunk/Makefile:r33020",
104},
105undef],
106    expectedNextLine => undef,
107},
108{
109    # New test
110    diffName => "simple: modified svn:mergeinfo",
111    inputText => <<'END',
112Modified: svn:mergeinfo
113   Reverse-merged /trunk/Makefile:r33020
114   Merged /trunk/Makefile:r41697
115END
116    expectedReturn => [
117{
118    name => "svn:mergeinfo",
119    propertyChangeDelta => 1,
120    value => "/trunk/Makefile:r41697",
121},
122undef],
123    expectedNextLine => undef,
124},
125####
126# Using SVN 1.4 syntax
127##
128{
129    # New test
130    diffName => "simple: modified svn:mergeinfo using SVN 1.4 syntax",
131    inputText => <<'END',
132Name: svn:mergeinfo
133   Reverse-merged /trunk/Makefile:r33020
134   Merged /trunk/Makefile:r41697
135END
136    expectedReturn => [
137{
138    name => "svn:mergeinfo",
139    propertyChangeDelta => 1,
140    value => "/trunk/Makefile:r41697",
141},
142undef],
143    expectedNextLine => undef,
144},
145{
146    # New test
147    diffName => "simple: delete svn:executable using SVN 1.4 syntax",
148    inputText => <<'END',
149Name: svn:executable
150   - *
151END
152    expectedReturn => [
153{
154    name => "svn:executable",
155    propertyChangeDelta => -1,
156    value => "*",
157},
158undef],
159    expectedNextLine => undef,
160},
161{
162    # New test
163    diffName => "simple: add svn:executable using SVN 1.4 syntax",
164    inputText => <<'END',
165Name: svn:executable
166   + *
167END
168    expectedReturn => [
169{
170    name => "svn:executable",
171    propertyChangeDelta => 1,
172    value => "*",
173},
174undef],
175    expectedNextLine => undef,
176},
177####
178# Property value followed by empty line and start of next diff
179##
180{
181    # New test
182    diffName => "add svn:executable, followed by empty line and start of next diff",
183    inputText => <<'END',
184Added: svn:executable
185   + *
186
187Index: Makefile.shared
188END
189    expectedReturn => [
190{
191    name => "svn:executable",
192    propertyChangeDelta => 1,
193    value => "*",
194},
195"\n"],
196    expectedNextLine => "Index: Makefile.shared\n",
197},
198{
199    # New test
200    diffName => "add svn:executable, followed by empty line and start of next diff using Windows line endings",
201    inputText => toWindowsLineEndings(<<'END',
202Added: svn:executable
203   + *
204
205Index: Makefile.shared
206END
207),
208    expectedReturn => [
209{
210    name => "svn:executable",
211    propertyChangeDelta => 1,
212    value => "*",
213},
214"\r\n"],
215    expectedNextLine => "Index: Makefile.shared\r\n",
216},
217{
218    # New test
219    diffName => "add svn:executable, followed by empty line and start of next property diff",
220    inputText => <<'END',
221Added: svn:executable
222   + *
223
224Property changes on: Makefile.shared
225END
226    expectedReturn => [
227{
228    name => "svn:executable",
229    propertyChangeDelta => 1,
230    value => "*",
231},
232"\n"],
233    expectedNextLine => "Property changes on: Makefile.shared\n",
234},
235{
236    # New test
237    diffName => "add svn:executable, followed by empty line and start of next property diff using Windows line endings",
238    inputText => toWindowsLineEndings(<<'END',
239Added: svn:executable
240   + *
241
242Property changes on: Makefile.shared
243END
244),
245    expectedReturn => [
246{
247    name => "svn:executable",
248    propertyChangeDelta => 1,
249    value => "*",
250},
251"\r\n"],
252    expectedNextLine => "Property changes on: Makefile.shared\r\n",
253},
254{
255    # New test
256    diffName => "multi-line '+' change, followed by empty line and start of next diff",
257    inputText => <<'END',
258Name: documentation
259   + A
260long sentence that spans
261multiple lines.
262
263Index: Makefile.shared
264END
265    expectedReturn => [
266{
267    name => "documentation",
268    propertyChangeDelta => 1,
269    value => "A\nlong sentence that spans\nmultiple lines.",
270},
271"\n"],
272    expectedNextLine => "Index: Makefile.shared\n",
273},
274{
275    # New test
276    diffName => "multi-line '+' change, followed by empty line and start of next diff using Windows line endings",
277    inputText => toWindowsLineEndings(<<'END',
278Name: documentation
279   + A
280long sentence that spans
281multiple lines.
282
283Index: Makefile.shared
284END
285),
286    expectedReturn => [
287{
288    name => "documentation",
289    propertyChangeDelta => 1,
290    value => "A\r\nlong sentence that spans\r\nmultiple lines.",
291},
292"\r\n"],
293    expectedNextLine => "Index: Makefile.shared\r\n",
294},
295{
296    # New test
297    diffName => "multi-line '+' change, followed by empty line and start of next property diff",
298    inputText => <<'END',
299Name: documentation
300   + A
301long sentence that spans
302multiple lines.
303
304Property changes on: Makefile.shared
305END
306    expectedReturn => [
307{
308    name => "documentation",
309    propertyChangeDelta => 1,
310    value => "A\nlong sentence that spans\nmultiple lines.",
311},
312"\n"],
313    expectedNextLine => "Property changes on: Makefile.shared\n",
314},
315{
316    # New test
317    diffName => "multi-line '+' change, followed by empty line and start of next property diff using Windows line endings",
318    inputText => toWindowsLineEndings(<<'END',
319Name: documentation
320   + A
321long sentence that spans
322multiple lines.
323
324Property changes on: Makefile.shared
325END
326),
327    expectedReturn => [
328{
329    name => "documentation",
330    propertyChangeDelta => 1,
331    value => "A\r\nlong sentence that spans\r\nmultiple lines.",
332},
333"\r\n"],
334    expectedNextLine => "Property changes on: Makefile.shared\r\n",
335},
336####
337# Property value followed by empty line and start of binary patch
338##
339{
340    # New test
341    diffName => "add svn:executable, followed by empty line and start of binary patch",
342    inputText => <<'END',
343Added: svn:executable
344   + *
345
346Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
347END
348    expectedReturn => [
349{
350    name => "svn:executable",
351    propertyChangeDelta => 1,
352    value => "*",
353},
354"\n"],
355    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n",
356},
357{
358    # New test
359    diffName => "add svn:executable, followed by empty line and start of binary patch using Windows line endings",
360    inputText => toWindowsLineEndings(<<'END',
361Added: svn:executable
362   + *
363
364Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
365END
366),
367    expectedReturn => [
368{
369    name => "svn:executable",
370    propertyChangeDelta => 1,
371    value => "*",
372},
373"\r\n"],
374    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\r\n",
375},
376{
377    # New test
378    diffName => "multi-line '+' change, followed by empty line and start of binary patch",
379    inputText => <<'END',
380Name: documentation
381   + A
382long sentence that spans
383multiple lines.
384
385Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
386END
387    expectedReturn => [
388{
389    name => "documentation",
390    propertyChangeDelta => 1,
391    value => "A\nlong sentence that spans\nmultiple lines.",
392},
393"\n"],
394    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n",
395},
396{
397    # New test
398    diffName => "multi-line '+' change, followed by empty line and start of binary patch using Windows line endings",
399    inputText => toWindowsLineEndings(<<'END',
400Name: documentation
401   + A
402long sentence that spans
403multiple lines.
404
405Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
406END
407),
408    expectedReturn => [
409{
410    name => "documentation",
411    propertyChangeDelta => 1,
412    value => "A\r\nlong sentence that spans\r\nmultiple lines.",
413},
414"\r\n"],
415    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\r\n",
416},
417{
418    # New test
419    diffName => "multi-line '-' change, followed by multi-line '+' change, empty line, and start of binary patch",
420    inputText => <<'END',
421Modified: documentation
422   - A
423long sentence that spans
424multiple lines.
425   + Another
426long sentence that spans
427multiple lines.
428
429Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
430END
431    expectedReturn => [
432{
433    name => "documentation",
434    propertyChangeDelta => 1,
435    value => "Another\nlong sentence that spans\nmultiple lines.",
436},
437"\n"],
438    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\n",
439},
440{
441    # New test
442    diffName => "multi-line '-' change, followed by multi-line '+' change, empty line, and start of binary patch using Windows line endings",
443    inputText => toWindowsLineEndings(<<'END',
444Modified: documentation
445   - A
446long sentence that spans
447multiple lines.
448   + Another
449long sentence that spans
450multiple lines.
451
452Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
453END
454),
455    expectedReturn => [
456{
457    name => "documentation",
458    propertyChangeDelta => 1,
459    value => "Another\r\nlong sentence that spans\r\nmultiple lines.",
460},
461"\r\n"],
462    expectedNextLine => "Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==\r\n",
463},
464####
465# Successive properties
466##
467{
468    # New test
469    diffName => "single-line '+' change followed by custom property with single-line '+' change",
470    inputText => <<'END',
471Added: svn:executable
472   + *
473Added: documentation
474   + A sentence.
475END
476    expectedReturn => [
477{
478    name => "svn:executable",
479    propertyChangeDelta => 1,
480    value => "*",
481},
482"Added: documentation\n"],
483    expectedNextLine => "   + A sentence.\n",
484},
485{
486    # New test
487    diffName => "multi-line '+' change, followed by svn:executable",
488    inputText => <<'END',
489Name: documentation
490   + A
491long sentence that spans
492multiple lines.
493Name: svn:executable
494   + *
495END
496    expectedReturn => [
497{
498    name => "documentation",
499    propertyChangeDelta => 1,
500    value => "A\nlong sentence that spans\nmultiple lines.",
501},
502"Name: svn:executable\n"],
503    expectedNextLine => "   + *\n",
504},
505{
506    # New test
507    diffName => "multi-line '-' change, followed by multi-line '+' change and add svn:executable",
508    inputText => <<'END',
509Modified: documentation
510   - A
511long sentence that spans
512multiple lines.
513   + Another
514long sentence that spans
515multiple lines.
516Added: svn:executable
517   + *
518END
519    expectedReturn => [
520{
521    name => "documentation",
522    propertyChangeDelta => 1,
523    value => "Another\nlong sentence that spans\nmultiple lines.",
524},
525"Added: svn:executable\n"],
526    expectedNextLine => "   + *\n",
527},
528{
529    # New test
530    diffName => "'Merged' change followed by 'Merged' change",
531    inputText => <<'END',
532Added: svn:mergeinfo
533   Merged /trunk/Makefile:r33020
534   Merged /trunk/Makefile.shared:r58350
535END
536    expectedReturn => [
537{
538    name => "svn:mergeinfo",
539    propertyChangeDelta => 1,
540    value => "/trunk/Makefile.shared:r58350",
541},
542undef],
543    expectedNextLine => undef,
544},
545{
546    # New test
547    diffName => "'Reverse-merged' change followed by 'Reverse-merged' change",
548    inputText => <<'END',
549Deleted: svn:mergeinfo
550   Reverse-merged /trunk/Makefile:r33020
551   Reverse-merged /trunk/Makefile.shared:r58350
552END
553    expectedReturn => [
554{
555    name => "svn:mergeinfo",
556    propertyChangeDelta => -1,
557    value => "/trunk/Makefile.shared:r58350",
558},
559undef],
560    expectedNextLine => undef,
561},
562####
563# Property values with trailing new lines.
564##
565# FIXME: We do not support property values with trailing new lines, since it is difficult to
566#        disambiguate them from the empty line that preceeds the contents of a binary patch as
567#        in the test case (above): "multi-line '+' change, followed by empty line and start of binary patch".
568{
569    # New test
570    diffName => "single-line '+' with trailing new line",
571    inputText => <<'END',
572Added: documentation
573   + A sentence.
574
575END
576    expectedReturn => [
577{
578    name => "documentation",
579    propertyChangeDelta => 1,
580    value => "A sentence.",
581},
582"\n"],
583    expectedNextLine => undef,
584},
585{
586    # New test
587    diffName => "single-line '+' with trailing new line using Windows line endings",
588    inputText => toWindowsLineEndings(<<'END',
589Added: documentation
590   + A sentence.
591
592END
593),
594    expectedReturn => [
595{
596    name => "documentation",
597    propertyChangeDelta => 1,
598    value => "A sentence.",
599},
600"\r\n"],
601    expectedNextLine => undef,
602},
603{
604    # New test
605    diffName => "single-line '+' with trailing new line, followed by empty line and start of binary patch",
606    inputText => <<'END',
607Added: documentation
608   + A sentence.
609
610
611Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
612END
613    expectedReturn => [
614{
615    name => "documentation",
616    propertyChangeDelta => 1,
617    value => "A sentence.",
618},
619"\n"],
620    expectedNextLine => "\n",
621},
622{
623    # New test
624    diffName => "single-line '+' with trailing new line, followed by empty line and start of binary patch using Windows line endings",
625    inputText => toWindowsLineEndings(<<'END',
626Added: documentation
627   + A sentence.
628
629
630Q1dTBx0AAAB42itg4GlgYJjGwMDDyODMxMDw34GBgQEAJPQDJA==
631END
632),
633    expectedReturn => [
634{
635    name => "documentation",
636    propertyChangeDelta => 1,
637    value => "A sentence.",
638},
639"\r\n"],
640    expectedNextLine => "\r\n",
641},
642{
643    # New test
644    diffName => "single-line '-' change with trailing new line, and single-line '+' change",
645    inputText => <<'END',
646Modified: documentation
647   - A long sentence.
648
649   + A sentence.
650END
651    expectedReturn => [
652{
653    name => "documentation",
654    propertyChangeDelta => -1, # Since we only interpret the '-' property.
655    value => "A long sentence.",
656},
657"\n"],
658    expectedNextLine => "   + A sentence.\n",
659},
660{
661    # New test
662    diffName => "single-line '-' change with trailing new line, and single-line '+' change using Windows line endings",
663    inputText => toWindowsLineEndings(<<'END',
664Modified: documentation
665   - A long sentence.
666
667   + A sentence.
668END
669),
670    expectedReturn => [
671{
672    name => "documentation",
673    propertyChangeDelta => -1, # Since we only interpret the '-' property.
674    value => "A long sentence.",
675},
676"\r\n"],
677    expectedNextLine => "   + A sentence.\r\n",
678},
679{
680    # New test
681    diffName => "multi-line '-' change with trailing new line, and multi-line '+' change",
682    inputText => <<'END',
683Modified: documentation
684   - A
685long sentence that spans
686multiple lines.
687
688   + Another
689long sentence that spans
690multiple lines.
691END
692    expectedReturn => [
693{
694    name => "documentation",
695    propertyChangeDelta => -1, # Since we only interpret the '-' property.
696    value => "A\nlong sentence that spans\nmultiple lines.",
697},
698"\n"],
699    expectedNextLine => "   + Another\n",
700},
701{
702    # New test
703    diffName => "multi-line '-' change with trailing new line, and multi-line '+' change using Windows line endings",
704    inputText => toWindowsLineEndings(<<'END',
705Modified: documentation
706   - A
707long sentence that spans
708multiple lines.
709
710   + Another
711long sentence that spans
712multiple lines.
713END
714),
715    expectedReturn => [
716{
717    name => "documentation",
718    propertyChangeDelta => -1, # Since we only interpret the '-' property.
719    value => "A\r\nlong sentence that spans\r\nmultiple lines.",
720},
721"\r\n"],
722    expectedNextLine => "   + Another\r\n",
723},
724);
725
726my $testCasesCount = @testCaseHashRefs;
727plan(tests => 2 * $testCasesCount); # Total number of assertions.
728
729foreach my $testCase (@testCaseHashRefs) {
730    my $testNameStart = "parseSvnProperty(): $testCase->{diffName}: comparing";
731
732    my $fileHandle;
733    open($fileHandle, "<", \$testCase->{inputText});
734    my $line = <$fileHandle>;
735
736    my @got = VCSUtils::parseSvnProperty($fileHandle, $line);
737    my $expectedReturn = $testCase->{expectedReturn};
738
739    is_deeply(\@got, $expectedReturn, "$testNameStart return value.");
740
741    my $gotNextLine = <$fileHandle>;
742    is($gotNextLine, $testCase->{expectedNextLine},  "$testNameStart next read line.");
743}
744