104
112
f = self.reopen_file()
115
def test_add_unicode_content(self):
116
# unicode content is not permitted in versioned files.
117
# versioned files version sequences of bytes only.
119
self.assertRaises(errors.BzrBadParameterUnicode,
120
vf.add_lines, 'a', [], ['a\n', u'b\n', 'c\n'])
122
(errors.BzrBadParameterUnicode, NotImplementedError),
123
vf.add_lines_with_ghosts, 'a', [], ['a\n', u'b\n', 'c\n'])
125
def test_inline_newline_throws(self):
126
# \r characters are not permitted in lines being added
128
self.assertRaises(errors.BzrBadParameterContainsNewline,
129
vf.add_lines, 'a', [], ['a\n\n'])
131
(errors.BzrBadParameterContainsNewline, NotImplementedError),
132
vf.add_lines_with_ghosts, 'a', [], ['a\n\n'])
133
# but inline CR's are allowed
134
vf.add_lines('a', [], ['a\r\n'])
136
vf.add_lines_with_ghosts('b', [], ['a\r\n'])
137
except NotImplementedError:
107
140
def test_get_delta(self):
108
141
f = self.get_file()
109
142
sha1s = self._setup_for_deltas(f)
391
424
# and should be a list
392
425
self.assertTrue(isinstance(f.__class__.get_suffixes(), list))
427
def build_graph(self, file, graph):
428
for node in topo_sort(graph.items()):
429
file.add_lines(node, graph[node], [])
394
431
def test_get_graph(self):
395
432
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': [],
437
self.build_graph(f, graph)
438
self.assertEqual(graph, f.get_graph())
440
def test_get_graph_partial(self):
448
complex_graph.update(simple_a)
453
complex_graph.update(simple_b)
460
complex_graph.update(simple_gam)
462
simple_b_gam.update(simple_gam)
463
simple_b_gam.update(simple_b)
464
self.build_graph(f, complex_graph)
465
self.assertEqual(simple_a, f.get_graph(['a']))
466
self.assertEqual(simple_b, f.get_graph(['b']))
467
self.assertEqual(simple_gam, f.get_graph(['gam']))
468
self.assertEqual(simple_b_gam, f.get_graph(['b', 'gam']))
404
470
def test_get_parents(self):
405
471
f = self.get_file()
783
866
versionedfile.InterVersionedFile.unregister_optimiser(InterString)
784
867
# now we should get the default InterVersionedFile object again.
785
868
self.assertGetsDefaultInterVersionedFile(dummy_a, dummy_b)
871
class TestReadonlyHttpMixin(object):
873
def test_readonly_http_works(self):
874
# we should be able to read from http with a versioned file.
876
# try an empty file access
877
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
878
self.assertEqual([], readonly_vf.versions())
880
vf.add_lines('1', [], ['a\n'])
881
vf.add_lines('2', ['1'], ['b\n', 'a\n'])
882
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
883
self.assertEqual(['1', '2'], vf.versions())
884
for version in readonly_vf.versions():
885
readonly_vf.get_lines(version)
888
class TestWeaveHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
891
return WeaveFile('foo', get_transport(self.get_url('.')), create=True)
893
def get_factory(self):
897
class TestKnitHTTP(TestCaseWithWebserver, TestReadonlyHttpMixin):
900
return KnitVersionedFile('foo', get_transport(self.get_url('.')),
901
delta=True, create=True)
903
def get_factory(self):
904
return KnitVersionedFile
907
class MergeCasesMixin(object):
909
def doMerge(self, base, a, b, mp):
910
from cStringIO import StringIO
911
from textwrap import dedent
917
w.add_lines('text0', [], map(addcrlf, base))
918
w.add_lines('text1', ['text0'], map(addcrlf, a))
919
w.add_lines('text2', ['text0'], map(addcrlf, b))
923
self.log('merge plan:')
924
p = list(w.plan_merge('text1', 'text2'))
925
for state, line in p:
927
self.log('%12s | %s' % (state, line[:-1]))
931
mt.writelines(w.weave_merge(p))
933
self.log(mt.getvalue())
935
mp = map(addcrlf, mp)
936
self.assertEqual(mt.readlines(), mp)
939
def testOneInsert(self):
945
def testSeparateInserts(self):
946
self.doMerge(['aaa', 'bbb', 'ccc'],
947
['aaa', 'xxx', 'bbb', 'ccc'],
948
['aaa', 'bbb', 'yyy', 'ccc'],
949
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
951
def testSameInsert(self):
952
self.doMerge(['aaa', 'bbb', 'ccc'],
953
['aaa', 'xxx', 'bbb', 'ccc'],
954
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'],
955
['aaa', 'xxx', 'bbb', 'yyy', 'ccc'])
956
overlappedInsertExpected = ['aaa', 'xxx', 'yyy', 'bbb']
957
def testOverlappedInsert(self):
958
self.doMerge(['aaa', 'bbb'],
959
['aaa', 'xxx', 'yyy', 'bbb'],
960
['aaa', 'xxx', 'bbb'], self.overlappedInsertExpected)
962
# really it ought to reduce this to
963
# ['aaa', 'xxx', 'yyy', 'bbb']
966
def testClashReplace(self):
967
self.doMerge(['aaa'],
970
['<<<<<<< ', 'xxx', '=======', 'yyy', 'zzz',
973
def testNonClashInsert1(self):
974
self.doMerge(['aaa'],
977
['<<<<<<< ', 'xxx', 'aaa', '=======', 'yyy', 'zzz',
980
def testNonClashInsert2(self):
981
self.doMerge(['aaa'],
987
def testDeleteAndModify(self):
988
"""Clashing delete and modification.
990
If one side modifies a region and the other deletes it then
991
there should be a conflict with one side blank.
994
#######################################
995
# skippd, not working yet
998
self.doMerge(['aaa', 'bbb', 'ccc'],
999
['aaa', 'ddd', 'ccc'],
1001
['<<<<<<<< ', 'aaa', '=======', '>>>>>>> ', 'ccc'])
1003
def _test_merge_from_strings(self, base, a, b, expected):
1005
w.add_lines('text0', [], base.splitlines(True))
1006
w.add_lines('text1', ['text0'], a.splitlines(True))
1007
w.add_lines('text2', ['text0'], b.splitlines(True))
1008
self.log('merge plan:')
1009
p = list(w.plan_merge('text1', 'text2'))
1010
for state, line in p:
1012
self.log('%12s | %s' % (state, line[:-1]))
1013
self.log('merge result:')
1014
result_text = ''.join(w.weave_merge(p))
1015
self.log(result_text)
1016
self.assertEqualDiff(result_text, expected)
1018
def test_weave_merge_conflicts(self):
1019
# does weave merge properly handle plans that end with unchanged?
1020
result = ''.join(self.get_file().weave_merge([('new-a', 'hello\n')]))
1021
self.assertEqual(result, 'hello\n')
1023
def test_deletion_extended(self):
1024
"""One side deletes, the other deletes more.
1041
self._test_merge_from_strings(base, a, b, result)
1043
def test_deletion_overlap(self):
1044
"""Delete overlapping regions with no other conflict.
1046
Arguably it'd be better to treat these as agreement, rather than
1047
conflict, but for now conflict is safer.
1075
self._test_merge_from_strings(base, a, b, result)
1077
def test_agreement_deletion(self):
1078
"""Agree to delete some lines, without conflicts."""
1100
self._test_merge_from_strings(base, a, b, result)
1102
def test_sync_on_deletion(self):
1103
"""Specific case of merge where we can synchronize incorrectly.
1105
A previous version of the weave merge concluded that the two versions
1106
agreed on deleting line 2, and this could be a synchronization point.
1107
Line 1 was then considered in isolation, and thought to be deleted on
1110
It's better to consider the whole thing as a disagreement region.
1121
a's replacement line 2
1134
a's replacement line 2
1141
self._test_merge_from_strings(base, a, b, result)
1144
class TestKnitMerge(TestCaseWithTransport, MergeCasesMixin):
1146
def get_file(self, name='foo'):
1147
return KnitVersionedFile(name, get_transport(self.get_url('.')),
1148
delta=True, create=True)
1150
def log_contents(self, w):
1154
class TestWeaveMerge(TestCaseWithTransport, MergeCasesMixin):
1156
def get_file(self, name='foo'):
1157
return WeaveFile(name, get_transport(self.get_url('.')), create=True)
1159
def log_contents(self, w):
1160
self.log('weave is:')
1162
write_weave(w, tmpf)
1163
self.log(tmpf.getvalue())
1165
overlappedInsertExpected = ['aaa', '<<<<<<< ', 'xxx', 'yyy', '=======',
1166
'xxx', '>>>>>>> ', 'bbb']