868
865
b'.*a-file(.|\n)*b-file')
871
class TestPatienceDiffLib(tests.TestCase):
874
super(TestPatienceDiffLib, self).setUp()
875
self._unique_lcs = _patiencediff_py.unique_lcs_py
876
self._recurse_matches = _patiencediff_py.recurse_matches_py
877
self._PatienceSequenceMatcher = \
878
_patiencediff_py.PatienceSequenceMatcher_py
880
def test_diff_unicode_string(self):
881
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
882
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
883
sm = self._PatienceSequenceMatcher(None, a, b)
884
mb = sm.get_matching_blocks()
885
self.assertEqual(35, len(mb))
887
def test_unique_lcs(self):
888
unique_lcs = self._unique_lcs
889
self.assertEqual(unique_lcs('', ''), [])
890
self.assertEqual(unique_lcs('', 'a'), [])
891
self.assertEqual(unique_lcs('a', ''), [])
892
self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
893
self.assertEqual(unique_lcs('a', 'b'), [])
894
self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
895
self.assertEqual(unique_lcs('abcde', 'cdeab'),
896
[(2, 0), (3, 1), (4, 2)])
897
self.assertEqual(unique_lcs('cdeab', 'abcde'),
898
[(0, 2), (1, 3), (2, 4)])
899
self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
901
self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
903
def test_recurse_matches(self):
904
def test_one(a, b, matches):
906
self._recurse_matches(
907
a, b, 0, 0, len(a), len(b), test_matches, 10)
908
self.assertEqual(test_matches, matches)
910
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
911
[(0, 0), (2, 2), (4, 4)])
912
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
913
[(0, 0), (2, 1), (4, 2)])
914
# Even though 'bc' is not unique globally, and is surrounded by
915
# non-matching lines, we should still match, because they are locally
917
test_one('abcdbce', 'afbcgdbce', [(0, 0), (1, 2), (2, 3), (3, 5),
918
(4, 6), (5, 7), (6, 8)])
920
# recurse_matches doesn't match non-unique
921
# lines surrounded by bogus text.
922
# The update has been done in patiencediff.SequenceMatcher instead
924
# This is what it could be
925
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
927
# This is what it currently gives:
928
test_one('aBccDe', 'abccde', [(0, 0), (5, 5)])
930
def assertDiffBlocks(self, a, b, expected_blocks):
931
"""Check that the sequence matcher returns the correct blocks.
933
:param a: A sequence to match
934
:param b: Another sequence to match
935
:param expected_blocks: The expected output, not including the final
936
matching block (len(a), len(b), 0)
938
matcher = self._PatienceSequenceMatcher(None, a, b)
939
blocks = matcher.get_matching_blocks()
941
self.assertEqual((len(a), len(b), 0), last)
942
self.assertEqual(expected_blocks, blocks)
944
def test_matching_blocks(self):
945
# Some basic matching tests
946
self.assertDiffBlocks('', '', [])
947
self.assertDiffBlocks([], [], [])
948
self.assertDiffBlocks('abc', '', [])
949
self.assertDiffBlocks('', 'abc', [])
950
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
951
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
952
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
953
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
954
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
955
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
956
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
957
# This may check too much, but it checks to see that
958
# a copied block stays attached to the previous section,
960
# difflib would tend to grab the trailing longest match
961
# which would make the diff not look right
962
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
963
[(0, 0, 6), (6, 11, 10)])
965
# make sure it supports passing in lists
966
self.assertDiffBlocks(
969
'how are you today?\n'],
971
'how are you today?\n'],
972
[(0, 0, 1), (2, 1, 1)])
974
# non unique lines surrounded by non-matching lines
976
self.assertDiffBlocks('aBccDe', 'abccde', [(0, 0, 1), (5, 5, 1)])
978
# But they only need to be locally unique
979
self.assertDiffBlocks('aBcDec', 'abcdec', [
980
(0, 0, 1), (2, 2, 1), (4, 4, 2)])
982
# non unique blocks won't be matched
983
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0, 0, 1), (8, 8, 1)])
985
# but locally unique ones will
986
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0, 0, 1), (2, 2, 2),
987
(5, 4, 1), (7, 5, 2), (10, 8, 1)])
989
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7, 7, 1)])
990
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
991
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
993
def test_matching_blocks_tuples(self):
994
# Some basic matching tests
995
self.assertDiffBlocks([], [], [])
996
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
997
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
998
self.assertDiffBlocks([('a',), ('b',), ('c,')],
999
[('a',), ('b',), ('c,')],
1001
self.assertDiffBlocks([('a',), ('b',), ('c,')],
1002
[('a',), ('b',), ('d,')],
1004
self.assertDiffBlocks([('d',), ('b',), ('c,')],
1005
[('a',), ('b',), ('c,')],
1007
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
1008
[('a',), ('b',), ('c,')],
1010
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
1011
[('a', 'b'), ('c', 'X'), ('e', 'f')],
1012
[(0, 0, 1), (2, 2, 1)])
1013
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
1014
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
1015
[(0, 0, 1), (2, 2, 1)])
1017
def test_opcodes(self):
1018
def chk_ops(a, b, expected_codes):
1019
s = self._PatienceSequenceMatcher(None, a, b)
1020
self.assertEqual(expected_codes, s.get_opcodes())
1024
chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
1025
chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
1026
chk_ops('abcd', 'abcd', [('equal', 0, 4, 0, 4)])
1027
chk_ops('abcd', 'abce', [('equal', 0, 3, 0, 3),
1028
('replace', 3, 4, 3, 4)
1030
chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
1031
('equal', 1, 4, 0, 3),
1032
('insert', 4, 4, 3, 4)
1034
chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
1035
('equal', 1, 5, 0, 4)
1037
chk_ops('abcde', 'abXde', [('equal', 0, 2, 0, 2),
1038
('replace', 2, 3, 2, 3),
1039
('equal', 3, 5, 3, 5)
1041
chk_ops('abcde', 'abXYZde', [('equal', 0, 2, 0, 2),
1042
('replace', 2, 3, 2, 5),
1043
('equal', 3, 5, 5, 7)
1045
chk_ops('abde', 'abXYZde', [('equal', 0, 2, 0, 2),
1046
('insert', 2, 2, 2, 5),
1047
('equal', 2, 4, 5, 7)
1049
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1050
[('equal', 0, 6, 0, 6),
1051
('insert', 6, 6, 6, 11),
1052
('equal', 6, 16, 11, 21)
1055
['hello there\n', 'world\n', 'how are you today?\n'],
1056
['hello there\n', 'how are you today?\n'],
1057
[('equal', 0, 1, 0, 1),
1058
('delete', 1, 2, 1, 1),
1059
('equal', 2, 3, 1, 2),
1061
chk_ops('aBccDe', 'abccde',
1062
[('equal', 0, 1, 0, 1),
1063
('replace', 1, 5, 1, 5),
1064
('equal', 5, 6, 5, 6),
1066
chk_ops('aBcDec', 'abcdec',
1067
[('equal', 0, 1, 0, 1),
1068
('replace', 1, 2, 1, 2),
1069
('equal', 2, 3, 2, 3),
1070
('replace', 3, 4, 3, 4),
1071
('equal', 4, 6, 4, 6),
1073
chk_ops('aBcdEcdFg', 'abcdecdfg',
1074
[('equal', 0, 1, 0, 1),
1075
('replace', 1, 8, 1, 8),
1076
('equal', 8, 9, 8, 9)
1078
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1079
[('equal', 0, 1, 0, 1),
1080
('replace', 1, 2, 1, 2),
1081
('equal', 2, 4, 2, 4),
1082
('delete', 4, 5, 4, 4),
1083
('equal', 5, 6, 4, 5),
1084
('delete', 6, 7, 5, 5),
1085
('equal', 7, 9, 5, 7),
1086
('replace', 9, 10, 7, 8),
1087
('equal', 10, 11, 8, 9)
1090
def test_grouped_opcodes(self):
1091
def chk_ops(a, b, expected_codes, n=3):
1092
s = self._PatienceSequenceMatcher(None, a, b)
1093
self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
1097
chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
1098
chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
1099
chk_ops('abcd', 'abcd', [])
1100
chk_ops('abcd', 'abce', [[('equal', 0, 3, 0, 3),
1101
('replace', 3, 4, 3, 4)
1103
chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
1104
('equal', 1, 4, 0, 3),
1105
('insert', 4, 4, 3, 4)
1107
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1108
[[('equal', 3, 6, 3, 6),
1109
('insert', 6, 6, 6, 11),
1110
('equal', 6, 9, 11, 14)
1112
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1113
[[('equal', 2, 6, 2, 6),
1114
('insert', 6, 6, 6, 11),
1115
('equal', 6, 10, 11, 15)
1117
chk_ops('Xabcdef', 'abcdef',
1118
[[('delete', 0, 1, 0, 0),
1119
('equal', 1, 4, 0, 3)
1121
chk_ops('abcdef', 'abcdefX',
1122
[[('equal', 3, 6, 3, 6),
1123
('insert', 6, 6, 6, 7)
1126
def test_multiple_ranges(self):
1127
# There was an earlier bug where we used a bad set of ranges,
1128
# this triggers that specific bug, to make sure it doesn't regress
1129
self.assertDiffBlocks('abcdefghijklmnop',
1130
'abcXghiYZQRSTUVWXYZijklmnop',
1131
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1133
self.assertDiffBlocks('ABCd efghIjk L',
1134
'AxyzBCn mo pqrstuvwI1 2 L',
1135
[(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1137
# These are rot13 code snippets.
1138
self.assertDiffBlocks('''\
1139
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1141
gnxrf_netf = ['svyr*']
1142
gnxrf_bcgvbaf = ['ab-erphefr']
1144
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1145
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1147
ercbegre = nqq_ercbegre_ahyy
1149
ercbegre = nqq_ercbegre_cevag
1150
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1153
pynff pzq_zxqve(Pbzznaq):
1154
'''.splitlines(True), '''\
1155
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1157
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1160
gnxrf_netf = ['svyr*']
1161
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1163
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1168
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1169
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1171
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1173
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1175
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1177
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1180
pynff pzq_zxqve(Pbzznaq):
1181
'''.splitlines(True), [(0, 0, 1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1183
def test_patience_unified_diff(self):
1184
txt_a = ['hello there\n',
1186
'how are you today?\n']
1187
txt_b = ['hello there\n',
1188
'how are you today?\n']
1189
unified_diff = patiencediff.unified_diff
1190
psm = self._PatienceSequenceMatcher
1191
self.assertEqual(['--- \n',
1193
'@@ -1,3 +1,2 @@\n',
1196
' how are you today?\n'
1197
], list(unified_diff(txt_a, txt_b,
1198
sequencematcher=psm)))
1199
txt_a = [x + '\n' for x in 'abcdefghijklmnop']
1200
txt_b = [x + '\n' for x in 'abcdefxydefghijklmnop']
1201
# This is the result with LongestCommonSubstring matching
1202
self.assertEqual(['--- \n',
1204
'@@ -1,6 +1,11 @@\n',
1215
' f\n'], list(unified_diff(txt_a, txt_b)))
1216
# And the patience diff
1217
self.assertEqual(['--- \n',
1219
'@@ -4,6 +4,11 @@\n',
1231
], list(unified_diff(txt_a, txt_b,
1232
sequencematcher=psm)))
1234
def test_patience_unified_diff_with_dates(self):
1235
txt_a = ['hello there\n',
1237
'how are you today?\n']
1238
txt_b = ['hello there\n',
1239
'how are you today?\n']
1240
unified_diff = patiencediff.unified_diff
1241
psm = self._PatienceSequenceMatcher
1242
self.assertEqual(['--- a\t2008-08-08\n',
1243
'+++ b\t2008-09-09\n',
1244
'@@ -1,3 +1,2 @@\n',
1247
' how are you today?\n'
1248
], list(unified_diff(txt_a, txt_b,
1249
fromfile='a', tofile='b',
1250
fromfiledate='2008-08-08',
1251
tofiledate='2008-09-09',
1252
sequencematcher=psm)))
1255
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1257
_test_needs_features = [features.compiled_patiencediff_feature]
1260
super(TestPatienceDiffLib_c, self).setUp()
1261
from breezy import _patiencediff_c
1262
self._unique_lcs = _patiencediff_c.unique_lcs_c
1263
self._recurse_matches = _patiencediff_c.recurse_matches_c
1264
self._PatienceSequenceMatcher = \
1265
_patiencediff_c.PatienceSequenceMatcher_c
1267
def test_unhashable(self):
1268
"""We should get a proper exception here."""
1269
# We need to be able to hash items in the sequence, lists are
1270
# unhashable, and thus cannot be diffed
1271
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1273
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1274
None, ['valid', []], [])
1275
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1276
None, ['valid'], [[]])
1277
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1278
None, ['valid'], ['valid', []])
1281
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1284
super(TestPatienceDiffLibFiles, self).setUp()
1285
self._PatienceSequenceMatcher = \
1286
_patiencediff_py.PatienceSequenceMatcher_py
1288
def test_patience_unified_diff_files(self):
1289
txt_a = [b'hello there\n',
1291
b'how are you today?\n']
1292
txt_b = [b'hello there\n',
1293
b'how are you today?\n']
1294
with open('a1', 'wb') as f:
1296
with open('b1', 'wb') as f:
1299
unified_diff_files = patiencediff.unified_diff_files
1300
psm = self._PatienceSequenceMatcher
1301
self.assertEqual([b'--- a1\n',
1303
b'@@ -1,3 +1,2 @@\n',
1306
b' how are you today?\n',
1307
], list(unified_diff_files(b'a1', b'b1',
1308
sequencematcher=psm)))
1310
txt_a = [x + '\n' for x in 'abcdefghijklmnop']
1311
txt_b = [x + '\n' for x in 'abcdefxydefghijklmnop']
1312
with open('a2', 'wt') as f:
1314
with open('b2', 'wt') as f:
1317
# This is the result with LongestCommonSubstring matching
1318
self.assertEqual([b'--- a2\n',
1320
b'@@ -1,6 +1,11 @@\n',
1331
b' f\n'], list(unified_diff_files(b'a2', b'b2')))
1333
# And the patience diff
1334
self.assertEqual([b'--- a2\n',
1336
b'@@ -4,6 +4,11 @@\n',
1348
list(unified_diff_files(b'a2', b'b2',
1349
sequencematcher=psm)))
1352
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1354
_test_needs_features = [features.compiled_patiencediff_feature]
1357
super(TestPatienceDiffLibFiles_c, self).setUp()
1358
from breezy import _patiencediff_c
1359
self._PatienceSequenceMatcher = \
1360
_patiencediff_c.PatienceSequenceMatcher_c
1363
class TestUsingCompiledIfAvailable(tests.TestCase):
1365
def test_PatienceSequenceMatcher(self):
1366
if features.compiled_patiencediff_feature.available():
1367
from breezy._patiencediff_c import PatienceSequenceMatcher_c
1368
self.assertIs(PatienceSequenceMatcher_c,
1369
patiencediff.PatienceSequenceMatcher)
1371
from breezy._patiencediff_py import PatienceSequenceMatcher_py
1372
self.assertIs(PatienceSequenceMatcher_py,
1373
patiencediff.PatienceSequenceMatcher)
1375
def test_unique_lcs(self):
1376
if features.compiled_patiencediff_feature.available():
1377
from breezy._patiencediff_c import unique_lcs_c
1378
self.assertIs(unique_lcs_c,
1379
patiencediff.unique_lcs)
1381
from breezy._patiencediff_py import unique_lcs_py
1382
self.assertIs(unique_lcs_py,
1383
patiencediff.unique_lcs)
1385
def test_recurse_matches(self):
1386
if features.compiled_patiencediff_feature.available():
1387
from breezy._patiencediff_c import recurse_matches_c
1388
self.assertIs(recurse_matches_c,
1389
patiencediff.recurse_matches)
1391
from breezy._patiencediff_py import recurse_matches_py
1392
self.assertIs(recurse_matches_py,
1393
patiencediff.recurse_matches)
1396
868
class TestDiffFromTool(tests.TestCaseWithTransport):
1398
870
def test_from_string(self):
1399
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1400
self.addCleanup(diff_obj.finish)
1401
self.assertEqual(['diff', '@old_path', '@new_path'],
1402
diff_obj.command_template)
871
diff_obj = diff.DiffFromTool.from_string(
872
['diff', '{old_path}', '{new_path}'],
874
self.addCleanup(diff_obj.finish)
875
self.assertEqual(['diff', '{old_path}', '{new_path}'],
876
diff_obj.command_template)
878
def test_from_string_no_paths(self):
879
diff_obj = diff.DiffFromTool.from_string(
880
['diff', "-u5"], None, None, None)
881
self.addCleanup(diff_obj.finish)
882
self.assertEqual(['diff', '-u5'],
883
diff_obj.command_template)
884
self.assertEqual(['diff', '-u5', 'old-path', 'new-path'],
885
diff_obj._get_command('old-path', 'new-path'))
1404
887
def test_from_string_u5(self):
1405
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
888
diff_obj = diff.DiffFromTool.from_string(
889
['diff', "-u 5", '{old_path}', '{new_path}'], None, None, None)
1407
890
self.addCleanup(diff_obj.finish)
1408
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
891
self.assertEqual(['diff', '-u 5', '{old_path}', '{new_path}'],
1409
892
diff_obj.command_template)
1410
893
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1411
894
diff_obj._get_command('old-path', 'new-path'))
1413
896
def test_from_string_path_with_backslashes(self):
1414
897
self.requireFeature(features.backslashdir_feature)
1415
tool = 'C:\\Tools\\Diff.exe'
898
tool = ['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}']
1416
899
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1417
900
self.addCleanup(diff_obj.finish)
1418
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
901
self.assertEqual(['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}'],
1419
902
diff_obj.command_template)
1420
903
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1421
904
diff_obj._get_command('old-path', 'new-path'))