~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Andrew Bennetts
  • Date: 2007-10-29 08:34:38 UTC
  • mto: (2535.4.22 streaming-smart-fetch)
  • mto: This revision was merged to the branch mainline in revision 2981.
  • Revision ID: andrew.bennetts@canonical.com-20071029083438-ke1vsv97dvgrvup5
ImproveĀ someĀ docstrings.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2012, 2014, 2016 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
18
from cStringIO import StringIO
 
19
import errno
19
20
import subprocess
20
 
import tempfile
21
 
 
22
 
from bzrlib import (
23
 
    diff,
24
 
    errors,
25
 
    osutils,
26
 
    patiencediff,
27
 
    _patiencediff_py,
28
 
    revision as _mod_revision,
29
 
    revisionspec,
30
 
    revisiontree,
31
 
    tests,
32
 
    transform,
33
 
    )
34
 
from bzrlib.tests import (
35
 
    features,
36
 
    EncodingAdapter,
37
 
)
38
 
from bzrlib.tests.blackbox.test_diff import subst_dates
39
 
from bzrlib.tests.scenarios import load_tests_apply_scenarios
40
 
 
41
 
 
42
 
load_tests = load_tests_apply_scenarios
43
 
 
 
21
from tempfile import TemporaryFile
 
22
 
 
23
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
 
24
from bzrlib.errors import BinaryFile, NoDiff
 
25
import bzrlib.osutils as osutils
 
26
import bzrlib.patiencediff
 
27
import bzrlib._patiencediff_py
 
28
from bzrlib.tests import (Feature, TestCase, TestCaseWithTransport,
 
29
                          TestCaseInTempDir, TestSkipped)
 
30
 
 
31
 
 
32
class _CompiledPatienceDiffFeature(Feature):
 
33
 
 
34
    def _probe(self):
 
35
        try:
 
36
            import bzrlib._patiencediff_c
 
37
        except ImportError:
 
38
            return False
 
39
        return True
 
40
 
 
41
    def feature_name(self):
 
42
        return 'bzrlib._patiencediff_c'
 
43
 
 
44
CompiledPatienceDiffFeature = _CompiledPatienceDiffFeature()
 
45
 
 
46
 
 
47
class _UnicodeFilename(Feature):
 
48
    """Does the filesystem support Unicode filenames?"""
 
49
 
 
50
    def _probe(self):
 
51
        try:
 
52
            os.stat(u'\u03b1')
 
53
        except UnicodeEncodeError:
 
54
            return False
 
55
        except (IOError, OSError):
 
56
            # The filesystem allows the Unicode filename but the file doesn't
 
57
            # exist.
 
58
            return True
 
59
        else:
 
60
            # The filesystem allows the Unicode filename and the file exists,
 
61
            # for some reason.
 
62
            return True
 
63
 
 
64
UnicodeFilename = _UnicodeFilename()
 
65
 
 
66
 
 
67
class TestUnicodeFilename(TestCase):
 
68
 
 
69
    def test_probe_passes(self):
 
70
        """UnicodeFilename._probe passes."""
 
71
        # We can't test much more than that because the behaviour depends
 
72
        # on the platform.
 
73
        UnicodeFilename._probe()
 
74
        
44
75
 
45
76
def udiff_lines(old, new, allow_binary=False):
46
77
    output = StringIO()
47
 
    diff.internal_diff('old', old, 'new', new, output, allow_binary)
 
78
    internal_diff('old', old, 'new', new, output, allow_binary)
48
79
    output.seek(0, 0)
49
80
    return output.readlines()
50
81
 
54
85
        # StringIO has no fileno, so it tests a different codepath
55
86
        output = StringIO()
56
87
    else:
57
 
        output = tempfile.TemporaryFile()
 
88
        output = TemporaryFile()
58
89
    try:
59
 
        diff.external_diff('old', old, 'new', new, output, diff_opts=['-u'])
60
 
    except errors.NoDiff:
61
 
        raise tests.TestSkipped('external "diff" not present to test')
 
90
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
91
    except NoDiff:
 
92
        raise TestSkipped('external "diff" not present to test')
62
93
    output.seek(0, 0)
63
94
    lines = output.readlines()
64
95
    output.close()
65
96
    return lines
66
97
 
67
98
 
68
 
class TestDiffOptions(tests.TestCase):
69
 
 
70
 
    def test_unified_added(self):
71
 
        """Check for default style '-u' only if no other style specified
72
 
        in 'diff-options'.
73
 
        """
74
 
        # Verify that style defaults to unified, id est '-u' appended
75
 
        # to option list, in the absence of an alternative style.
76
 
        self.assertEqual(['-a', '-u'], diff.default_style_unified(['-a']))
77
 
 
78
 
 
79
 
class TestDiffOptionsScenarios(tests.TestCase):
80
 
 
81
 
    scenarios = [(s, dict(style=s)) for s in diff.style_option_list]
82
 
    style = None # Set by load_tests_apply_scenarios from scenarios
83
 
 
84
 
    def test_unified_not_added(self):
85
 
        # Verify that for all valid style options, '-u' is not
86
 
        # appended to option list.
87
 
        ret_opts = diff.default_style_unified(diff_opts=["%s" % (self.style,)])
88
 
        self.assertEqual(["%s" % (self.style,)], ret_opts)
89
 
 
90
 
 
91
 
class TestDiff(tests.TestCase):
 
99
class TestDiff(TestCase):
92
100
 
93
101
    def test_add_nl(self):
94
102
        """diff generates a valid diff for patches that add a newline"""
95
103
        lines = udiff_lines(['boo'], ['boo\n'])
96
104
        self.check_patch(lines)
97
 
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
 
105
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
98
106
            ## "expected no-nl, got %r" % lines[4]
99
107
 
100
108
    def test_add_nl_2(self):
103
111
        """
104
112
        lines = udiff_lines(['boo'], ['goo\n'])
105
113
        self.check_patch(lines)
106
 
        self.assertEqual(lines[4], '\\ No newline at end of file\n')
 
114
        self.assertEquals(lines[4], '\\ No newline at end of file\n')
107
115
            ## "expected no-nl, got %r" % lines[4]
108
116
 
109
117
    def test_remove_nl(self):
112
120
        """
113
121
        lines = udiff_lines(['boo\n'], ['boo'])
114
122
        self.check_patch(lines)
115
 
        self.assertEqual(lines[5], '\\ No newline at end of file\n')
 
123
        self.assertEquals(lines[5], '\\ No newline at end of file\n')
116
124
            ## "expected no-nl, got %r" % lines[5]
117
125
 
118
126
    def check_patch(self, lines):
119
 
        self.assertTrue(len(lines) > 1)
 
127
        self.assert_(len(lines) > 1)
120
128
            ## "Not enough lines for a file header for patch:\n%s" % "".join(lines)
121
 
        self.assertTrue(lines[0].startswith ('---'))
 
129
        self.assert_(lines[0].startswith ('---'))
122
130
            ## 'No orig line for patch:\n%s' % "".join(lines)
123
 
        self.assertTrue(lines[1].startswith ('+++'))
 
131
        self.assert_(lines[1].startswith ('+++'))
124
132
            ## 'No mod line for patch:\n%s' % "".join(lines)
125
 
        self.assertTrue(len(lines) > 2)
 
133
        self.assert_(len(lines) > 2)
126
134
            ## "No hunks for patch:\n%s" % "".join(lines)
127
 
        self.assertTrue(lines[2].startswith('@@'))
 
135
        self.assert_(lines[2].startswith('@@'))
128
136
            ## "No hunk header for patch:\n%s" % "".join(lines)
129
 
        self.assertTrue('@@' in lines[2][2:])
 
137
        self.assert_('@@' in lines[2][2:])
130
138
            ## "Unterminated hunk header for patch:\n%s" % "".join(lines)
131
139
 
132
140
    def test_binary_lines(self):
133
 
        empty = []
134
 
        uni_lines = [1023 * 'a' + '\x00']
135
 
        self.assertRaises(errors.BinaryFile, udiff_lines, uni_lines , empty)
136
 
        self.assertRaises(errors.BinaryFile, udiff_lines, empty, uni_lines)
137
 
        udiff_lines(uni_lines , empty, allow_binary=True)
138
 
        udiff_lines(empty, uni_lines, allow_binary=True)
 
141
        self.assertRaises(BinaryFile, udiff_lines, [1023 * 'a' + '\x00'], [])
 
142
        self.assertRaises(BinaryFile, udiff_lines, [], [1023 * 'a' + '\x00'])
 
143
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
 
144
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
139
145
 
140
146
    def test_external_diff(self):
141
147
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
151
157
        self.check_patch(lines)
152
158
 
153
159
    def test_external_diff_binary_lang_c(self):
 
160
        old_env = {}
154
161
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
155
 
            self.overrideEnv(lang, 'C')
156
 
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
157
 
        # Older versions of diffutils say "Binary files", newer
158
 
        # versions just say "Files".
159
 
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
160
 
        self.assertEqual(lines[1:], ['\n'])
 
162
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
 
163
        try:
 
164
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
165
            # Older versions of diffutils say "Binary files", newer
 
166
            # versions just say "Files".
 
167
            self.assertContainsRe(lines[0],
 
168
                                  '(Binary f|F)iles old and new differ\n')
 
169
            self.assertEquals(lines[1:], ['\n'])
 
170
        finally:
 
171
            for lang, old_val in old_env.iteritems():
 
172
                osutils.set_or_unset_env(lang, old_val)
161
173
 
162
174
    def test_no_external_diff(self):
163
175
        """Check that NoDiff is raised when diff is not available"""
164
 
        # Make sure no 'diff' command is available
165
 
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
166
 
        self.overrideEnv('PATH', '')
167
 
        self.assertRaises(errors.NoDiff, diff.external_diff,
168
 
                          'old', ['boo\n'], 'new', ['goo\n'],
169
 
                          StringIO(), diff_opts=['-u'])
170
 
 
 
176
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
177
        orig_path = os.environ['PATH']
 
178
        try:
 
179
            os.environ['PATH'] = ''
 
180
            self.assertRaises(NoDiff, external_diff,
 
181
                              'old', ['boo\n'], 'new', ['goo\n'],
 
182
                              StringIO(), diff_opts=['-u'])
 
183
        finally:
 
184
            os.environ['PATH'] = orig_path
 
185
        
171
186
    def test_internal_diff_default(self):
172
187
        # Default internal diff encoding is utf8
173
188
        output = StringIO()
174
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
175
 
                           u'new_\xe5', ['new_text\n'], output)
 
189
        internal_diff(u'old_\xb5', ['old_text\n'],
 
190
                    u'new_\xe5', ['new_text\n'], output)
176
191
        lines = output.getvalue().splitlines(True)
177
192
        self.check_patch(lines)
178
 
        self.assertEqual(['--- old_\xc2\xb5\n',
 
193
        self.assertEquals(['--- old_\xc2\xb5\n',
179
194
                           '+++ new_\xc3\xa5\n',
180
195
                           '@@ -1,1 +1,1 @@\n',
181
196
                           '-old_text\n',
186
201
 
187
202
    def test_internal_diff_utf8(self):
188
203
        output = StringIO()
189
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
190
 
                           u'new_\xe5', ['new_text\n'], output,
191
 
                           path_encoding='utf8')
 
204
        internal_diff(u'old_\xb5', ['old_text\n'],
 
205
                    u'new_\xe5', ['new_text\n'], output,
 
206
                    path_encoding='utf8')
192
207
        lines = output.getvalue().splitlines(True)
193
208
        self.check_patch(lines)
194
 
        self.assertEqual(['--- old_\xc2\xb5\n',
 
209
        self.assertEquals(['--- old_\xc2\xb5\n',
195
210
                           '+++ new_\xc3\xa5\n',
196
211
                           '@@ -1,1 +1,1 @@\n',
197
212
                           '-old_text\n',
202
217
 
203
218
    def test_internal_diff_iso_8859_1(self):
204
219
        output = StringIO()
205
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
206
 
                           u'new_\xe5', ['new_text\n'], output,
207
 
                           path_encoding='iso-8859-1')
 
220
        internal_diff(u'old_\xb5', ['old_text\n'],
 
221
                    u'new_\xe5', ['new_text\n'], output,
 
222
                    path_encoding='iso-8859-1')
208
223
        lines = output.getvalue().splitlines(True)
209
224
        self.check_patch(lines)
210
 
        self.assertEqual(['--- old_\xb5\n',
 
225
        self.assertEquals(['--- old_\xb5\n',
211
226
                           '+++ new_\xe5\n',
212
227
                           '@@ -1,1 +1,1 @@\n',
213
228
                           '-old_text\n',
216
231
                          ]
217
232
                          , lines)
218
233
 
219
 
    def test_internal_diff_no_content(self):
220
 
        output = StringIO()
221
 
        diff.internal_diff(u'old', [], u'new', [], output)
222
 
        self.assertEqual('', output.getvalue())
223
 
 
224
 
    def test_internal_diff_no_changes(self):
225
 
        output = StringIO()
226
 
        diff.internal_diff(u'old', ['text\n', 'contents\n'],
227
 
                           u'new', ['text\n', 'contents\n'],
228
 
                           output)
229
 
        self.assertEqual('', output.getvalue())
230
 
 
231
234
    def test_internal_diff_returns_bytes(self):
232
235
        import StringIO
233
236
        output = StringIO.StringIO()
234
 
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
235
 
                            u'new_\xe5', ['new_text\n'], output)
236
 
        self.assertIsInstance(output.getvalue(), str,
 
237
        internal_diff(u'old_\xb5', ['old_text\n'],
 
238
                    u'new_\xe5', ['new_text\n'], output)
 
239
        self.failUnless(isinstance(output.getvalue(), str),
237
240
            'internal_diff should return bytestrings')
238
241
 
239
 
    def test_internal_diff_default_context(self):
240
 
        output = StringIO()
241
 
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
242
 
                           'same_text\n','same_text\n','old_text\n'],
243
 
                           'new', ['same_text\n','same_text\n','same_text\n',
244
 
                           'same_text\n','same_text\n','new_text\n'], output)
245
 
        lines = output.getvalue().splitlines(True)
246
 
        self.check_patch(lines)
247
 
        self.assertEqual(['--- old\n',
248
 
                           '+++ new\n',
249
 
                           '@@ -3,4 +3,4 @@\n',
250
 
                           ' same_text\n',
251
 
                           ' same_text\n',
252
 
                           ' same_text\n',
253
 
                           '-old_text\n',
254
 
                           '+new_text\n',
255
 
                           '\n',
256
 
                          ]
257
 
                          , lines)
258
 
 
259
 
    def test_internal_diff_no_context(self):
260
 
        output = StringIO()
261
 
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
262
 
                           'same_text\n','same_text\n','old_text\n'],
263
 
                           'new', ['same_text\n','same_text\n','same_text\n',
264
 
                           'same_text\n','same_text\n','new_text\n'], output,
265
 
                           context_lines=0)
266
 
        lines = output.getvalue().splitlines(True)
267
 
        self.check_patch(lines)
268
 
        self.assertEqual(['--- old\n',
269
 
                           '+++ new\n',
270
 
                           '@@ -6,1 +6,1 @@\n',
271
 
                           '-old_text\n',
272
 
                           '+new_text\n',
273
 
                           '\n',
274
 
                          ]
275
 
                          , lines)
276
 
 
277
 
    def test_internal_diff_more_context(self):
278
 
        output = StringIO()
279
 
        diff.internal_diff('old', ['same_text\n','same_text\n','same_text\n',
280
 
                           'same_text\n','same_text\n','old_text\n'],
281
 
                           'new', ['same_text\n','same_text\n','same_text\n',
282
 
                           'same_text\n','same_text\n','new_text\n'], output,
283
 
                           context_lines=4)
284
 
        lines = output.getvalue().splitlines(True)
285
 
        self.check_patch(lines)
286
 
        self.assertEqual(['--- old\n',
287
 
                           '+++ new\n',
288
 
                           '@@ -2,5 +2,5 @@\n',
289
 
                           ' same_text\n',
290
 
                           ' same_text\n',
291
 
                           ' same_text\n',
292
 
                           ' same_text\n',
293
 
                           '-old_text\n',
294
 
                           '+new_text\n',
295
 
                           '\n',
296
 
                          ]
297
 
                          , lines)
298
 
 
299
 
 
300
 
 
301
 
 
302
 
 
303
 
class TestDiffFiles(tests.TestCaseInTempDir):
 
242
 
 
243
class TestDiffFiles(TestCaseInTempDir):
304
244
 
305
245
    def test_external_diff_binary(self):
306
246
        """The output when using external diff should use diff's i18n error"""
308
248
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
309
249
 
310
250
        cmd = ['diff', '-u', '--binary', 'old', 'new']
311
 
        with open('old', 'wb') as f: f.write('\x00foobar\n')
312
 
        with open('new', 'wb') as f: f.write('foo\x00bar\n')
 
251
        open('old', 'wb').write('\x00foobar\n')
 
252
        open('new', 'wb').write('foo\x00bar\n')
313
253
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
314
254
                                     stdin=subprocess.PIPE)
315
255
        out, err = pipe.communicate()
319
259
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
320
260
 
321
261
 
322
 
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
323
 
    output = StringIO()
324
 
    if working_tree is not None:
325
 
        extra_trees = (working_tree,)
326
 
    else:
327
 
        extra_trees = ()
328
 
    diff.show_diff_trees(tree1, tree2, output,
329
 
        specific_files=specific_files,
330
 
        extra_trees=extra_trees, old_label='old/',
331
 
        new_label='new/')
332
 
    return output.getvalue()
333
 
 
334
 
 
335
 
class TestDiffDates(tests.TestCaseWithTransport):
 
262
class TestShowDiffTreesHelper(TestCaseWithTransport):
 
263
    """Has a helper for running show_diff_trees"""
 
264
 
 
265
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
266
        output = StringIO()
 
267
        if working_tree is not None:
 
268
            extra_trees = (working_tree,)
 
269
        else:
 
270
            extra_trees = ()
 
271
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
 
272
                        extra_trees=extra_trees, old_label='old/',
 
273
                        new_label='new/')
 
274
        return output.getvalue()
 
275
 
 
276
 
 
277
class TestDiffDates(TestShowDiffTreesHelper):
336
278
 
337
279
    def setUp(self):
338
280
        super(TestDiffDates, self).setUp()
373
315
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
374
316
 
375
317
    def test_diff_rev_tree_working_tree(self):
376
 
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
 
318
        output = self.get_diff(self.wt.basis_tree(), self.wt)
377
319
        # note that the date for old/file1 is from rev 2 rather than from
378
320
        # the basis revision (rev 4)
379
321
        self.assertEqualDiff(output, '''\
389
331
    def test_diff_rev_tree_rev_tree(self):
390
332
        tree1 = self.b.repository.revision_tree('rev-2')
391
333
        tree2 = self.b.repository.revision_tree('rev-3')
392
 
        output = get_diff_as_string(tree1, tree2)
 
334
        output = self.get_diff(tree1, tree2)
393
335
        self.assertEqualDiff(output, '''\
394
336
=== modified file 'file2'
395
337
--- old/file2\t2006-04-01 00:00:00 +0000
399
341
+file2 contents at rev 3
400
342
 
401
343
''')
402
 
 
 
344
        
403
345
    def test_diff_add_files(self):
404
 
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
 
346
        tree1 = self.b.repository.revision_tree(None)
405
347
        tree2 = self.b.repository.revision_tree('rev-1')
406
 
        output = get_diff_as_string(tree1, tree2)
 
348
        output = self.get_diff(tree1, tree2)
407
349
        # the files have the epoch time stamp for the tree in which
408
350
        # they don't exist.
409
351
        self.assertEqualDiff(output, '''\
424
366
    def test_diff_remove_files(self):
425
367
        tree1 = self.b.repository.revision_tree('rev-3')
426
368
        tree2 = self.b.repository.revision_tree('rev-4')
427
 
        output = get_diff_as_string(tree1, tree2)
 
369
        output = self.get_diff(tree1, tree2)
428
370
        # the file has the epoch time stamp for the tree in which
429
371
        # it doesn't exist.
430
372
        self.assertEqualDiff(output, '''\
441
383
        self.wt.rename_one('file1', 'file1b')
442
384
        old_tree = self.b.repository.revision_tree('rev-1')
443
385
        new_tree = self.b.repository.revision_tree('rev-4')
444
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
 
386
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
445
387
                            working_tree=self.wt)
446
388
        self.assertContainsRe(out, 'file1\t')
447
389
 
453
395
        self.wt.rename_one('file1', 'dir1/file1')
454
396
        old_tree = self.b.repository.revision_tree('rev-1')
455
397
        new_tree = self.b.repository.revision_tree('rev-4')
456
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
 
398
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
457
399
                            working_tree=self.wt)
458
400
        self.assertContainsRe(out, 'file1\t')
459
 
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
 
401
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
460
402
                            working_tree=self.wt)
461
403
        self.assertNotContainsRe(out, 'file1\t')
462
404
 
463
405
 
464
 
class TestShowDiffTrees(tests.TestCaseWithTransport):
 
406
 
 
407
class TestShowDiffTrees(TestShowDiffTreesHelper):
465
408
    """Direct tests for show_diff_trees"""
466
409
 
467
410
    def test_modified_file(self):
472
415
        tree.commit('one', rev_id='rev-1')
473
416
 
474
417
        self.build_tree_contents([('tree/file', 'new contents\n')])
475
 
        d = get_diff_as_string(tree.basis_tree(), tree)
476
 
        self.assertContainsRe(d, "=== modified file 'file'\n")
477
 
        self.assertContainsRe(d, '--- old/file\t')
478
 
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
479
 
        self.assertContainsRe(d, '-contents\n'
480
 
                                 '\\+new contents\n')
 
418
        diff = self.get_diff(tree.basis_tree(), tree)
 
419
        self.assertContainsRe(diff, "=== modified file 'file'\n")
 
420
        self.assertContainsRe(diff, '--- old/file\t')
 
421
        self.assertContainsRe(diff, '\\+\\+\\+ new/file\t')
 
422
        self.assertContainsRe(diff, '-contents\n'
 
423
                                    '\\+new contents\n')
481
424
 
482
425
    def test_modified_file_in_renamed_dir(self):
483
426
        """Test when a file is modified in a renamed directory."""
489
432
 
490
433
        tree.rename_one('dir', 'other')
491
434
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
492
 
        d = get_diff_as_string(tree.basis_tree(), tree)
493
 
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
494
 
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
 
435
        diff = self.get_diff(tree.basis_tree(), tree)
 
436
        self.assertContainsRe(diff, "=== renamed directory 'dir' => 'other'\n")
 
437
        self.assertContainsRe(diff, "=== modified file 'other/file'\n")
495
438
        # XXX: This is technically incorrect, because it used to be at another
496
439
        # location. What to do?
497
 
        self.assertContainsRe(d, '--- old/dir/file\t')
498
 
        self.assertContainsRe(d, '\\+\\+\\+ new/other/file\t')
499
 
        self.assertContainsRe(d, '-contents\n'
500
 
                                 '\\+new contents\n')
 
440
        self.assertContainsRe(diff, '--- old/dir/file\t')
 
441
        self.assertContainsRe(diff, '\\+\\+\\+ new/other/file\t')
 
442
        self.assertContainsRe(diff, '-contents\n'
 
443
                                    '\\+new contents\n')
501
444
 
502
445
    def test_renamed_directory(self):
503
446
        """Test when only a directory is only renamed."""
508
451
        tree.commit('one', rev_id='rev-1')
509
452
 
510
453
        tree.rename_one('dir', 'newdir')
511
 
        d = get_diff_as_string(tree.basis_tree(), tree)
 
454
        diff = self.get_diff(tree.basis_tree(), tree)
512
455
        # Renaming a directory should be a single "you renamed this dir" even
513
456
        # when there are files inside.
514
 
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
 
457
        self.assertEqual("=== renamed directory 'dir' => 'newdir'\n", diff)
515
458
 
516
459
    def test_renamed_file(self):
517
460
        """Test when a file is only renamed."""
521
464
        tree.commit('one', rev_id='rev-1')
522
465
 
523
466
        tree.rename_one('file', 'newname')
524
 
        d = get_diff_as_string(tree.basis_tree(), tree)
525
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
 
467
        diff = self.get_diff(tree.basis_tree(), tree)
 
468
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
526
469
        # We shouldn't have a --- or +++ line, because there is no content
527
470
        # change
528
 
        self.assertNotContainsRe(d, '---')
 
471
        self.assertNotContainsRe(diff, '---')
529
472
 
530
473
    def test_renamed_and_modified_file(self):
531
474
        """Test when a file is only renamed."""
536
479
 
537
480
        tree.rename_one('file', 'newname')
538
481
        self.build_tree_contents([('tree/newname', 'new contents\n')])
539
 
        d = get_diff_as_string(tree.basis_tree(), tree)
540
 
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
541
 
        self.assertContainsRe(d, '--- old/file\t')
542
 
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
543
 
        self.assertContainsRe(d, '-contents\n'
544
 
                                 '\\+new contents\n')
545
 
 
546
 
 
547
 
    def test_internal_diff_exec_property(self):
548
 
        tree = self.make_branch_and_tree('tree')
549
 
 
550
 
        tt = transform.TreeTransform(tree)
551
 
        tt.new_file('a', tt.root, 'contents\n', 'a-id', True)
552
 
        tt.new_file('b', tt.root, 'contents\n', 'b-id', False)
553
 
        tt.new_file('c', tt.root, 'contents\n', 'c-id', True)
554
 
        tt.new_file('d', tt.root, 'contents\n', 'd-id', False)
555
 
        tt.new_file('e', tt.root, 'contents\n', 'control-e-id', True)
556
 
        tt.new_file('f', tt.root, 'contents\n', 'control-f-id', False)
557
 
        tt.apply()
558
 
        tree.commit('one', rev_id='rev-1')
559
 
 
560
 
        tt = transform.TreeTransform(tree)
561
 
        tt.set_executability(False, tt.trans_id_file_id('a-id'))
562
 
        tt.set_executability(True, tt.trans_id_file_id('b-id'))
563
 
        tt.set_executability(False, tt.trans_id_file_id('c-id'))
564
 
        tt.set_executability(True, tt.trans_id_file_id('d-id'))
565
 
        tt.apply()
566
 
        tree.rename_one('c', 'new-c')
567
 
        tree.rename_one('d', 'new-d')
568
 
 
569
 
        d = get_diff_as_string(tree.basis_tree(), tree)
570
 
 
571
 
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
572
 
                                  ".*\+x to -x.*\)")
573
 
        self.assertContainsRe(d, r"file 'b'.*\(properties changed:"
574
 
                                  ".*-x to \+x.*\)")
575
 
        self.assertContainsRe(d, r"file 'c'.*\(properties changed:"
576
 
                                  ".*\+x to -x.*\)")
577
 
        self.assertContainsRe(d, r"file 'd'.*\(properties changed:"
578
 
                                  ".*-x to \+x.*\)")
579
 
        self.assertNotContainsRe(d, r"file 'e'")
580
 
        self.assertNotContainsRe(d, r"file 'f'")
 
482
        diff = self.get_diff(tree.basis_tree(), tree)
 
483
        self.assertContainsRe(diff, "=== renamed file 'file' => 'newname'\n")
 
484
        self.assertContainsRe(diff, '--- old/file\t')
 
485
        self.assertContainsRe(diff, '\\+\\+\\+ new/newname\t')
 
486
        self.assertContainsRe(diff, '-contents\n'
 
487
                                    '\\+new contents\n')
581
488
 
582
489
    def test_binary_unicode_filenames(self):
583
490
        """Test that contents of files are *not* encoded in UTF-8 when there
584
491
        is a binary file in the diff.
585
492
        """
586
493
        # See https://bugs.launchpad.net/bugs/110092.
587
 
        self.requireFeature(features.UnicodeFilenameFeature)
 
494
        self.requireFeature(UnicodeFilename)
588
495
 
589
496
        # This bug isn't triggered with cStringIO.
590
497
        from StringIO import StringIO
598
505
        tree.add([alpha], ['file-id'])
599
506
        tree.add([omega], ['file-id-2'])
600
507
        diff_content = StringIO()
601
 
        diff.show_diff_trees(tree.basis_tree(), tree, diff_content)
602
 
        d = diff_content.getvalue()
603
 
        self.assertContainsRe(d, r"=== added file '%s'" % alpha_utf8)
604
 
        self.assertContainsRe(d, "Binary files a/%s.*and b/%s.* differ\n"
605
 
                              % (alpha_utf8, alpha_utf8))
606
 
        self.assertContainsRe(d, r"=== added file '%s'" % omega_utf8)
607
 
        self.assertContainsRe(d, r"--- a/%s" % (omega_utf8,))
608
 
        self.assertContainsRe(d, r"\+\+\+ b/%s" % (omega_utf8,))
 
508
        show_diff_trees(tree.basis_tree(), tree, diff_content)
 
509
        diff = diff_content.getvalue()
 
510
        self.assertContainsRe(diff, r"=== added file '%s'" % alpha_utf8)
 
511
        self.assertContainsRe(
 
512
            diff, "Binary files a/%s.*and b/%s.* differ\n" % (alpha_utf8, alpha_utf8))
 
513
        self.assertContainsRe(diff, r"=== added file '%s'" % omega_utf8)
 
514
        self.assertContainsRe(diff, r"--- a/%s" % (omega_utf8,))
 
515
        self.assertContainsRe(diff, r"\+\+\+ b/%s" % (omega_utf8,))
609
516
 
610
517
    def test_unicode_filename(self):
611
518
        """Test when the filename are unicode."""
612
 
        self.requireFeature(features.UnicodeFilenameFeature)
 
519
        self.requireFeature(UnicodeFilename)
613
520
 
614
521
        alpha, omega = u'\u03b1', u'\u03c9'
615
522
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
630
537
        tree.add(['add_'+alpha], ['file-id'])
631
538
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
632
539
 
633
 
        d = get_diff_as_string(tree.basis_tree(), tree)
634
 
        self.assertContainsRe(d,
 
540
        diff = self.get_diff(tree.basis_tree(), tree)
 
541
        self.assertContainsRe(diff,
635
542
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
636
 
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
637
 
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
638
 
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
639
 
 
640
 
    def test_unicode_filename_path_encoding(self):
641
 
        """Test for bug #382699: unicode filenames on Windows should be shown
642
 
        in user encoding.
643
 
        """
644
 
        self.requireFeature(features.UnicodeFilenameFeature)
645
 
        # The word 'test' in Russian
646
 
        _russian_test = u'\u0422\u0435\u0441\u0442'
647
 
        directory = _russian_test + u'/'
648
 
        test_txt = _russian_test + u'.txt'
649
 
        u1234 = u'\u1234.txt'
650
 
 
651
 
        tree = self.make_branch_and_tree('.')
652
 
        self.build_tree_contents([
653
 
            (test_txt, 'foo\n'),
654
 
            (u1234, 'foo\n'),
655
 
            (directory, None),
656
 
            ])
657
 
        tree.add([test_txt, u1234, directory])
658
 
 
659
 
        sio = StringIO()
660
 
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
661
 
            path_encoding='cp1251')
662
 
 
663
 
        output = subst_dates(sio.getvalue())
664
 
        shouldbe = ('''\
665
 
=== added directory '%(directory)s'
666
 
=== added file '%(test_txt)s'
667
 
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
668
 
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
669
 
@@ -0,0 +1,1 @@
670
 
+foo
671
 
 
672
 
=== added file '?.txt'
673
 
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
674
 
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
675
 
@@ -0,0 +1,1 @@
676
 
+foo
677
 
 
678
 
''' % {'directory': _russian_test.encode('cp1251'),
679
 
       'test_txt': test_txt.encode('cp1251'),
680
 
      })
681
 
        self.assertEqualDiff(output, shouldbe)
682
 
 
683
 
 
684
 
class DiffWasIs(diff.DiffPath):
685
 
 
686
 
    def diff(self, file_id, old_path, new_path, old_kind, new_kind):
687
 
        self.to_file.write('was: ')
688
 
        self.to_file.write(self.old_tree.get_file(file_id).read())
689
 
        self.to_file.write('is: ')
690
 
        self.to_file.write(self.new_tree.get_file(file_id).read())
691
 
        pass
692
 
 
693
 
 
694
 
class TestDiffTree(tests.TestCaseWithTransport):
695
 
 
696
 
    def setUp(self):
697
 
        super(TestDiffTree, self).setUp()
698
 
        self.old_tree = self.make_branch_and_tree('old-tree')
699
 
        self.old_tree.lock_write()
700
 
        self.addCleanup(self.old_tree.unlock)
701
 
        self.new_tree = self.make_branch_and_tree('new-tree')
702
 
        self.new_tree.lock_write()
703
 
        self.addCleanup(self.new_tree.unlock)
704
 
        self.differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
705
 
 
706
 
    def test_diff_text(self):
707
 
        self.build_tree_contents([('old-tree/olddir/',),
708
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
709
 
        self.old_tree.add('olddir')
710
 
        self.old_tree.add('olddir/oldfile', 'file-id')
711
 
        self.build_tree_contents([('new-tree/newdir/',),
712
 
                                  ('new-tree/newdir/newfile', 'new\n')])
713
 
        self.new_tree.add('newdir')
714
 
        self.new_tree.add('newdir/newfile', 'file-id')
715
 
        differ = diff.DiffText(self.old_tree, self.new_tree, StringIO())
716
 
        differ.diff_text('file-id', None, 'old label', 'new label')
717
 
        self.assertEqual(
718
 
            '--- old label\n+++ new label\n@@ -1,1 +0,0 @@\n-old\n\n',
719
 
            differ.to_file.getvalue())
720
 
        differ.to_file.seek(0)
721
 
        differ.diff_text(None, 'file-id', 'old label', 'new label')
722
 
        self.assertEqual(
723
 
            '--- old label\n+++ new label\n@@ -0,0 +1,1 @@\n+new\n\n',
724
 
            differ.to_file.getvalue())
725
 
        differ.to_file.seek(0)
726
 
        differ.diff_text('file-id', 'file-id', 'old label', 'new label')
727
 
        self.assertEqual(
728
 
            '--- old label\n+++ new label\n@@ -1,1 +1,1 @@\n-old\n+new\n\n',
729
 
            differ.to_file.getvalue())
730
 
 
731
 
    def test_diff_deletion(self):
732
 
        self.build_tree_contents([('old-tree/file', 'contents'),
733
 
                                  ('new-tree/file', 'contents')])
734
 
        self.old_tree.add('file', 'file-id')
735
 
        self.new_tree.add('file', 'file-id')
736
 
        os.unlink('new-tree/file')
737
 
        self.differ.show_diff(None)
738
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '-contents')
739
 
 
740
 
    def test_diff_creation(self):
741
 
        self.build_tree_contents([('old-tree/file', 'contents'),
742
 
                                  ('new-tree/file', 'contents')])
743
 
        self.old_tree.add('file', 'file-id')
744
 
        self.new_tree.add('file', 'file-id')
745
 
        os.unlink('old-tree/file')
746
 
        self.differ.show_diff(None)
747
 
        self.assertContainsRe(self.differ.to_file.getvalue(), '\+contents')
748
 
 
749
 
    def test_diff_symlink(self):
750
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
751
 
        differ.diff_symlink('old target', None)
752
 
        self.assertEqual("=== target was 'old target'\n",
753
 
                         differ.to_file.getvalue())
754
 
 
755
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
756
 
        differ.diff_symlink(None, 'new target')
757
 
        self.assertEqual("=== target is 'new target'\n",
758
 
                         differ.to_file.getvalue())
759
 
 
760
 
        differ = diff.DiffSymlink(self.old_tree, self.new_tree, StringIO())
761
 
        differ.diff_symlink('old target', 'new target')
762
 
        self.assertEqual("=== target changed 'old target' => 'new target'\n",
763
 
                         differ.to_file.getvalue())
764
 
 
765
 
    def test_diff(self):
766
 
        self.build_tree_contents([('old-tree/olddir/',),
767
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
768
 
        self.old_tree.add('olddir')
769
 
        self.old_tree.add('olddir/oldfile', 'file-id')
770
 
        self.build_tree_contents([('new-tree/newdir/',),
771
 
                                  ('new-tree/newdir/newfile', 'new\n')])
772
 
        self.new_tree.add('newdir')
773
 
        self.new_tree.add('newdir/newfile', 'file-id')
774
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
775
 
        self.assertContainsRe(
776
 
            self.differ.to_file.getvalue(),
777
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
778
 
             ' \@\@\n-old\n\+new\n\n')
779
 
 
780
 
    def test_diff_kind_change(self):
781
 
        self.requireFeature(features.SymlinkFeature)
782
 
        self.build_tree_contents([('old-tree/olddir/',),
783
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
784
 
        self.old_tree.add('olddir')
785
 
        self.old_tree.add('olddir/oldfile', 'file-id')
786
 
        self.build_tree(['new-tree/newdir/'])
787
 
        os.symlink('new', 'new-tree/newdir/newfile')
788
 
        self.new_tree.add('newdir')
789
 
        self.new_tree.add('newdir/newfile', 'file-id')
790
 
        self.differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
791
 
        self.assertContainsRe(
792
 
            self.differ.to_file.getvalue(),
793
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+0,0'
794
 
             ' \@\@\n-old\n\n')
795
 
        self.assertContainsRe(self.differ.to_file.getvalue(),
796
 
                              "=== target is u'new'\n")
797
 
 
798
 
    def test_diff_directory(self):
799
 
        self.build_tree(['new-tree/new-dir/'])
800
 
        self.new_tree.add('new-dir', 'new-dir-id')
801
 
        self.differ.diff('new-dir-id', None, 'new-dir')
802
 
        self.assertEqual(self.differ.to_file.getvalue(), '')
803
 
 
804
 
    def create_old_new(self):
805
 
        self.build_tree_contents([('old-tree/olddir/',),
806
 
                                  ('old-tree/olddir/oldfile', 'old\n')])
807
 
        self.old_tree.add('olddir')
808
 
        self.old_tree.add('olddir/oldfile', 'file-id')
809
 
        self.build_tree_contents([('new-tree/newdir/',),
810
 
                                  ('new-tree/newdir/newfile', 'new\n')])
811
 
        self.new_tree.add('newdir')
812
 
        self.new_tree.add('newdir/newfile', 'file-id')
813
 
 
814
 
    def test_register_diff(self):
815
 
        self.create_old_new()
816
 
        old_diff_factories = diff.DiffTree.diff_factories
817
 
        diff.DiffTree.diff_factories=old_diff_factories[:]
818
 
        diff.DiffTree.diff_factories.insert(0, DiffWasIs.from_diff_tree)
819
 
        try:
820
 
            differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO())
821
 
        finally:
822
 
            diff.DiffTree.diff_factories = old_diff_factories
823
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
824
 
        self.assertNotContainsRe(
825
 
            differ.to_file.getvalue(),
826
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
827
 
             ' \@\@\n-old\n\+new\n\n')
828
 
        self.assertContainsRe(differ.to_file.getvalue(),
829
 
                              'was: old\nis: new\n')
830
 
 
831
 
    def test_extra_factories(self):
832
 
        self.create_old_new()
833
 
        differ = diff.DiffTree(self.old_tree, self.new_tree, StringIO(),
834
 
                               extra_factories=[DiffWasIs.from_diff_tree])
835
 
        differ.diff('file-id', 'olddir/oldfile', 'newdir/newfile')
836
 
        self.assertNotContainsRe(
837
 
            differ.to_file.getvalue(),
838
 
            r'--- olddir/oldfile.*\n\+\+\+ newdir/newfile.*\n\@\@ -1,1 \+1,1'
839
 
             ' \@\@\n-old\n\+new\n\n')
840
 
        self.assertContainsRe(differ.to_file.getvalue(),
841
 
                              'was: old\nis: new\n')
842
 
 
843
 
    def test_alphabetical_order(self):
844
 
        self.build_tree(['new-tree/a-file'])
845
 
        self.new_tree.add('a-file')
846
 
        self.build_tree(['old-tree/b-file'])
847
 
        self.old_tree.add('b-file')
848
 
        self.differ.show_diff(None)
849
 
        self.assertContainsRe(self.differ.to_file.getvalue(),
850
 
            '.*a-file(.|\n)*b-file')
851
 
 
852
 
 
853
 
class TestPatienceDiffLib(tests.TestCase):
 
543
        self.assertContainsRe(diff, "=== added file 'add_%s'"%autf8)
 
544
        self.assertContainsRe(diff, "=== modified file 'mod_%s'"%autf8)
 
545
        self.assertContainsRe(diff, "=== removed file 'del_%s'"%autf8)
 
546
 
 
547
class TestPatienceDiffLib(TestCase):
854
548
 
855
549
    def setUp(self):
856
550
        super(TestPatienceDiffLib, self).setUp()
857
 
        self._unique_lcs = _patiencediff_py.unique_lcs_py
858
 
        self._recurse_matches = _patiencediff_py.recurse_matches_py
 
551
        self._unique_lcs = bzrlib._patiencediff_py.unique_lcs_py
 
552
        self._recurse_matches = bzrlib._patiencediff_py.recurse_matches_py
859
553
        self._PatienceSequenceMatcher = \
860
 
            _patiencediff_py.PatienceSequenceMatcher_py
861
 
 
862
 
    def test_diff_unicode_string(self):
863
 
        a = ''.join([unichr(i) for i in range(4000, 4500, 3)])
864
 
        b = ''.join([unichr(i) for i in range(4300, 4800, 2)])
865
 
        sm = self._PatienceSequenceMatcher(None, a, b)
866
 
        mb = sm.get_matching_blocks()
867
 
        self.assertEqual(35, len(mb))
 
554
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
868
555
 
869
556
    def test_unique_lcs(self):
870
557
        unique_lcs = self._unique_lcs
871
 
        self.assertEqual(unique_lcs('', ''), [])
872
 
        self.assertEqual(unique_lcs('', 'a'), [])
873
 
        self.assertEqual(unique_lcs('a', ''), [])
874
 
        self.assertEqual(unique_lcs('a', 'a'), [(0,0)])
875
 
        self.assertEqual(unique_lcs('a', 'b'), [])
876
 
        self.assertEqual(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
877
 
        self.assertEqual(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
878
 
        self.assertEqual(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
879
 
        self.assertEqual(unique_lcs('abXde', 'abYde'), [(0,0), (1,1),
 
558
        self.assertEquals(unique_lcs('', ''), [])
 
559
        self.assertEquals(unique_lcs('', 'a'), [])
 
560
        self.assertEquals(unique_lcs('a', ''), [])
 
561
        self.assertEquals(unique_lcs('a', 'a'), [(0,0)])
 
562
        self.assertEquals(unique_lcs('a', 'b'), [])
 
563
        self.assertEquals(unique_lcs('ab', 'ab'), [(0,0), (1,1)])
 
564
        self.assertEquals(unique_lcs('abcde', 'cdeab'), [(2,0), (3,1), (4,2)])
 
565
        self.assertEquals(unique_lcs('cdeab', 'abcde'), [(0,2), (1,3), (2,4)])
 
566
        self.assertEquals(unique_lcs('abXde', 'abYde'), [(0,0), (1,1), 
880
567
                                                         (3,3), (4,4)])
881
 
        self.assertEqual(unique_lcs('acbac', 'abc'), [(2,1)])
 
568
        self.assertEquals(unique_lcs('acbac', 'abc'), [(2,1)])
882
569
 
883
570
    def test_recurse_matches(self):
884
571
        def test_one(a, b, matches):
885
572
            test_matches = []
886
573
            self._recurse_matches(
887
574
                a, b, 0, 0, len(a), len(b), test_matches, 10)
888
 
            self.assertEqual(test_matches, matches)
 
575
            self.assertEquals(test_matches, matches)
889
576
 
890
577
        test_one(['a', '', 'b', '', 'c'], ['a', 'a', 'b', 'c', 'c'],
891
578
                 [(0, 0), (2, 2), (4, 4)])
897
584
        test_one('abcdbce', 'afbcgdbce', [(0,0), (1, 2), (2, 3), (3, 5),
898
585
                                          (4, 6), (5, 7), (6, 8)])
899
586
 
900
 
        # recurse_matches doesn't match non-unique
 
587
        # recurse_matches doesn't match non-unique 
901
588
        # lines surrounded by bogus text.
902
589
        # The update has been done in patiencediff.SequenceMatcher instead
903
590
 
907
594
        # This is what it currently gives:
908
595
        test_one('aBccDe', 'abccde', [(0,0), (5,5)])
909
596
 
910
 
    def assertDiffBlocks(self, a, b, expected_blocks):
911
 
        """Check that the sequence matcher returns the correct blocks.
912
 
 
913
 
        :param a: A sequence to match
914
 
        :param b: Another sequence to match
915
 
        :param expected_blocks: The expected output, not including the final
916
 
            matching block (len(a), len(b), 0)
917
 
        """
918
 
        matcher = self._PatienceSequenceMatcher(None, a, b)
919
 
        blocks = matcher.get_matching_blocks()
920
 
        last = blocks.pop()
921
 
        self.assertEqual((len(a), len(b), 0), last)
922
 
        self.assertEqual(expected_blocks, blocks)
923
 
 
924
597
    def test_matching_blocks(self):
 
598
        def chk_blocks(a, b, expected_blocks):
 
599
            # difflib always adds a signature of the total
 
600
            # length, with no matching entries at the end
 
601
            s = self._PatienceSequenceMatcher(None, a, b)
 
602
            blocks = s.get_matching_blocks()
 
603
            self.assertEquals((len(a), len(b), 0), blocks[-1])
 
604
            self.assertEquals(expected_blocks, blocks[:-1])
 
605
 
925
606
        # Some basic matching tests
926
 
        self.assertDiffBlocks('', '', [])
927
 
        self.assertDiffBlocks([], [], [])
928
 
        self.assertDiffBlocks('abc', '', [])
929
 
        self.assertDiffBlocks('', 'abc', [])
930
 
        self.assertDiffBlocks('abcd', 'abcd', [(0, 0, 4)])
931
 
        self.assertDiffBlocks('abcd', 'abce', [(0, 0, 3)])
932
 
        self.assertDiffBlocks('eabc', 'abce', [(1, 0, 3)])
933
 
        self.assertDiffBlocks('eabce', 'abce', [(1, 0, 4)])
934
 
        self.assertDiffBlocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
935
 
        self.assertDiffBlocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
936
 
        self.assertDiffBlocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
937
 
        # This may check too much, but it checks to see that
 
607
        chk_blocks('', '', [])
 
608
        chk_blocks([], [], [])
 
609
        chk_blocks('abc', '', [])
 
610
        chk_blocks('', 'abc', [])
 
611
        chk_blocks('abcd', 'abcd', [(0, 0, 4)])
 
612
        chk_blocks('abcd', 'abce', [(0, 0, 3)])
 
613
        chk_blocks('eabc', 'abce', [(1, 0, 3)])
 
614
        chk_blocks('eabce', 'abce', [(1, 0, 4)])
 
615
        chk_blocks('abcde', 'abXde', [(0, 0, 2), (3, 3, 2)])
 
616
        chk_blocks('abcde', 'abXYZde', [(0, 0, 2), (3, 5, 2)])
 
617
        chk_blocks('abde', 'abXYZde', [(0, 0, 2), (2, 5, 2)])
 
618
        # This may check too much, but it checks to see that 
938
619
        # a copied block stays attached to the previous section,
939
620
        # not the later one.
940
621
        # difflib would tend to grab the trailing longest match
941
622
        # which would make the diff not look right
942
 
        self.assertDiffBlocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
943
 
                              [(0, 0, 6), (6, 11, 10)])
 
623
        chk_blocks('abcdefghijklmnop', 'abcdefxydefghijklmnop',
 
624
                   [(0, 0, 6), (6, 11, 10)])
944
625
 
945
626
        # make sure it supports passing in lists
946
 
        self.assertDiffBlocks(
 
627
        chk_blocks(
947
628
                   ['hello there\n',
948
629
                    'world\n',
949
630
                    'how are you today?\n'],
953
634
 
954
635
        # non unique lines surrounded by non-matching lines
955
636
        # won't be found
956
 
        self.assertDiffBlocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
 
637
        chk_blocks('aBccDe', 'abccde', [(0,0,1), (5,5,1)])
957
638
 
958
639
        # But they only need to be locally unique
959
 
        self.assertDiffBlocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
 
640
        chk_blocks('aBcDec', 'abcdec', [(0,0,1), (2,2,1), (4,4,2)])
960
641
 
961
642
        # non unique blocks won't be matched
962
 
        self.assertDiffBlocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
 
643
        chk_blocks('aBcdEcdFg', 'abcdecdfg', [(0,0,1), (8,8,1)])
963
644
 
964
645
        # but locally unique ones will
965
 
        self.assertDiffBlocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
 
646
        chk_blocks('aBcdEeXcdFg', 'abcdecdfg', [(0,0,1), (2,2,2),
966
647
                                              (5,4,1), (7,5,2), (10,8,1)])
967
648
 
968
 
        self.assertDiffBlocks('abbabbXd', 'cabbabxd', [(7,7,1)])
969
 
        self.assertDiffBlocks('abbabbbb', 'cabbabbc', [])
970
 
        self.assertDiffBlocks('bbbbbbbb', 'cbbbbbbc', [])
971
 
 
972
 
    def test_matching_blocks_tuples(self):
973
 
        # Some basic matching tests
974
 
        self.assertDiffBlocks([], [], [])
975
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')], [], [])
976
 
        self.assertDiffBlocks([], [('a',), ('b',), ('c,')], [])
977
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
978
 
                              [('a',), ('b',), ('c,')],
979
 
                              [(0, 0, 3)])
980
 
        self.assertDiffBlocks([('a',), ('b',), ('c,')],
981
 
                              [('a',), ('b',), ('d,')],
982
 
                              [(0, 0, 2)])
983
 
        self.assertDiffBlocks([('d',), ('b',), ('c,')],
984
 
                              [('a',), ('b',), ('c,')],
985
 
                              [(1, 1, 2)])
986
 
        self.assertDiffBlocks([('d',), ('a',), ('b',), ('c,')],
987
 
                              [('a',), ('b',), ('c,')],
988
 
                              [(1, 0, 3)])
989
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
990
 
                              [('a', 'b'), ('c', 'X'), ('e', 'f')],
991
 
                              [(0, 0, 1), (2, 2, 1)])
992
 
        self.assertDiffBlocks([('a', 'b'), ('c', 'd'), ('e', 'f')],
993
 
                              [('a', 'b'), ('c', 'dX'), ('e', 'f')],
994
 
                              [(0, 0, 1), (2, 2, 1)])
 
649
        chk_blocks('abbabbXd', 'cabbabxd', [(7,7,1)])
 
650
        chk_blocks('abbabbbb', 'cabbabbc', [])
 
651
        chk_blocks('bbbbbbbb', 'cbbbbbbc', [])
995
652
 
996
653
    def test_opcodes(self):
997
654
        def chk_ops(a, b, expected_codes):
998
655
            s = self._PatienceSequenceMatcher(None, a, b)
999
 
            self.assertEqual(expected_codes, s.get_opcodes())
 
656
            self.assertEquals(expected_codes, s.get_opcodes())
1000
657
 
1001
658
        chk_ops('', '', [])
1002
659
        chk_ops([], [], [])
1040
697
                 ('delete', 1,2, 1,1),
1041
698
                 ('equal',  2,3, 1,2),
1042
699
                ])
1043
 
        chk_ops('aBccDe', 'abccde',
 
700
        chk_ops('aBccDe', 'abccde', 
1044
701
                [('equal',   0,1, 0,1),
1045
702
                 ('replace', 1,5, 1,5),
1046
703
                 ('equal',   5,6, 5,6),
1047
704
                ])
1048
 
        chk_ops('aBcDec', 'abcdec',
 
705
        chk_ops('aBcDec', 'abcdec', 
1049
706
                [('equal',   0,1, 0,1),
1050
707
                 ('replace', 1,2, 1,2),
1051
708
                 ('equal',   2,3, 2,3),
1052
709
                 ('replace', 3,4, 3,4),
1053
710
                 ('equal',   4,6, 4,6),
1054
711
                ])
1055
 
        chk_ops('aBcdEcdFg', 'abcdecdfg',
 
712
        chk_ops('aBcdEcdFg', 'abcdecdfg', 
1056
713
                [('equal',   0,1, 0,1),
1057
714
                 ('replace', 1,8, 1,8),
1058
715
                 ('equal',   8,9, 8,9)
1059
716
                ])
1060
 
        chk_ops('aBcdEeXcdFg', 'abcdecdfg',
 
717
        chk_ops('aBcdEeXcdFg', 'abcdecdfg', 
1061
718
                [('equal',   0,1, 0,1),
1062
719
                 ('replace', 1,2, 1,2),
1063
720
                 ('equal',   2,4, 2,4),
1072
729
    def test_grouped_opcodes(self):
1073
730
        def chk_ops(a, b, expected_codes, n=3):
1074
731
            s = self._PatienceSequenceMatcher(None, a, b)
1075
 
            self.assertEqual(expected_codes, list(s.get_grouped_opcodes(n)))
 
732
            self.assertEquals(expected_codes, list(s.get_grouped_opcodes(n)))
1076
733
 
1077
734
        chk_ops('', '', [])
1078
735
        chk_ops([], [], [])
1109
766
    def test_multiple_ranges(self):
1110
767
        # There was an earlier bug where we used a bad set of ranges,
1111
768
        # this triggers that specific bug, to make sure it doesn't regress
1112
 
        self.assertDiffBlocks('abcdefghijklmnop',
1113
 
                              'abcXghiYZQRSTUVWXYZijklmnop',
1114
 
                              [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
1115
 
 
1116
 
        self.assertDiffBlocks('ABCd efghIjk  L',
1117
 
                              'AxyzBCn mo pqrstuvwI1 2  L',
1118
 
                              [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
 
769
        def chk_blocks(a, b, expected_blocks):
 
770
            # difflib always adds a signature of the total
 
771
            # length, with no matching entries at the end
 
772
            s = self._PatienceSequenceMatcher(None, a, b)
 
773
            blocks = s.get_matching_blocks()
 
774
            x = blocks.pop()
 
775
            self.assertEquals(x, (len(a), len(b), 0))
 
776
            self.assertEquals(expected_blocks, blocks)
 
777
 
 
778
        chk_blocks('abcdefghijklmnop'
 
779
                 , 'abcXghiYZQRSTUVWXYZijklmnop'
 
780
                 , [(0, 0, 3), (6, 4, 3), (9, 20, 7)])
 
781
 
 
782
        chk_blocks('ABCd efghIjk  L'
 
783
                 , 'AxyzBCn mo pqrstuvwI1 2  L'
 
784
                 , [(0,0,1), (1, 4, 2), (9, 19, 1), (12, 23, 3)])
1119
785
 
1120
786
        # These are rot13 code snippets.
1121
 
        self.assertDiffBlocks('''\
 
787
        chk_blocks('''\
1122
788
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1123
789
    """
1124
790
    gnxrf_netf = ['svyr*']
1125
791
    gnxrf_bcgvbaf = ['ab-erphefr']
1126
 
 
 
792
  
1127
793
    qrs eha(frys, svyr_yvfg, ab_erphefr=Snyfr):
1128
794
        sebz omeyvo.nqq vzcbeg fzneg_nqq, nqq_ercbegre_cevag, nqq_ercbegre_ahyy
1129
795
        vs vf_dhvrg():
1137
803
'''.splitlines(True), '''\
1138
804
    trg nqqrq jura lbh nqq n svyr va gur qverpgbel.
1139
805
 
1140
 
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl
 
806
    --qel-eha jvyy fubj juvpu svyrf jbhyq or nqqrq, ohg abg npghnyyl 
1141
807
    nqq gurz.
1142
808
    """
1143
809
    gnxrf_netf = ['svyr*']
1170
836
                 'how are you today?\n']
1171
837
        txt_b = ['hello there\n',
1172
838
                 'how are you today?\n']
1173
 
        unified_diff = patiencediff.unified_diff
 
839
        unified_diff = bzrlib.patiencediff.unified_diff
1174
840
        psm = self._PatienceSequenceMatcher
1175
 
        self.assertEqual(['--- \n',
1176
 
                           '+++ \n',
 
841
        self.assertEquals([ '---  \n',
 
842
                           '+++  \n',
1177
843
                           '@@ -1,3 +1,2 @@\n',
1178
844
                           ' hello there\n',
1179
845
                           '-world\n',
1184
850
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1185
851
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1186
852
        # This is the result with LongestCommonSubstring matching
1187
 
        self.assertEqual(['--- \n',
1188
 
                           '+++ \n',
 
853
        self.assertEquals(['---  \n',
 
854
                           '+++  \n',
1189
855
                           '@@ -1,6 +1,11 @@\n',
1190
856
                           ' a\n',
1191
857
                           ' b\n',
1200
866
                           ' f\n']
1201
867
                          , list(unified_diff(txt_a, txt_b)))
1202
868
        # And the patience diff
1203
 
        self.assertEqual(['--- \n',
1204
 
                           '+++ \n',
 
869
        self.assertEquals(['---  \n',
 
870
                           '+++  \n',
1205
871
                           '@@ -4,6 +4,11 @@\n',
1206
872
                           ' d\n',
1207
873
                           ' e\n',
1218
884
                          , list(unified_diff(txt_a, txt_b,
1219
885
                                 sequencematcher=psm)))
1220
886
 
1221
 
    def test_patience_unified_diff_with_dates(self):
1222
 
        txt_a = ['hello there\n',
1223
 
                 'world\n',
1224
 
                 'how are you today?\n']
1225
 
        txt_b = ['hello there\n',
1226
 
                 'how are you today?\n']
1227
 
        unified_diff = patiencediff.unified_diff
1228
 
        psm = self._PatienceSequenceMatcher
1229
 
        self.assertEqual(['--- a\t2008-08-08\n',
1230
 
                           '+++ b\t2008-09-09\n',
1231
 
                           '@@ -1,3 +1,2 @@\n',
1232
 
                           ' hello there\n',
1233
 
                           '-world\n',
1234
 
                           ' how are you today?\n'
1235
 
                          ]
1236
 
                          , list(unified_diff(txt_a, txt_b,
1237
 
                                 fromfile='a', tofile='b',
1238
 
                                 fromfiledate='2008-08-08',
1239
 
                                 tofiledate='2008-09-09',
1240
 
                                 sequencematcher=psm)))
1241
 
 
1242
887
 
1243
888
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1244
889
 
1245
 
    _test_needs_features = [features.compiled_patiencediff_feature]
 
890
    _test_needs_features = [CompiledPatienceDiffFeature]
1246
891
 
1247
892
    def setUp(self):
1248
893
        super(TestPatienceDiffLib_c, self).setUp()
1249
 
        from bzrlib import _patiencediff_c
1250
 
        self._unique_lcs = _patiencediff_c.unique_lcs_c
1251
 
        self._recurse_matches = _patiencediff_c.recurse_matches_c
 
894
        import bzrlib._patiencediff_c
 
895
        self._unique_lcs = bzrlib._patiencediff_c.unique_lcs_c
 
896
        self._recurse_matches = bzrlib._patiencediff_c.recurse_matches_c
1252
897
        self._PatienceSequenceMatcher = \
1253
 
            _patiencediff_c.PatienceSequenceMatcher_c
1254
 
 
1255
 
    def test_unhashable(self):
1256
 
        """We should get a proper exception here."""
1257
 
        # We need to be able to hash items in the sequence, lists are
1258
 
        # unhashable, and thus cannot be diffed
1259
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1260
 
                                         None, [[]], [])
1261
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1262
 
                                         None, ['valid', []], [])
1263
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1264
 
                                         None, ['valid'], [[]])
1265
 
        e = self.assertRaises(TypeError, self._PatienceSequenceMatcher,
1266
 
                                         None, ['valid'], ['valid', []])
1267
 
 
1268
 
 
1269
 
class TestPatienceDiffLibFiles(tests.TestCaseInTempDir):
 
898
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
899
 
 
900
 
 
901
class TestPatienceDiffLibFiles(TestCaseInTempDir):
1270
902
 
1271
903
    def setUp(self):
1272
904
        super(TestPatienceDiffLibFiles, self).setUp()
1273
905
        self._PatienceSequenceMatcher = \
1274
 
            _patiencediff_py.PatienceSequenceMatcher_py
 
906
            bzrlib._patiencediff_py.PatienceSequenceMatcher_py
1275
907
 
1276
908
    def test_patience_unified_diff_files(self):
1277
909
        txt_a = ['hello there\n',
1279
911
                 'how are you today?\n']
1280
912
        txt_b = ['hello there\n',
1281
913
                 'how are you today?\n']
1282
 
        with open('a1', 'wb') as f: f.writelines(txt_a)
1283
 
        with open('b1', 'wb') as f: f.writelines(txt_b)
 
914
        open('a1', 'wb').writelines(txt_a)
 
915
        open('b1', 'wb').writelines(txt_b)
1284
916
 
1285
 
        unified_diff_files = patiencediff.unified_diff_files
 
917
        unified_diff_files = bzrlib.patiencediff.unified_diff_files
1286
918
        psm = self._PatienceSequenceMatcher
1287
 
        self.assertEqual(['--- a1\n',
1288
 
                           '+++ b1\n',
 
919
        self.assertEquals(['--- a1 \n',
 
920
                           '+++ b1 \n',
1289
921
                           '@@ -1,3 +1,2 @@\n',
1290
922
                           ' hello there\n',
1291
923
                           '-world\n',
1296
928
 
1297
929
        txt_a = map(lambda x: x+'\n', 'abcdefghijklmnop')
1298
930
        txt_b = map(lambda x: x+'\n', 'abcdefxydefghijklmnop')
1299
 
        with open('a2', 'wb') as f: f.writelines(txt_a)
1300
 
        with open('b2', 'wb') as f: f.writelines(txt_b)
 
931
        open('a2', 'wb').writelines(txt_a)
 
932
        open('b2', 'wb').writelines(txt_b)
1301
933
 
1302
934
        # This is the result with LongestCommonSubstring matching
1303
 
        self.assertEqual(['--- a2\n',
1304
 
                           '+++ b2\n',
 
935
        self.assertEquals(['--- a2 \n',
 
936
                           '+++ b2 \n',
1305
937
                           '@@ -1,6 +1,11 @@\n',
1306
938
                           ' a\n',
1307
939
                           ' b\n',
1317
949
                          , list(unified_diff_files('a2', 'b2')))
1318
950
 
1319
951
        # And the patience diff
1320
 
        self.assertEqual(['--- a2\n',
1321
 
                          '+++ b2\n',
1322
 
                          '@@ -4,6 +4,11 @@\n',
1323
 
                          ' d\n',
1324
 
                          ' e\n',
1325
 
                          ' f\n',
1326
 
                          '+x\n',
1327
 
                          '+y\n',
1328
 
                          '+d\n',
1329
 
                          '+e\n',
1330
 
                          '+f\n',
1331
 
                          ' g\n',
1332
 
                          ' h\n',
1333
 
                          ' i\n'],
1334
 
                         list(unified_diff_files('a2', 'b2',
1335
 
                                                 sequencematcher=psm)))
 
952
        self.assertEquals(['--- a2 \n',
 
953
                           '+++ b2 \n',
 
954
                           '@@ -4,6 +4,11 @@\n',
 
955
                           ' d\n',
 
956
                           ' e\n',
 
957
                           ' f\n',
 
958
                           '+x\n',
 
959
                           '+y\n',
 
960
                           '+d\n',
 
961
                           '+e\n',
 
962
                           '+f\n',
 
963
                           ' g\n',
 
964
                           ' h\n',
 
965
                           ' i\n',
 
966
                          ]
 
967
                          , list(unified_diff_files('a2', 'b2',
 
968
                                 sequencematcher=psm)))
1336
969
 
1337
970
 
1338
971
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1339
972
 
1340
 
    _test_needs_features = [features.compiled_patiencediff_feature]
 
973
    _test_needs_features = [CompiledPatienceDiffFeature]
1341
974
 
1342
975
    def setUp(self):
1343
976
        super(TestPatienceDiffLibFiles_c, self).setUp()
1344
 
        from bzrlib import _patiencediff_c
 
977
        import bzrlib._patiencediff_c
1345
978
        self._PatienceSequenceMatcher = \
1346
 
            _patiencediff_c.PatienceSequenceMatcher_c
1347
 
 
1348
 
 
1349
 
class TestUsingCompiledIfAvailable(tests.TestCase):
 
979
            bzrlib._patiencediff_c.PatienceSequenceMatcher_c
 
980
 
 
981
 
 
982
class TestUsingCompiledIfAvailable(TestCase):
1350
983
 
1351
984
    def test_PatienceSequenceMatcher(self):
1352
 
        if features.compiled_patiencediff_feature.available():
 
985
        if CompiledPatienceDiffFeature.available():
1353
986
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1354
987
            self.assertIs(PatienceSequenceMatcher_c,
1355
 
                          patiencediff.PatienceSequenceMatcher)
 
988
                          bzrlib.patiencediff.PatienceSequenceMatcher)
1356
989
        else:
1357
990
            from bzrlib._patiencediff_py import PatienceSequenceMatcher_py
1358
991
            self.assertIs(PatienceSequenceMatcher_py,
1359
 
                          patiencediff.PatienceSequenceMatcher)
 
992
                          bzrlib.patiencediff.PatienceSequenceMatcher)
1360
993
 
1361
994
    def test_unique_lcs(self):
1362
 
        if features.compiled_patiencediff_feature.available():
 
995
        if CompiledPatienceDiffFeature.available():
1363
996
            from bzrlib._patiencediff_c import unique_lcs_c
1364
997
            self.assertIs(unique_lcs_c,
1365
 
                          patiencediff.unique_lcs)
 
998
                          bzrlib.patiencediff.unique_lcs)
1366
999
        else:
1367
1000
            from bzrlib._patiencediff_py import unique_lcs_py
1368
1001
            self.assertIs(unique_lcs_py,
1369
 
                          patiencediff.unique_lcs)
 
1002
                          bzrlib.patiencediff.unique_lcs)
1370
1003
 
1371
1004
    def test_recurse_matches(self):
1372
 
        if features.compiled_patiencediff_feature.available():
 
1005
        if CompiledPatienceDiffFeature.available():
1373
1006
            from bzrlib._patiencediff_c import recurse_matches_c
1374
1007
            self.assertIs(recurse_matches_c,
1375
 
                          patiencediff.recurse_matches)
 
1008
                          bzrlib.patiencediff.recurse_matches)
1376
1009
        else:
1377
1010
            from bzrlib._patiencediff_py import recurse_matches_py
1378
1011
            self.assertIs(recurse_matches_py,
1379
 
                          patiencediff.recurse_matches)
1380
 
 
1381
 
 
1382
 
class TestDiffFromTool(tests.TestCaseWithTransport):
1383
 
 
1384
 
    def test_from_string(self):
1385
 
        diff_obj = diff.DiffFromTool.from_string('diff', None, None, None)
1386
 
        self.addCleanup(diff_obj.finish)
1387
 
        self.assertEqual(['diff', '@old_path', '@new_path'],
1388
 
            diff_obj.command_template)
1389
 
 
1390
 
    def test_from_string_u5(self):
1391
 
        diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
1392
 
                                                 None, None, None)
1393
 
        self.addCleanup(diff_obj.finish)
1394
 
        self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
1395
 
                         diff_obj.command_template)
1396
 
        self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
1397
 
                         diff_obj._get_command('old-path', 'new-path'))
1398
 
 
1399
 
    def test_from_string_path_with_backslashes(self):
1400
 
        self.requireFeature(features.backslashdir_feature)
1401
 
        tool = 'C:\\Tools\\Diff.exe'
1402
 
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1403
 
        self.addCleanup(diff_obj.finish)
1404
 
        self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
1405
 
                         diff_obj.command_template)
1406
 
        self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
1407
 
                         diff_obj._get_command('old-path', 'new-path'))
1408
 
 
1409
 
    def test_execute(self):
1410
 
        output = StringIO()
1411
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1412
 
                                      'print "@old_path @new_path"'],
1413
 
                                     None, None, output)
1414
 
        self.addCleanup(diff_obj.finish)
1415
 
        diff_obj._execute('old', 'new')
1416
 
        self.assertEqual(output.getvalue().rstrip(), 'old new')
1417
 
 
1418
 
    def test_execute_missing(self):
1419
 
        diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
1420
 
                                     None, None, None)
1421
 
        self.addCleanup(diff_obj.finish)
1422
 
        e = self.assertRaises(errors.ExecutableMissing, diff_obj._execute,
1423
 
                              'old', 'new')
1424
 
        self.assertEqual('a-tool-which-is-unlikely-to-exist could not be found'
1425
 
                         ' on this machine', str(e))
1426
 
 
1427
 
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1428
 
        self.requireFeature(features.AttribFeature)
1429
 
        output = StringIO()
1430
 
        tree = self.make_branch_and_tree('tree')
1431
 
        self.build_tree_contents([('tree/file', 'content')])
1432
 
        tree.add('file', 'file-id')
1433
 
        tree.commit('old tree')
1434
 
        tree.lock_read()
1435
 
        self.addCleanup(tree.unlock)
1436
 
        basis_tree = tree.basis_tree()
1437
 
        basis_tree.lock_read()
1438
 
        self.addCleanup(basis_tree.unlock)
1439
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1440
 
                                      'print "@old_path @new_path"'],
1441
 
                                     basis_tree, tree, output)
1442
 
        diff_obj._prepare_files('file-id', 'file', 'file')
1443
 
        # The old content should be readonly
1444
 
        self.assertReadableByAttrib(diff_obj._root, 'old\\file',
1445
 
                                    r'R.*old\\file$')
1446
 
        # The new content should use the tree object, not a 'new' file anymore
1447
 
        self.assertEndsWith(tree.basedir, 'work/tree')
1448
 
        self.assertReadableByAttrib(tree.basedir, 'file', r'work\\tree\\file$')
1449
 
 
1450
 
    def assertReadableByAttrib(self, cwd, relpath, regex):
1451
 
        proc = subprocess.Popen(['attrib', relpath],
1452
 
                                stdout=subprocess.PIPE,
1453
 
                                cwd=cwd)
1454
 
        (result, err) = proc.communicate()
1455
 
        self.assertContainsRe(result.replace('\r\n', '\n'), regex)
1456
 
 
1457
 
    def test_prepare_files(self):
1458
 
        output = StringIO()
1459
 
        tree = self.make_branch_and_tree('tree')
1460
 
        self.build_tree_contents([('tree/oldname', 'oldcontent')])
1461
 
        self.build_tree_contents([('tree/oldname2', 'oldcontent2')])
1462
 
        tree.add('oldname', 'file-id')
1463
 
        tree.add('oldname2', 'file2-id')
1464
 
        # Earliest allowable date on FAT32 filesystems is 1980-01-01
1465
 
        tree.commit('old tree', timestamp=315532800)
1466
 
        tree.rename_one('oldname', 'newname')
1467
 
        tree.rename_one('oldname2', 'newname2')
1468
 
        self.build_tree_contents([('tree/newname', 'newcontent')])
1469
 
        self.build_tree_contents([('tree/newname2', 'newcontent2')])
1470
 
        old_tree = tree.basis_tree()
1471
 
        old_tree.lock_read()
1472
 
        self.addCleanup(old_tree.unlock)
1473
 
        tree.lock_read()
1474
 
        self.addCleanup(tree.unlock)
1475
 
        diff_obj = diff.DiffFromTool(['python', '-c',
1476
 
                                      'print "@old_path @new_path"'],
1477
 
                                     old_tree, tree, output)
1478
 
        self.addCleanup(diff_obj.finish)
1479
 
        self.assertContainsRe(diff_obj._root, 'bzr-diff-[^/]*')
1480
 
        old_path, new_path = diff_obj._prepare_files('file-id', 'oldname',
1481
 
                                                     'newname')
1482
 
        self.assertContainsRe(old_path, 'old/oldname$')
1483
 
        self.assertEqual(315532800, os.stat(old_path).st_mtime)
1484
 
        self.assertContainsRe(new_path, 'tree/newname$')
1485
 
        self.assertFileEqual('oldcontent', old_path)
1486
 
        self.assertFileEqual('newcontent', new_path)
1487
 
        if osutils.host_os_dereferences_symlinks():
1488
 
            self.assertTrue(os.path.samefile('tree/newname', new_path))
1489
 
        # make sure we can create files with the same parent directories
1490
 
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1491
 
 
1492
 
 
1493
 
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
1494
 
 
1495
 
    def test_encodable_filename(self):
1496
 
        # Just checks file path for external diff tool.
1497
 
        # We cannot change CPython's internal encoding used by os.exec*.
1498
 
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1499
 
                                    None, None, None)
1500
 
        for _, scenario in EncodingAdapter.encoding_scenarios:
1501
 
            encoding = scenario['encoding']
1502
 
            dirname = scenario['info']['directory']
1503
 
            filename = scenario['info']['filename']
1504
 
 
1505
 
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1506
 
            relpath = dirname + u'/' + filename
1507
 
            fullpath = diffobj._safe_filename('safe', relpath)
1508
 
            self.assertEqual(fullpath,
1509
 
                             fullpath.encode(encoding).decode(encoding))
1510
 
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1511
 
 
1512
 
    def test_unencodable_filename(self):
1513
 
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
1514
 
                                    None, None, None)
1515
 
        for _, scenario in EncodingAdapter.encoding_scenarios:
1516
 
            encoding = scenario['encoding']
1517
 
            dirname = scenario['info']['directory']
1518
 
            filename = scenario['info']['filename']
1519
 
 
1520
 
            if encoding == 'iso-8859-1':
1521
 
                encoding = 'iso-8859-2'
1522
 
            else:
1523
 
                encoding = 'iso-8859-1'
1524
 
 
1525
 
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
1526
 
            relpath = dirname + u'/' + filename
1527
 
            fullpath = diffobj._safe_filename('safe', relpath)
1528
 
            self.assertEqual(fullpath,
1529
 
                             fullpath.encode(encoding).decode(encoding))
1530
 
            self.assertTrue(fullpath.startswith(diffobj._root + '/safe'))
1531
 
 
1532
 
 
1533
 
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1534
 
 
1535
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1536
 
        """Call get_trees_and_branches_to_diff_locked."""
1537
 
        return diff.get_trees_and_branches_to_diff_locked(
1538
 
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1539
 
 
1540
 
    def test_basic(self):
1541
 
        tree = self.make_branch_and_tree('tree')
1542
 
        (old_tree, new_tree,
1543
 
         old_branch, new_branch,
1544
 
         specific_files, extra_trees) = self.call_gtabtd(
1545
 
             ['tree'], None, None, None)
1546
 
 
1547
 
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1548
 
        self.assertEqual(_mod_revision.NULL_REVISION,
1549
 
                         old_tree.get_revision_id())
1550
 
        self.assertEqual(tree.basedir, new_tree.basedir)
1551
 
        self.assertEqual(tree.branch.base, old_branch.base)
1552
 
        self.assertEqual(tree.branch.base, new_branch.base)
1553
 
        self.assertIs(None, specific_files)
1554
 
        self.assertIs(None, extra_trees)
1555
 
 
1556
 
    def test_with_rev_specs(self):
1557
 
        tree = self.make_branch_and_tree('tree')
1558
 
        self.build_tree_contents([('tree/file', 'oldcontent')])
1559
 
        tree.add('file', 'file-id')
1560
 
        tree.commit('old tree', timestamp=0, rev_id="old-id")
1561
 
        self.build_tree_contents([('tree/file', 'newcontent')])
1562
 
        tree.commit('new tree', timestamp=0, rev_id="new-id")
1563
 
 
1564
 
        revisions = [revisionspec.RevisionSpec.from_string('1'),
1565
 
                     revisionspec.RevisionSpec.from_string('2')]
1566
 
        (old_tree, new_tree,
1567
 
         old_branch, new_branch,
1568
 
         specific_files, extra_trees) = self.call_gtabtd(
1569
 
            ['tree'], revisions, None, None)
1570
 
 
1571
 
        self.assertIsInstance(old_tree, revisiontree.RevisionTree)
1572
 
        self.assertEqual("old-id", old_tree.get_revision_id())
1573
 
        self.assertIsInstance(new_tree, revisiontree.RevisionTree)
1574
 
        self.assertEqual("new-id", new_tree.get_revision_id())
1575
 
        self.assertEqual(tree.branch.base, old_branch.base)
1576
 
        self.assertEqual(tree.branch.base, new_branch.base)
1577
 
        self.assertIs(None, specific_files)
1578
 
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
 
1012
                          bzrlib.patiencediff.recurse_matches)