79
"""Simple file-like object that allows writes with any type and records."""
82
self.write_record = []
84
def write(self, data):
85
self.write_record.append(data)
87
def check_types(self, testcase, expected_type):
89
any(not isinstance(o, expected_type) for o in self.write_record),
90
"Not all writes of type %s: %r" % (
91
expected_type.__name__, self.write_record))
94
class TestDiffOptions(tests.TestCase):
96
def test_unified_added(self):
97
"""Check for default style '-u' only if no other style specified
100
# Verify that style defaults to unified, id est '-u' appended
101
# to option list, in the absence of an alternative style.
102
self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
105
class TestDiffOptionsScenarios(tests.TestCase):
107
scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
108
style = None # Set by load_tests_apply_scenarios from scenarios
110
def test_unified_not_added(self):
111
# Verify that for all valid style options, '-u' is not
112
# appended to option list.
113
ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
114
self.assertEqual(["%s" % (self.style,)], ret_opts)
83
117
class TestDiff(tests.TestCase):
85
119
def test_add_nl(self):
86
120
"""diff generates a valid diff for patches that add a newline"""
87
lines = udiff_lines(['boo'], ['boo\n'])
121
lines = udiff_lines([b'boo'], [b'boo\n'])
88
122
self.check_patch(lines)
89
self.assertEquals(lines[4], '\\ No newline at end of file\n')
90
## "expected no-nl, got %r" % lines[4]
123
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
124
## "expected no-nl, got %r" % lines[4]
92
126
def test_add_nl_2(self):
93
127
"""diff generates a valid diff for patches that change last line and
96
lines = udiff_lines(['boo'], ['goo\n'])
130
lines = udiff_lines([b'boo'], [b'goo\n'])
97
131
self.check_patch(lines)
98
self.assertEquals(lines[4], '\\ No newline at end of file\n')
99
## "expected no-nl, got %r" % lines[4]
132
self.assertEqual(lines[4], b'\\ No newline at end of file\n')
133
## "expected no-nl, got %r" % lines[4]
101
135
def test_remove_nl(self):
102
136
"""diff generates a valid diff for patches that change last line and
105
lines = udiff_lines(['boo\n'], ['boo'])
139
lines = udiff_lines([b'boo\n'], [b'boo'])
106
140
self.check_patch(lines)
107
self.assertEquals(lines[5], '\\ No newline at end of file\n')
108
## "expected no-nl, got %r" % lines[5]
141
self.assertEqual(lines[5], b'\\ No newline at end of file\n')
142
## "expected no-nl, got %r" % lines[5]
110
144
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)
145
self.assertTrue(len(lines) > 1)
146
## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
147
self.assertTrue(lines[0].startswith(b'---'))
148
## 'No orig line for patch:\n%s' % "".join(lines)
149
self.assertTrue(lines[1].startswith(b'+++'))
150
## 'No mod line for patch:\n%s' % "".join(lines)
151
self.assertTrue(len(lines) > 2)
152
## "No hunks for patch:\n%s" % "".join(lines)
153
self.assertTrue(lines[2].startswith(b'@@'))
154
## "No hunk header for patch:\n%s" % "".join(lines)
155
self.assertTrue(b'@@' in lines[2][2:])
156
## "Unterminated hunk header for patch:\n%s" % "".join(lines)
124
158
def test_binary_lines(self):
126
uni_lines = [1023 * 'a' + '\x00']
127
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
160
uni_lines = [1023 * b'a' + b'\x00']
161
self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines, empty)
128
162
self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
129
udiff_lines(uni_lines , empty, allow_binary=True)
163
udiff_lines(uni_lines, empty, allow_binary=True)
130
164
udiff_lines(empty, uni_lines, allow_binary=True)
132
166
def test_external_diff(self):
133
lines = external_udiff_lines(['boo\n'], ['goo\n'])
167
lines = external_udiff_lines([b'boo\n'], [b'goo\n'])
134
168
self.check_patch(lines)
135
self.assertEqual('\n', lines[-1])
169
self.assertEqual(b'\n', lines[-1])
137
171
def test_external_diff_no_fileno(self):
138
172
# Make sure that we can handle not having a fileno, even
139
173
# if the diff is large
140
lines = external_udiff_lines(['boo\n']*10000,
174
lines = external_udiff_lines([b'boo\n'] * 10000,
142
176
use_stringio=True)
143
177
self.check_patch(lines)
145
179
def test_external_diff_binary_lang_c(self):
147
180
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)
181
self.overrideEnv(lang, 'C')
182
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
183
# Older versions of diffutils say "Binary files", newer
184
# versions just say "Files".
185
self.assertContainsRe(
186
lines[0], b'(Binary f|F)iles old and new differ\n')
187
self.assertEqual(lines[1:], [b'\n'])
160
189
def test_no_external_diff(self):
161
190
"""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
191
# Make sure no 'diff' command is available
192
# XXX: Weird, using None instead of '' breaks the test -- vila 20101216
193
self.overrideEnv('PATH', '')
194
self.assertRaises(errors.NoDiff, diff.external_diff,
195
b'old', [b'boo\n'], b'new', [b'goo\n'],
196
BytesIO(), diff_opts=['-u'])
172
198
def test_internal_diff_default(self):
173
199
# Default internal diff encoding is utf8
175
diff.internal_diff(u'old_\xb5', ['old_text\n'],
176
u'new_\xe5', ['new_text\n'], output)
201
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
202
u'new_\xe5', [b'new_text\n'], output)
177
203
lines = output.getvalue().splitlines(True)
178
204
self.check_patch(lines)
179
self.assertEquals(['--- old_\xc2\xb5\n',
180
'+++ new_\xc3\xa5\n',
205
self.assertEqual([b'--- old_\xc2\xb5\n',
206
b'+++ new_\xc3\xa5\n',
207
b'@@ -1,1 +1,1 @@\n',
188
213
def test_internal_diff_utf8(self):
190
diff.internal_diff(u'old_\xb5', ['old_text\n'],
191
u'new_\xe5', ['new_text\n'], output,
215
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
216
u'new_\xe5', [b'new_text\n'], output,
192
217
path_encoding='utf8')
193
218
lines = output.getvalue().splitlines(True)
194
219
self.check_patch(lines)
195
self.assertEquals(['--- old_\xc2\xb5\n',
196
'+++ new_\xc3\xa5\n',
220
self.assertEqual([b'--- old_\xc2\xb5\n',
221
b'+++ new_\xc3\xa5\n',
222
b'@@ -1,1 +1,1 @@\n',
204
228
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,
230
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
231
u'new_\xe5', [b'new_text\n'], output,
208
232
path_encoding='iso-8859-1')
209
233
lines = output.getvalue().splitlines(True)
210
234
self.check_patch(lines)
211
self.assertEquals(['--- old_\xb5\n',
235
self.assertEqual([b'--- old_\xb5\n',
237
b'@@ -1,1 +1,1 @@\n',
220
243
def test_internal_diff_no_content(self):
222
245
diff.internal_diff(u'old', [], u'new', [], output)
223
self.assertEqual('', output.getvalue())
246
self.assertEqual(b'', output.getvalue())
225
248
def test_internal_diff_no_changes(self):
227
diff.internal_diff(u'old', ['text\n', 'contents\n'],
228
u'new', ['text\n', 'contents\n'],
250
diff.internal_diff(u'old', [b'text\n', b'contents\n'],
251
u'new', [b'text\n', b'contents\n'],
230
self.assertEqual('', output.getvalue())
253
self.assertEqual(b'', output.getvalue())
232
255
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')
257
diff.internal_diff(u'old_\xb5', [b'old_text\n'],
258
u'new_\xe5', [b'new_text\n'], output)
259
output.check_types(self, bytes)
261
def test_internal_diff_default_context(self):
263
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
264
b'same_text\n', b'same_text\n', b'old_text\n'],
265
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
266
b'same_text\n', b'same_text\n', b'new_text\n'], output)
267
lines = output.getvalue().splitlines(True)
268
self.check_patch(lines)
269
self.assertEqual([b'--- old\n',
271
b'@@ -3,4 +3,4 @@\n',
280
def test_internal_diff_no_context(self):
282
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
283
b'same_text\n', b'same_text\n', b'old_text\n'],
284
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
285
b'same_text\n', b'same_text\n', b'new_text\n'], output,
287
lines = output.getvalue().splitlines(True)
288
self.check_patch(lines)
289
self.assertEqual([b'--- old\n',
291
b'@@ -6,1 +6,1 @@\n',
297
def test_internal_diff_more_context(self):
299
diff.internal_diff('old', [b'same_text\n', b'same_text\n', b'same_text\n',
300
b'same_text\n', b'same_text\n', b'old_text\n'],
301
'new', [b'same_text\n', b'same_text\n', b'same_text\n',
302
b'same_text\n', b'same_text\n', b'new_text\n'], output,
304
lines = output.getvalue().splitlines(True)
305
self.check_patch(lines)
306
self.assertEqual([b'--- old\n',
308
b'@@ -2,5 +2,5 @@\n',
241
319
class TestDiffFiles(tests.TestCaseInTempDir):
243
321
def test_external_diff_binary(self):
244
322
"""The output when using external diff should use diff's i18n error"""
323
for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
324
self.overrideEnv(lang, 'C')
245
325
# Make sure external_diff doesn't fail in the current LANG
246
lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
326
lines = external_udiff_lines([b'\x00foobar\n'], [b'foo\x00bar\n'])
248
328
cmd = ['diff', '-u', '--binary', 'old', 'new']
249
open('old', 'wb').write('\x00foobar\n')
250
open('new', 'wb').write('foo\x00bar\n')
329
with open('old', 'wb') as f:
330
f.write(b'\x00foobar\n')
331
with open('new', 'wb') as f:
332
f.write(b'foo\x00bar\n')
251
333
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
252
stdin=subprocess.PIPE)
334
stdin=subprocess.PIPE)
253
335
out, err = pipe.communicate()
254
# Diff returns '2' on Binary files.
255
self.assertEqual(2, pipe.returncode)
256
336
# 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):
337
self.assertEqual(out.splitlines(True) + [b'\n'], lines)
340
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
342
if working_tree is not None:
343
extra_trees = (working_tree,)
346
diff.show_diff_trees(tree1, tree2, output,
347
specific_files=specific_files,
348
extra_trees=extra_trees, old_label='old/',
350
return output.getvalue()
353
class TestDiffDates(tests.TestCaseWithTransport):
279
356
super(TestDiffDates, self).setUp()
280
357
self.wt = self.make_branch_and_tree('.')
281
358
self.b = self.wt.branch
282
359
self.build_tree_contents([
283
('file1', 'file1 contents at rev 1\n'),
284
('file2', 'file2 contents at rev 1\n')
360
('file1', b'file1 contents at rev 1\n'),
361
('file2', b'file2 contents at rev 1\n')
286
363
self.wt.add(['file1', 'file2'])
288
365
message='Revision 1',
289
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
366
timestamp=1143849600, # 2006-04-01 00:00:00 UTC
292
self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
369
self.build_tree_contents([('file1', b'file1 contents at rev 2\n')])
294
371
message='Revision 2',
295
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
372
timestamp=1143936000, # 2006-04-02 00:00:00 UTC
298
self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
375
self.build_tree_contents([('file2', b'file2 contents at rev 3\n')])
300
377
message='Revision 3',
301
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
378
timestamp=1144022400, # 2006-04-03 00:00:00 UTC
304
381
self.wt.remove(['file2'])
306
383
message='Revision 4',
307
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
384
timestamp=1144108800, # 2006-04-04 00:00:00 UTC
310
387
self.build_tree_contents([
311
('file1', 'file1 contents in working tree\n')
388
('file1', b'file1 contents in working tree\n')
313
390
# 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
391
os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
316
393
def test_diff_rev_tree_working_tree(self):
317
output = self.get_diff(self.wt.basis_tree(), self.wt)
394
output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
395
# note that the date for old/file1 is from rev 2 rather than from
319
396
# the basis revision (rev 4)
320
self.assertEqualDiff(output, '''\
397
self.assertEqualDiff(output, b'''\
321
398
=== modified file 'file1'
322
399
--- old/file1\t2006-04-02 00:00:00 +0000
323
400
+++ new/file1\t2006-04-05 00:00:00 +0000
393
470
self.wt.add(['dir1', 'dir2'])
394
471
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):
472
old_tree = self.b.repository.revision_tree(b'rev-1')
473
new_tree = self.b.repository.revision_tree(b'rev-4')
474
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
475
working_tree=self.wt)
476
self.assertContainsRe(out, b'file1\t')
477
out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
478
working_tree=self.wt)
479
self.assertNotContainsRe(out, b'file1\t')
482
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
483
"""Direct tests for show_diff_trees"""
409
485
def test_modified_file(self):
410
486
"""Test when a file is modified."""
411
487
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')
488
self.build_tree_contents([('tree/file', b'contents\n')])
489
tree.add(['file'], [b'file-id'])
490
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'
492
self.build_tree_contents([('tree/file', b'new contents\n')])
493
d = get_diff_as_string(tree.basis_tree(), tree)
494
self.assertContainsRe(d, b"=== modified file 'file'\n")
495
self.assertContainsRe(d, b'--- old/file\t')
496
self.assertContainsRe(d, b'\\+\\+\\+ new/file\t')
497
self.assertContainsRe(d, b'-contents\n'
498
b'\\+new contents\n')
424
500
def test_modified_file_in_renamed_dir(self):
425
501
"""Test when a file is modified in a renamed directory."""
426
502
tree = self.make_branch_and_tree('tree')
427
503
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')
504
self.build_tree_contents([('tree/dir/file', b'contents\n')])
505
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
506
tree.commit('one', rev_id=b'rev-1')
432
508
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")
509
self.build_tree_contents([('tree/other/file', b'new contents\n')])
510
d = get_diff_as_string(tree.basis_tree(), tree)
511
self.assertContainsRe(d, b"=== renamed directory 'dir' => 'other'\n")
512
self.assertContainsRe(d, b"=== modified file 'other/file'\n")
437
513
# XXX: This is technically incorrect, because it used to be at another
438
514
# 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'
515
self.assertContainsRe(d, b'--- old/dir/file\t')
516
self.assertContainsRe(d, b'\\+\\+\\+ new/other/file\t')
517
self.assertContainsRe(d, b'-contents\n'
518
b'\\+new contents\n')
444
520
def test_renamed_directory(self):
445
521
"""Test when only a directory is only renamed."""
446
522
tree = self.make_branch_and_tree('tree')
447
523
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')
524
self.build_tree_contents([('tree/dir/file', b'contents\n')])
525
tree.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
526
tree.commit('one', rev_id=b'rev-1')
452
528
tree.rename_one('dir', 'newdir')
453
d = self.get_diff(tree.basis_tree(), tree)
529
d = get_diff_as_string(tree.basis_tree(), tree)
454
530
# Renaming a directory should be a single "you renamed this dir" even
455
531
# when there are files inside.
456
self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
532
self.assertEqual(d, b"=== renamed directory 'dir' => 'newdir'\n")
458
534
def test_renamed_file(self):
459
535
"""Test when a file is only renamed."""
460
536
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')
537
self.build_tree_contents([('tree/file', b'contents\n')])
538
tree.add(['file'], [b'file-id'])
539
tree.commit('one', rev_id=b'rev-1')
465
541
tree.rename_one('file', 'newname')
466
d = self.get_diff(tree.basis_tree(), tree)
467
self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
542
d = get_diff_as_string(tree.basis_tree(), tree)
543
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
468
544
# We shouldn't have a --- or +++ line, because there is no content
470
self.assertNotContainsRe(d, '---')
546
self.assertNotContainsRe(d, b'---')
472
548
def test_renamed_and_modified_file(self):
473
549
"""Test when a file is only renamed."""
474
550
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')
551
self.build_tree_contents([('tree/file', b'contents\n')])
552
tree.add(['file'], [b'file-id'])
553
tree.commit('one', rev_id=b'rev-1')
479
555
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'
556
self.build_tree_contents([('tree/newname', b'new contents\n')])
557
d = get_diff_as_string(tree.basis_tree(), tree)
558
self.assertContainsRe(d, b"=== renamed file 'file' => 'newname'\n")
559
self.assertContainsRe(d, b'--- old/file\t')
560
self.assertContainsRe(d, b'\\+\\+\\+ new/newname\t')
561
self.assertContainsRe(d, b'-contents\n'
562
b'\\+new contents\n')
489
564
def test_internal_diff_exec_property(self):
490
565
tree = self.make_branch_and_tree('tree')
492
567
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)
568
tt.new_file('a', tt.root, [b'contents\n'], b'a-id', True)
569
tt.new_file('b', tt.root, [b'contents\n'], b'b-id', False)
570
tt.new_file('c', tt.root, [b'contents\n'], b'c-id', True)
571
tt.new_file('d', tt.root, [b'contents\n'], b'd-id', False)
572
tt.new_file('e', tt.root, [b'contents\n'], b'control-e-id', True)
573
tt.new_file('f', tt.root, [b'contents\n'], b'control-f-id', False)
500
tree.commit('one', rev_id='rev-1')
575
tree.commit('one', rev_id=b'rev-1')
502
577
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'))
578
tt.set_executability(False, tt.trans_id_file_id(b'a-id'))
579
tt.set_executability(True, tt.trans_id_file_id(b'b-id'))
580
tt.set_executability(False, tt.trans_id_file_id(b'c-id'))
581
tt.set_executability(True, tt.trans_id_file_id(b'd-id'))
508
583
tree.rename_one('c', 'new-c')
509
584
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'")
586
d = get_diff_as_string(tree.basis_tree(), tree)
588
self.assertContainsRe(d, br"file 'a'.*\(properties changed:"
590
self.assertContainsRe(d, br"file 'b'.*\(properties changed:"
592
self.assertContainsRe(d, br"file 'c'.*\(properties changed:"
594
self.assertContainsRe(d, br"file 'd'.*\(properties changed:"
596
self.assertNotContainsRe(d, br"file 'e'")
597
self.assertNotContainsRe(d, br"file 'f'")
525
599
def test_binary_unicode_filenames(self):
526
600
"""Test that contents of files are *not* encoded in UTF-8 when there
527
601
is a binary file in the diff.
529
603
# See https://bugs.launchpad.net/bugs/110092.
530
self.requireFeature(tests.UnicodeFilenameFeature)
604
self.requireFeature(features.UnicodeFilenameFeature)
532
# This bug isn't triggered with cStringIO.
533
from StringIO import StringIO
534
606
tree = self.make_branch_and_tree('tree')
535
607
alpha, omega = u'\u03b1', u'\u03c9'
536
608
alpha_utf8, omega_utf8 = alpha.encode('utf8'), omega.encode('utf8')
537
609
self.build_tree_contents(
538
[('tree/' + alpha, chr(0)),
610
[('tree/' + alpha, b'\0'),
539
611
('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()
612
(b'The %s and the %s\n' % (alpha_utf8, omega_utf8)))])
613
tree.add([alpha], [b'file-id'])
614
tree.add([omega], [b'file-id-2'])
615
diff_content = StubO()
544
616
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"
617
diff_content.check_types(self, bytes)
618
d = b''.join(diff_content.write_record)
619
self.assertContainsRe(d, br"=== added file '%s'" % alpha_utf8)
620
self.assertContainsRe(d, b"Binary files a/%s.*and b/%s.* differ\n"
548
621
% (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,))
622
self.assertContainsRe(d, br"=== added file '%s'" % omega_utf8)
623
self.assertContainsRe(d, br"--- a/%s" % (omega_utf8,))
624
self.assertContainsRe(d, br"\+\+\+ b/%s" % (omega_utf8,))
553
626
def test_unicode_filename(self):
554
627
"""Test when the filename are unicode."""
555
self.requireFeature(tests.UnicodeFilenameFeature)
628
self.requireFeature(features.UnicodeFilenameFeature)
557
630
alpha, omega = u'\u03b1', u'\u03c9'
558
631
autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
560
633
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)
634
self.build_tree_contents([('tree/ren_' + alpha, b'contents\n')])
635
tree.add(['ren_' + alpha], [b'file-id-2'])
636
self.build_tree_contents([('tree/del_' + alpha, b'contents\n')])
637
tree.add(['del_' + alpha], [b'file-id-3'])
638
self.build_tree_contents([('tree/mod_' + alpha, b'contents\n')])
639
tree.add(['mod_' + alpha], [b'file-id-4'])
641
tree.commit('one', rev_id=b'rev-1')
643
tree.rename_one('ren_' + alpha, 'ren_' + omega)
644
tree.remove('del_' + alpha)
645
self.build_tree_contents([('tree/add_' + alpha, b'contents\n')])
646
tree.add(['add_' + alpha], [b'file-id'])
647
self.build_tree_contents([('tree/mod_' + alpha, b'contents_mod\n')])
649
d = get_diff_as_string(tree.basis_tree(), tree)
577
650
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)
651
b"=== renamed file 'ren_%s' => 'ren_%s'\n" % (autf8, outf8))
652
self.assertContainsRe(d, b"=== added file 'add_%s'" % autf8)
653
self.assertContainsRe(d, b"=== modified file 'mod_%s'" % autf8)
654
self.assertContainsRe(d, b"=== removed file 'del_%s'" % autf8)
656
def test_unicode_filename_path_encoding(self):
657
"""Test for bug #382699: unicode filenames on Windows should be shown
660
self.requireFeature(features.UnicodeFilenameFeature)
661
# The word 'test' in Russian
662
_russian_test = u'\u0422\u0435\u0441\u0442'
663
directory = _russian_test + u'/'
664
test_txt = _russian_test + u'.txt'
665
u1234 = u'\u1234.txt'
667
tree = self.make_branch_and_tree('.')
668
self.build_tree_contents([
669
(test_txt, b'foo\n'),
673
tree.add([test_txt, u1234, directory])
676
diff.show_diff_trees(tree.basis_tree(), tree, sio,
677
path_encoding='cp1251')
679
output = subst_dates(sio.getvalue())
681
=== added directory '%(directory)s'
682
=== added file '%(test_txt)s'
683
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
684
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
688
=== added file '?.txt'
689
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
690
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
694
''' % {b'directory': _russian_test.encode('cp1251'),
695
b'test_txt': test_txt.encode('cp1251'),
697
self.assertEqualDiff(output, shouldbe)
584
700
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())
702
def diff(self, old_path, new_path, old_kind, new_kind):
703
self.to_file.write(b'was: ')
704
self.to_file.write(self.old_tree.get_file(old_path).read())
705
self.to_file.write(b'is: ')
706
self.to_file.write(self.new_tree.get_file(new_path).read())
594
709
class TestDiffTree(tests.TestCaseWithTransport):
601
716
self.new_tree = self.make_branch_and_tree('new-tree')
602
717
self.new_tree.lock_write()
603
718
self.addCleanup(self.new_tree.unlock)
604
self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
719
self.differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
606
721
def test_diff_text(self):
607
722
self.build_tree_contents([('old-tree/olddir/',),
608
('old-tree/olddir/oldfile', 'old\n')])
723
('old-tree/olddir/oldfile', b'old\n')])
609
724
self.old_tree.add('olddir')
610
self.old_tree.add('olddir/oldfile', 'file-id')
725
self.old_tree.add('olddir/oldfile', b'file-id')
611
726
self.build_tree_contents([('new-tree/newdir/',),
612
('new-tree/newdir/newfile', 'new\n')])
727
('new-tree/newdir/newfile', b'new\n')])
613
728
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',
729
self.new_tree.add('newdir/newfile', b'file-id')
730
differ = diff.DiffText(self.old_tree, self.new_tree, BytesIO())
731
differ.diff_text('olddir/oldfile', None, 'old label', 'new label')
733
b'--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
734
differ.to_file.getvalue())
735
differ.to_file.seek(0)
736
differ.diff_text(None, 'newdir/newfile',
737
'old label', 'new label')
739
b'--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
740
differ.to_file.getvalue())
741
differ.to_file.seek(0)
742
differ.diff_text('olddir/oldfile', 'newdir/newfile',
743
'old label', 'new label')
745
b'--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
629
746
differ.to_file.getvalue())
631
748
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')
749
self.build_tree_contents([('old-tree/file', b'contents'),
750
('new-tree/file', b'contents')])
751
self.old_tree.add('file', b'file-id')
752
self.new_tree.add('file', b'file-id')
636
753
os.unlink('new-tree/file')
637
754
self.differ.show_diff(None)
638
self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
755
self.assertContainsRe(self.differ.to_file.getvalue(), b'-contents')
640
757
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')
758
self.build_tree_contents([('old-tree/file', b'contents'),
759
('new-tree/file', b'contents')])
760
self.old_tree.add('file', b'file-id')
761
self.new_tree.add('file', b'file-id')
645
762
os.unlink('old-tree/file')
646
763
self.differ.show_diff(None)
647
self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
764
self.assertContainsRe(self.differ.to_file.getvalue(), br'\+contents')
649
766
def test_diff_symlink(self):
650
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
767
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
651
768
differ.diff_symlink('old target', None)
652
self.assertEqual("=== target was 'old target'\n",
769
self.assertEqual(b"=== target was 'old target'\n",
653
770
differ.to_file.getvalue())
655
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
772
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
656
773
differ.diff_symlink(None, 'new target')
657
self.assertEqual("=== target is 'new target'\n",
774
self.assertEqual(b"=== target is 'new target'\n",
658
775
differ.to_file.getvalue())
660
differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
777
differ = diff.DiffSymlink(self.old_tree, self.new_tree, BytesIO())
661
778
differ.diff_symlink('old target', 'new target')
662
self.assertEqual("=== target changed 'old target' => 'new target'\n",
779
self.assertEqual(b"=== target changed 'old target' => 'new target'\n",
663
780
differ.to_file.getvalue())
665
782
def test_diff(self):
666
783
self.build_tree_contents([('old-tree/olddir/',),
667
('old-tree/olddir/oldfile', 'old\n')])
784
('old-tree/olddir/oldfile', b'old\n')])
668
785
self.old_tree.add('olddir')
669
self.old_tree.add('olddir/oldfile', 'file-id')
786
self.old_tree.add('olddir/oldfile', b'file-id')
670
787
self.build_tree_contents([('new-tree/newdir/',),
671
('new-tree/newdir/newfile', 'new\n')])
788
('new-tree/newdir/newfile', b'new\n')])
672
789
self.new_tree.add('newdir')
673
self.new_tree.add('newdir/newfile', 'file-id')
674
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
790
self.new_tree.add('newdir/newfile', b'file-id')
791
self.differ.diff('olddir/oldfile', 'newdir/newfile')
675
792
self.assertContainsRe(
676
793
self.differ.to_file.getvalue(),
677
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
678
' \@\@\n-old\n\+new\n\n')
794
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
795
br' \@\@\n-old\n\+new\n\n')
680
797
def test_diff_kind_change(self):
681
self.requireFeature(tests.SymlinkFeature)
798
self.requireFeature(features.SymlinkFeature)
682
799
self.build_tree_contents([('old-tree/olddir/',),
683
('old-tree/olddir/oldfile', 'old\n')])
800
('old-tree/olddir/oldfile', b'old\n')])
684
801
self.old_tree.add('olddir')
685
self.old_tree.add('olddir/oldfile', 'file-id')
802
self.old_tree.add('olddir/oldfile', b'file-id')
686
803
self.build_tree(['new-tree/newdir/'])
687
804
os.symlink('new', 'new-tree/newdir/newfile')
688
805
self.new_tree.add('newdir')
689
self.new_tree.add('newdir/newfile', 'file-id')
690
self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
806
self.new_tree.add('newdir/newfile', b'file-id')
807
self.differ.diff('olddir/oldfile', 'newdir/newfile')
691
808
self.assertContainsRe(
692
809
self.differ.to_file.getvalue(),
693
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
810
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
695
812
self.assertContainsRe(self.differ.to_file.getvalue(),
696
"=== target is u'new'\n")
813
b"=== target is 'new'\n")
698
815
def test_diff_directory(self):
699
816
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(), '')
817
self.new_tree.add('new-dir', b'new-dir-id')
818
self.differ.diff(None, 'new-dir')
819
self.assertEqual(self.differ.to_file.getvalue(), b'')
704
821
def create_old_new(self):
705
822
self.build_tree_contents([('old-tree/olddir/',),
706
('old-tree/olddir/oldfile', 'old\n')])
823
('old-tree/olddir/oldfile', b'old\n')])
707
824
self.old_tree.add('olddir')
708
self.old_tree.add('olddir/oldfile', 'file-id')
825
self.old_tree.add('olddir/oldfile', b'file-id')
709
826
self.build_tree_contents([('new-tree/newdir/',),
710
('new-tree/newdir/newfile', 'new\n')])
827
('new-tree/newdir/newfile', b'new\n')])
711
828
self.new_tree.add('newdir')
712
self.new_tree.add('newdir/newfile', 'file-id')
829
self.new_tree.add('newdir/newfile', b'file-id')
714
831
def test_register_diff(self):
715
832
self.create_old_new()
716
833
old_diff_factories = diff.DiffTree.diff_factories
717
diff.DiffTree.diff_factories=old_diff_factories[:]
834
diff.DiffTree.diff_factories = old_diff_factories[:]
718
835
diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
720
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
837
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO())
722
839
diff.DiffTree.diff_factories = old_diff_factories
723
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
840
differ.diff('olddir/oldfile', 'newdir/newfile')
724
841
self.assertNotContainsRe(
725
842
differ.to_file.getvalue(),
726
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
727
' \@\@\n-old\n\+new\n\n')
843
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
844
br' \@\@\n-old\n\+new\n\n')
728
845
self.assertContainsRe(differ.to_file.getvalue(),
729
'was: old\nis: new\n')
846
b'was: old\nis: new\n')
731
848
def test_extra_factories(self):
732
849
self.create_old_new()
733
differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
850
differ = diff.DiffTree(self.old_tree, self.new_tree, BytesIO(),
734
851
extra_factories=[DiffWasIs.from_diff_tree])
735
differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
852
differ.diff('olddir/oldfile', 'newdir/newfile')
736
853
self.assertNotContainsRe(
737
854
differ.to_file.getvalue(),
738
r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
739
' \@\@\n-old\n\+new\n\n')
855
br'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
856
br' \@\@\n-old\n\+new\n\n')
740
857
self.assertContainsRe(differ.to_file.getvalue(),
741
'was: old\nis: new\n')
858
b'was: old\nis: new\n')
743
860
def test_alphabetical_order(self):
744
861
self.build_tree(['new-tree/a-file'])
764
881
b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
765
882
sm = self._PatienceSequenceMatcher(None, a, b)
766
883
mb = sm.get_matching_blocks()
767
self.assertEquals(35, len(mb))
884
self.assertEqual(35, len(mb))
769
886
def test_unique_lcs(self):
770
887
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)])
888
self.assertEqual(unique_lcs('', ''), [])
889
self.assertEqual(unique_lcs('', 'a'), [])
890
self.assertEqual(unique_lcs('a', ''), [])
891
self.assertEqual(unique_lcs('a', 'a'), [(0, 0)])
892
self.assertEqual(unique_lcs('a', 'b'), [])
893
self.assertEqual(unique_lcs('ab', 'ab'), [(0, 0), (1, 1)])
894
self.assertEqual(unique_lcs('abcde', 'cdeab'),
895
[(2, 0), (3, 1), (4, 2)])
896
self.assertEqual(unique_lcs('cdeab', 'abcde'),
897
[(0, 2), (1, 3), (2, 4)])
898
self.assertEqual(unique_lcs('abXde', 'abYde'), [(0, 0), (1, 1),
900
self.assertEqual(unique_lcs('acbac', 'abc'), [(2, 1)])
783
902
def test_recurse_matches(self):
784
903
def test_one(a, b, matches):
785
904
test_matches = []
786
905
self._recurse_matches(
787
906
a, b, 0, 0, len(a), len(b), test_matches, 10)
788
self.assertEquals(test_matches, matches)
907
self.assertEqual(test_matches, matches)
790
909
test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
791
910
[(0, 0), (2, 2), (4, 4)])
896
1016
def test_opcodes(self):
897
1017
def chk_ops(a, b, expected_codes):
898
1018
s = self._PatienceSequenceMatcher(None, a, b)
899
self.assertEquals(expected_codes, s.get_opcodes())
1019
self.assertEqual(expected_codes, s.get_opcodes())
901
1021
chk_ops('', '', [])
902
1022
chk_ops([], [], [])
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),
1023
chk_ops('abc', '', [('delete', 0, 3, 0, 0)])
1024
chk_ops('', 'abc', [('insert', 0, 0, 0, 3)])
1025
chk_ops('abcd', 'abcd', [('equal', 0, 4, 0, 4)])
1026
chk_ops('abcd', 'abce', [('equal', 0, 3, 0, 3),
1027
('replace', 3, 4, 3, 4)
1029
chk_ops('eabc', 'abce', [('delete', 0, 1, 0, 0),
1030
('equal', 1, 4, 0, 3),
1031
('insert', 4, 4, 3, 4)
1033
chk_ops('eabce', 'abce', [('delete', 0, 1, 0, 0),
1034
('equal', 1, 5, 0, 4)
920
chk_ops('abcde', 'abXYZde', [('equal', 0,2, 0,2),
921
('replace', 2,3, 2,5),
1036
chk_ops('abcde', 'abXde', [('equal', 0, 2, 0, 2),
1037
('replace', 2, 3, 2, 3),
1038
('equal', 3, 5, 3, 5)
1040
chk_ops('abcde', 'abXYZde', [('equal', 0, 2, 0, 2),
1041
('replace', 2, 3, 2, 5),
1042
('equal', 3, 5, 5, 7)
1044
chk_ops('abde', 'abXYZde', [('equal', 0, 2, 0, 2),
1045
('insert', 2, 2, 2, 5),
1046
('equal', 2, 4, 5, 7)
924
chk_ops('abde', 'abXYZde', [('equal', 0,2, 0,2),
925
('insert', 2,2, 2,5),
928
1048
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
929
[('equal', 0,6, 0,6),
930
('insert', 6,6, 6,11),
931
('equal', 6,16, 11,21)
1049
[('equal', 0, 6, 0, 6),
1050
('insert', 6, 6, 6, 11),
1051
('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),
1054
['hello there\n', 'world\n', 'how are you today?\n'],
1055
['hello there\n', 'how are you today?\n'],
1056
[('equal', 0, 1, 0, 1),
1057
('delete', 1, 2, 1, 1),
1058
('equal', 2, 3, 1, 2),
943
1060
chk_ops('aBccDe', 'abccde',
944
[('equal', 0,1, 0,1),
945
('replace', 1,5, 1,5),
1061
[('equal', 0, 1, 0, 1),
1062
('replace', 1, 5, 1, 5),
1063
('equal', 5, 6, 5, 6),
948
1065
chk_ops('aBcDec', 'abcdec',
949
[('equal', 0,1, 0,1),
950
('replace', 1,2, 1,2),
952
('replace', 3,4, 3,4),
1066
[('equal', 0, 1, 0, 1),
1067
('replace', 1, 2, 1, 2),
1068
('equal', 2, 3, 2, 3),
1069
('replace', 3, 4, 3, 4),
1070
('equal', 4, 6, 4, 6),
955
1072
chk_ops('aBcdEcdFg', 'abcdecdfg',
956
[('equal', 0,1, 0,1),
957
('replace', 1,8, 1,8),
1073
[('equal', 0, 1, 0, 1),
1074
('replace', 1, 8, 1, 8),
1075
('equal', 8, 9, 8, 9)
960
1077
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)
1078
[('equal', 0, 1, 0, 1),
1079
('replace', 1, 2, 1, 2),
1080
('equal', 2, 4, 2, 4),
1081
('delete', 4, 5, 4, 4),
1082
('equal', 5, 6, 4, 5),
1083
('delete', 6, 7, 5, 5),
1084
('equal', 7, 9, 5, 7),
1085
('replace', 9, 10, 7, 8),
1086
('equal', 10, 11, 8, 9)
972
1089
def test_grouped_opcodes(self):
973
1090
def chk_ops(a, b, expected_codes, n=3):
974
1091
s = self._PatienceSequenceMatcher(None, a, b)
975
self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1092
self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
977
1094
chk_ops('', '', [])
978
1095
chk_ops([], [], [])
979
chk_ops('abc', '', [[('delete', 0,3, 0,0)]])
980
chk_ops('', 'abc', [[('insert', 0,0, 0,3)]])
1096
chk_ops('abc', '', [[('delete', 0, 3, 0, 0)]])
1097
chk_ops('', 'abc', [[('insert', 0, 0, 0, 3)]])
981
1098
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),
1099
chk_ops('abcd', 'abce', [[('equal', 0, 3, 0, 3),
1100
('replace', 3, 4, 3, 4)
1102
chk_ops('eabc', 'abce', [[('delete', 0, 1, 0, 0),
1103
('equal', 1, 4, 0, 3),
1104
('insert', 4, 4, 3, 4)
989
1106
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
990
[[('equal', 3,6, 3,6),
991
('insert', 6,6, 6,11),
992
('equal', 6,9, 11,14)
1107
[[('equal', 3, 6, 3, 6),
1108
('insert', 6, 6, 6, 11),
1109
('equal', 6, 9, 11, 14)
994
1111
chk_ops('abcdefghijklmnop', 'abcdefxydefghijklmnop',
995
[[('equal', 2,6, 2,6),
996
('insert', 6,6, 6,11),
997
('equal', 6,10, 11,15)
1112
[[('equal', 2, 6, 2, 6),
1113
('insert', 6, 6, 6, 11),
1114
('equal', 6, 10, 11, 15)
999
1116
chk_ops('Xabcdef', 'abcdef',
1000
[[('delete', 0,1, 0,0),
1117
[[('delete', 0, 1, 0, 0),
1118
('equal', 1, 4, 0, 3)
1003
1120
chk_ops('abcdef', 'abcdefX',
1004
[[('equal', 3,6, 3,6),
1005
('insert', 6,6, 6,7)
1121
[[('equal', 3, 6, 3, 6),
1122
('insert', 6, 6, 6, 7)
1009
1125
def test_multiple_ranges(self):
1010
1126
# There was an earlier bug where we used a bad set of ranges,
1011
1127
# this triggers that specific bug, to make sure it doesn't regress
1356
1468
self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1358
1470
def test_prepare_files(self):
1360
1472
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')
1473
self.build_tree_contents([('tree/oldname', b'oldcontent')])
1474
self.build_tree_contents([('tree/oldname2', b'oldcontent2')])
1475
tree.add('oldname', b'file-id')
1476
tree.add('oldname2', b'file2-id')
1365
1477
# Earliest allowable date on FAT32 filesystems is 1980-01-01
1366
1478
tree.commit('old tree', timestamp=315532800)
1367
1479
tree.rename_one('oldname', 'newname')
1368
1480
tree.rename_one('oldname2', 'newname2')
1369
self.build_tree_contents([('tree/newname', 'newcontent')])
1370
self.build_tree_contents([('tree/newname2', 'newcontent2')])
1481
self.build_tree_contents([('tree/newname', b'newcontent')])
1482
self.build_tree_contents([('tree/newname2', b'newcontent2')])
1371
1483
old_tree = tree.basis_tree()
1372
1484
old_tree.lock_read()
1373
1485
self.addCleanup(old_tree.unlock)
1374
1486
tree.lock_read()
1375
1487
self.addCleanup(tree.unlock)
1376
diff_obj = diff.DiffFromTool(['python', '-c',
1488
diff_obj = diff.DiffFromTool([sys.executable, '-c',
1377
1489
'print "@old_path @new_path"'],
1378
1490
old_tree, tree, output)
1379
1491
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',
1492
self.assertContainsRe(diff_obj._root, 'brz-diff-[^/]*')
1493
old_path, new_path = diff_obj._prepare_files(
1494
'oldname', 'newname')
1383
1495
self.assertContainsRe(old_path, 'old/oldname$')
1384
1496
self.assertEqual(315532800, os.stat(old_path).st_mtime)
1385
1497
self.assertContainsRe(new_path, 'tree/newname$')
1386
self.assertFileEqual('oldcontent', old_path)
1387
self.assertFileEqual('newcontent', new_path)
1498
self.assertFileEqual(b'oldcontent', old_path)
1499
self.assertFileEqual(b'newcontent', new_path)
1388
1500
if osutils.host_os_dereferences_symlinks():
1389
1501
self.assertTrue(os.path.samefile('tree/newname', new_path))
1390
1502
# make sure we can create files with the same parent directories
1391
diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1503
diff_obj._prepare_files('oldname2', 'newname2')
1506
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1508
def test_encodable_filename(self):
1509
# Just checks file path for external diff tool.
1510
# We cannot change CPython's internal encoding used by os.exec*.
1511
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1513
for _, scenario in EncodingAdapter.encoding_scenarios:
1514
encoding = scenario['encoding']
1515
dirname = scenario['info']['directory']
1516
filename = scenario['info']['filename']
1518
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1519
relpath = dirname + u'/' + filename
1520
fullpath = diffobj._safe_filename('safe', relpath)
1521
self.assertEqual(fullpath,
1522
fullpath.encode(encoding).decode(encoding))
1523
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1525
def test_unencodable_filename(self):
1526
diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1528
for _, scenario in EncodingAdapter.encoding_scenarios:
1529
encoding = scenario['encoding']
1530
dirname = scenario['info']['directory']
1531
filename = scenario['info']['filename']
1533
if encoding == 'iso-8859-1':
1534
encoding = 'iso-8859-2'
1536
encoding = 'iso-8859-1'
1538
self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1539
relpath = dirname + u'/' + filename
1540
fullpath = diffobj._safe_filename('safe', relpath)
1541
self.assertEqual(fullpath,
1542
fullpath.encode(encoding).decode(encoding))
1543
self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1394
1546
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1396
1548
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.
1549
"""Call get_trees_and_branches_to_diff_locked."""
1400
1550
return diff.get_trees_and_branches_to_diff_locked(
1401
1551
path_list, revision_specs, old_url, new_url, self.addCleanup)