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