~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Robert Collins
  • Date: 2007-03-08 04:06:06 UTC
  • mfrom: (2323.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2442.
  • Revision ID: robertc@robertcollins.net-20070308040606-84gsniv56huiyjt4
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Development 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
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
import os
17
18
from cStringIO import StringIO
 
19
import errno
 
20
import subprocess
 
21
from tempfile import TemporaryFile
18
22
 
19
 
from bzrlib.diff import internal_diff
20
 
from bzrlib.errors import BinaryFile
 
23
from bzrlib.diff import internal_diff, external_diff, show_diff_trees
 
24
from bzrlib.errors import BinaryFile, NoDiff
21
25
import bzrlib.patiencediff
22
 
from bzrlib.tests import TestCase, TestCaseInTempDir
 
26
from bzrlib.tests import (TestCase, TestCaseWithTransport,
 
27
                          TestCaseInTempDir, TestSkipped)
23
28
 
24
29
 
25
30
def udiff_lines(old, new, allow_binary=False):
29
34
    return output.readlines()
30
35
 
31
36
 
 
37
def external_udiff_lines(old, new, use_stringio=False):
 
38
    if use_stringio:
 
39
        # StringIO has no fileno, so it tests a different codepath
 
40
        output = StringIO()
 
41
    else:
 
42
        output = TemporaryFile()
 
43
    try:
 
44
        external_diff('old', old, 'new', new, output, diff_opts=['-u'])
 
45
    except NoDiff:
 
46
        raise TestSkipped('external "diff" not present to test')
 
47
    output.seek(0, 0)
 
48
    lines = output.readlines()
 
49
    output.close()
 
50
    return lines
 
51
 
 
52
 
32
53
class TestDiff(TestCase):
33
54
 
34
55
    def test_add_nl(self):
76
97
        udiff_lines([1023 * 'a' + '\x00'], [], allow_binary=True)
77
98
        udiff_lines([], [1023 * 'a' + '\x00'], allow_binary=True)
78
99
 
 
100
    def test_external_diff(self):
 
101
        lines = external_udiff_lines(['boo\n'], ['goo\n'])
 
102
        self.check_patch(lines)
 
103
        self.assertEqual('\n', lines[-1])
 
104
 
 
105
    def test_external_diff_no_fileno(self):
 
106
        # Make sure that we can handle not having a fileno, even
 
107
        # if the diff is large
 
108
        lines = external_udiff_lines(['boo\n']*10000,
 
109
                                     ['goo\n']*10000,
 
110
                                     use_stringio=True)
 
111
        self.check_patch(lines)
 
112
 
 
113
    def test_external_diff_binary_lang_c(self):
 
114
        orig_lang = os.environ.get('LANG')
 
115
        orig_lc_all = os.environ.get('LC_ALL')
 
116
        try:
 
117
            os.environ['LANG'] = 'C'
 
118
            os.environ['LC_ALL'] = 'C'
 
119
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
120
            # Older versions of diffutils say "Binary files", newer
 
121
            # versions just say "Files".
 
122
            self.assertContainsRe(lines[0],
 
123
                                  '(Binary f|F)iles old and new differ\n')
 
124
            self.assertEquals(lines[1:], ['\n'])
 
125
        finally:
 
126
            for name, value in [('LANG', orig_lang), ('LC_ALL', orig_lc_all)]:
 
127
                if value is None:
 
128
                    del os.environ[name]
 
129
                else:
 
130
                    os.environ[name] = value
 
131
 
 
132
    def test_no_external_diff(self):
 
133
        """Check that NoDiff is raised when diff is not available"""
 
134
        # Use os.environ['PATH'] to make sure no 'diff' command is available
 
135
        orig_path = os.environ['PATH']
 
136
        try:
 
137
            os.environ['PATH'] = ''
 
138
            self.assertRaises(NoDiff, external_diff,
 
139
                              'old', ['boo\n'], 'new', ['goo\n'],
 
140
                              StringIO(), diff_opts=['-u'])
 
141
        finally:
 
142
            os.environ['PATH'] = orig_path
 
143
        
79
144
    def test_internal_diff_default(self):
80
145
        # Default internal diff encoding is utf8
81
146
        output = StringIO()
83
148
                    u'new_\xe5', ['new_text\n'], output)
84
149
        lines = output.getvalue().splitlines(True)
85
150
        self.check_patch(lines)
86
 
        self.assertEquals(['--- old_\xc2\xb5\t\n',
87
 
                           '+++ new_\xc3\xa5\t\n',
 
151
        self.assertEquals(['--- old_\xc2\xb5\n',
 
152
                           '+++ new_\xc3\xa5\n',
88
153
                           '@@ -1,1 +1,1 @@\n',
89
154
                           '-old_text\n',
90
155
                           '+new_text\n',
99
164
                    path_encoding='utf8')
100
165
        lines = output.getvalue().splitlines(True)
101
166
        self.check_patch(lines)
102
 
        self.assertEquals(['--- old_\xc2\xb5\t\n',
103
 
                           '+++ new_\xc3\xa5\t\n',
 
167
        self.assertEquals(['--- old_\xc2\xb5\n',
 
168
                           '+++ new_\xc3\xa5\n',
104
169
                           '@@ -1,1 +1,1 @@\n',
105
170
                           '-old_text\n',
106
171
                           '+new_text\n',
115
180
                    path_encoding='iso-8859-1')
116
181
        lines = output.getvalue().splitlines(True)
117
182
        self.check_patch(lines)
118
 
        self.assertEquals(['--- old_\xb5\t\n',
119
 
                           '+++ new_\xe5\t\n',
 
183
        self.assertEquals(['--- old_\xb5\n',
 
184
                           '+++ new_\xe5\n',
120
185
                           '@@ -1,1 +1,1 @@\n',
121
186
                           '-old_text\n',
122
187
                           '+new_text\n',
133
198
            'internal_diff should return bytestrings')
134
199
 
135
200
 
 
201
class TestDiffFiles(TestCaseInTempDir):
 
202
 
 
203
    def test_external_diff_binary(self):
 
204
        """The output when using external diff should use diff's i18n error"""
 
205
        # Make sure external_diff doesn't fail in the current LANG
 
206
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
207
 
 
208
        cmd = ['diff', '-u', '--binary', 'old', 'new']
 
209
        open('old', 'wb').write('\x00foobar\n')
 
210
        open('new', 'wb').write('foo\x00bar\n')
 
211
        pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE,
 
212
                                     stdin=subprocess.PIPE)
 
213
        out, err = pipe.communicate()
 
214
        # Diff returns '2' on Binary files.
 
215
        self.assertEqual(2, pipe.returncode)
 
216
        # We should output whatever diff tells us, plus a trailing newline
 
217
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
 
218
 
 
219
 
 
220
class TestDiffDates(TestCaseWithTransport):
 
221
 
 
222
    def setUp(self):
 
223
        super(TestDiffDates, self).setUp()
 
224
        self.wt = self.make_branch_and_tree('.')
 
225
        self.b = self.wt.branch
 
226
        self.build_tree_contents([
 
227
            ('file1', 'file1 contents at rev 1\n'),
 
228
            ('file2', 'file2 contents at rev 1\n')
 
229
            ])
 
230
        self.wt.add(['file1', 'file2'])
 
231
        self.wt.commit(
 
232
            message='Revision 1',
 
233
            timestamp=1143849600, # 2006-04-01 00:00:00 UTC
 
234
            timezone=0,
 
235
            rev_id='rev-1')
 
236
        self.build_tree_contents([('file1', 'file1 contents at rev 2\n')])
 
237
        self.wt.commit(
 
238
            message='Revision 2',
 
239
            timestamp=1143936000, # 2006-04-02 00:00:00 UTC
 
240
            timezone=28800,
 
241
            rev_id='rev-2')
 
242
        self.build_tree_contents([('file2', 'file2 contents at rev 3\n')])
 
243
        self.wt.commit(
 
244
            message='Revision 3',
 
245
            timestamp=1144022400, # 2006-04-03 00:00:00 UTC
 
246
            timezone=-3600,
 
247
            rev_id='rev-3')
 
248
        self.wt.remove(['file2'])
 
249
        self.wt.commit(
 
250
            message='Revision 4',
 
251
            timestamp=1144108800, # 2006-04-04 00:00:00 UTC
 
252
            timezone=0,
 
253
            rev_id='rev-4')
 
254
        self.build_tree_contents([
 
255
            ('file1', 'file1 contents in working tree\n')
 
256
            ])
 
257
        # set the date stamps for files in the working tree to known values
 
258
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
 
259
 
 
260
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
 
261
        output = StringIO()
 
262
        if working_tree is not None:
 
263
            extra_trees = (working_tree,)
 
264
        else:
 
265
            extra_trees = ()
 
266
        show_diff_trees(tree1, tree2, output, specific_files=specific_files,
 
267
                        extra_trees=extra_trees, old_label='old/', 
 
268
                        new_label='new/')
 
269
        return output.getvalue()
 
270
 
 
271
    def test_diff_rev_tree_working_tree(self):
 
272
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
273
        # note that the date for old/file1 is from rev 2 rather than from
 
274
        # the basis revision (rev 4)
 
275
        self.assertEqualDiff(output, '''\
 
276
=== modified file 'file1'
 
277
--- old/file1\t2006-04-02 00:00:00 +0000
 
278
+++ new/file1\t2006-04-05 00:00:00 +0000
 
279
@@ -1,1 +1,1 @@
 
280
-file1 contents at rev 2
 
281
+file1 contents in working tree
 
282
 
 
283
''')
 
284
 
 
285
    def test_diff_rev_tree_rev_tree(self):
 
286
        tree1 = self.b.repository.revision_tree('rev-2')
 
287
        tree2 = self.b.repository.revision_tree('rev-3')
 
288
        output = self.get_diff(tree1, tree2)
 
289
        self.assertEqualDiff(output, '''\
 
290
=== modified file 'file2'
 
291
--- old/file2\t2006-04-01 00:00:00 +0000
 
292
+++ new/file2\t2006-04-03 00:00:00 +0000
 
293
@@ -1,1 +1,1 @@
 
294
-file2 contents at rev 1
 
295
+file2 contents at rev 3
 
296
 
 
297
''')
 
298
        
 
299
    def test_diff_add_files(self):
 
300
        tree1 = self.b.repository.revision_tree(None)
 
301
        tree2 = self.b.repository.revision_tree('rev-1')
 
302
        output = self.get_diff(tree1, tree2)
 
303
        # the files have the epoch time stamp for the tree in which
 
304
        # they don't exist.
 
305
        self.assertEqualDiff(output, '''\
 
306
=== added file 'file1'
 
307
--- old/file1\t1970-01-01 00:00:00 +0000
 
308
+++ new/file1\t2006-04-01 00:00:00 +0000
 
309
@@ -0,0 +1,1 @@
 
310
+file1 contents at rev 1
 
311
 
 
312
=== added file 'file2'
 
313
--- old/file2\t1970-01-01 00:00:00 +0000
 
314
+++ new/file2\t2006-04-01 00:00:00 +0000
 
315
@@ -0,0 +1,1 @@
 
316
+file2 contents at rev 1
 
317
 
 
318
''')
 
319
 
 
320
    def test_diff_remove_files(self):
 
321
        tree1 = self.b.repository.revision_tree('rev-3')
 
322
        tree2 = self.b.repository.revision_tree('rev-4')
 
323
        output = self.get_diff(tree1, tree2)
 
324
        # the file has the epoch time stamp for the tree in which
 
325
        # it doesn't exist.
 
326
        self.assertEqualDiff(output, '''\
 
327
=== removed file 'file2'
 
328
--- old/file2\t2006-04-03 00:00:00 +0000
 
329
+++ new/file2\t1970-01-01 00:00:00 +0000
 
330
@@ -1,1 +0,0 @@
 
331
-file2 contents at rev 3
 
332
 
 
333
''')
 
334
 
 
335
    def test_show_diff_specified(self):
 
336
        """A working tree filename can be used to identify a file"""
 
337
        self.wt.rename_one('file1', 'file1b')
 
338
        old_tree = self.b.repository.revision_tree('rev-1')
 
339
        new_tree = self.b.repository.revision_tree('rev-4')
 
340
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'], 
 
341
                            working_tree=self.wt)
 
342
        self.assertContainsRe(out, 'file1\t')
 
343
 
 
344
    def test_recursive_diff(self):
 
345
        """Children of directories are matched"""
 
346
        os.mkdir('dir1')
 
347
        os.mkdir('dir2')
 
348
        self.wt.add(['dir1', 'dir2'])
 
349
        self.wt.rename_one('file1', 'dir1/file1')
 
350
        old_tree = self.b.repository.revision_tree('rev-1')
 
351
        new_tree = self.b.repository.revision_tree('rev-4')
 
352
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'], 
 
353
                            working_tree=self.wt)
 
354
        self.assertContainsRe(out, 'file1\t')
 
355
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'], 
 
356
                            working_tree=self.wt)
 
357
        self.assertNotContainsRe(out, 'file1\t')
 
358
 
 
359
 
136
360
class TestPatienceDiffLib(TestCase):
137
361
 
138
362
    def test_unique_lcs(self):