76
"""Simple file-like object that allows writes with any type and records."""
79
self.write_record = []
81
def write(self, data):
82
self.write_record.append(data)
84
def check_types(self, testcase, expected_type):
86
any(not isinstance(o, expected_type) for o in self.write_record),
87
"Not all writes of type %s: %r" % (
88
expected_type.__name__, self.write_record))
91
class TestDiffOptions(tests.TestCase):
93
def test_unified_added(self):
94
"""Check for default style '-u' only if no other style specified
97
# Verify that style defaults to unified, id est '-u' appended
98
# to option list, in the absence of an alternative style.
99
self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
102
class TestDiffOptionsScenarios(tests.TestCase):
104
scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
105
style = None # Set by load_tests_apply_scenarios from scenarios
107
def test_unified_not_added(self):
108
# Verify that for all valid style options, '-u' is not
109
# appended to option list.
110
ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
111
self.assertEqual(["%s" % (self.style,)], ret_opts)
83
114
class TestDiff(tests.TestCase):
85
116
def test_add_nl(self):
86
117
"""diff generates a valid diff for patches that add a newline"""
87
lines = udiff_lines(['boo'], ['boo\n'])
118
lines = udiff_lines([b'boo'], [b'boo\n'])
88
119
self.check_patch(lines)
89
self.assertEquals(lines[4], '\\ No newline at end of file\n')
90
## "expected no-nl, got %r" % lines[4]
120
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
121
## "expected no-nl, got %r" % lines[4]
92
123
def test_add_nl_2(self):
93
124
"""diff generates a valid diff for patches that change last line and
96
lines = udiff_lines(['boo'], ['goo\n'])
127
lines = udiff_lines([b'boo'], [b'goo\n'])
97
128
self.check_patch(lines)
98
self.assertEquals(lines[4], '\\ No newline at end of file\n')
99
## "expected no-nl, got %r" % lines[4]
129
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
130
## "expected no-nl, got %r" % lines[4]
101
132
def test_remove_nl(self):
102
133
"""diff generates a valid diff for patches that change last line and
105
lines = udiff_lines(['boo\n'], ['boo'])
136
lines = udiff_lines([b'boo\n'], [b'boo'])
106
137
self.check_patch(lines)
107
self.assertEquals(lines[5], '\\ No newline at end of file\n')
108
## "expected no-nl, got %r" % lines[5]
138
self.assertEqual(lines[5], b'\\ No newline at end of file\n')
139
## "expected no-nl, got %r" % lines[5]
110
141
def check_patch(self, lines):
111
self.assert_(len(lines) > 1)
112
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
113
self.assert_(lines[0].startswith ('---'))
114
## 'No orig line for patch:\n%s' % "".join(lines)
115
self.assert_(lines[1].startswith ('+++'))
116
## 'No mod line for patch:\n%s' % "".join(lines)
117
self.assert_(len(lines) > 2)
118
## "No hunks for patch:\n%s" % "".join(lines)
119
self.assert_(lines[2].startswith('@@'))
120
## "No hunk header for patch:\n%s" % "".join(lines)
121
self.assert_('@@' in lines[2][2:])
122
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
142
self.assertTrue(len(lines) > 1)
143
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
144
self.assertTrue(lines[0].startswith(b'---'))
145
## 'No orig line for patch:\n%s' % "".join(lines)
146
self.assertTrue(lines[1].startswith(b'+++'))
147
## 'No mod line for patch:\n%s' % "".join(lines)
148
self.assertTrue(len(lines) > 2)
149
## "No hunks for patch:\n%s" % "".join(lines)
150
self.assertTrue(lines[2].startswith(b'@@'))
151
## "No hunk header for patch:\n%s" % "".join(lines)
152
self.assertTrue(b'@@' in lines[2][2:])
153
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
124
155
def test_binary_lines(self):
126
uni_lines = [1023 * 'a' + '\x00']
127
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
157
uni_lines = [1023 * b'a' + b'\x00']
158
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
128
159
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
udiff_lines(uni_lines , empty, allow_binary=True)
160
udiff_lines(uni_lines, empty, allow_binary=True)
130
161
udiff_lines(empty, uni_lines, allow_binary=True)
132
163
def test_external_diff(self):
133
lines = external_udiff_lines(['boo\n'], ['goo\n'])
164
lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
134
165
self.check_patch(lines)
135
self.assertEqual('\n', lines[-1])
166
self.assertEqual(b'\n', lines[-1])
137
168
def test_external_diff_no_fileno(self):
138
169
# Make sure that we can handle not having a fileno, even
139
170
# if the diff is large
140
lines = external_udiff_lines(['boo\n']*10000,
171
lines = external_udiff_lines([b'boo\n'] * 10000,
142
173
use_stringio=True)
143
174
self.check_patch(lines)
145
176
def test_external_diff_binary_lang_c(self):
147
177
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
148
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
150
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
151
# Older versions of diffutils say "Binary files", newer
152
# versions just say "Files".
153
self.assertContainsRe(lines[0],
154
'(Binary f|F)iles old and new differ\n')
155
self.assertEquals(lines[1:], ['\n'])
157
for lang, old_val in old_env.iteritems():
158
osutils.set_or_unset_env(lang, old_val)
178
self.overrideEnv(lang, 'C')
179
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
180
# Older versions of diffutils say "Binary files", newer
181
# versions just say "Files".
182
self.assertContainsRe(
183
lines[0], b'(Binary f|F)iles old and new differ\n')
184
self.assertEqual(lines[1:], [b'\n'])
160
186
def test_no_external_diff(self):
161
187
"""Check that NoDiff is raised when diff is not available"""
162
# Use os.environ['PATH'] to make sure no 'diff' command is available
163
orig_path = os.environ['PATH']
165
os.environ['PATH'] = ''
166
self.assertRaises(errors.NoDiff, diff.external_diff,
167
'old', ['boo\n'], 'new', ['goo\n'],
168
StringIO(), diff_opts=['-u'])
170
os.environ['PATH'] = orig_path
188
# Make sure no 'diff' command is available
189
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
190
self.overrideEnv('PATH', '')
191
self.assertRaises(errors.NoDiff, diff.external_diff,
192
b'old', [b'boo\n'], b'new', [b'goo\n'],
193
BytesIO(), diff_opts=['-u'])
172
195
def test_internal_diff_default(self):
173
196
# Default internal diff encoding is utf8
175
diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
u'new_\xe5', ['new_text\n'], output)
198
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
199
u'new_\xe5', [b'new_text\n'], output)
177
200
lines = output.getvalue().splitlines(True)
178
201
self.check_patch(lines)
179
self.assertEquals(['--- old_\xc2\xb5\n',
180
'+++ new_\xc3\xa5\n',
202
self.assertEqual([b'--- old_\xc2\xb5\n',
203
b'+++ new_\xc3\xa5\n',
204
b'@@ -1,1 +1,1 @@\n',
188
210
def test_internal_diff_utf8(self):
190
diff.internal_diff(u'old_\xb5', ['old_text\n'],
191
u'new_\xe5', ['new_text\n'], output,
212
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
213
u'new_\xe5', [b'new_text\n'], output,
192
214
path_encoding='utf8')
193
215
lines = output.getvalue().splitlines(True)
194
216
self.check_patch(lines)
195
self.assertEquals(['--- old_\xc2\xb5\n',
196
'+++ new_\xc3\xa5\n',
217
self.assertEqual([b'--- old_\xc2\xb5\n',
218
b'+++ new_\xc3\xa5\n',
219
b'@@ -1,1 +1,1 @@\n',
204
225
def test_internal_diff_iso_8859_1(self):
206
diff.internal_diff(u'old_\xb5', ['old_text\n'],
207
u'new_\xe5', ['new_text\n'], output,
227
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
228
u'new_\xe5', [b'new_text\n'], output,
208
229
path_encoding='iso-8859-1')
209
230
lines = output.getvalue().splitlines(True)
210
231
self.check_patch(lines)
211
self.assertEquals(['--- old_\xb5\n',
232
self.assertEqual([b'--- old_\xb5\n',
234
b'@@ -1,1 +1,1 @@\n',
220
240
def test_internal_diff_no_content(self):
222
242
diff.internal_diff(u'old', [], u'new', [], output)
223
self.assertEqual('', output.getvalue())
243
self.assertEqual(b'', output.getvalue())
225
245
def test_internal_diff_no_changes(self):
227
diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
u'new', ['text\n', 'contents\n'],
247
diff.internal_diff(u'old', [b'text\n', b'contents\n'],
248
u'new', [b'text\n', b'contents\n'],
230
self.assertEqual('', output.getvalue())
250
self.assertEqual(b'', output.getvalue())
232
252
def test_internal_diff_returns_bytes(self):
234
output = StringIO.StringIO()
235
diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
u'new_\xe5', ['new_text\n'], output)
237
self.failUnless(isinstance(output.getvalue(), str),
238
'internal_diff should return bytestrings')
254
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
255
u'new_\xe5', [b'new_text\n'], output)
256
output.check_types(self, bytes)
258
def test_internal_diff_default_context(self):
260
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
261
b'same_text\n', b'same_text\n', b'old_text\n'],
262
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
263
b'same_text\n', b'same_text\n', b'new_text\n'], output)
264
lines = output.getvalue().splitlines(True)
265
self.check_patch(lines)
266
self.assertEqual([b'--- old\n',
268
b'@@ -3,4 +3,4 @@\n',
277
def test_internal_diff_no_context(self):
279
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
280
b'same_text\n', b'same_text\n', b'old_text\n'],
281
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
282
b'same_text\n', b'same_text\n', b'new_text\n'], output,
284
lines = output.getvalue().splitlines(True)
285
self.check_patch(lines)
286
self.assertEqual([b'--- old\n',
288
b'@@ -6,1 +6,1 @@\n',
294
def test_internal_diff_more_context(self):
296
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
297
b'same_text\n', b'same_text\n', b'old_text\n'],
298
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
299
b'same_text\n', b'same_text\n', b'new_text\n'], output,
301
lines = output.getvalue().splitlines(True)
302
self.check_patch(lines)
303
self.assertEqual([b'--- old\n',
305
b'@@ -2,5 +2,5 @@\n',
241
316
class TestDiffFiles(tests.TestCaseInTempDir):
243
318
def test_external_diff_binary(self):
244
319
"""The output when using external diff should use diff's i18n error"""
320
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
321
self.overrideEnv(lang, 'C')
245
322
# Make sure external_diff doesn't fail in the current LANG
246
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
323
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
248
325
cmd = ['diff', '-u', '--binary', 'old', 'new']
249
open('old', 'wb').write('\x00foobar\n')
250
open('new', 'wb').write('foo\x00bar\n')
326
with open('old', 'wb') as f:
327
f.write(b'\x00foobar\n')
328
with open('new', 'wb') as f:
329
f.write(b'foo\x00bar\n')
251
330
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
stdin=subprocess.PIPE)
331
stdin=subprocess.PIPE)
253
332
out, err = pipe.communicate()
254
# Diff returns '2' on Binary files.
255
self.assertEqual(2, pipe.returncode)
256
333
# We should output whatever diff tells us, plus a trailing newline
257
self.assertEqual(out.splitlines(True) + ['\n'], lines)
260
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
261
"""Has a helper for running show_diff_trees"""
263
def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
265
if working_tree is not None:
266
extra_trees = (working_tree,)
269
diff.show_diff_trees(tree1, tree2, output,
270
specific_files=specific_files,
271
extra_trees=extra_trees, old_label='old/',
273
return output.getvalue()
276
class TestDiffDates(TestShowDiffTreesHelper):
334
self.assertEqual(out.splitlines(True) + [b'\n'], lines)
337
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
339
if working_tree is not None:
340
extra_trees = (working_tree,)
343
diff.show_diff_trees(tree1, tree2, output,
344
specific_files=specific_files,
345
extra_trees=extra_trees, old_label='old/',
347
return output.getvalue()
350
class TestDiffDates(tests.TestCaseWithTransport):
279
353
super(TestDiffDates, self).setUp()
280
354
self.wt = self.make_branch_and_tree('.')
281
355
self.b = self.wt.branch
282
356
self.build_tree_contents([
283
('file1', 'file1 contents at rev 1\n'),
284
('file2', 'file2 contents at rev 1\n')
357
('file1', b'file1 contents at rev 1\n'),
358
('file2', b'file2 contents at rev 1\n')
286
360
self.wt.add(['file1', 'file2'])
288
362
message='Revision 1',
289
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
363
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
292
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
366
self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
294
368
message='Revision 2',
295
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
369
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
298
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
372
self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
300
374
message='Revision 3',
301
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
375
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
304
378
self.wt.remove(['file2'])
306
380
message='Revision 4',
307
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
381
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
310
384
self.build_tree_contents([
311
('file1', 'file1 contents in working tree\n')
385
('file1', b'file1 contents in working tree\n')
313
387
# set the date stamps for files in the working tree to known values
314
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
388
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
316
390
def test_diff_rev_tree_working_tree(self):
317
output = self.get_diff(self.wt.basis_tree(), self.wt)
391
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
392
# note that the date for old/file1 is from rev 2 rather than from
319
393
# the basis revision (rev 4)
320
self.assertEqualDiff(output, '''\
394
self.assertEqualDiff(output, b'''\
321
395
=== modified file 'file1'
322
396
--- old/file1\t2006-04-02 00:00:00 +0000
323
397
+++ new/file1\t2006-04-05 00:00:00 +0000
393
467
self.wt.add(['dir1', 'dir2'])
394
468
self.wt.rename_one('file1', 'dir1/file1')
395
old_tree = self.b.repository.revision_tree('rev-1')
396
new_tree = self.b.repository.revision_tree('rev-4')
397
out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
398
working_tree=self.wt)
399
self.assertContainsRe(out, 'file1\t')
400
out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
401
working_tree=self.wt)
402
self.assertNotContainsRe(out, 'file1\t')
406
class TestShowDiffTrees(TestShowDiffTreesHelper):
469
old_tree = self.b.repository.revision_tree(b'rev-1')
470
new_tree = self.b.repository.revision_tree(b'rev-4')
471
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
472
working_tree=self.wt)
473
self.assertContainsRe(out, b'file1\t')
474
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
475
working_tree=self.wt)
476
self.assertNotContainsRe(out, b'file1\t')
479
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
480
"""Direct tests for show_diff_trees"""
409
482
def test_modified_file(self):
410
483
"""Test when a file is modified."""
411
484
tree = self.make_branch_and_tree('tree')
412
self.build_tree_contents([('tree/file', 'contents\n')])
413
tree.add(['file'], ['file-id'])
414
tree.commit('one', rev_id='rev-1')
485
self.build_tree_contents([('tree/file', b'contents\n')])
486
tree.add(['file'], [b'file-id'])
487
tree.commit('one', rev_id=b'rev-1')
416
self.build_tree_contents([('tree/file', 'new contents\n')])
417
d = self.get_diff(tree.basis_tree(), tree)
418
self.assertContainsRe(d, "=== modified file 'file'\n")
419
self.assertContainsRe(d, '--- old/file\t')
420
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
421
self.assertContainsRe(d, '-contents\n'
489
self.build_tree_contents([('tree/file', b'new contents\n')])
490
d = get_diff_as_string(tree.basis_tree(), tree)
491
self.assertContainsRe(d, b"=== modified file 'file'\n")
492
self.assertContainsRe(d, b'--- old/file\t')
493
self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
494
self.assertContainsRe(d, b'-contents\n'
495
b'\\+new contents\n')
424
497
def test_modified_file_in_renamed_dir(self):
425
498
"""Test when a file is modified in a renamed directory."""
426
499
tree = self.make_branch_and_tree('tree')
427
500
self.build_tree(['tree/dir/'])
428
self.build_tree_contents([('tree/dir/file', 'contents\n')])
429
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
430
tree.commit('one', rev_id='rev-1')
501
self.build_tree_contents([('tree/dir/file', b'contents\n')])
502
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
503
tree.commit('one', rev_id=b'rev-1')
432
505
tree.rename_one('dir', 'other')
433
self.build_tree_contents([('tree/other/file', 'new contents\n')])
434
d = self.get_diff(tree.basis_tree(), tree)
435
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
self.assertContainsRe(d, "=== modified file 'other/file'\n")
506
self.build_tree_contents([('tree/other/file', b'new contents\n')])
507
d = get_diff_as_string(tree.basis_tree(), tree)
508
self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
509
self.assertContainsRe(d, b"=== modified file 'other/file'\n")
437
510
# XXX: This is technically incorrect, because it used to be at another
438
511
# location. What to do?
439
self.assertContainsRe(d, '--- old/dir/file\t')
440
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
441
self.assertContainsRe(d, '-contents\n'
512
self.assertContainsRe(d, b'--- old/dir/file\t')
513
self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
514
self.assertContainsRe(d, b'-contents\n'
515
b'\\+new contents\n')
444
517
def test_renamed_directory(self):
445
518
"""Test when only a directory is only renamed."""
446
519
tree = self.make_branch_and_tree('tree')
447
520
self.build_tree(['tree/dir/'])
448
self.build_tree_contents([('tree/dir/file', 'contents\n')])
449
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
450
tree.commit('one', rev_id='rev-1')
521
self.build_tree_contents([('tree/dir/file', b'contents\n')])
522
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
523
tree.commit('one', rev_id=b'rev-1')
452
525
tree.rename_one('dir', 'newdir')
453
d = self.get_diff(tree.basis_tree(), tree)
526
d = get_diff_as_string(tree.basis_tree(), tree)
454
527
# Renaming a directory should be a single "you renamed this dir" even
455
528
# when there are files inside.
456
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
529
self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
458
531
def test_renamed_file(self):
459
532
"""Test when a file is only renamed."""
460
533
tree = self.make_branch_and_tree('tree')
461
self.build_tree_contents([('tree/file', 'contents\n')])
462
tree.add(['file'], ['file-id'])
463
tree.commit('one', rev_id='rev-1')
534
self.build_tree_contents([('tree/file', b'contents\n')])
535
tree.add(['file'], [b'file-id'])
536
tree.commit('one', rev_id=b'rev-1')
465
538
tree.rename_one('file', 'newname')
466
d = self.get_diff(tree.basis_tree(), tree)
467
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
539
d = get_diff_as_string(tree.basis_tree(), tree)
540
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
468
541
# We shouldn't have a --- or +++ line, because there is no content
470
self.assertNotContainsRe(d, '---')
543
self.assertNotContainsRe(d, b'---')
472
545
def test_renamed_and_modified_file(self):
473
546
"""Test when a file is only renamed."""
474
547
tree = self.make_branch_and_tree('tree')
475
self.build_tree_contents([('tree/file', 'contents\n')])
476
tree.add(['file'], ['file-id'])
477
tree.commit('one', rev_id='rev-1')
548
self.build_tree_contents([('tree/file', b'contents\n')])
549
tree.add(['file'], [b'file-id'])
550
tree.commit('one', rev_id=b'rev-1')
479
552
tree.rename_one('file', 'newname')
480
self.build_tree_contents([('tree/newname', 'new contents\n')])
481
d = self.get_diff(tree.basis_tree(), tree)
482
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
self.assertContainsRe(d, '--- old/file\t')
484
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
485
self.assertContainsRe(d, '-contents\n'
553
self.build_tree_contents([('tree/newname', b'new contents\n')])
554
d = get_diff_as_string(tree.basis_tree(), tree)
555
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
556
self.assertContainsRe(d, b'--- old/file\t')
557
self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
558
self.assertContainsRe(d, b'-contents\n'
559
b'\\+new contents\n')
489
561
def test_internal_diff_exec_property(self):
490
562
tree = self.make_branch_and_tree('tree')
492
tt = transform.TreeTransform(tree)
493
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
494
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
495
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
496
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
497
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
498
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
564
tt = tree.get_transform()
565
tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
566
tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
567
tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
568
tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
569
tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
570
tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
500
tree.commit('one', rev_id='rev-1')
572
tree.commit('one', rev_id=b'rev-1')
502
tt = transform.TreeTransform(tree)
503
tt.set_executability(False, tt.trans_id_file_id('a-id'))
504
tt.set_executability(True, tt.trans_id_file_id('b-id'))
505
tt.set_executability(False, tt.trans_id_file_id('c-id'))
506
tt.set_executability(True, tt.trans_id_file_id('d-id'))
574
tt = tree.get_transform()
575
tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
576
tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
577
tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
578
tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
508
580
tree.rename_one('c', 'new-c')
509
581
tree.rename_one('d', 'new-d')
511
d = self.get_diff(tree.basis_tree(), tree)
513
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
515
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
517
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
519
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
521
self.assertNotContainsRe(d, r"file 'e'")
522
self.assertNotContainsRe(d, r"file 'f'")
583
d = get_diff_as_string(tree.basis_tree(), tree)
585
self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
587
self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
589
self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
591
self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
593
self.assertNotContainsRe(d, br"file 'e'")
594
self.assertNotContainsRe(d, br"file 'f'")
525
596
def test_binary_unicode_filenames(self):
526
597
"""Test that contents of files are *not* encoded in UTF-8 when there
527
598
is a binary file in the diff.
529
600
# See https://bugs.launchpad.net/bugs/110092.
530
self.requireFeature(tests.UnicodeFilenameFeature)
601
self.requireFeature(features.UnicodeFilenameFeature)
532
# This bug isn't triggered with cStringIO.
533
from StringIO import StringIO
534
603
tree = self.make_branch_and_tree('tree')
535
604
alpha, omega = u'\u03b1', u'\u03c9'
536
605
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
537
606
self.build_tree_contents(
538
[('tree/' + alpha, chr(0)),
607
[('tree/' + alpha, b'\0'),
539
608
('tree/' + omega,
540
('The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
541
tree.add([alpha], ['file-id'])
542
tree.add([omega], ['file-id-2'])
543
diff_content = StringIO()
609
(b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
610
tree.add([alpha], [b'file-id'])
611
tree.add([omega], [b'file-id-2'])
612
diff_content = StubO()
544
613
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
545
d = diff_content.getvalue()
546
self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
547
self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
614
diff_content.check_types(self, bytes)
615
d = b''.join(diff_content.write_record)
616
self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
617
self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
548
618
% (alpha_utf8, alpha_utf8))
549
self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
550
self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
551
self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
619
self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
620
self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
621
self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_utf8,))
553
623
def test_unicode_filename(self):
554
624
"""Test when the filename are unicode."""
555
self.requireFeature(tests.UnicodeFilenameFeature)
625
self.requireFeature(features.UnicodeFilenameFeature)
557
627
alpha, omega = u'\u03b1', u'\u03c9'
558
628
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
560
630
tree = self.make_branch_and_tree('tree')
561
self.build_tree_contents([('tree/ren_'+alpha, 'contents\n')])
562
tree.add(['ren_'+alpha], ['file-id-2'])
563
self.build_tree_contents([('tree/del_'+alpha, 'contents\n')])
564
tree.add(['del_'+alpha], ['file-id-3'])
565
self.build_tree_contents([('tree/mod_'+alpha, 'contents\n')])
566
tree.add(['mod_'+alpha], ['file-id-4'])
568
tree.commit('one', rev_id='rev-1')
570
tree.rename_one('ren_'+alpha, 'ren_'+omega)
571
tree.remove('del_'+alpha)
572
self.build_tree_contents([('tree/add_'+alpha, 'contents\n')])
573
tree.add(['add_'+alpha], ['file-id'])
574
self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
576
d = self.get_diff(tree.basis_tree(), tree)
631
self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
632
tree.add(['ren_' + alpha], [b'file-id-2'])
633
self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
634
tree.add(['del_' + alpha], [b'file-id-3'])
635
self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
636
tree.add(['mod_' + alpha], [b'file-id-4'])
638
tree.commit('one', rev_id=b'rev-1')
640
tree.rename_one('ren_' + alpha, 'ren_' + omega)
641
tree.remove('del_' + alpha)
642
self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
643
tree.add(['add_' + alpha], [b'file-id'])
644
self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
646
d = get_diff_as_string(tree.basis_tree(), tree)
577
647
self.assertContainsRe(d,
578
"=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
648
b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
649
self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
650
self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
651
self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
653
def test_unicode_filename_path_encoding(self):
654
"""Test for bug #382699: unicode filenames on Windows should be shown
657
self.requireFeature(features.UnicodeFilenameFeature)
658
# The word 'test' in Russian
659
_russian_test = u'\u0422\u0435\u0441\u0442'
660
directory = _russian_test + u'/'
661
test_txt = _russian_test + u'.txt'
662
u1234 = u'\u1234.txt'
664
tree = self.make_branch_and_tree('.')
665
self.build_tree_contents([
666
(test_txt, b'foo\n'),
670
tree.add([test_txt, u1234, directory])
673
diff.show_diff_trees(tree.basis_tree(), tree, sio,
674
path_encoding='cp1251')
676
output = subst_dates(sio.getvalue())
678
=== added directory '%(directory)s'
679
=== added file '%(test_txt)s'
680
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
681
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
685
=== added file '?.txt'
686
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
687
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
691
''' % {b'directory': _russian_test.encode('cp1251'),
692
b'test_txt': test_txt.encode('cp1251'),
694
self.assertEqualDiff(output, shouldbe)
584
697
class DiffWasIs(diff.DiffPath):
586
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
587
self.to_file.write('was: ')
588
self.to_file.write(self.old_tree.get_file(file_id).read())
589
self.to_file.write('is: ')
590
self.to_file.write(self.new_tree.get_file(file_id).read())
699
def diff(self, old_path, new_path, old_kind, new_kind):
700
self.to_file.write(b'was: ')
701
self.to_file.write(self.old_tree.get_file(old_path).read())
702
self.to_file.write(b'is: ')
703
self.to_file.write(self.new_tree.get_file(new_path).read())
594
706
class TestDiffTree(tests.TestCaseWithTransport):
601
713
self.new_tree = self.make_branch_and_tree('new-tree')
602
714
self.new_tree.lock_write()
603
715
self.addCleanup(self.new_tree.unlock)
604
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
716
self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
606
718
def test_diff_text(self):
607
719
self.build_tree_contents([('old-tree/olddir/',),
608
('old-tree/olddir/oldfile', 'old\n')])
720
('old-tree/olddir/oldfile', b'old\n')])
609
721
self.old_tree.add('olddir')
610
self.old_tree.add('olddir/oldfile', 'file-id')
722
self.old_tree.add('olddir/oldfile', b'file-id')
611
723
self.build_tree_contents([('new-tree/newdir/',),
612
('new-tree/newdir/newfile', 'new\n')])
724
('new-tree/newdir/newfile', b'new\n')])
613
725
self.new_tree.add('newdir')
614
self.new_tree.add('newdir/newfile', 'file-id')
615
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
616
differ.diff_text('file-id', None, 'old label', 'new label')
618
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
619
differ.to_file.getvalue())
620
differ.to_file.seek(0)
621
differ.diff_text(None, 'file-id', 'old label', 'new label')
623
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
624
differ.to_file.getvalue())
625
differ.to_file.seek(0)
626
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
628
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
726
self.new_tree.add('newdir/newfile', b'file-id')
727
differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
728
differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
730
b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
731
differ.to_file.getvalue())
732
differ.to_file.seek(0)
733
differ.diff_text(None, 'newdir/newfile',
734
'old label', 'new label')
736
b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
737
differ.to_file.getvalue())
738
differ.to_file.seek(0)
739
differ.diff_text('olddir/oldfile', 'newdir/newfile',
740
'old label', 'new label')
742
b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
743
differ.to_file.getvalue())
631
745
def test_diff_deletion(self):
632
self.build_tree_contents([('old-tree/file', 'contents'),
633
('new-tree/file', 'contents')])
634
self.old_tree.add('file', 'file-id')
635
self.new_tree.add('file', 'file-id')
746
self.build_tree_contents([('old-tree/file', b'contents'),
747
('new-tree/file', b'contents')])
748
self.old_tree.add('file', b'file-id')
749
self.new_tree.add('file', b'file-id')
636
750
os.unlink('new-tree/file')
637
751
self.differ.show_diff(None)
638
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
752
self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
640
754
def test_diff_creation(self):
641
self.build_tree_contents([('old-tree/file', 'contents'),
642
('new-tree/file', 'contents')])
643
self.old_tree.add('file', 'file-id')
644
self.new_tree.add('file', 'file-id')
755
self.build_tree_contents([('old-tree/file', b'contents'),
756
('new-tree/file', b'contents')])
757
self.old_tree.add('file', b'file-id')
758
self.new_tree.add('file', b'file-id')
645
759
os.unlink('old-tree/file')
646
760
self.differ.show_diff(None)
647
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
761
self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
649
763
def test_diff_symlink(self):
650
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
764
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
765
differ.diff_symlink('old target', None)
652
self.assertEqual("=== target was 'old target'\n",
766
self.assertEqual(b"=== target was 'old target'\n",
653
767
differ.to_file.getvalue())
655
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
769
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
770
differ.diff_symlink(None, 'new target')
657
self.assertEqual("=== target is 'new target'\n",
771
self.assertEqual(b"=== target is 'new target'\n",
658
772
differ.to_file.getvalue())
660
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
774
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
775
differ.diff_symlink('old target', 'new target')
662
self.assertEqual("=== target changed 'old target' => 'new target'\n",
776
self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
663
777
differ.to_file.getvalue())
665
779
def test_diff(self):
666
780
self.build_tree_contents([('old-tree/olddir/',),
667
('old-tree/olddir/oldfile', 'old\n')])
781
('old-tree/olddir/oldfile', b'old\n')])
668
782
self.old_tree.add('olddir')
669
self.old_tree.add('olddir/oldfile', 'file-id')
783
self.old_tree.add('olddir/oldfile', b'file-id')
670
784
self.build_tree_contents([('new-tree/newdir/',),
671
('new-tree/newdir/newfile', 'new\n')])
785
('new-tree/newdir/newfile', b'new\n')])
672
786
self.new_tree.add('newdir')
673
self.new_tree.add('newdir/newfile', 'file-id')
674
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
787
self.new_tree.add('newdir/newfile', b'file-id')
788
self.differ.diff('olddir/oldfile', 'newdir/newfile')
675
789
self.assertContainsRe(
676
790
self.differ.to_file.getvalue(),
677
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
' \@\@\n-old\n\+new\n\n')
791
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
792
br' \@\@\n-old\n\+new\n\n')
680
794
def test_diff_kind_change(self):
681
self.requireFeature(tests.SymlinkFeature)
795
self.requireFeature(features.SymlinkFeature)
682
796
self.build_tree_contents([('old-tree/olddir/',),
683
('old-tree/olddir/oldfile', 'old\n')])
797
('old-tree/olddir/oldfile', b'old\n')])
684
798
self.old_tree.add('olddir')
685
self.old_tree.add('olddir/oldfile', 'file-id')
799
self.old_tree.add('olddir/oldfile', b'file-id')
686
800
self.build_tree(['new-tree/newdir/'])
687
801
os.symlink('new', 'new-tree/newdir/newfile')
688
802
self.new_tree.add('newdir')
689
self.new_tree.add('newdir/newfile', 'file-id')
690
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
803
self.new_tree.add('newdir/newfile', b'file-id')
804
self.differ.diff('olddir/oldfile', 'newdir/newfile')
691
805
self.assertContainsRe(
692
806
self.differ.to_file.getvalue(),
693
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
807
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
695
809
self.assertContainsRe(self.differ.to_file.getvalue(),
696
"=== target is u'new'\n")
810
b"=== target is 'new'\n")
698
812
def test_diff_directory(self):
699
813
self.build_tree(['new-tree/new-dir/'])
700
self.new_tree.add('new-dir', 'new-dir-id')
701
self.differ.diff('new-dir-id', None, 'new-dir')
702
self.assertEqual(self.differ.to_file.getvalue(), '')
814
self.new_tree.add('new-dir', b'new-dir-id')
815
self.differ.diff(None, 'new-dir')
816
self.assertEqual(self.differ.to_file.getvalue(), b'')
704
818
def create_old_new(self):
705
819
self.build_tree_contents([('old-tree/olddir/',),
706
('old-tree/olddir/oldfile', 'old\n')])
820
('old-tree/olddir/oldfile', b'old\n')])
707
821
self.old_tree.add('olddir')
708
self.old_tree.add('olddir/oldfile', 'file-id')
822
self.old_tree.add('olddir/oldfile', b'file-id')
709
823
self.build_tree_contents([('new-tree/newdir/',),
710
('new-tree/newdir/newfile', 'new\n')])
824
('new-tree/newdir/newfile', b'new\n')])
711
825
self.new_tree.add('newdir')
712
self.new_tree.add('newdir/newfile', 'file-id')
826
self.new_tree.add('newdir/newfile', b'file-id')
714
828
def test_register_diff(self):
715
829
self.create_old_new()
716
830
old_diff_factories = diff.DiffTree.diff_factories
717
diff.DiffTree.diff_factories=old_diff_factories[:]
831
diff.DiffTree.diff_factories = old_diff_factories[:]
718
832
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
720
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
834
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
722
836
diff.DiffTree.diff_factories = old_diff_factories
723
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
837
differ.diff('olddir/oldfile', 'newdir/newfile')
724
838
self.assertNotContainsRe(
725
839
differ.to_file.getvalue(),
726
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
' \@\@\n-old\n\+new\n\n')
840
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
841
br' \@\@\n-old\n\+new\n\n')
728
842
self.assertContainsRe(differ.to_file.getvalue(),
729
'was: old\nis: new\n')
843
b'was: old\nis: new\n')
731
845
def test_extra_factories(self):
732
846
self.create_old_new()
733
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
847
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
848
extra_factories=[DiffWasIs.from_diff_tree])
735
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
849
differ.diff('olddir/oldfile', 'newdir/newfile')
736
850
self.assertNotContainsRe(
737
851
differ.to_file.getvalue(),
738
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
' \@\@\n-old\n\+new\n\n')
852
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
853
br' \@\@\n-old\n\+new\n\n')
740
854
self.assertContainsRe(differ.to_file.getvalue(),
741
'was: old\nis: new\n')
855
b'was: old\nis: new\n')
743
857
def test_alphabetical_order(self):
744
858
self.build_tree(['new-tree/a-file'])
747
861
self.old_tree.add('b-file')
748
862
self.differ.show_diff(None)
749
863
self.assertContainsRe(self.differ.to_file.getvalue(),
750
'.*a-file(.|\n)*b-file')
753
class TestPatienceDiffLib(tests.TestCase):
756
super(TestPatienceDiffLib, self).setUp()
757
self._unique_lcs = _patiencediff_py.unique_lcs_py
758
self._recurse_matches = _patiencediff_py.recurse_matches_py
759
self._PatienceSequenceMatcher = \
760
_patiencediff_py.PatienceSequenceMatcher_py
762
def test_diff_unicode_string(self):
763
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
764
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
sm = self._PatienceSequenceMatcher(None, a, b)
766
mb = sm.get_matching_blocks()
767
self.assertEquals(35, len(mb))
769
def test_unique_lcs(self):
770
unique_lcs = self._unique_lcs
771
self.assertEquals(unique_lcs('', ''), [])
772
self.assertEquals(unique_lcs('', 'a'), [])
773
self.assertEquals(unique_lcs('a', ''), [])
774
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
775
self.assertEquals(unique_lcs('a', 'b'), [])
776
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
777
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
778
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
779
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
781
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
783
def test_recurse_matches(self):
784
def test_one(a, b, matches):
786
self._recurse_matches(
787
a, b, 0, 0, len(a), len(b), test_matches, 10)
788
self.assertEquals(test_matches, matches)
790
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
[(0, 0), (2, 2), (4, 4)])
792
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
793
[(0, 0), (2, 1), (4, 2)])
794
# Even though 'bc' is not unique globally, and is surrounded by
795
# non-matching lines, we should still match, because they are locally
797
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
798
(4, 6), (5, 7), (6, 8)])
800
# recurse_matches doesn't match non-unique
801
# lines surrounded by bogus text.
802
# The update has been done in patiencediff.SequenceMatcher instead
804
# This is what it could be
805
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
807
# This is what it currently gives:
808
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
810
def assertDiffBlocks(self, a, b, expected_blocks):
811
"""Check that the sequence matcher returns the correct blocks.
813
:param a: A sequence to match
814
:param b: Another sequence to match
815
:param expected_blocks: The expected output, not including the final
816
matching block (len(a), len(b), 0)
818
matcher = self._PatienceSequenceMatcher(None, a, b)
819
blocks = matcher.get_matching_blocks()
821
self.assertEqual((len(a), len(b), 0), last)
822
self.assertEqual(expected_blocks, blocks)
824
def test_matching_blocks(self):
825
# Some basic matching tests
826
self.assertDiffBlocks('', '', [])
827
self.assertDiffBlocks([], [], [])
828
self.assertDiffBlocks('abc', '', [])
829
self.assertDiffBlocks('', 'abc', [])
830
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
831
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
832
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
833
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
834
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
835
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
836
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
837
# This may check too much, but it checks to see that
838
# a copied block stays attached to the previous section,
840
# difflib would tend to grab the trailing longest match
841
# which would make the diff not look right
842
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
843
[(0, 0, 6), (6, 11, 10)])
845
# make sure it supports passing in lists
846
self.assertDiffBlocks(
849
'how are you today?\n'],
851
'how are you today?\n'],
852
[(0, 0, 1), (2, 1, 1)])
854
# non unique lines surrounded by non-matching lines
856
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
858
# But they only need to be locally unique
859
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
861
# non unique blocks won't be matched
862
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
864
# but locally unique ones will
865
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
866
(5,4,1), (7,5,2), (10,8,1)])
868
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
869
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
870
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
872
def test_matching_blocks_tuples(self):
873
# Some basic matching tests
874
self.assertDiffBlocks([], [], [])
875
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
876
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
877
self.assertDiffBlocks([('a',), ('b',), ('c,')],
878
[('a',), ('b',), ('c,')],
880
self.assertDiffBlocks([('a',), ('b',), ('c,')],
881
[('a',), ('b',), ('d,')],
883
self.assertDiffBlocks([('d',), ('b',), ('c,')],
884
[('a',), ('b',), ('c,')],
886
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
887
[('a',), ('b',), ('c,')],
889
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
890
[('a', 'b'), ('c', 'X'), ('e', 'f')],
891
[(0, 0, 1), (2, 2, 1)])
892
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
893
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
894
[(0, 0, 1), (2, 2, 1)])
896
def test_opcodes(self):
897
def chk_ops(a, b, expected_codes):
898
s = self._PatienceSequenceMatcher(None, a, b)
899
self.assertEquals(expected_codes, s.get_opcodes())
903
chk_ops('abc', '', [('delete', 0,3, 0,0)])
904
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
905
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
906
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
907
('replace', 3,4, 3,4)
909
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
913
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
916
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
917
('replace', 2,3, 2,3),
920
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
921
('replace', 2,3, 2,5),
924
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
925
('insert', 2,2, 2,5),
928
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
[('equal', 0,6, 0,6),
930
('insert', 6,6, 6,11),
931
('equal', 6,16, 11,21)
936
, 'how are you today?\n'],
938
, 'how are you today?\n'],
939
[('equal', 0,1, 0,1),
940
('delete', 1,2, 1,1),
943
chk_ops('aBccDe', 'abccde',
944
[('equal', 0,1, 0,1),
945
('replace', 1,5, 1,5),
948
chk_ops('aBcDec', 'abcdec',
949
[('equal', 0,1, 0,1),
950
('replace', 1,2, 1,2),
952
('replace', 3,4, 3,4),
955
chk_ops('aBcdEcdFg', 'abcdecdfg',
956
[('equal', 0,1, 0,1),
957
('replace', 1,8, 1,8),
960
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
961
[('equal', 0,1, 0,1),
962
('replace', 1,2, 1,2),
964
('delete', 4,5, 4,4),
966
('delete', 6,7, 5,5),
968
('replace', 9,10, 7,8),
969
('equal', 10,11, 8,9)
972
def test_grouped_opcodes(self):
973
def chk_ops(a, b, expected_codes, n=3):
974
s = self._PatienceSequenceMatcher(None, a, b)
975
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
979
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
981
chk_ops('abcd', 'abcd', [])
982
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
983
('replace', 3,4, 3,4)
985
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
989
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
[[('equal', 3,6, 3,6),
991
('insert', 6,6, 6,11),
992
('equal', 6,9, 11,14)
994
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
[[('equal', 2,6, 2,6),
996
('insert', 6,6, 6,11),
997
('equal', 6,10, 11,15)
999
chk_ops('Xabcdef', 'abcdef',
1000
[[('delete', 0,1, 0,0),
1003
chk_ops('abcdef', 'abcdefX',
1004
[[('equal', 3,6, 3,6),
1005
('insert', 6,6, 6,7)
1009
def test_multiple_ranges(self):
1010
# There was an earlier bug where we used a bad set of ranges,
1011
# this triggers that specific bug, to make sure it doesn't regress
1012
self.assertDiffBlocks('abcdefghijklmnop',
1013
'abcXghiYZQRSTUVWXYZijklmnop',
1014
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1016
self.assertDiffBlocks('ABCd efghIjk L',
1017
'AxyzBCn mo pqrstuvwI1 2 L',
1018
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1020
# These are rot13 code snippets.
1021
self.assertDiffBlocks('''\
1022
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1024
gnxrf_netf = ['svyr*']
1025
gnxrf_bcgvbaf = ['ab-erphefr']
1027
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1028
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1030
ercbegre = nqq_ercbegre_ahyy
1032
ercbegre = nqq_ercbegre_cevag
1033
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1036
pynff pzq_zxqve(Pbzznaq):
1037
'''.splitlines(True), '''\
1038
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1040
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1043
gnxrf_netf = ['svyr*']
1044
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1046
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1051
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1052
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1054
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1056
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1058
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1060
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1063
pynff pzq_zxqve(Pbzznaq):
1064
'''.splitlines(True)
1065
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1067
def test_patience_unified_diff(self):
1068
txt_a = ['hello there\n',
1070
'how are you today?\n']
1071
txt_b = ['hello there\n',
1072
'how are you today?\n']
1073
unified_diff = patiencediff.unified_diff
1074
psm = self._PatienceSequenceMatcher
1075
self.assertEquals(['--- \n',
1077
'@@ -1,3 +1,2 @@\n',
1080
' how are you today?\n'
1082
, list(unified_diff(txt_a, txt_b,
1083
sequencematcher=psm)))
1084
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1085
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1086
# This is the result with LongestCommonSubstring matching
1087
self.assertEquals(['--- \n',
1089
'@@ -1,6 +1,11 @@\n',
1101
, list(unified_diff(txt_a, txt_b)))
1102
# And the patience diff
1103
self.assertEquals(['--- \n',
1105
'@@ -4,6 +4,11 @@\n',
1118
, list(unified_diff(txt_a, txt_b,
1119
sequencematcher=psm)))
1121
def test_patience_unified_diff_with_dates(self):
1122
txt_a = ['hello there\n',
1124
'how are you today?\n']
1125
txt_b = ['hello there\n',
1126
'how are you today?\n']
1127
unified_diff = patiencediff.unified_diff
1128
psm = self._PatienceSequenceMatcher
1129
self.assertEquals(['--- a\t2008-08-08\n',
1130
'+++ b\t2008-09-09\n',
1131
'@@ -1,3 +1,2 @@\n',
1134
' how are you today?\n'
1136
, list(unified_diff(txt_a, txt_b,
1137
fromfile='a', tofile='b',
1138
fromfiledate='2008-08-08',
1139
tofiledate='2008-09-09',
1140
sequencematcher=psm)))
1143
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1145
_test_needs_features = [compiled_patiencediff_feature]
1148
super(TestPatienceDiffLib_c, self).setUp()
1149
from bzrlib import _patiencediff_c
1150
self._unique_lcs = _patiencediff_c.unique_lcs_c
1151
self._recurse_matches = _patiencediff_c.recurse_matches_c
1152
self._PatienceSequenceMatcher = \
1153
_patiencediff_c.PatienceSequenceMatcher_c
1155
def test_unhashable(self):
1156
"""We should get a proper exception here."""
1157
# We need to be able to hash items in the sequence, lists are
1158
# unhashable, and thus cannot be diffed
1159
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1161
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1162
None, ['valid', []], [])
1163
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1164
None, ['valid'], [[]])
1165
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1166
None, ['valid'], ['valid', []])
1169
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1172
super(TestPatienceDiffLibFiles, self).setUp()
1173
self._PatienceSequenceMatcher = \
1174
_patiencediff_py.PatienceSequenceMatcher_py
1176
def test_patience_unified_diff_files(self):
1177
txt_a = ['hello there\n',
1179
'how are you today?\n']
1180
txt_b = ['hello there\n',
1181
'how are you today?\n']
1182
open('a1', 'wb').writelines(txt_a)
1183
open('b1', 'wb').writelines(txt_b)
1185
unified_diff_files = patiencediff.unified_diff_files
1186
psm = self._PatienceSequenceMatcher
1187
self.assertEquals(['--- a1\n',
1189
'@@ -1,3 +1,2 @@\n',
1192
' how are you today?\n',
1194
, list(unified_diff_files('a1', 'b1',
1195
sequencematcher=psm)))
1197
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1198
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1199
open('a2', 'wb').writelines(txt_a)
1200
open('b2', 'wb').writelines(txt_b)
1202
# This is the result with LongestCommonSubstring matching
1203
self.assertEquals(['--- a2\n',
1205
'@@ -1,6 +1,11 @@\n',
1217
, list(unified_diff_files('a2', 'b2')))
1219
# And the patience diff
1220
self.assertEquals(['--- a2\n',
1222
'@@ -4,6 +4,11 @@\n',
1235
, list(unified_diff_files('a2', 'b2',
1236
sequencematcher=psm)))
1239
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1241
_test_needs_features = [compiled_patiencediff_feature]
1244
super(TestPatienceDiffLibFiles_c, self).setUp()
1245
from bzrlib import _patiencediff_c
1246
self._PatienceSequenceMatcher = \
1247
_patiencediff_c.PatienceSequenceMatcher_c
1250
class TestUsingCompiledIfAvailable(tests.TestCase):
1252
def test_PatienceSequenceMatcher(self):
1253
if compiled_patiencediff_feature.available():
1254
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1255
self.assertIs(PatienceSequenceMatcher_c,
1256
patiencediff.PatienceSequenceMatcher)
1258
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1259
self.assertIs(PatienceSequenceMatcher_py,
1260
patiencediff.PatienceSequenceMatcher)
1262
def test_unique_lcs(self):
1263
if compiled_patiencediff_feature.available():
1264
from bzrlib._patiencediff_c import unique_lcs_c
1265
self.assertIs(unique_lcs_c,
1266
patiencediff.unique_lcs)
1268
from bzrlib._patiencediff_py import unique_lcs_py
1269
self.assertIs(unique_lcs_py,
1270
patiencediff.unique_lcs)
1272
def test_recurse_matches(self):
1273
if compiled_patiencediff_feature.available():
1274
from bzrlib._patiencediff_c import recurse_matches_c
1275
self.assertIs(recurse_matches_c,
1276
patiencediff.recurse_matches)
1278
from bzrlib._patiencediff_py import recurse_matches_py
1279
self.assertIs(recurse_matches_py,
1280
patiencediff.recurse_matches)
864
b'.*a-file(.|\n)*b-file')
1283
867
class TestDiffFromTool(tests.TestCaseWithTransport):
1356
940
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1358
942
def test_prepare_files(self):
1360
944
tree = self.make_branch_and_tree('tree')
1361
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1362
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1363
tree.add('oldname', 'file-id')
1364
tree.add('oldname2', 'file2-id')
945
self.build_tree_contents([('tree/oldname', b'oldcontent')])
946
self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
947
tree.add('oldname', b'file-id')
948
tree.add('oldname2', b'file2-id')
1365
949
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1366
950
tree.commit('old tree', timestamp=315532800)
1367
951
tree.rename_one('oldname', 'newname')
1368
952
tree.rename_one('oldname2', 'newname2')
1369
self.build_tree_contents([('tree/newname', 'newcontent')])
1370
self.build_tree_contents([('tree/newname2', 'newcontent2')])
953
self.build_tree_contents([('tree/newname', b'newcontent')])
954
self.build_tree_contents([('tree/newname2', b'newcontent2')])
1371
955
old_tree = tree.basis_tree()
1372
956
old_tree.lock_read()
1373
957
self.addCleanup(old_tree.unlock)
1374
958
tree.lock_read()
1375
959
self.addCleanup(tree.unlock)
1376
diff_obj = diff.DiffFromTool(['python', '-c',
960
diff_obj = diff.DiffFromTool([sys.executable, '-c',
1377
961
'print "@old_path @new_path"'],
1378
962
old_tree, tree, output)
1379
963
self.addCleanup(diff_obj.finish)
1380
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1381
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
964
self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
965
old_path, new_path = diff_obj._prepare_files(
966
'oldname', 'newname')
1383
967
self.assertContainsRe(old_path, 'old/oldname$')
1384
968
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
969
self.assertContainsRe(new_path, 'tree/newname$')
1386
self.assertFileEqual('oldcontent', old_path)
1387
self.assertFileEqual('newcontent', new_path)
970
self.assertFileEqual(b'oldcontent', old_path)
971
self.assertFileEqual(b'newcontent', new_path)
1388
972
if osutils.host_os_dereferences_symlinks():
1389
973
self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
974
# make sure we can create files with the same parent directories
1391
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
975
diff_obj._prepare_files('oldname2', 'newname2')
978
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
980
def test_encodable_filename(self):
981
# Just checks file path for external diff tool.
982
# We cannot change CPython's internal encoding used by os.exec*.
983
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
985
for _, scenario in EncodingAdapter.encoding_scenarios:
986
encoding = scenario['encoding']
987
dirname = scenario['info']['directory']
988
filename = scenario['info']['filename']
990
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
991
relpath = dirname + u'/' + filename
992
fullpath = diffobj._safe_filename('safe', relpath)
993
self.assertEqual(fullpath,
994
fullpath.encode(encoding).decode(encoding))
995
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
997
def test_unencodable_filename(self):
998
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1000
for _, scenario in EncodingAdapter.encoding_scenarios:
1001
encoding = scenario['encoding']
1002
dirname = scenario['info']['directory']
1003
filename = scenario['info']['filename']
1005
if encoding == 'iso-8859-1':
1006
encoding = 'iso-8859-2'
1008
encoding = 'iso-8859-1'
1010
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1011
relpath = dirname + u'/' + filename
1012
fullpath = diffobj._safe_filename('safe', relpath)
1013
self.assertEqual(fullpath,
1014
fullpath.encode(encoding).decode(encoding))
1015
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1394
1018
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1396
1020
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1397
"""Call get_trees_and_branches_to_diff_locked. Overridden by
1398
TestGetTreesAndBranchesToDiff.
1021
"""Call get_trees_and_branches_to_diff_locked."""
1400
1022
return diff.get_trees_and_branches_to_diff_locked(
1401
1023
path_list, revision_specs, old_url, new_url, self.addCleanup)