77
"""Simple file-like object that allows writes with any type and records."""
80
self.write_record = []
82
def write(self, data):
83
self.write_record.append(data)
85
def check_types(self, testcase, expected_type):
87
any(not isinstance(o, expected_type) for o in self.write_record),
88
"Not all writes of type %s: %r" % (
89
expected_type.__name__, self.write_record))
92
class TestDiffOptions(tests.TestCase):
94
def test_unified_added(self):
95
"""Check for default style '-u' only if no other style specified
98
# Verify that style defaults to unified, id est '-u' appended
99
# to option list, in the absence of an alternative style.
100
self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
103
class TestDiffOptionsScenarios(tests.TestCase):
105
scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
106
style = None # Set by load_tests_apply_scenarios from scenarios
108
def test_unified_not_added(self):
109
# Verify that for all valid style options, '-u' is not
110
# appended to option list.
111
ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
112
self.assertEqual(["%s" % (self.style,)], ret_opts)
115
84
class TestDiff(tests.TestCase):
117
86
def test_add_nl(self):
118
87
"""diff generates a valid diff for patches that add a newline"""
119
lines = udiff_lines([b'boo'], [b'boo\n'])
88
lines = udiff_lines(['boo'], ['boo\n'])
120
89
self.check_patch(lines)
121
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
122
## "expected no-nl, got %r" % lines[4]
90
self.assertEquals(lines[4], '\\ No newline at end of file\n')
91
## "expected no-nl, got %r" % lines[4]
124
93
def test_add_nl_2(self):
125
94
"""diff generates a valid diff for patches that change last line and
128
lines = udiff_lines([b'boo'], [b'goo\n'])
97
lines = udiff_lines(['boo'], ['goo\n'])
129
98
self.check_patch(lines)
130
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
131
## "expected no-nl, got %r" % lines[4]
99
self.assertEquals(lines[4], '\\ No newline at end of file\n')
100
## "expected no-nl, got %r" % lines[4]
133
102
def test_remove_nl(self):
134
103
"""diff generates a valid diff for patches that change last line and
137
lines = udiff_lines([b'boo\n'], [b'boo'])
106
lines = udiff_lines(['boo\n'], ['boo'])
138
107
self.check_patch(lines)
139
self.assertEqual(lines[5], b'\\ No newline at end of file\n')
140
## "expected no-nl, got %r" % lines[5]
108
self.assertEquals(lines[5], '\\ No newline at end of file\n')
109
## "expected no-nl, got %r" % lines[5]
142
111
def check_patch(self, lines):
143
self.assertTrue(len(lines) > 1)
144
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
145
self.assertTrue(lines[0].startswith(b'---'))
146
## 'No orig line for patch:\n%s' % "".join(lines)
147
self.assertTrue(lines[1].startswith(b'+++'))
148
## 'No mod line for patch:\n%s' % "".join(lines)
149
self.assertTrue(len(lines) > 2)
150
## "No hunks for patch:\n%s" % "".join(lines)
151
self.assertTrue(lines[2].startswith(b'@@'))
152
## "No hunk header for patch:\n%s" % "".join(lines)
153
self.assertTrue(b'@@' in lines[2][2:])
154
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
112
self.assert_(len(lines) > 1)
113
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
114
self.assert_(lines[0].startswith ('---'))
115
## 'No orig line for patch:\n%s' % "".join(lines)
116
self.assert_(lines[1].startswith ('+++'))
117
## 'No mod line for patch:\n%s' % "".join(lines)
118
self.assert_(len(lines) > 2)
119
## "No hunks for patch:\n%s" % "".join(lines)
120
self.assert_(lines[2].startswith('@@'))
121
## "No hunk header for patch:\n%s" % "".join(lines)
122
self.assert_('@@' in lines[2][2:])
123
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
156
125
def test_binary_lines(self):
158
uni_lines = [1023 * b'a' + b'\x00']
159
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
127
uni_lines = [1023 * 'a' + '\x00']
128
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
160
129
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
161
udiff_lines(uni_lines, empty, allow_binary=True)
130
udiff_lines(uni_lines , empty, allow_binary=True)
162
131
udiff_lines(empty, uni_lines, allow_binary=True)
164
133
def test_external_diff(self):
165
lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
134
lines = external_udiff_lines(['boo\n'], ['goo\n'])
166
135
self.check_patch(lines)
167
self.assertEqual(b'\n', lines[-1])
136
self.assertEqual('\n', lines[-1])
169
138
def test_external_diff_no_fileno(self):
170
139
# Make sure that we can handle not having a fileno, even
171
140
# if the diff is large
172
lines = external_udiff_lines([b'boo\n'] * 10000,
141
lines = external_udiff_lines(['boo\n']*10000,
174
143
use_stringio=True)
175
144
self.check_patch(lines)
177
146
def test_external_diff_binary_lang_c(self):
178
148
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
179
self.overrideEnv(lang, 'C')
180
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
181
# Older versions of diffutils say "Binary files", newer
182
# versions just say "Files".
183
self.assertContainsRe(
184
lines[0], b'(Binary f|F)iles old and new differ\n')
185
self.assertEqual(lines[1:], [b'\n'])
149
old_env[lang] = osutils.set_or_unset_env(lang, 'C')
151
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
152
# Older versions of diffutils say "Binary files", newer
153
# versions just say "Files".
154
self.assertContainsRe(lines[0],
155
'(Binary f|F)iles old and new differ\n')
156
self.assertEquals(lines[1:], ['\n'])
158
for lang, old_val in old_env.iteritems():
159
osutils.set_or_unset_env(lang, old_val)
187
161
def test_no_external_diff(self):
188
162
"""Check that NoDiff is raised when diff is not available"""
189
# Make sure no 'diff' command is available
190
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
191
self.overrideEnv('PATH', '')
192
self.assertRaises(errors.NoDiff, diff.external_diff,
193
b'old', [b'boo\n'], b'new', [b'goo\n'],
194
BytesIO(), diff_opts=['-u'])
163
# Use os.environ['PATH'] to make sure no 'diff' command is available
164
orig_path = os.environ['PATH']
166
os.environ['PATH'] = ''
167
self.assertRaises(errors.NoDiff, diff.external_diff,
168
'old', ['boo\n'], 'new', ['goo\n'],
169
StringIO(), diff_opts=['-u'])
171
os.environ['PATH'] = orig_path
196
173
def test_internal_diff_default(self):
197
174
# Default internal diff encoding is utf8
199
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
200
u'new_\xe5', [b'new_text\n'], output)
176
diff.internal_diff(u'old_\xb5', ['old_text\n'],
177
u'new_\xe5', ['new_text\n'], output)
201
178
lines = output.getvalue().splitlines(True)
202
179
self.check_patch(lines)
203
self.assertEqual([b'--- old_\xc2\xb5\n',
204
b'+++ new_\xc3\xa5\n',
205
b'@@ -1,1 +1,1 @@\n',
180
self.assertEquals(['--- old_\xc2\xb5\n',
181
'+++ new_\xc3\xa5\n',
211
189
def test_internal_diff_utf8(self):
213
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
214
u'new_\xe5', [b'new_text\n'], output,
191
diff.internal_diff(u'old_\xb5', ['old_text\n'],
192
u'new_\xe5', ['new_text\n'], output,
215
193
path_encoding='utf8')
216
194
lines = output.getvalue().splitlines(True)
217
195
self.check_patch(lines)
218
self.assertEqual([b'--- old_\xc2\xb5\n',
219
b'+++ new_\xc3\xa5\n',
220
b'@@ -1,1 +1,1 @@\n',
196
self.assertEquals(['--- old_\xc2\xb5\n',
197
'+++ new_\xc3\xa5\n',
226
205
def test_internal_diff_iso_8859_1(self):
228
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
229
u'new_\xe5', [b'new_text\n'], output,
207
diff.internal_diff(u'old_\xb5', ['old_text\n'],
208
u'new_\xe5', ['new_text\n'], output,
230
209
path_encoding='iso-8859-1')
231
210
lines = output.getvalue().splitlines(True)
232
211
self.check_patch(lines)
233
self.assertEqual([b'--- old_\xb5\n',
235
b'@@ -1,1 +1,1 @@\n',
212
self.assertEquals(['--- old_\xb5\n',
241
221
def test_internal_diff_no_content(self):
243
223
diff.internal_diff(u'old', [], u'new', [], output)
244
self.assertEqual(b'', output.getvalue())
224
self.assertEqual('', output.getvalue())
246
226
def test_internal_diff_no_changes(self):
248
diff.internal_diff(u'old', [b'text\n', b'contents\n'],
249
u'new', [b'text\n', b'contents\n'],
228
diff.internal_diff(u'old', ['text\n', 'contents\n'],
229
u'new', ['text\n', 'contents\n'],
251
self.assertEqual(b'', output.getvalue())
231
self.assertEqual('', output.getvalue())
253
233
def test_internal_diff_returns_bytes(self):
255
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
256
u'new_\xe5', [b'new_text\n'], output)
257
output.check_types(self, bytes)
259
def test_internal_diff_default_context(self):
261
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
262
b'same_text\n', b'same_text\n', b'old_text\n'],
263
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
264
b'same_text\n', b'same_text\n', b'new_text\n'], output)
265
lines = output.getvalue().splitlines(True)
266
self.check_patch(lines)
267
self.assertEqual([b'--- old\n',
269
b'@@ -3,4 +3,4 @@\n',
278
def test_internal_diff_no_context(self):
280
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
281
b'same_text\n', b'same_text\n', b'old_text\n'],
282
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
283
b'same_text\n', b'same_text\n', b'new_text\n'], output,
285
lines = output.getvalue().splitlines(True)
286
self.check_patch(lines)
287
self.assertEqual([b'--- old\n',
289
b'@@ -6,1 +6,1 @@\n',
295
def test_internal_diff_more_context(self):
297
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
298
b'same_text\n', b'same_text\n', b'old_text\n'],
299
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
300
b'same_text\n', b'same_text\n', b'new_text\n'], output,
302
lines = output.getvalue().splitlines(True)
303
self.check_patch(lines)
304
self.assertEqual([b'--- old\n',
306
b'@@ -2,5 +2,5 @@\n',
235
output = StringIO.StringIO()
236
diff.internal_diff(u'old_\xb5', ['old_text\n'],
237
u'new_\xe5', ['new_text\n'], output)
238
self.failUnless(isinstance(output.getvalue(), str),
239
'internal_diff should return bytestrings')
317
242
class TestDiffFiles(tests.TestCaseInTempDir):
319
244
def test_external_diff_binary(self):
320
245
"""The output when using external diff should use diff's i18n error"""
321
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
322
self.overrideEnv(lang, 'C')
323
246
# Make sure external_diff doesn't fail in the current LANG
324
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
247
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
326
249
cmd = ['diff', '-u', '--binary', 'old', 'new']
327
with open('old', 'wb') as f:
328
f.write(b'\x00foobar\n')
329
with open('new', 'wb') as f:
330
f.write(b'foo\x00bar\n')
250
open('old', 'wb').write('\x00foobar\n')
251
open('new', 'wb').write('foo\x00bar\n')
331
252
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
332
stdin=subprocess.PIPE)
253
stdin=subprocess.PIPE)
333
254
out, err = pipe.communicate()
255
# Diff returns '2' on Binary files.
256
self.assertEqual(2, pipe.returncode)
334
257
# We should output whatever diff tells us, plus a trailing newline
335
self.assertEqual(out.splitlines(True) + [b'\n'], lines)
338
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
340
if working_tree is not None:
341
extra_trees = (working_tree,)
344
diff.show_diff_trees(tree1, tree2, output,
345
specific_files=specific_files,
346
extra_trees=extra_trees, old_label='old/',
348
return output.getvalue()
351
class TestDiffDates(tests.TestCaseWithTransport):
258
self.assertEqual(out.splitlines(True) + ['\n'], lines)
261
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
262
"""Has a helper for running show_diff_trees"""
264
def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
266
if working_tree is not None:
267
extra_trees = (working_tree,)
270
diff.show_diff_trees(tree1, tree2, output,
271
specific_files=specific_files,
272
extra_trees=extra_trees, old_label='old/',
274
return output.getvalue()
277
class TestDiffDates(TestShowDiffTreesHelper):
354
280
super(TestDiffDates, self).setUp()
355
281
self.wt = self.make_branch_and_tree('.')
356
282
self.b = self.wt.branch
357
283
self.build_tree_contents([
358
('file1', b'file1 contents at rev 1\n'),
359
('file2', b'file2 contents at rev 1\n')
284
('file1', 'file1 contents at rev 1\n'),
285
('file2', 'file2 contents at rev 1\n')
361
287
self.wt.add(['file1', 'file2'])
363
289
message='Revision 1',
364
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
290
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
367
self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
293
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
369
295
message='Revision 2',
370
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
296
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
373
self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
299
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
375
301
message='Revision 3',
376
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
302
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
379
305
self.wt.remove(['file2'])
381
307
message='Revision 4',
382
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
308
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
385
311
self.build_tree_contents([
386
('file1', b'file1 contents in working tree\n')
312
('file1', 'file1 contents in working tree\n')
388
314
# set the date stamps for files in the working tree to known values
389
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
315
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
391
317
def test_diff_rev_tree_working_tree(self):
392
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
output = self.get_diff(self.wt.basis_tree(), self.wt)
393
319
# note that the date for old/file1 is from rev 2 rather than from
394
320
# the basis revision (rev 4)
395
self.assertEqualDiff(output, b'''\
321
self.assertEqualDiff(output, '''\
396
322
=== modified file 'file1'
397
323
--- old/file1\t2006-04-02 00:00:00 +0000
398
324
+++ new/file1\t2006-04-05 00:00:00 +0000
468
394
self.wt.add(['dir1', 'dir2'])
469
395
self.wt.rename_one('file1', 'dir1/file1')
470
old_tree = self.b.repository.revision_tree(b'rev-1')
471
new_tree = self.b.repository.revision_tree(b'rev-4')
472
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
473
working_tree=self.wt)
474
self.assertContainsRe(out, b'file1\t')
475
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
476
working_tree=self.wt)
477
self.assertNotContainsRe(out, b'file1\t')
480
class TestShowDiffTrees(tests.TestCaseWithTransport):
396
old_tree = self.b.repository.revision_tree('rev-1')
397
new_tree = self.b.repository.revision_tree('rev-4')
398
out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
399
working_tree=self.wt)
400
self.assertContainsRe(out, 'file1\t')
401
out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
402
working_tree=self.wt)
403
self.assertNotContainsRe(out, 'file1\t')
407
class TestShowDiffTrees(TestShowDiffTreesHelper):
481
408
"""Direct tests for show_diff_trees"""
483
410
def test_modified_file(self):
484
411
"""Test when a file is modified."""
485
412
tree = self.make_branch_and_tree('tree')
486
self.build_tree_contents([('tree/file', b'contents\n')])
487
tree.add(['file'], [b'file-id'])
488
tree.commit('one', rev_id=b'rev-1')
413
self.build_tree_contents([('tree/file', 'contents\n')])
414
tree.add(['file'], ['file-id'])
415
tree.commit('one', rev_id='rev-1')
490
self.build_tree_contents([('tree/file', b'new contents\n')])
491
d = get_diff_as_string(tree.basis_tree(), tree)
492
self.assertContainsRe(d, b"=== modified file 'file'\n")
493
self.assertContainsRe(d, b'--- old/file\t')
494
self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
495
self.assertContainsRe(d, b'-contents\n'
496
b'\\+new contents\n')
417
self.build_tree_contents([('tree/file', 'new contents\n')])
418
d = self.get_diff(tree.basis_tree(), tree)
419
self.assertContainsRe(d, "=== modified file 'file'\n")
420
self.assertContainsRe(d, '--- old/file\t')
421
self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
422
self.assertContainsRe(d, '-contents\n'
498
425
def test_modified_file_in_renamed_dir(self):
499
426
"""Test when a file is modified in a renamed directory."""
500
427
tree = self.make_branch_and_tree('tree')
501
428
self.build_tree(['tree/dir/'])
502
self.build_tree_contents([('tree/dir/file', b'contents\n')])
503
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
504
tree.commit('one', rev_id=b'rev-1')
429
self.build_tree_contents([('tree/dir/file', 'contents\n')])
430
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
431
tree.commit('one', rev_id='rev-1')
506
433
tree.rename_one('dir', 'other')
507
self.build_tree_contents([('tree/other/file', b'new contents\n')])
508
d = get_diff_as_string(tree.basis_tree(), tree)
509
self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
510
self.assertContainsRe(d, b"=== modified file 'other/file'\n")
434
self.build_tree_contents([('tree/other/file', 'new contents\n')])
435
d = self.get_diff(tree.basis_tree(), tree)
436
self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
437
self.assertContainsRe(d, "=== modified file 'other/file'\n")
511
438
# XXX: This is technically incorrect, because it used to be at another
512
439
# location. What to do?
513
self.assertContainsRe(d, b'--- old/dir/file\t')
514
self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
515
self.assertContainsRe(d, b'-contents\n'
516
b'\\+new contents\n')
440
self.assertContainsRe(d, '--- old/dir/file\t')
441
self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
442
self.assertContainsRe(d, '-contents\n'
518
445
def test_renamed_directory(self):
519
446
"""Test when only a directory is only renamed."""
520
447
tree = self.make_branch_and_tree('tree')
521
448
self.build_tree(['tree/dir/'])
522
self.build_tree_contents([('tree/dir/file', b'contents\n')])
523
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
524
tree.commit('one', rev_id=b'rev-1')
449
self.build_tree_contents([('tree/dir/file', 'contents\n')])
450
tree.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
451
tree.commit('one', rev_id='rev-1')
526
453
tree.rename_one('dir', 'newdir')
527
d = get_diff_as_string(tree.basis_tree(), tree)
454
d = self.get_diff(tree.basis_tree(), tree)
528
455
# Renaming a directory should be a single "you renamed this dir" even
529
456
# when there are files inside.
530
self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
457
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
532
459
def test_renamed_file(self):
533
460
"""Test when a file is only renamed."""
534
461
tree = self.make_branch_and_tree('tree')
535
self.build_tree_contents([('tree/file', b'contents\n')])
536
tree.add(['file'], [b'file-id'])
537
tree.commit('one', rev_id=b'rev-1')
462
self.build_tree_contents([('tree/file', 'contents\n')])
463
tree.add(['file'], ['file-id'])
464
tree.commit('one', rev_id='rev-1')
539
466
tree.rename_one('file', 'newname')
540
d = get_diff_as_string(tree.basis_tree(), tree)
541
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
467
d = self.get_diff(tree.basis_tree(), tree)
468
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
542
469
# We shouldn't have a --- or +++ line, because there is no content
544
self.assertNotContainsRe(d, b'---')
471
self.assertNotContainsRe(d, '---')
546
473
def test_renamed_and_modified_file(self):
547
474
"""Test when a file is only renamed."""
548
475
tree = self.make_branch_and_tree('tree')
549
self.build_tree_contents([('tree/file', b'contents\n')])
550
tree.add(['file'], [b'file-id'])
551
tree.commit('one', rev_id=b'rev-1')
476
self.build_tree_contents([('tree/file', 'contents\n')])
477
tree.add(['file'], ['file-id'])
478
tree.commit('one', rev_id='rev-1')
553
480
tree.rename_one('file', 'newname')
554
self.build_tree_contents([('tree/newname', b'new contents\n')])
555
d = get_diff_as_string(tree.basis_tree(), tree)
556
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
557
self.assertContainsRe(d, b'--- old/file\t')
558
self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
559
self.assertContainsRe(d, b'-contents\n'
560
b'\\+new contents\n')
481
self.build_tree_contents([('tree/newname', 'new contents\n')])
482
d = self.get_diff(tree.basis_tree(), tree)
483
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
484
self.assertContainsRe(d, '--- old/file\t')
485
self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
486
self.assertContainsRe(d, '-contents\n'
562
490
def test_internal_diff_exec_property(self):
563
491
tree = self.make_branch_and_tree('tree')
565
tt = tree.get_transform()
566
tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
567
tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
568
tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
569
tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
570
tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
571
tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
493
tt = transform.TreeTransform(tree)
494
tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
495
tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
496
tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
497
tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
498
tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
499
tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
573
tree.commit('one', rev_id=b'rev-1')
501
tree.commit('one', rev_id='rev-1')
575
tt = tree.get_transform()
576
tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
577
tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
578
tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
579
tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
503
tt = transform.TreeTransform(tree)
504
tt.set_executability(False, tt.trans_id_file_id('a-id'))
505
tt.set_executability(True, tt.trans_id_file_id('b-id'))
506
tt.set_executability(False, tt.trans_id_file_id('c-id'))
507
tt.set_executability(True, tt.trans_id_file_id('d-id'))
581
509
tree.rename_one('c', 'new-c')
582
510
tree.rename_one('d', 'new-d')
584
d = get_diff_as_string(tree.basis_tree(), tree)
512
d = self.get_diff(tree.basis_tree(), tree)
586
self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
588
self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
590
self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
592
self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
594
self.assertNotContainsRe(d, br"file 'e'")
595
self.assertNotContainsRe(d, br"file 'f'")
514
self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
516
self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
518
self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
520
self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
522
self.assertNotContainsRe(d, r"file 'e'")
523
self.assertNotContainsRe(d, r"file 'f'")
597
525
def test_binary_unicode_filenames(self):
598
526
"""Test that contents of files are *not* encoded in UTF-8 when there
599
527
is a binary file in the diff.
601
529
# See https://bugs.launchpad.net/bugs/110092.
602
self.requireFeature(features.UnicodeFilenameFeature)
530
self.requireFeature(tests.UnicodeFilenameFeature)
532
# This bug isn't triggered with cStringIO.
533
from StringIO import StringIO
604
534
tree = self.make_branch_and_tree('tree')
605
535
alpha, omega = u'\u03b1', u'\u03c9'
606
536
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
607
537
self.build_tree_contents(
608
[('tree/' + alpha, b'\0'),
538
[('tree/' + alpha, chr(0)),
609
539
('tree/' + omega,
610
(b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
611
tree.add([alpha], [b'file-id'])
612
tree.add([omega], [b'file-id-2'])
613
diff_content = StubO()
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()
614
544
diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
615
diff_content.check_types(self, bytes)
616
d = b''.join(diff_content.write_record)
617
self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
618
self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
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"
619
548
% (alpha_utf8, alpha_utf8))
620
self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
621
self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
622
self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_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,))
624
553
def test_unicode_filename(self):
625
554
"""Test when the filename are unicode."""
626
self.requireFeature(features.UnicodeFilenameFeature)
555
self.requireFeature(tests.UnicodeFilenameFeature)
628
557
alpha, omega = u'\u03b1', u'\u03c9'
629
558
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
631
560
tree = self.make_branch_and_tree('tree')
632
self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
633
tree.add(['ren_' + alpha], [b'file-id-2'])
634
self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
635
tree.add(['del_' + alpha], [b'file-id-3'])
636
self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
637
tree.add(['mod_' + alpha], [b'file-id-4'])
639
tree.commit('one', rev_id=b'rev-1')
641
tree.rename_one('ren_' + alpha, 'ren_' + omega)
642
tree.remove('del_' + alpha)
643
self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
644
tree.add(['add_' + alpha], [b'file-id'])
645
self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
647
d = get_diff_as_string(tree.basis_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)
648
577
self.assertContainsRe(d,
649
b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
650
self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
651
self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
652
self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
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)
654
583
def test_unicode_filename_path_encoding(self):
655
584
"""Test for bug #382699: unicode filenames on Windows should be shown
656
585
in user encoding.
658
self.requireFeature(features.UnicodeFilenameFeature)
587
self.requireFeature(tests.UnicodeFilenameFeature)
659
588
# The word 'test' in Russian
660
589
_russian_test = u'\u0422\u0435\u0441\u0442'
661
590
directory = _russian_test + u'/'
714
644
self.new_tree = self.make_branch_and_tree('new-tree')
715
645
self.new_tree.lock_write()
716
646
self.addCleanup(self.new_tree.unlock)
717
self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
647
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
719
649
def test_diff_text(self):
720
650
self.build_tree_contents([('old-tree/olddir/',),
721
('old-tree/olddir/oldfile', b'old\n')])
651
('old-tree/olddir/oldfile', 'old\n')])
722
652
self.old_tree.add('olddir')
723
self.old_tree.add('olddir/oldfile', b'file-id')
653
self.old_tree.add('olddir/oldfile', 'file-id')
724
654
self.build_tree_contents([('new-tree/newdir/',),
725
('new-tree/newdir/newfile', b'new\n')])
655
('new-tree/newdir/newfile', 'new\n')])
726
656
self.new_tree.add('newdir')
727
self.new_tree.add('newdir/newfile', b'file-id')
728
differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
729
differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
731
b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
732
differ.to_file.getvalue())
733
differ.to_file.seek(0)
734
differ.diff_text(None, 'newdir/newfile',
735
'old label', 'new label')
737
b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
738
differ.to_file.getvalue())
739
differ.to_file.seek(0)
740
differ.diff_text('olddir/oldfile', 'newdir/newfile',
741
'old label', 'new label')
743
b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
657
self.new_tree.add('newdir/newfile', 'file-id')
658
differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
659
differ.diff_text('file-id', None, 'old label', 'new label')
661
'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
662
differ.to_file.getvalue())
663
differ.to_file.seek(0)
664
differ.diff_text(None, 'file-id', 'old label', 'new label')
666
'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
667
differ.to_file.getvalue())
668
differ.to_file.seek(0)
669
differ.diff_text('file-id', 'file-id', 'old label', 'new label')
671
'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
744
672
differ.to_file.getvalue())
746
674
def test_diff_deletion(self):
747
self.build_tree_contents([('old-tree/file', b'contents'),
748
('new-tree/file', b'contents')])
749
self.old_tree.add('file', b'file-id')
750
self.new_tree.add('file', b'file-id')
675
self.build_tree_contents([('old-tree/file', 'contents'),
676
('new-tree/file', 'contents')])
677
self.old_tree.add('file', 'file-id')
678
self.new_tree.add('file', 'file-id')
751
679
os.unlink('new-tree/file')
752
680
self.differ.show_diff(None)
753
self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
681
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
755
683
def test_diff_creation(self):
756
self.build_tree_contents([('old-tree/file', b'contents'),
757
('new-tree/file', b'contents')])
758
self.old_tree.add('file', b'file-id')
759
self.new_tree.add('file', b'file-id')
684
self.build_tree_contents([('old-tree/file', 'contents'),
685
('new-tree/file', 'contents')])
686
self.old_tree.add('file', 'file-id')
687
self.new_tree.add('file', 'file-id')
760
688
os.unlink('old-tree/file')
761
689
self.differ.show_diff(None)
762
self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
690
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
764
692
def test_diff_symlink(self):
765
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
693
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
766
694
differ.diff_symlink('old target', None)
767
self.assertEqual(b"=== target was 'old target'\n",
695
self.assertEqual("=== target was 'old target'\n",
768
696
differ.to_file.getvalue())
770
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
698
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
771
699
differ.diff_symlink(None, 'new target')
772
self.assertEqual(b"=== target is 'new target'\n",
700
self.assertEqual("=== target is 'new target'\n",
773
701
differ.to_file.getvalue())
775
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
703
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
776
704
differ.diff_symlink('old target', 'new target')
777
self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
705
self.assertEqual("=== target changed 'old target' => 'new target'\n",
778
706
differ.to_file.getvalue())
780
708
def test_diff(self):
781
709
self.build_tree_contents([('old-tree/olddir/',),
782
('old-tree/olddir/oldfile', b'old\n')])
710
('old-tree/olddir/oldfile', 'old\n')])
783
711
self.old_tree.add('olddir')
784
self.old_tree.add('olddir/oldfile', b'file-id')
712
self.old_tree.add('olddir/oldfile', 'file-id')
785
713
self.build_tree_contents([('new-tree/newdir/',),
786
('new-tree/newdir/newfile', b'new\n')])
714
('new-tree/newdir/newfile', 'new\n')])
787
715
self.new_tree.add('newdir')
788
self.new_tree.add('newdir/newfile', b'file-id')
789
self.differ.diff('olddir/oldfile', 'newdir/newfile')
716
self.new_tree.add('newdir/newfile', 'file-id')
717
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
790
718
self.assertContainsRe(
791
719
self.differ.to_file.getvalue(),
792
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
793
br' \@\@\n-old\n\+new\n\n')
720
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
721
' \@\@\n-old\n\+new\n\n')
795
723
def test_diff_kind_change(self):
796
self.requireFeature(features.SymlinkFeature)
724
self.requireFeature(tests.SymlinkFeature)
797
725
self.build_tree_contents([('old-tree/olddir/',),
798
('old-tree/olddir/oldfile', b'old\n')])
726
('old-tree/olddir/oldfile', 'old\n')])
799
727
self.old_tree.add('olddir')
800
self.old_tree.add('olddir/oldfile', b'file-id')
728
self.old_tree.add('olddir/oldfile', 'file-id')
801
729
self.build_tree(['new-tree/newdir/'])
802
730
os.symlink('new', 'new-tree/newdir/newfile')
803
731
self.new_tree.add('newdir')
804
self.new_tree.add('newdir/newfile', b'file-id')
805
self.differ.diff('olddir/oldfile', 'newdir/newfile')
732
self.new_tree.add('newdir/newfile', 'file-id')
733
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
806
734
self.assertContainsRe(
807
735
self.differ.to_file.getvalue(),
808
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
736
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
810
738
self.assertContainsRe(self.differ.to_file.getvalue(),
811
b"=== target is 'new'\n")
739
"=== target is u'new'\n")
813
741
def test_diff_directory(self):
814
742
self.build_tree(['new-tree/new-dir/'])
815
self.new_tree.add('new-dir', b'new-dir-id')
816
self.differ.diff(None, 'new-dir')
817
self.assertEqual(self.differ.to_file.getvalue(), b'')
743
self.new_tree.add('new-dir', 'new-dir-id')
744
self.differ.diff('new-dir-id', None, 'new-dir')
745
self.assertEqual(self.differ.to_file.getvalue(), '')
819
747
def create_old_new(self):
820
748
self.build_tree_contents([('old-tree/olddir/',),
821
('old-tree/olddir/oldfile', b'old\n')])
749
('old-tree/olddir/oldfile', 'old\n')])
822
750
self.old_tree.add('olddir')
823
self.old_tree.add('olddir/oldfile', b'file-id')
751
self.old_tree.add('olddir/oldfile', 'file-id')
824
752
self.build_tree_contents([('new-tree/newdir/',),
825
('new-tree/newdir/newfile', b'new\n')])
753
('new-tree/newdir/newfile', 'new\n')])
826
754
self.new_tree.add('newdir')
827
self.new_tree.add('newdir/newfile', b'file-id')
755
self.new_tree.add('newdir/newfile', 'file-id')
829
757
def test_register_diff(self):
830
758
self.create_old_new()
831
759
old_diff_factories = diff.DiffTree.diff_factories
832
diff.DiffTree.diff_factories = old_diff_factories[:]
760
diff.DiffTree.diff_factories=old_diff_factories[:]
833
761
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
835
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
763
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
837
765
diff.DiffTree.diff_factories = old_diff_factories
838
differ.diff('olddir/oldfile', 'newdir/newfile')
766
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
839
767
self.assertNotContainsRe(
840
768
differ.to_file.getvalue(),
841
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
842
br' \@\@\n-old\n\+new\n\n')
769
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
770
' \@\@\n-old\n\+new\n\n')
843
771
self.assertContainsRe(differ.to_file.getvalue(),
844
b'was: old\nis: new\n')
772
'was: old\nis: new\n')
846
774
def test_extra_factories(self):
847
775
self.create_old_new()
848
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
776
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
849
777
extra_factories=[DiffWasIs.from_diff_tree])
850
differ.diff('olddir/oldfile', 'newdir/newfile')
778
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
851
779
self.assertNotContainsRe(
852
780
differ.to_file.getvalue(),
853
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
854
br' \@\@\n-old\n\+new\n\n')
781
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
782
' \@\@\n-old\n\+new\n\n')
855
783
self.assertContainsRe(differ.to_file.getvalue(),
856
b'was: old\nis: new\n')
784
'was: old\nis: new\n')
858
786
def test_alphabetical_order(self):
859
787
self.build_tree(['new-tree/a-file'])
862
790
self.old_tree.add('b-file')
863
791
self.differ.show_diff(None)
864
792
self.assertContainsRe(self.differ.to_file.getvalue(),
865
b'.*a-file(.|\n)*b-file')
793
'.*a-file(.|\n)*b-file')
796
class TestPatienceDiffLib(tests.TestCase):
799
super(TestPatienceDiffLib, self).setUp()
800
self._unique_lcs = _patiencediff_py.unique_lcs_py
801
self._recurse_matches = _patiencediff_py.recurse_matches_py
802
self._PatienceSequenceMatcher = \
803
_patiencediff_py.PatienceSequenceMatcher_py
805
def test_diff_unicode_string(self):
806
a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
807
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
808
sm = self._PatienceSequenceMatcher(None, a, b)
809
mb = sm.get_matching_blocks()
810
self.assertEquals(35, len(mb))
812
def test_unique_lcs(self):
813
unique_lcs = self._unique_lcs
814
self.assertEquals(unique_lcs('', ''), [])
815
self.assertEquals(unique_lcs('', 'a'), [])
816
self.assertEquals(unique_lcs('a', ''), [])
817
self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
818
self.assertEquals(unique_lcs('a', 'b'), [])
819
self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
820
self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
821
self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
822
self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
824
self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
826
def test_recurse_matches(self):
827
def test_one(a, b, matches):
829
self._recurse_matches(
830
a, b, 0, 0, len(a), len(b), test_matches, 10)
831
self.assertEquals(test_matches, matches)
833
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
834
[(0, 0), (2, 2), (4, 4)])
835
test_one(['a', 'c', 'b', 'a', 'c'], ['a', 'b', 'c'],
836
[(0, 0), (2, 1), (4, 2)])
837
# Even though 'bc' is not unique globally, and is surrounded by
838
# non-matching lines, we should still match, because they are locally
840
test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
841
(4, 6), (5, 7), (6, 8)])
843
# recurse_matches doesn't match non-unique
844
# lines surrounded by bogus text.
845
# The update has been done in patiencediff.SequenceMatcher instead
847
# This is what it could be
848
#test_one('aBccDe', 'abccde', [(0,0), (2,2), (3,3), (5,5)])
850
# This is what it currently gives:
851
test_one('aBccDe', 'abccde', [(0,0), (5,5)])
853
def assertDiffBlocks(self, a, b, expected_blocks):
854
"""Check that the sequence matcher returns the correct blocks.
856
:param a: A sequence to match
857
:param b: Another sequence to match
858
:param expected_blocks: The expected output, not including the final
859
matching block (len(a), len(b), 0)
861
matcher = self._PatienceSequenceMatcher(None, a, b)
862
blocks = matcher.get_matching_blocks()
864
self.assertEqual((len(a), len(b), 0), last)
865
self.assertEqual(expected_blocks, blocks)
867
def test_matching_blocks(self):
868
# Some basic matching tests
869
self.assertDiffBlocks('', '', [])
870
self.assertDiffBlocks([], [], [])
871
self.assertDiffBlocks('abc', '', [])
872
self.assertDiffBlocks('', 'abc', [])
873
self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
874
self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
875
self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
876
self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
877
self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
878
self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
879
self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
880
# This may check too much, but it checks to see that
881
# a copied block stays attached to the previous section,
883
# difflib would tend to grab the trailing longest match
884
# which would make the diff not look right
885
self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
886
[(0, 0, 6), (6, 11, 10)])
888
# make sure it supports passing in lists
889
self.assertDiffBlocks(
892
'how are you today?\n'],
894
'how are you today?\n'],
895
[(0, 0, 1), (2, 1, 1)])
897
# non unique lines surrounded by non-matching lines
899
self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
901
# But they only need to be locally unique
902
self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
904
# non unique blocks won't be matched
905
self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
907
# but locally unique ones will
908
self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
909
(5,4,1), (7,5,2), (10,8,1)])
911
self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
912
self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
913
self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
915
def test_matching_blocks_tuples(self):
916
# Some basic matching tests
917
self.assertDiffBlocks([], [], [])
918
self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
919
self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
920
self.assertDiffBlocks([('a',), ('b',), ('c,')],
921
[('a',), ('b',), ('c,')],
923
self.assertDiffBlocks([('a',), ('b',), ('c,')],
924
[('a',), ('b',), ('d,')],
926
self.assertDiffBlocks([('d',), ('b',), ('c,')],
927
[('a',), ('b',), ('c,')],
929
self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
930
[('a',), ('b',), ('c,')],
932
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
933
[('a', 'b'), ('c', 'X'), ('e', 'f')],
934
[(0, 0, 1), (2, 2, 1)])
935
self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
936
[('a', 'b'), ('c', 'dX'), ('e', 'f')],
937
[(0, 0, 1), (2, 2, 1)])
939
def test_opcodes(self):
940
def chk_ops(a, b, expected_codes):
941
s = self._PatienceSequenceMatcher(None, a, b)
942
self.assertEquals(expected_codes, s.get_opcodes())
946
chk_ops('abc', '', [('delete', 0,3, 0,0)])
947
chk_ops('', 'abc', [('insert', 0,0, 0,3)])
948
chk_ops('abcd', 'abcd', [('equal', 0,4, 0,4)])
949
chk_ops('abcd', 'abce', [('equal', 0,3, 0,3),
950
('replace', 3,4, 3,4)
952
chk_ops('eabc', 'abce', [('delete', 0,1, 0,0),
956
chk_ops('eabce', 'abce', [('delete', 0,1, 0,0),
959
chk_ops('abcde', 'abXde', [('equal', 0,2, 0,2),
960
('replace', 2,3, 2,3),
963
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
964
('replace', 2,3, 2,5),
967
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
968
('insert', 2,2, 2,5),
971
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
972
[('equal', 0,6, 0,6),
973
('insert', 6,6, 6,11),
974
('equal', 6,16, 11,21)
979
, 'how are you today?\n'],
981
, 'how are you today?\n'],
982
[('equal', 0,1, 0,1),
983
('delete', 1,2, 1,1),
986
chk_ops('aBccDe', 'abccde',
987
[('equal', 0,1, 0,1),
988
('replace', 1,5, 1,5),
991
chk_ops('aBcDec', 'abcdec',
992
[('equal', 0,1, 0,1),
993
('replace', 1,2, 1,2),
995
('replace', 3,4, 3,4),
998
chk_ops('aBcdEcdFg', 'abcdecdfg',
999
[('equal', 0,1, 0,1),
1000
('replace', 1,8, 1,8),
1003
chk_ops('aBcdEeXcdFg', 'abcdecdfg',
1004
[('equal', 0,1, 0,1),
1005
('replace', 1,2, 1,2),
1006
('equal', 2,4, 2,4),
1007
('delete', 4,5, 4,4),
1008
('equal', 5,6, 4,5),
1009
('delete', 6,7, 5,5),
1010
('equal', 7,9, 5,7),
1011
('replace', 9,10, 7,8),
1012
('equal', 10,11, 8,9)
1015
def test_grouped_opcodes(self):
1016
def chk_ops(a, b, expected_codes, n=3):
1017
s = self._PatienceSequenceMatcher(None, a, b)
1018
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1022
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
1023
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1024
chk_ops('abcd', 'abcd', [])
1025
chk_ops('abcd', 'abce', [[('equal', 0,3, 0,3),
1026
('replace', 3,4, 3,4)
1028
chk_ops('eabc', 'abce', [[('delete', 0,1, 0,0),
1029
('equal', 1,4, 0,3),
1030
('insert', 4,4, 3,4)
1032
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1033
[[('equal', 3,6, 3,6),
1034
('insert', 6,6, 6,11),
1035
('equal', 6,9, 11,14)
1037
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
1038
[[('equal', 2,6, 2,6),
1039
('insert', 6,6, 6,11),
1040
('equal', 6,10, 11,15)
1042
chk_ops('Xabcdef', 'abcdef',
1043
[[('delete', 0,1, 0,0),
1046
chk_ops('abcdef', 'abcdefX',
1047
[[('equal', 3,6, 3,6),
1048
('insert', 6,6, 6,7)
1052
def test_multiple_ranges(self):
1053
# There was an earlier bug where we used a bad set of ranges,
1054
# this triggers that specific bug, to make sure it doesn't regress
1055
self.assertDiffBlocks('abcdefghijklmnop',
1056
'abcXghiYZQRSTUVWXYZijklmnop',
1057
[(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1059
self.assertDiffBlocks('ABCd efghIjk L',
1060
'AxyzBCn mo pqrstuvwI1 2 L',
1061
[(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1063
# These are rot13 code snippets.
1064
self.assertDiffBlocks('''\
1065
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1067
gnxrf_netf = ['svyr*']
1068
gnxrf_bcgvbaf = ['ab-erphefr']
1070
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1071
sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1073
ercbegre = nqq_ercbegre_ahyy
1075
ercbegre = nqq_ercbegre_cevag
1076
fzneg_nqq(svyr_yvfg, abg ab_erphefr, ercbegre)
1079
pynff pzq_zxqve(Pbzznaq):
1080
'''.splitlines(True), '''\
1081
trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1083
--qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
1086
gnxrf_netf = ['svyr*']
1087
gnxrf_bcgvbaf = ['ab-erphefr', 'qel-eha']
1089
qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr, qel_eha=Snyfr):
1094
# Guvf vf cbvagyrff, ohg V'q engure abg envfr na reebe
1095
npgvba = omeyvo.nqq.nqq_npgvba_ahyy
1097
npgvba = omeyvo.nqq.nqq_npgvba_cevag
1099
npgvba = omeyvo.nqq.nqq_npgvba_nqq
1101
npgvba = omeyvo.nqq.nqq_npgvba_nqq_naq_cevag
1103
omeyvo.nqq.fzneg_nqq(svyr_yvfg, abg ab_erphefr, npgvba)
1106
pynff pzq_zxqve(Pbzznaq):
1107
'''.splitlines(True)
1108
, [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1110
def test_patience_unified_diff(self):
1111
txt_a = ['hello there\n',
1113
'how are you today?\n']
1114
txt_b = ['hello there\n',
1115
'how are you today?\n']
1116
unified_diff = patiencediff.unified_diff
1117
psm = self._PatienceSequenceMatcher
1118
self.assertEquals(['--- \n',
1120
'@@ -1,3 +1,2 @@\n',
1123
' how are you today?\n'
1125
, list(unified_diff(txt_a, txt_b,
1126
sequencematcher=psm)))
1127
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1128
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1129
# This is the result with LongestCommonSubstring matching
1130
self.assertEquals(['--- \n',
1132
'@@ -1,6 +1,11 @@\n',
1144
, list(unified_diff(txt_a, txt_b)))
1145
# And the patience diff
1146
self.assertEquals(['--- \n',
1148
'@@ -4,6 +4,11 @@\n',
1161
, list(unified_diff(txt_a, txt_b,
1162
sequencematcher=psm)))
1164
def test_patience_unified_diff_with_dates(self):
1165
txt_a = ['hello there\n',
1167
'how are you today?\n']
1168
txt_b = ['hello there\n',
1169
'how are you today?\n']
1170
unified_diff = patiencediff.unified_diff
1171
psm = self._PatienceSequenceMatcher
1172
self.assertEquals(['--- a\t2008-08-08\n',
1173
'+++ b\t2008-09-09\n',
1174
'@@ -1,3 +1,2 @@\n',
1177
' how are you today?\n'
1179
, list(unified_diff(txt_a, txt_b,
1180
fromfile='a', tofile='b',
1181
fromfiledate='2008-08-08',
1182
tofiledate='2008-09-09',
1183
sequencematcher=psm)))
1186
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1188
_test_needs_features = [compiled_patiencediff_feature]
1191
super(TestPatienceDiffLib_c, self).setUp()
1192
from bzrlib import _patiencediff_c
1193
self._unique_lcs = _patiencediff_c.unique_lcs_c
1194
self._recurse_matches = _patiencediff_c.recurse_matches_c
1195
self._PatienceSequenceMatcher = \
1196
_patiencediff_c.PatienceSequenceMatcher_c
1198
def test_unhashable(self):
1199
"""We should get a proper exception here."""
1200
# We need to be able to hash items in the sequence, lists are
1201
# unhashable, and thus cannot be diffed
1202
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1204
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1205
None, ['valid', []], [])
1206
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1207
None, ['valid'], [[]])
1208
e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1209
None, ['valid'], ['valid', []])
1212
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
1215
super(TestPatienceDiffLibFiles, self).setUp()
1216
self._PatienceSequenceMatcher = \
1217
_patiencediff_py.PatienceSequenceMatcher_py
1219
def test_patience_unified_diff_files(self):
1220
txt_a = ['hello there\n',
1222
'how are you today?\n']
1223
txt_b = ['hello there\n',
1224
'how are you today?\n']
1225
open('a1', 'wb').writelines(txt_a)
1226
open('b1', 'wb').writelines(txt_b)
1228
unified_diff_files = patiencediff.unified_diff_files
1229
psm = self._PatienceSequenceMatcher
1230
self.assertEquals(['--- a1\n',
1232
'@@ -1,3 +1,2 @@\n',
1235
' how are you today?\n',
1237
, list(unified_diff_files('a1', 'b1',
1238
sequencematcher=psm)))
1240
txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1241
txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1242
open('a2', 'wb').writelines(txt_a)
1243
open('b2', 'wb').writelines(txt_b)
1245
# This is the result with LongestCommonSubstring matching
1246
self.assertEquals(['--- a2\n',
1248
'@@ -1,6 +1,11 @@\n',
1260
, list(unified_diff_files('a2', 'b2')))
1262
# And the patience diff
1263
self.assertEquals(['--- a2\n',
1265
'@@ -4,6 +4,11 @@\n',
1278
, list(unified_diff_files('a2', 'b2',
1279
sequencematcher=psm)))
1282
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1284
_test_needs_features = [compiled_patiencediff_feature]
1287
super(TestPatienceDiffLibFiles_c, self).setUp()
1288
from bzrlib import _patiencediff_c
1289
self._PatienceSequenceMatcher = \
1290
_patiencediff_c.PatienceSequenceMatcher_c
1293
class TestUsingCompiledIfAvailable(tests.TestCase):
1295
def test_PatienceSequenceMatcher(self):
1296
if compiled_patiencediff_feature.available():
1297
from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1298
self.assertIs(PatienceSequenceMatcher_c,
1299
patiencediff.PatienceSequenceMatcher)
1301
from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1302
self.assertIs(PatienceSequenceMatcher_py,
1303
patiencediff.PatienceSequenceMatcher)
1305
def test_unique_lcs(self):
1306
if compiled_patiencediff_feature.available():
1307
from bzrlib._patiencediff_c import unique_lcs_c
1308
self.assertIs(unique_lcs_c,
1309
patiencediff.unique_lcs)
1311
from bzrlib._patiencediff_py import unique_lcs_py
1312
self.assertIs(unique_lcs_py,
1313
patiencediff.unique_lcs)
1315
def test_recurse_matches(self):
1316
if compiled_patiencediff_feature.available():
1317
from bzrlib._patiencediff_c import recurse_matches_c
1318
self.assertIs(recurse_matches_c,
1319
patiencediff.recurse_matches)
1321
from bzrlib._patiencediff_py import recurse_matches_py
1322
self.assertIs(recurse_matches_py,
1323
patiencediff.recurse_matches)
868
1326
class TestDiffFromTool(tests.TestCaseWithTransport):
870
1328
def test_from_string(self):
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'))
1329
diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1330
self.addCleanup(diff_obj.finish)
1331
self.assertEqual(['diff', '@old_path', '@new_path'],
1332
diff_obj.command_template)
887
1334
def test_from_string_u5(self):
888
diff_obj = diff.DiffFromTool.from_string(
889
['diff', "-u 5", '{old_path}', '{new_path}'], None, None, None)
1335
diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
890
1337
self.addCleanup(diff_obj.finish)
891
self.assertEqual(['diff', '-u 5', '{old_path}', '{new_path}'],
1338
self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
892
1339
diff_obj.command_template)
893
1340
self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
894
1341
diff_obj._get_command('old-path', 'new-path'))
896
1343
def test_from_string_path_with_backslashes(self):
897
1344
self.requireFeature(features.backslashdir_feature)
898
tool = ['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}']
1345
tool = 'C:\\Tools\\Diff.exe'
899
1346
diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
900
1347
self.addCleanup(diff_obj.finish)
901
self.assertEqual(['C:\\Tools\\Diff.exe', '{old_path}', '{new_path}'],
1348
self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
902
1349
diff_obj.command_template)
903
1350
self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
904
1351
diff_obj._get_command('old-path', 'new-path'))
906
1353
def test_execute(self):
908
diff_obj = diff.DiffFromTool([sys.executable, '-c',
909
'print("{old_path} {new_path}")'],
1355
diff_obj = diff.DiffFromTool(['python', '-c',
1356
'print "@old_path @new_path"'],
910
1357
None, None, output)
911
1358
self.addCleanup(diff_obj.finish)
912
1359
diff_obj._execute('old', 'new')
913
self.assertEqual(output.getvalue().rstrip(), b'old new')
1360
self.assertEqual(output.getvalue().rstrip(), 'old new')
915
def test_execute_missing(self):
1362
def test_excute_missing(self):
916
1363
diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
917
1364
None, None, None)
918
1365
self.addCleanup(diff_obj.finish)
952
1399
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
954
1401
def test_prepare_files(self):
956
1403
tree = self.make_branch_and_tree('tree')
957
self.build_tree_contents([('tree/oldname', b'oldcontent')])
958
self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
959
tree.add('oldname', b'file-id')
960
tree.add('oldname2', b'file2-id')
1404
self.build_tree_contents([('tree/oldname', 'oldcontent')])
1405
self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1406
tree.add('oldname', 'file-id')
1407
tree.add('oldname2', 'file2-id')
961
1408
# Earliest allowable date on FAT32 filesystems is 1980-01-01
962
1409
tree.commit('old tree', timestamp=315532800)
963
1410
tree.rename_one('oldname', 'newname')
964
1411
tree.rename_one('oldname2', 'newname2')
965
self.build_tree_contents([('tree/newname', b'newcontent')])
966
self.build_tree_contents([('tree/newname2', b'newcontent2')])
1412
self.build_tree_contents([('tree/newname', 'newcontent')])
1413
self.build_tree_contents([('tree/newname2', 'newcontent2')])
967
1414
old_tree = tree.basis_tree()
968
1415
old_tree.lock_read()
969
1416
self.addCleanup(old_tree.unlock)
970
1417
tree.lock_read()
971
1418
self.addCleanup(tree.unlock)
972
diff_obj = diff.DiffFromTool([sys.executable, '-c',
973
'print "{old_path} {new_path}"'],
1419
diff_obj = diff.DiffFromTool(['python', '-c',
1420
'print "@old_path @new_path"'],
974
1421
old_tree, tree, output)
975
1422
self.addCleanup(diff_obj.finish)
976
self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
977
old_path, new_path = diff_obj._prepare_files(
978
'oldname', 'newname')
1423
self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1424
old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
979
1426
self.assertContainsRe(old_path, 'old/oldname$')
980
1427
self.assertEqual(315532800, os.stat(old_path).st_mtime)
981
1428
self.assertContainsRe(new_path, 'tree/newname$')
982
self.assertFileEqual(b'oldcontent', old_path)
983
self.assertFileEqual(b'newcontent', new_path)
1429
self.assertFileEqual('oldcontent', old_path)
1430
self.assertFileEqual('newcontent', new_path)
984
1431
if osutils.host_os_dereferences_symlinks():
985
1432
self.assertTrue(os.path.samefile('tree/newname', new_path))
986
1433
# make sure we can create files with the same parent directories
987
diff_obj._prepare_files('oldname2', 'newname2')
990
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
992
def test_encodable_filename(self):
993
# Just checks file path for external diff tool.
994
# We cannot change CPython's internal encoding used by os.exec*.
995
diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
997
for _, scenario in EncodingAdapter.encoding_scenarios:
998
encoding = scenario['encoding']
999
dirname = scenario['info']['directory']
1000
filename = scenario['info']['filename']
1002
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1003
relpath = dirname + u'/' + filename
1004
fullpath = diffobj._safe_filename('safe', relpath)
1005
self.assertEqual(fullpath,
1006
fullpath.encode(encoding).decode(encoding))
1007
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1009
def test_unencodable_filename(self):
1010
diffobj = diff.DiffFromTool(['dummy', '{old_path}', '{new_path}'],
1012
for _, scenario in EncodingAdapter.encoding_scenarios:
1013
encoding = scenario['encoding']
1014
dirname = scenario['info']['directory']
1015
filename = scenario['info']['filename']
1017
if encoding == 'iso-8859-1':
1018
encoding = 'iso-8859-2'
1020
encoding = 'iso-8859-1'
1022
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1023
relpath = dirname + u'/' + filename
1024
fullpath = diffobj._safe_filename('safe', relpath)
1025
self.assertEqual(fullpath,
1026
fullpath.encode(encoding).decode(encoding))
1027
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1434
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1030
1437
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1032
1439
def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1033
"""Call get_trees_and_branches_to_diff_locked."""
1034
exit_stack = cleanup.ExitStack()
1035
self.addCleanup(exit_stack.close)
1440
"""Call get_trees_and_branches_to_diff_locked. Overridden by
1441
TestGetTreesAndBranchesToDiff.
1036
1443
return diff.get_trees_and_branches_to_diff_locked(
1037
path_list, revision_specs, old_url, new_url, exit_stack)
1444
path_list, revision_specs, old_url, new_url, self.addCleanup)
1039
1446
def test_basic(self):
1040
1447
tree = self.make_branch_and_tree('tree')