2 # -*- coding: utf-8 -*-
3 # Copyright (c) 2005, Giovanni Bajo
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 from cStringIO
import StringIO
31 # IMPORTANT NOTE TO TEST AUTHORS
33 # Any quoted strings inside the arguments of the parameter "cmd" must
34 # be enclosed in double-, not single-quotes, so that the command parser
35 # knows to keep them together. For example, do not write this:
36 # launch("svn ci -m 'log comment'") # BAD
37 # ...but one of these:
38 # launch('svn ci -m "log comment"') # GOOD
39 # launch("svn ci -m \"log comment\"") # GOOD, but why?
40 # Otherwise, you get an error saying
41 # '<path>/comment' is not under version control
42 # ...when running the tests on Windows.
45 # True/False constants are Python 2.2+
51 class TestCase_kwextract(unittest
.TestCase
):
53 self
.assertEqual(svnmerge
.kwextract("$Rev: 134 rasky $"), "134 rasky")
54 self
.assertEqual(svnmerge
.kwextract("$Date: 2005-09-25 13:45 CET+1$"),
55 "2005-09-25 13:45 CET+1")
57 def test_failure(self
):
58 self
.assertEqual(svnmerge
.kwextract("$Rev: $"), "<unknown>")
59 self
.assertEqual(svnmerge
.kwextract("$Date$"), "<unknown>")
61 class TestCase_launch(unittest
.TestCase
):
68 out
= svnmerge
.launch(self
.cmd
)
71 self
.assertEqual(o
[-1], "\n")
73 def test_failure(self
):
74 self
.assertRaises(svnmerge
.LaunchError
, svnmerge
.launch
, self
.cmd
*10)
76 def test_failurecode(self
):
78 svnmerge
.launch(self
.cmd
*10)
79 except svnmerge
.LaunchError
, (ret
, cmd
, out
):
80 self
.assertNotEqual(ret
, 0)
81 self
.assertNotEqual(ret
, None)
83 self
.assertEqual(cmd
, self
.cmd
*10)
85 self
.fail("svnmerge.launch did not cause a LaunchError as expected")
87 class TestCase_PrefixLines(unittest
.TestCase
):
89 self
.assertEqual("zz\n", svnmerge
.prefix_lines("zz", "\n"))
90 self
.assertEqual("zzfoo\n", svnmerge
.prefix_lines("zz", "foo\n"))
91 self
.assertEqual("zzfoo\nzzbar\n", svnmerge
.prefix_lines("zz", "foo\nbar\n"))
92 self
.assertEqual("zz\nzzfoo\n", svnmerge
.prefix_lines("zz", "\nfoo\n"))
93 self
.assertEqual("zz\nzzfoo\nzzbar\n", svnmerge
.prefix_lines("zz", "\nfoo\nbar\n"))
95 class TestCase_RevisionSet(unittest
.TestCase
):
96 def test_constr_string(self
):
97 rs
= svnmerge
.RevisionSet("10- 15, 12-48,2 ")
98 self
.assert_(17 in rs
)
100 self
.assert_(9 not in rs
)
102 rs
= svnmerge
.RevisionSet("10: 15, 12:48,2 ")
103 self
.assert_(17 in rs
)
104 self
.assert_(2 in rs
)
105 self
.assert_(9 not in rs
)
107 def test_constr_dict(self
):
108 rs
= svnmerge
.RevisionSet({18:1, 24:1, 25:1, 43:1})
109 self
.assert_(24 in rs
)
110 self
.assert_(18 in rs
)
111 self
.assert_(44 not in rs
)
113 def test_constr_error(self
):
114 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10-12-15")
115 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10;12-15")
116 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10,foo,3-15")
118 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10:12:15")
119 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10;12:15")
120 self
.assertRaises(ValueError, svnmerge
.RevisionSet
, "10,foo,3:15")
122 def test_normalized(self
):
123 rs
= svnmerge
.RevisionSet("8-15,16-18, 4-6, 9, 18, 1-1, 3-3")
124 self
.assertEqual(rs
.normalized(), [(1,1), (3,6), (8,18)])
125 self
.assertEqual(str(rs
), "1,3-6,8-18")
127 rs
= svnmerge
.RevisionSet("8:15,16:18, 4:6, 9, 18, 1:1, 3:3")
128 self
.assertEqual(rs
.normalized(), [(1,1), (3,6), (8,18)])
129 self
.assertEqual(str(rs
), "1,3-6,8-18")
131 def test_sorted(self
):
132 "Test the sorted() function of the RevisionSet class."
133 rs
= svnmerge
.RevisionSet("8-15,16-18, 4-6, 9, 18, 1-1, 3-3")
134 self
.assertEqual(rs
.sorted(), [1, 3, 4, 5, 6, 8, 9, 10, 11,
135 12, 13, 14, 15, 16, 17, 18])
137 rs
= svnmerge
.RevisionSet("8:15,16:18, 4:6, 9, 18, 1:1, 3:3")
138 self
.assertEqual(rs
.sorted(), [1, 3, 4, 5, 6, 8, 9, 10, 11,
139 12, 13, 14, 15, 16, 17, 18])
141 def test_length(self
):
142 rs
= svnmerge
.RevisionSet("3-8")
143 self
.assertEqual(len(rs
), 6)
144 rs
= svnmerge
.RevisionSet("3-8,4-10")
145 self
.assertEqual(len(rs
), 8)
146 rs
= svnmerge
.RevisionSet("1,3,5")
147 self
.assertEqual(len(rs
), 3)
149 rs
= svnmerge
.RevisionSet("3:8")
150 self
.assertEqual(len(rs
), 6)
151 rs
= svnmerge
.RevisionSet("3:8,4:10")
152 self
.assertEqual(len(rs
), 8)
153 rs
= svnmerge
.RevisionSet("1,3,5")
154 self
.assertEqual(len(rs
), 3)
162 rs
= svnmerge
.RevisionSet("4-13,1-5,34,20-22,18-21")
163 self
.assertEqual(list(iter(rs
)), range(1,14)+range(18,23)+[34])
165 rs
= svnmerge
.RevisionSet("4:13,1:5,34,20:22,18:21")
166 self
.assertEqual(list(iter(rs
)), range(1,14)+range(18,23)+[34])
168 def test_union(self
):
169 rs
= svnmerge
.RevisionSet("3-8,4-10") | svnmerge
.RevisionSet("7-14,1")
170 self
.assertEqual(str(rs
), "1,3-14")
172 rs
= svnmerge
.RevisionSet("3:8,4:10") | svnmerge
.RevisionSet("7:14,1")
173 self
.assertEqual(str(rs
), "1,3-14")
175 def test_subtraction(self
):
176 rs
= svnmerge
.RevisionSet("3-8,4-10") - svnmerge
.RevisionSet("7-14,1")
177 self
.assertEqual(str(rs
), "3-6")
179 rs
= svnmerge
.RevisionSet("3:8,4:10") - svnmerge
.RevisionSet("7:14,1")
180 self
.assertEqual(str(rs
), "3-6")
182 def test_constr_empty(self
):
183 rs
= svnmerge
.RevisionSet("")
184 self
.assertEqual(str(rs
), "")
186 class TestCase_MinimalMergeIntervals(unittest
.TestCase
):
187 def test_basic(self
):
188 rs
= svnmerge
.RevisionSet("4-8,12,18,24")
189 phantom
= svnmerge
.RevisionSet("8-11,13-16,19-23")
190 revs
= svnmerge
.minimal_merge_intervals(rs
, phantom
)
191 self
.assertEqual(revs
, [(4,12), (18,24)])
193 class TestCase_SvnMerge(unittest
.TestCase
):
194 def svnmerge(self
, cmds
, *args
, **kwargs
):
195 return self
.svnmerge2(cmds
.split(), *args
, **kwargs
)
197 def svnmerge2(self
, args
, error
=False, match
=None, nonmatch
=None):
199 sys
.stdout
= sys
.stderr
= out
202 # Clear svnmerge's internal cache before running any
204 svnmerge
._cache
_svninfo
= {}
205 svnmerge
._cache
_reporoot
= {}
207 ret
= svnmerge
.main(args
)
208 except SystemExit, e
:
211 sys
.stdout
= sys
.__stdout
__
212 sys
.stderr
= sys
.__stderr
__
218 self
.assertNotEqual(ret
, 0,
219 "svnmerge did not fail, with this output:\n%s" % out
.getvalue())
221 self
.assertEqual(ret
, 0,
222 "svnmerge failed, with this output:\n%s" % out
.getvalue())
224 if match
is not None:
225 self
.assert_(re
.search(match
, out
.getvalue()),
226 "pattern %r not found in output:\n%s" % (match
, out
.getvalue()))
227 if nonmatch
is not None:
228 self
.assert_(not re
.search(nonmatch
, out
.getvalue()),
229 "pattern %r found in output:\n%s" % (nonmatch
, out
.getvalue()))
231 return out
.getvalue()
233 def _parseoutput(self
, ret
, out
, error
=False, match
=None, nonmatch
=None):
235 self
.assertNotEqual(ret
,
237 "svnmerge did not fail, with this output:\n%s" % out
)
239 self
.assertEqual(ret
,
241 "svnmerge failed, with this output:\n%s" % out
)
243 if match
is not None:
244 self
.assert_(re
.search(match
, out
),
245 "pattern %r not found in output:\n%s" % (match
, out
))
247 if nonmatch
is not None:
248 self
.assert_(not re
.search(nonmatch
, out
),
249 "pattern %r found in output:\n%s" % (nonmatch
, out
))
253 def launch(self
, cmd
, **kwargs
):
255 out
= svnmerge
.launch(cmd
, split_lines
=False)
256 except svnmerge
.LaunchError
, (ret
, cmd
, out
):
257 return self
._parseoutput
(ret
, out
, **kwargs
)
258 return self
._parseoutput
(0, out
, **kwargs
)
260 class TestCase_CommandLineOptions(TestCase_SvnMerge
):
261 def test_empty(self
):
264 def test_help_commands(self
):
265 self
.svnmerge("help")
266 self
.svnmerge("--help")
268 for cmd
in svnmerge
.command_table
.keys():
269 self
.svnmerge("help %s" % cmd
)
270 self
.svnmerge("%s --help" % cmd
)
271 self
.svnmerge("%s -h" % cmd
)
273 def test_wrong_commands(self
):
274 self
.svnmerge("asijdoiasjd", error
=True)
275 self
.svnmerge("help asijdoiasjd", error
=True)
277 def test_wrong_option(self
):
278 self
.svnmerge("--asdsad", error
=True)
279 self
.svnmerge("help --asdsad", error
=True)
280 self
.svnmerge("init --asdsad", error
=True)
281 self
.svnmerge("--asdsad init", error
=True)
283 def test_version(self
):
284 out
= self
.svnmerge("--version")
285 self
.assert_(out
.find("Giovanni Bajo") >= 0)
286 out
= self
.svnmerge("-V")
287 self
.assert_(out
.find("Giovanni Bajo") >= 0)
288 out
= self
.svnmerge("init -V")
289 self
.assert_(out
.find("Giovanni Bajo") >= 0)
291 def testOptionOrder(self
):
292 """Make sure you can intermix command name, arguments and
293 options in any order."""
294 self
.svnmerge("--log avail",
296 match
=r
"no integration info") # accepted
297 self
.svnmerge("-l avail",
299 match
=r
"no integration info") # accepted
300 self
.svnmerge("-r123 merge",
302 match
=r
"no integration info") # accepted
303 self
.svnmerge("-s -v -r92481 merge",
305 match
=r
"no integration info") # accepted
306 self
.svnmerge("--log merge",
308 match
=r
"option --log not recognized")
309 self
.svnmerge("--diff foobar", error
=True, match
=r
"foobar")
311 # This requires gnu_getopt support to be parsed
312 if hasattr(getopt
, "gnu_getopt"):
313 self
.svnmerge("-r123 merge . --log",
315 match
=r
"option --log not recognized")
319 return os
.environ
["TEMP"]
322 if os
.name
== "posix":
327 def onerror(func
, path
, excinfo
):
328 if func
in [os
.remove
, os
.rmdir
]:
329 if os
.path
.exists(path
):
330 os
.chmod(path
, stat
.S_IWRITE
)
333 if os
.path
.isdir(path
):
334 shutil
.rmtree(path
, onerror
=onerror
)
336 def get_template_path():
337 p
= os
.path
.join(temp_path(), "__svnmerge_test_template")
338 return os
.path
.abspath(p
)
341 p
= os
.path
.join(temp_path(), "__svnmerge_test")
342 return os
.path
.abspath(p
)
344 def abspath_to_url(path
):
345 assert path
== os
.path
.abspath(path
)
346 path
= path
.replace("\\", "/")
349 return "file://" + path
351 class TestCase_TestRepo(TestCase_SvnMerge
):
353 """Creates a working copy of a branch at r13 with the
354 following structure, containing revisions (3-6, 13):
361 ...from a repository with the following structure:
373 testYYY-branch/ 11 (renamed from testXXX-branch in 12)
377 test-branch/ 13 (copied from trunk@6)
383 self
.cwd
= os
.getcwd()
385 test_path
= get_test_path()
386 template_path
= get_template_path()
388 self
.template_path
= template_path
389 self
.test_path
= test_path
391 self
.template_repo_path
= os
.path
.join(template_path
, "repo")
392 self
.template_repo_url
= abspath_to_url(self
.template_repo_path
)
394 self
.test_repo_path
= os
.path
.join(test_path
, "repo")
395 self
.test_repo_url
= abspath_to_url(self
.test_repo_path
)
397 if not os
.path
.isdir(template_path
):
398 rmtree(template_path
)
399 os
.makedirs(template_path
)
400 os
.chdir(template_path
)
403 svnadmin create --fs-type fsfs %(TEMPLATE_REPO_PATH)s
404 svn mkdir -m "create /branches" %(TEMPLATE_REPO_URL)s/branches
405 svn mkdir -m "create /tags" %(TEMPLATE_REPO_URL)s/tags
406 svn mkdir -m "create /trunk" %(TEMPLATE_REPO_URL)s/trunk
407 svn co %(TEMPLATE_REPO_URL)s/trunk trunk
411 open("test1", "w").write("test 1")
412 open("test2", "w").write("test 2")
413 open("test3", "w").write("test 3")
414 open("test4", "w").write("test 4")
415 open("test5", "w").write("test 5")
419 svn ci -m "add test1"
421 svn ci -m "add test2"
423 svn ci -m "add test3"
424 svn mkdir -m "create /foobar" %(TEMPLATE_REPO_URL)s/foobar
425 svn rm -m "remove /foobar" %(TEMPLATE_REPO_URL)s/foobar
427 svn ci -m "add test4"
429 svn ci -m "add test5"
430 svn cp -r6 -m "create branch" %(TEMPLATE_REPO_URL)s/trunk %(TEMPLATE_REPO_URL)s/branches/testXXX-branch
431 svn mv -m "rename branch" %(TEMPLATE_REPO_URL)s/branches/testXXX-branch %(TEMPLATE_REPO_URL)s/branches/testYYY-branch
432 svn cp -r6 -m "create branch" %(TEMPLATE_REPO_URL)s/trunk %(TEMPLATE_REPO_URL)s/branches/test-branch
437 self
.launch("svn co %(TEMPLATE_REPO_URL)s/branches/test-branch")
441 rmtree(self
.test_path
)
442 shutil
.copytree(self
.template_path
, self
.test_path
)
443 os
.chdir(self
.test_path
)
445 # Relocate the test working copies from using the template
446 # repository to the test repository so the template repository
447 # is not affected by commits.
448 self
.launch("svn switch --relocate %(TEMPLATE_REPO_URL)s %(TEST_REPO_URL)s trunk test-branch")
450 os
.chdir("test-branch")
452 # Always remove the template directory when the tests have
454 atexit
.register(lambda: rmtree(template_path
))
458 rmtree(self
.test_path
)
460 def command_dict(self
):
462 "TEMPLATE_PATH": self
.template_path
,
463 "TEMPLATE_REPO_PATH": self
.template_repo_path
,
464 "TEMPLATE_REPO_URL": self
.template_repo_url
,
465 "TEST_PATH": self
.test_path
,
466 "TEST_REPO_PATH": self
.test_repo_path
,
467 "TEST_REPO_URL": self
.test_repo_url
,
470 def launch(self
, cmd
, *args
, **kwargs
):
471 cmd
= cmd
% self
.command_dict()
472 return TestCase_SvnMerge
.launch(self
, cmd
, *args
, **kwargs
)
474 def multilaunch(self
, cmds
):
475 for cmd
in cmds
.split("\n"):
478 svnmerge
.launch(cmd
% self
.command_dict())
481 self
.multilaunch("svn revert -R .")
483 def getproperty(self
):
484 out
= svnmerge
.launch("svn pg %s ." % svnmerge
.opts
["prop"])
488 return out
[0].strip()
490 def getBlockedProperty(self
):
491 out
= svnmerge
.launch("svn pg %s ." % svnmerge
.opts
["block-prop"])
495 return out
[0].strip()
500 self
.svnmerge("init", error
=True, match
=r
"working dir")
501 self
.svnmerge("avail", error
=True, match
=r
"working dir")
502 self
.svnmerge("integrated", error
=True, match
=r
"working dir")
503 self
.svnmerge("merge", error
=True, match
=r
"working dir")
504 self
.svnmerge("block", error
=True, match
=r
"working dir")
505 self
.svnmerge("unblock", error
=True, match
=r
"working dir")
507 def testCheckNoIntegrationInfo(self
):
508 self
.svnmerge("avail", error
=True, match
=r
"no integration")
509 self
.svnmerge("integrated", error
=True, match
=r
"no integration")
510 self
.svnmerge("merge", error
=True, match
=r
"no integration")
511 self
.svnmerge("block", error
=True, match
=r
"no integration")
512 self
.svnmerge("unblock", error
=True, match
=r
"no integration")
514 def testSelfReferentialInit(self
):
515 self
.svnmerge2(["init", self
.test_repo_url
+ "/branches/test-branch"],
516 error
=True, match
=r
"cannot init integration source")
518 def testBlocked(self
):
520 # Initialize svnmerge
521 self
.svnmerge("init")
522 self
.launch("svn commit -F svnmerge-commit-message.txt",
523 match
=r
"Committed revision")
525 # Block revisions that have already been merged
526 self
.svnmerge("block -r5", error
=True, match
=r
"no available revisions")
528 # Block phantom revisions
529 self
.svnmerge("block -r8", error
=True, match
=r
"no available revisions")
531 # Block available revisions
532 self
.svnmerge("block -r9", match
=r
"'svnmerge-blocked' set")
533 self
.launch("svn commit -F svnmerge-commit-message.txt",
534 match
=r
"Committed revision")
536 # Check that the revision is still available
537 self
.svnmerge("avail", match
=r
"\A10$")
539 # Check that the revision was blocked correctly
540 self
.svnmerge("avail -B", match
=r
"\A9$")
542 # Check that both revisions are available with avail -A
543 self
.svnmerge("avail -A", match
=r
"\A9-10$")
545 # Block all remaining revisions
546 self
.svnmerge("block", match
=r
"'svnmerge-blocked' set")
547 self
.launch("svn commit -F svnmerge-commit-message.txt",
548 match
=r
"Committed revision")
550 # Check that all revisions were blocked correctly
551 self
.svnmerge("avail -B", match
=r
"\A9-10$")
553 # Check that all revisions are available using avail -A
554 self
.svnmerge("avail -A", match
=r
"\A9-10$")
556 # Check that no revisions are available, now that they have
558 self
.svnmerge("avail", match
=r
"\A\Z")
560 # Unblock all revisions
561 self
.svnmerge("unblock", match
=r
"'svnmerge-blocked' deleted")
562 self
.launch("svn commit -F svnmerge-commit-message.txt",
563 match
=r
"Committed revision")
565 # Check that all revisions are available
566 self
.svnmerge("avail", match
=r
"\A9-10$")
567 self
.svnmerge("avail -A", match
=r
"\A9-10$")
569 # Check that no revisions are blocked
570 self
.svnmerge("avail -B", match
=r
"\A$")
572 def testTransitiveMerge(self
):
574 Test a merge of a change from testYYY-branch -> test-branch -> trunk
577 self
.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
580 self
.launch("svn up")
581 self
.svnmerge("init ../test-branch")
582 self
.launch('svn ci -m "init test-branch -> trunk"',
583 match
=r
"Committed revision 14")
585 os
.chdir("../test-branch")
586 self
.svnmerge("init -r 1-6 ../testYYY-branch")
587 self
.launch('svn ci -m "init testYYY-branch -> test-branch"',
588 match
=r
"Committed revision 15")
590 os
.chdir("../testYYY-branch")
591 open("test6", "w").write("test6")
592 self
.launch("svn add test6")
593 self
.launch('svn ci -m "add test6"',
594 match
=r
"Committed revision 16")
596 os
.chdir("../test-branch")
597 self
.svnmerge("merge -r 16")
598 self
.launch('svn ci -m "merge r16"',
599 match
=r
"Committed revision 17")
601 #os.chdir("../test-branch")
602 open("test5", "w").write("test5")
603 self
.launch("svn add test5")
604 self
.launch('svn ci -m "add test5"',
605 match
=r
"Committed revision 18")
608 self
.svnmerge("block -r 18")
609 self
.launch('svn ci -m "block r18"',
610 match
=r
"Committed revision 19")
612 #os.chdir("../trunk")
613 out
= self
.svnmerge("merge -r 17")
615 self
.launch('svn ci -m "merge r17 from test-branch"',
616 match
=r
"Committed revision 20")
618 p
= self
.getproperty()
619 self
.assertEqual(p
, '/branches/test-branch:1-13,17')
620 p
= self
.getBlockedProperty()
621 self
.assertEqual(p
, '/branches/test-branch:18')
623 def testTransitiveMergeWithBlock(self
):
625 Test a merge of a change from testYYY-branch -> test-branch -> trunk
628 self
.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
631 self
.launch("svn up")
632 self
.svnmerge("init ../test-branch")
633 self
.launch('svn ci -m "init test-branch -> trunk"',
634 match
=r
"Committed revision 14")
636 os
.chdir("../test-branch")
637 self
.svnmerge("init -r 1-6 ../testYYY-branch")
638 self
.launch('svn ci -m "init testYYY-branch -> test-branch"',
639 match
=r
"Committed revision 15")
641 os
.chdir("../testYYY-branch")
642 open("test4", "w").write("test4")
643 self
.launch("svn add test4")
644 self
.launch('svn ci -m "add test4"',
645 match
=r
"Committed revision 16")
647 os
.chdir("../test-branch")
648 self
.svnmerge("block -r 16")
649 self
.launch('svn ci -m "block r16"',
650 match
=r
"Committed revision 17")
652 #os.chdir("../test-branch")
653 open("test5", "w").write("test5")
654 self
.launch("svn add test5")
655 self
.launch('svn ci -m "add test5"',
656 match
=r
"Committed revision 18")
659 self
.svnmerge("block -r 18")
660 self
.launch('svn ci -m "block r18"',
661 match
=r
"Committed revision 19")
663 #os.chdir("../trunk")
664 self
.svnmerge("merge -r 17")
665 self
.launch('svn ci -m "merge r17 from test-branch"',
666 match
=r
"Committed revision 20")
668 p
= self
.getproperty()
669 self
.assertEqual(p
, '/branches/test-branch:1-13,17')
670 p
= self
.getBlockedProperty()
671 self
.assertEqual(p
, '/branches/test-branch:18')
674 self
.svnmerge("init")
675 p
= self
.getproperty()
676 self
.assertEqual("/trunk:1-6", p
)
678 self
.svnmerge("avail", match
=r
"\A9-10$")
679 self
.svnmerge("avail -v", match
=r
"phantom.*7-8")
681 self
.svnmerge("avail -B", match
=r
"\A$")
682 self
.svnmerge("avail -A", match
=r
"\A9-10$")
684 self
.svnmerge("avail --log", match
=r
"| r7.*| r8")
685 self
.svnmerge("avail --diff -r9", match
=r
"Index: test4")
687 self
.svnmerge("avail --log -r5", match
=r
"\A\Z")
688 self
.svnmerge("avail --diff -r5", match
=r
"\A\Z")
690 self
.svnmerge("integrated", match
=r
"^3-6$")
691 self
.svnmerge("integrated --log -r5", match
=r
"| r5 ")
692 self
.svnmerge("integrated --diff -r5", match
=r
"Index: test2")
694 def test_log_msg_suggest(self
):
695 self
.svnmerge("init -vf commit-log.txt", match
=r
"wrote commit message")
696 self
.assert_(os
.path
.exists("commit-log.txt"))
697 os
.remove("commit-log.txt")
699 def testInitForce(self
):
700 open("test1", "a").write("foo")
701 self
.svnmerge("init", error
=True, match
=r
"clean")
702 self
.svnmerge("init -F")
703 p
= self
.getproperty()
704 self
.assertEqual("/trunk:1-6", p
)
706 def testUninit(self
):
707 """Test that uninit works, for both merged and blocked revisions."""
709 self
.launch("svn co %(TEST_REPO_URL)s/branches/testYYY-branch testYYY-branch")
712 # Not using switch, so must update to get latest repository rev.
713 self
.launch("svn update", match
=r
"At revision 13")
714 self
.svnmerge2(["init", "-r1-13",
715 self
.test_repo_url
+ "/branches/test-branch"])
716 self
.launch("svn commit -F svnmerge-commit-message.txt",
717 match
=r
"Committed revision 14")
719 self
.svnmerge2(["init", self
.test_repo_url
+ "/branches/testYYY-branch"])
720 self
.launch("svn commit -F svnmerge-commit-message.txt",
721 match
=r
"Committed revision 15")
723 # Create changes on test-branch that we can block
725 os
.chdir("test-branch")
726 # Not using switch, so must update to get latest repository rev.
727 self
.launch("svn update", match
=r
"At revision 15")
729 open("test1", "w").write("test 1-changed_on_test-branch")
731 self
.launch("svn commit -m \"Change to test1 on test-branch\"",
732 match
=r
"Committed revision 16")
734 # Create changes on testYYY-branch that we can block
736 os
.chdir("testYYY-branch")
737 # Not using switch, so must update to get latest repository rev.
738 self
.launch("svn update", match
=r
"At revision 16")
740 open("test2", "w").write("test 2-changed_on_testYYY-branch")
742 self
.launch("svn commit -m \"Change to test2 on testYYY-branch\"",
743 match
=r
"Committed revision 17")
745 # Block changes from both branches on the trunk
748 # Not using switch, so must update to get latest repository rev.
749 self
.launch("svn update", match
=r
"At revision 17")
750 self
.svnmerge("block -S testYYY-branch", match
=r
"'svnmerge-blocked' set")
751 self
.launch("svn commit -F svnmerge-commit-message.txt",
752 match
=r
"Committed revision 18")
754 self
.svnmerge("block -S test-branch", match
=r
"'svnmerge-blocked' set")
755 self
.launch("svn commit -F svnmerge-commit-message.txt",
756 match
=r
"Committed revision 19")
759 self
.svnmerge2(["uninit", "--source", self
.test_repo_url
+ "/branches/testYYY-branch"])
761 # Check that the merged property for testYYY-branch was removed, but
762 # not for test-branch
763 pmerged
= self
.getproperty()
764 self
.assertEqual("/branches/test-branch:1-13", pmerged
)
766 # Check that the blocked property for testYYY-branch was removed, but
767 # not for test-branch
768 pblocked
= self
.getBlockedProperty()
769 self
.assertEqual("/branches/test-branch:16", pblocked
)
771 self
.launch("svn commit -F svnmerge-commit-message.txt",
772 match
=r
"Committed revision 20")
774 self
.svnmerge2(["uninit", "--source", self
.test_repo_url
+ "/branches/test-branch"])
776 # Check that the merged and blocked properties for test-branch have been removed too
777 pmerged
= self
.getproperty()
778 self
.assertEqual(None, pmerged
)
780 pblocked
= self
.getBlockedProperty()
781 self
.assertEqual(None, pblocked
)
783 def testUninitForce(self
):
784 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
786 self
.launch("svn commit -F svnmerge-commit-message.txt",
787 match
=r
"Committed revision")
789 self
.svnmerge2(["init", self
.test_repo_url
+ "/branches/testYYY-branch"])
791 self
.launch("svn commit -F svnmerge-commit-message.txt",
792 match
=r
"Committed revision")
794 p
= self
.getproperty()
795 self
.assert_(re
.search("/branches/testYYY-branch:1-\d+? /trunk:1-\d+?", p
))
797 open("test1", "a").write("foo")
799 self
.svnmerge("uninit --source " + self
.test_repo_url
+ "/branches/testYYY-branch",
800 error
=True, match
=r
"clean")
802 self
.svnmerge("uninit -F --source " + self
.test_repo_url
+ "/branches/testYYY-branch")
803 p
= self
.getproperty()
804 self
.assert_(re
.search("^/trunk:1-\d+", p
))
806 def testCheckNoCopyfrom(self
):
809 self
.svnmerge("init", error
=True, match
=r
"no copyfrom")
811 def testInitScenarios(self
):
812 """ Run various scenarios w/ svnmerge.py init and verify
813 the default values that are set as the integrated
816 # Run init with branch as merge source and trunk as merge target
819 self
.svnmerge("init ../test-branch")
820 # Verify range ends at rev in which branch was created
821 self
.launch("svn proplist -v", match
=r
":1-13")
824 # Run init with TRUNK as merge source and BRANCH as merge target
826 os
.chdir("test-branch")
827 self
.svnmerge("init ../trunk")
828 # Verify range ends at rev of trunk which was copied to create branch
829 self
.launch("svn proplist -v", match
=r
":1-6")
832 # Same thing, but with no explicit parameter (should work implicitly)
833 self
.svnmerge("init")
834 # Verify range ends at rev of trunk which was copied to create branch
835 self
.launch("svn proplist -v", match
=r
":1-6")
838 # Run init with TRUNK as merge src, & any other branch which is not
839 # a copy of trunk (or the source from which trunk was copied)
840 # as the merge target.
843 self
.launch('svn mkdir -m "create /other" %(TEST_REPO_URL)s/other') # creates r14
844 self
.launch("svn co %(TEST_REPO_URL)s/other")
846 self
.svnmerge("init ../trunk")
847 # Verify integrated range ends with merge source's latest rev as of
848 # the time of initialization:
849 self
.launch("svn proplist -v", match
=r
":1-14")
852 # Run init w/ explicit parms; verify them
853 self
.svnmerge("init -r 1-999 ../trunk")
854 self
.launch("svn proplist -v", match
=r
":1-999")
856 def testTrimmedAvailMerge(self
):
857 """Check that both avail and merge do not search for phantom revs too hard."""
858 self
.svnmerge("init")
859 self
.svnmerge("avail -vv -r8-9", match
=r
"svn --non-interactive log.*-r8:9")
860 self
.svnmerge("merge -F -vv -r8-9", match
=r
"svn --non-interactive log.*-r8:9")
861 self
.svnmerge("avail -vv -r2", nonmatch
=r
"svn log")
862 self
.svnmerge("integrated", match
=r
"^3-6,8-9$")
864 def testMergeRecordOnly(self
):
865 """Check that flagging revisions as manually merged works."""
866 self
.svnmerge("init")
867 self
.svnmerge("avail -vv -r9", match
=r
"svn --non-interactive log.*-r9:9")
868 self
.svnmerge("merge --record-only -F -vv -r9",
869 nonmatch
=r
"svn merge -r 8:9")
870 self
.svnmerge("avail -r9", match
=r
"\A$")
871 self
.svnmerge("integrated", match
=r
"^3-6,9$")
872 self
.svnmerge("integrated -r9", match
=r
"^9$")
874 def testBidirectionalMerges(self
):
875 """Check that reflected revisions are recognized properly for bidirectional merges."""
878 os
.chdir("test-branch")
880 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
881 self
.launch("svn commit -F svnmerge-commit-message.txt",
882 match
=r
"Committed revision 14")
883 os
.remove("svnmerge-commit-message.txt")
888 # Not using switch, so must update to get latest repository rev.
889 self
.launch("svn update", match
=r
"At revision 14")
890 self
.svnmerge2(["init", "-r1-14",
891 self
.test_repo_url
+ "/branches/test-branch"])
892 self
.launch("svn commit -F svnmerge-commit-message.txt",
893 match
=r
"Committed revision 15")
894 os
.remove("svnmerge-commit-message.txt")
896 open("test1", "w").write("test 1-changed_on_trunk")
898 self
.launch("svn commit -m \"Change to test1 on trunk\"",
899 match
=r
"Committed revision 16")
901 self
.svnmerge("integrated", match
=r
"^13-14$")
904 os
.chdir("test-branch")
906 # Not using switch, so must update to get latest repository rev.
907 self
.launch("svn update", match
=r
"At revision 16")
909 # test-branch was copied from trunk's r6. So non-phantom revs
910 # since that point should still be available to merge from
911 # trunk to test-branch:
912 self
.svnmerge("avail -vv --bidirectional", match
=r
"\n9-10,16$")
913 self
.svnmerge("merge -vv --bidirectional", match
=r
"svn --non-interactive merge --force -r 15:16")
914 p
= self
.getproperty()
915 self
.assertEqual("/trunk:1-16", p
)
916 self
.svnmerge("integrated", match
=r
"^3-16$")
918 self
.launch("svn commit -F svnmerge-commit-message.txt",
919 match
=r
"Committed revision 17")
920 os
.remove("svnmerge-commit-message.txt")
922 open("test1", "w").write("test 1-changed_on_branch_after_merge_from_trunk")
924 self
.launch("svn commit -m \"Change to test1 on branch\"",
925 match
=r
"Committed revision 18")
930 # Not using switch, so must update to get latest repository rev.
931 self
.launch("svn update", match
=r
"At revision 18")
933 # Ensure default is not to check for reflected revisions.
934 self
.svnmerge("avail -vv", match
=r
"\n17-18$")
936 # Now check reflected revision is excluded with --bidirectional flag.
937 self
.svnmerge("avail -vv --bidirectional", match
=r
"\n18$")
939 self
.svnmerge("merge -vv --bidirectional", match
=r
"svn --non-interactive merge --force -r 17:18")
940 p
= self
.getproperty()
941 self
.assertEqual("/branches/test-branch:1-18", p
)
943 self
.svnmerge("integrated", match
=r
"^13-18$")
945 def testBidirectionalMergesMultiBranch(self
):
946 """Check that merges from a second branch are not considered reflected for other branches."""
951 svn cp -m "Create test-branch2" %(TEST_REPO_URL)s/trunk %(TEST_REPO_URL)s/branches/test-branch2
952 svn co %(TEST_REPO_URL)s/branches/test-branch2 test-branch2
955 os
.chdir("test-branch")
957 self
.svnmerge2(["init", "-r1-13", self
.test_repo_url
+ "/trunk"])
958 self
.launch("svn commit -F svnmerge-commit-message.txt",
959 match
=r
"Committed revision 15")
960 os
.remove("svnmerge-commit-message.txt")
963 os
.chdir("test-branch2")
965 self
.svnmerge2(["init", "-r1-14", self
.test_repo_url
+ "/trunk"])
966 self
.launch("svn commit -F svnmerge-commit-message.txt",
967 match
=r
"Committed revision 16")
968 os
.remove("svnmerge-commit-message.txt")
973 # Not using switch, so must update to get latest repository rev.
974 self
.launch("svn update", match
=r
"At revision 16")
976 self
.svnmerge2(["init", "-r1-16",
977 self
.test_repo_url
+ "/branches/test-branch"])
978 self
.launch("svn commit -F svnmerge-commit-message.txt",
979 match
=r
"Committed revision 17")
980 os
.remove("svnmerge-commit-message.txt")
981 self
.svnmerge2(["init", "-r1-17",
982 self
.test_repo_url
+ "/branches/test-branch2"])
983 self
.launch("svn commit -F svnmerge-commit-message.txt",
984 match
=r
"Committed revision 18")
985 os
.remove("svnmerge-commit-message.txt")
988 os
.chdir("test-branch2")
990 open("test1", "w").write("test 1-changed_on_branch2")
992 self
.launch("svn commit -m \"Change to test1 on branch2\"",
993 match
=r
"Committed revision 19")
998 # Not using switch, so must update to get latest repository rev.
999 self
.launch("svn update", match
=r
"At revision 19")
1002 self
.svnmerge("merge -vv -S branch2",
1003 match
=r
"merge --force -r 18:19")
1004 p
= self
.getproperty()
1005 self
.assertEqual("/branches/test-branch:1-16 /branches/test-branch2:1-19", p
)
1007 self
.svnmerge("integrated -S branch2", match
=r
"^14-19$")
1008 self
.svnmerge("integrated -S ../test-branch", match
=r
"^13-16$")
1010 self
.launch("svn commit -F svnmerge-commit-message.txt",
1011 match
=r
"Committed revision 20")
1012 os
.remove("svnmerge-commit-message.txt")
1015 os
.chdir("test-branch")
1017 # Not using switch, so must update to get latest repository rev.
1018 self
.launch("svn update", match
=r
"At revision 20")
1020 # Initialized revs should not be available for merge
1021 self
.svnmerge("avail -v --bidirectional", match
=r
"initialized.*17-18")
1023 # Latest revision on trunk which was merged from test-branch2
1024 # should be available for test-branch with --bidirectional flag.
1025 self
.svnmerge("avail -vv --bidirectional", match
=r
"merged are:\n20$")
1027 self
.svnmerge("merge -vv --bidirectional", match
=r
"merge --force -r 19:20")
1028 p
= self
.getproperty()
1029 self
.assertEqual("/trunk:1-20", p
)
1031 self
.svnmerge("integrated", match
=r
"^3-20$")
1033 def testRollbackWithoutInit(self
):
1034 """Rollback should error out if invoked prior to init"""
1036 self
.svnmerge("rollback -vv -S ../trunk",
1038 match
= r
"no integration info available for path")
1040 def testRollbackOutsidePossibleRange(self
):
1041 """`svnmerge rollback' should error out if range contains revisions prior to
1042 SOURCE creation date."""
1044 # Initialize svnmerge
1045 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1046 self
.launch("svn commit -F svnmerge-commit-message.txt",
1047 match
= r
"Committed revision 14")
1048 os
.remove("svnmerge-commit-message.txt")
1050 expected_error
= r
"""Specified revision range falls out of the rollback range."""
1051 self
.svnmerge("rollback -vv -S ../trunk -r 2-14",
1053 match
= expected_error
)
1055 def testRollbackWithoutRevisionOpt(self
):
1056 """`svnmerge rollback' should error out if -r option is not given"""
1058 # Initialize svnmerge
1059 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1060 self
.launch("svn commit -F svnmerge-commit-message.txt",
1061 match
=r
"Committed revision 14")
1062 os
.remove("svnmerge-commit-message.txt")
1064 self
.svnmerge("rollback -vv -S ../trunk",
1066 match
= r
"The '-r' option is mandatory for rollback")
1068 def testInitAndRollbackRecordOnly(self
):
1069 """Init svnmerge, modify source head, merge, rollback --record-only."""
1071 # Initialize svnmerge
1072 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1073 self
.launch("svn commit -F svnmerge-commit-message.txt",
1074 match
= r
"Committed revision 14")
1075 os
.remove("svnmerge-commit-message.txt")
1077 # Rollback record-only
1078 expected_output
= r
"property 'svnmerge-integrated' set on '.'"
1079 detested_output
= r
"""
1082 self
.svnmerge("rollback -vv --record-only -S ../trunk -r5-7",
1083 match
= expected_output
,
1084 nonmatch
= detested_output
)
1086 def testInitAndRollback(self
):
1087 """Init svnmerge, modify source head, merge, rollback."""
1089 # Initialize svnmerge
1090 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1091 self
.launch("svn commit -F svnmerge-commit-message.txt",
1092 match
= r
"Committed revision 14")
1093 os
.remove("svnmerge-commit-message.txt")
1095 # Svnmerge rollback r5-7
1096 expected_output
= "D\s+test2\s+D\s+test3"
1097 self
.svnmerge("rollback -vv -S ../trunk -r5-7",
1098 match
= expected_output
)
1100 def testMergeAndRollbackEmptyRevisionRange(self
):
1101 """Init svnmerge, modify source head, merge, rollback where no merge
1104 # Initialize svnmerge
1105 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1106 self
.launch("svn commit -F svnmerge-commit-message.txt",
1107 match
= r
"Committed revision 14")
1108 os
.remove("svnmerge-commit-message.txt")
1110 # Make changes to trunk
1111 os
.chdir("../trunk")
1112 open("newfile", "w").close()
1113 self
.launch("svn add newfile")
1114 self
.launch('svn commit -m "Adding newfile"', match
=r
"Committed revision 15")
1115 open("anothernewfile", "w").close()
1116 self
.launch("svn add anothernewfile")
1117 self
.launch('svn commit -m "Adding anothernewfile"', match
=r
"Committed revision 16")
1119 # Svnmerge block r15,16
1120 os
.chdir("../test-branch")
1121 self
.launch("svn up ..",
1123 self
.svnmerge("block -r 15,16 -S ../trunk")
1124 self
.launch("svn commit -F svnmerge-commit-message.txt",
1125 match
= r
"Committed revision 17")
1126 self
.svnmerge("merge -S ../trunk")
1127 self
.launch("svn commit -F svnmerge-commit-message.txt")
1129 # Svnmerge rollback r15-16
1130 self
.svnmerge("rollback -vv -S ../trunk -r15-16",
1132 match
= r
"Nothing to rollback in revision range r15-16")
1134 def testMergeAndRollback(self
):
1135 """Init svnmerge, modify source head, merge, rollback."""
1137 # Initialize svnmerge
1138 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1139 self
.launch("svn commit -F svnmerge-commit-message.txt",
1140 match
= r
"Committed revision 14")
1141 os
.remove("svnmerge-commit-message.txt")
1143 # Make changes to trunk
1144 os
.chdir("../trunk")
1145 open("newfile", "w").close()
1146 self
.launch("svn add newfile")
1147 self
.launch('svn commit -m "Adding newfile"', match
=r
"Committed revision 15")
1149 # Svnmerge merge r15
1150 os
.chdir("../test-branch")
1151 self
.svnmerge("merge -r 15 -S ../trunk")
1152 self
.launch("svn commit -F svnmerge-commit-message.txt",
1153 match
= r
"Committed revision 16")
1155 # Svnmerge rollback r15
1156 self
.svnmerge("rollback -vv -S ../trunk -r15",
1157 match
= r
"-r 15:14")
1159 def testBlockMergeAndRollback(self
):
1160 """Init svnmerge, block, modify head, merge, rollback."""
1162 # Initialize svnmerge
1163 self
.svnmerge2(["init", self
.test_repo_url
+ "/trunk"])
1164 self
.launch("svn commit -F svnmerge-commit-message.txt",
1165 match
= r
"Committed revision 14")
1166 os
.remove("svnmerge-commit-message.txt")
1168 # Make changes to trunk
1169 os
.chdir("../trunk")
1170 open("newfile", "w").close()
1171 self
.launch("svn add newfile")
1172 self
.launch('svn commit -m "Adding newfile"', match
=r
"Committed revision 15")
1173 open("anothernewfile", "w").close()
1174 self
.launch("svn add anothernewfile")
1175 self
.launch('svn commit -m "Adding anothernewfile"', match
=r
"Committed revision 16")
1177 # Svnmerge block r16, merge r15
1178 os
.chdir("../test-branch")
1179 self
.launch("svn up ..",
1181 self
.svnmerge("block -r 16 -S ../trunk")
1182 self
.launch("svn commit -F svnmerge-commit-message.txt",
1183 match
= r
"Committed revision 17")
1184 self
.svnmerge("merge -S ../trunk",
1185 nonmatch
= r
"A anothernewfile",
1186 match
= r
"A newfile")
1187 self
.launch("svn commit -F svnmerge-commit-message.txt",
1188 match
= r
"Committed revision 18")
1190 # Svnmerge rollback revision range 15-18 (in effect only 15,17)
1191 self
.svnmerge("rollback -vv -S ../trunk -r15-18",
1192 nonmatch
= r
"D anothernewfile")
1194 def testMergeWithPotentialPropertyConflict(self
):
1195 """Init branch B, merge changes from branch A to branch B,
1196 init branch C, and attempt a merge of changes from branch B to
1199 # Initialize merge info for test-branch.
1200 self
.svnmerge2(["init", "-r 3-6", self
.test_repo_url
+ "/trunk"])
1201 self
.launch("svn commit -F svnmerge-commit-message.txt",
1202 match
= r
"Committed revision 14")
1203 os
.remove("svnmerge-commit-message.txt")
1205 # Make a change to trunk.
1206 os
.chdir("../trunk")
1207 open("newfile", "w").close()
1208 self
.launch("svn add newfile")
1209 self
.launch('svn commit -m "Adding newfile"',
1210 match
=r
"Committed revision 15")
1212 # Merge a change from trunk to test-branch.
1213 os
.chdir("../test-branch")
1214 self
.svnmerge("merge -r 15 -S ../trunk")
1215 self
.launch("svn commit -F svnmerge-commit-message.txt",
1216 match
=r
"Committed revision 16")
1218 # Get a WC for testYYY-branch.
1220 self
.launch("svn co %s/branches/testYYY-branch" % self
.test_repo_url
)
1221 os
.chdir("testYYY-branch")
1223 # Initialize merge info for testYYY-branch.
1224 self
.svnmerge2(["init", "-r 13",
1225 self
.test_repo_url
+ "/branches/test-branch"])
1226 self
.launch("svn commit -F svnmerge-commit-message.txt",
1227 match
=r
"Committed revision 17")
1228 os
.remove("svnmerge-commit-message.txt")
1230 ### FIXME: Unfortunately, we're getting a conflict for the
1231 ### merge info property in the following svnmerge invocation
1232 ### due to Subversion's (reasonable) lack of property value
1233 ### merging (which isn't possible without knowing a property's
1236 # Attempt a merge of changes from test-branch to
1238 self
.svnmerge("merge -r 16 -S ../test-branch")
1240 self
.launch("svn commit -F svnmerge-commit-message.txt",
1241 match
=r
"Committed revision 18")
1242 except AssertionError:
1243 self
.assert_(os
.path
.isfile("dir_conflicts.prej"))
1245 if __name__
== "__main__":
1246 # If an existing template repository and working copy for testing
1247 # exists, then always remove it. This prevents any problems if
1248 # this test suite is modified and there exists an older template
1249 # directory that may be used. This will also prevent problems if
1250 # in a previous run of this script, the template was being created
1251 # when the script was canceled, leaving it in an inconsistent
1253 template_path
= get_template_path()
1254 if os
.path
.exists(template_path
):
1255 rmtree(template_path
)