104
115
f = self.reopen_file()
118
def test_add_unicode_content(self):
119
# unicode content is not permitted in versioned files.
120
# versioned files version sequences of bytes only.
122
self.assertRaises(errors.BzrBadParameterUnicode,
123
vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
125
(errors.BzrBadParameterUnicode, NotImplementedError),
126
vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
128
def test_inline_newline_throws(self):
129
# \r characters are not permitted in lines being added
131
self.assertRaises(errors.BzrBadParameterContainsNewline,
132
vf.add_lines, 'a', [], ['a\n\n'])
134
(errors.BzrBadParameterContainsNewline, NotImplementedError),
135
vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
136
# but inline CR's are allowed
137
vf.add_lines('a', [], ['a\r\n'])
139
vf.add_lines_with_ghosts('b', [], ['a\r\n'])
140
except NotImplementedError:
143
def test_add_reserved(self):
145
self.assertRaises(errors.ReservedId,
146
vf.add_lines, 'a:', [], ['a\n', 'b\n', 'c\n'])
148
self.assertRaises(errors.ReservedId,
149
vf.add_delta, 'a:', [], None, 'sha1', False, ((0, 0, 0, []),))
151
def test_get_reserved(self):
153
self.assertRaises(errors.ReservedId, vf.get_delta, 'b:')
154
self.assertRaises(errors.ReservedId, vf.get_texts, ['b:'])
155
self.assertRaises(errors.ReservedId, vf.get_lines, 'b:')
156
self.assertRaises(errors.ReservedId, vf.get_text, 'b:')
107
158
def test_get_delta(self):
108
159
f = self.get_file()
109
160
sha1s = self._setup_for_deltas(f)
391
442
# and should be a list
392
443
self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
445
def build_graph(self, file, graph):
446
for node in topo_sort(graph.items()):
447
file.add_lines(node, graph[node], [])
394
449
def test_get_graph(self):
395
450
f = self.get_file()
396
f.add_lines('v1', [], ['hello\n'])
397
f.add_lines('v2', ['v1'], ['hello\n', 'world\n'])
398
f.add_lines('v3', ['v2'], ['hello\n', 'cruel\n', 'world\n'])
399
self.assertEqual({'v1': [],
455
self.build_graph(f, graph)
456
self.assertEqual(graph, f.get_graph())
458
def test_get_graph_partial(self):
466
complex_graph.update(simple_a)
471
complex_graph.update(simple_b)
478
complex_graph.update(simple_gam)
480
simple_b_gam.update(simple_gam)
481
simple_b_gam.update(simple_b)
482
self.build_graph(f, complex_graph)
483
self.assertEqual(simple_a, f.get_graph(['a']))
484
self.assertEqual(simple_b, f.get_graph(['b']))
485
self.assertEqual(simple_gam, f.get_graph(['gam']))
486
self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
404
488
def test_get_parents(self):
405
489
f = self.get_file()
499
594
'otherchild\n':0,
596
progress = InstrumentedProgress()
501
597
# iterate over the lines
502
for line in vf.iter_lines_added_or_present_in_versions(versions):
598
for line in vf.iter_lines_added_or_present_in_versions(versions,
601
if []!= progress.updates:
602
self.assertEqual(expected, progress.updates)
505
lines = iter_with_versions(['child', 'otherchild'])
604
lines = iter_with_versions(['child', 'otherchild'],
605
[('Walking content.', 0, 2),
606
('Walking content.', 1, 2),
607
('Walking content.', 2, 2)])
506
608
# we must see child and otherchild
507
609
self.assertTrue(lines['child\n'] > 0)
508
610
self.assertTrue(lines['otherchild\n'] > 0)
509
611
# we dont care if we got more than that.
512
lines = iter_with_versions(None)
614
lines = iter_with_versions(None, [('Walking content.', 0, 5),
615
('Walking content.', 1, 5),
616
('Walking content.', 2, 5),
617
('Walking content.', 3, 5),
618
('Walking content.', 4, 5),
619
('Walking content.', 5, 5)])
513
620
# all lines must be seen at least once
514
621
self.assertTrue(lines['base\n'] > 0)
515
622
self.assertTrue(lines['lancestor\n'] > 0)
783
908
versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
909
# now we should get the default InterVersionedFile object again.
785
910
self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
913
class TestReadonlyHttpMixin(object):
915
def test_readonly_http_works(self):
916
# we should be able to read from http with a versioned file.
918
# try an empty file access
919
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
920
self.assertEqual([], readonly_vf.versions())
922
vf.add_lines('1', [], ['a\n'])
923
vf.add_lines('2', ['1'], ['b\n', 'a\n'])
924
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
925
self.assertEqual(['1', '2'], vf.versions())
926
for version in readonly_vf.versions():
927
readonly_vf.get_lines(version)
930
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
933
return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
935
def get_factory(self):
939
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
942
return KnitVersionedFile('foo', get_transport(self.get_url('.')),
943
delta=True, create=True)
945
def get_factory(self):
946
return KnitVersionedFile
949
class MergeCasesMixin(object):
951
def doMerge(self, base, a, b, mp):
952
from cStringIO import StringIO
953
from textwrap import dedent
959
w.add_lines('text0', [], map(addcrlf, base))
960
w.add_lines('text1', ['text0'], map(addcrlf, a))
961
w.add_lines('text2', ['text0'], map(addcrlf, b))
965
self.log('merge plan:')
966
p = list(w.plan_merge('text1', 'text2'))
967
for state, line in p:
969
self.log('%12s | %s' % (state, line[:-1]))
973
mt.writelines(w.weave_merge(p))
975
self.log(mt.getvalue())
977
mp = map(addcrlf, mp)
978
self.assertEqual(mt.readlines(), mp)
981
def testOneInsert(self):
987
def testSeparateInserts(self):
988
self.doMerge(['aaa', 'bbb', 'ccc'],
989
['aaa', 'xxx', 'bbb', 'ccc'],
990
['aaa', 'bbb', 'yyy', 'ccc'],
991
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
993
def testSameInsert(self):
994
self.doMerge(['aaa', 'bbb', 'ccc'],
995
['aaa', 'xxx', 'bbb', 'ccc'],
996
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
997
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
998
overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
999
def testOverlappedInsert(self):
1000
self.doMerge(['aaa', 'bbb'],
1001
['aaa', 'xxx', 'yyy', 'bbb'],
1002
['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
1004
# really it ought to reduce this to
1005
# ['aaa', 'xxx', 'yyy', 'bbb']
1008
def testClashReplace(self):
1009
self.doMerge(['aaa'],
1012
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
1015
def testNonClashInsert1(self):
1016
self.doMerge(['aaa'],
1019
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
1022
def testNonClashInsert2(self):
1023
self.doMerge(['aaa'],
1029
def testDeleteAndModify(self):
1030
"""Clashing delete and modification.
1032
If one side modifies a region and the other deletes it then
1033
there should be a conflict with one side blank.
1036
#######################################
1037
# skippd, not working yet
1040
self.doMerge(['aaa', 'bbb', 'ccc'],
1041
['aaa', 'ddd', 'ccc'],
1043
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
1045
def _test_merge_from_strings(self, base, a, b, expected):
1047
w.add_lines('text0', [], base.splitlines(True))
1048
w.add_lines('text1', ['text0'], a.splitlines(True))
1049
w.add_lines('text2', ['text0'], b.splitlines(True))
1050
self.log('merge plan:')
1051
p = list(w.plan_merge('text1', 'text2'))
1052
for state, line in p:
1054
self.log('%12s | %s' % (state, line[:-1]))
1055
self.log('merge result:')
1056
result_text = ''.join(w.weave_merge(p))
1057
self.log(result_text)
1058
self.assertEqualDiff(result_text, expected)
1060
def test_weave_merge_conflicts(self):
1061
# does weave merge properly handle plans that end with unchanged?
1062
result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1063
self.assertEqual(result, 'hello\n')
1065
def test_deletion_extended(self):
1066
"""One side deletes, the other deletes more.
1083
self._test_merge_from_strings(base, a, b, result)
1085
def test_deletion_overlap(self):
1086
"""Delete overlapping regions with no other conflict.
1088
Arguably it'd be better to treat these as agreement, rather than
1089
conflict, but for now conflict is safer.
1117
self._test_merge_from_strings(base, a, b, result)
1119
def test_agreement_deletion(self):
1120
"""Agree to delete some lines, without conflicts."""
1142
self._test_merge_from_strings(base, a, b, result)
1144
def test_sync_on_deletion(self):
1145
"""Specific case of merge where we can synchronize incorrectly.
1147
A previous version of the weave merge concluded that the two versions
1148
agreed on deleting line 2, and this could be a synchronization point.
1149
Line 1 was then considered in isolation, and thought to be deleted on
1152
It's better to consider the whole thing as a disagreement region.
1163
a's replacement line 2
1176
a's replacement line 2
1183
self._test_merge_from_strings(base, a, b, result)
1186
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
1188
def get_file(self, name='foo'):
1189
return KnitVersionedFile(name, get_transport(self.get_url('.')),
1190
delta=True, create=True)
1192
def log_contents(self, w):
1196
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
1198
def get_file(self, name='foo'):
1199
return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1201
def log_contents(self, w):
1202
self.log('weave is:')
1204
write_weave(w, tmpf)
1205
self.log(tmpf.getvalue())
1207
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1208
'xxx', '>>>>>>> ', 'bbb']