~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_diff.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
33
33
    transform,
34
34
    )
35
35
from bzrlib.symbol_versioning import deprecated_in
36
 
from bzrlib.tests import test_win32utils
37
 
 
38
 
 
39
 
class _AttribFeature(tests.Feature):
40
 
 
41
 
    def _probe(self):
42
 
        if (sys.platform not in ('cygwin', 'win32')):
43
 
            return False
44
 
        try:
45
 
            proc = subprocess.Popen(['attrib', '.'], stdout=subprocess.PIPE)
46
 
        except OSError, e:
47
 
            return False
48
 
        return (0 == proc.wait())
49
 
 
50
 
    def feature_name(self):
51
 
        return 'attrib Windows command-line tool'
52
 
 
53
 
AttribFeature = _AttribFeature()
54
 
 
55
 
 
56
 
compiled_patiencediff_feature = tests.ModuleAvailableFeature(
57
 
                                    'bzrlib._patiencediff_c')
 
36
from bzrlib.tests import features, EncodingAdapter
 
37
from bzrlib.tests.blackbox.test_diff import subst_dates
 
38
from bzrlib.tests import (
 
39
    features,
 
40
    )
58
41
 
59
42
 
60
43
def udiff_lines(old, new, allow_binary=False):
143
126
        self.check_patch(lines)
144
127
 
145
128
    def test_external_diff_binary_lang_c(self):
146
 
        old_env = {}
147
129
        for lang in ('LANG', 'LC_ALL', 'LANGUAGE'):
148
 
            old_env[lang] = osutils.set_or_unset_env(lang, 'C')
149
 
        try:
150
 
            lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
151
 
            # Older versions of diffutils say "Binary files", newer
152
 
            # versions just say "Files".
153
 
            self.assertContainsRe(lines[0],
154
 
                                  '(Binary f|F)iles old and new differ\n')
155
 
            self.assertEquals(lines[1:], ['\n'])
156
 
        finally:
157
 
            for lang, old_val in old_env.iteritems():
158
 
                osutils.set_or_unset_env(lang, old_val)
 
130
            self.overrideEnv(lang, 'C')
 
131
        lines = external_udiff_lines(['\x00foobar\n'], ['foo\x00bar\n'])
 
132
        # Older versions of diffutils say "Binary files", newer
 
133
        # versions just say "Files".
 
134
        self.assertContainsRe(lines[0], '(Binary f|F)iles old and new differ\n')
 
135
        self.assertEquals(lines[1:], ['\n'])
159
136
 
160
137
    def test_no_external_diff(self):
161
138
        """Check that NoDiff is raised when diff is not available"""
162
 
        # Use os.environ['PATH'] to make sure no 'diff' command is available
163
 
        orig_path = os.environ['PATH']
164
 
        try:
165
 
            os.environ['PATH'] = ''
166
 
            self.assertRaises(errors.NoDiff, diff.external_diff,
167
 
                              'old', ['boo\n'], 'new', ['goo\n'],
168
 
                              StringIO(), diff_opts=['-u'])
169
 
        finally:
170
 
            os.environ['PATH'] = orig_path
 
139
        # Make sure no 'diff' command is available
 
140
        # XXX: Weird, using None instead of '' breaks the test -- vila 20101216
 
141
        self.overrideEnv('PATH', '')
 
142
        self.assertRaises(errors.NoDiff, diff.external_diff,
 
143
                          'old', ['boo\n'], 'new', ['goo\n'],
 
144
                          StringIO(), diff_opts=['-u'])
171
145
 
172
146
    def test_internal_diff_default(self):
173
147
        # Default internal diff encoding is utf8
234
208
        output = StringIO.StringIO()
235
209
        diff.internal_diff(u'old_\xb5', ['old_text\n'],
236
210
                            u'new_\xe5', ['new_text\n'], output)
237
 
        self.failUnless(isinstance(output.getvalue(), str),
 
211
        self.assertIsInstance(output.getvalue(), str,
238
212
            'internal_diff should return bytestrings')
239
213
 
240
214
 
257
231
        self.assertEqual(out.splitlines(True) + ['\n'], lines)
258
232
 
259
233
 
260
 
class TestShowDiffTreesHelper(tests.TestCaseWithTransport):
261
 
    """Has a helper for running show_diff_trees"""
262
 
 
263
 
    def get_diff(self, tree1, tree2, specific_files=None, working_tree=None):
264
 
        output = StringIO()
265
 
        if working_tree is not None:
266
 
            extra_trees = (working_tree,)
267
 
        else:
268
 
            extra_trees = ()
269
 
        diff.show_diff_trees(tree1, tree2, output,
270
 
                             specific_files=specific_files,
271
 
                             extra_trees=extra_trees, old_label='old/',
272
 
                             new_label='new/')
273
 
        return output.getvalue()
274
 
 
275
 
 
276
 
class TestDiffDates(TestShowDiffTreesHelper):
 
234
def get_diff_as_string(tree1, tree2, specific_files=None, working_tree=None):
 
235
    output = StringIO()
 
236
    if working_tree is not None:
 
237
        extra_trees = (working_tree,)
 
238
    else:
 
239
        extra_trees = ()
 
240
    diff.show_diff_trees(tree1, tree2, output,
 
241
        specific_files=specific_files,
 
242
        extra_trees=extra_trees, old_label='old/',
 
243
        new_label='new/')
 
244
    return output.getvalue()
 
245
 
 
246
 
 
247
class TestDiffDates(tests.TestCaseWithTransport):
277
248
 
278
249
    def setUp(self):
279
250
        super(TestDiffDates, self).setUp()
314
285
        os.utime('file1', (1144195200, 1144195200)) # 2006-04-05 00:00:00 UTC
315
286
 
316
287
    def test_diff_rev_tree_working_tree(self):
317
 
        output = self.get_diff(self.wt.basis_tree(), self.wt)
 
288
        output = get_diff_as_string(self.wt.basis_tree(), self.wt)
318
289
        # note that the date for old/file1 is from rev 2 rather than from
319
290
        # the basis revision (rev 4)
320
291
        self.assertEqualDiff(output, '''\
330
301
    def test_diff_rev_tree_rev_tree(self):
331
302
        tree1 = self.b.repository.revision_tree('rev-2')
332
303
        tree2 = self.b.repository.revision_tree('rev-3')
333
 
        output = self.get_diff(tree1, tree2)
 
304
        output = get_diff_as_string(tree1, tree2)
334
305
        self.assertEqualDiff(output, '''\
335
306
=== modified file 'file2'
336
307
--- old/file2\t2006-04-01 00:00:00 +0000
344
315
    def test_diff_add_files(self):
345
316
        tree1 = self.b.repository.revision_tree(_mod_revision.NULL_REVISION)
346
317
        tree2 = self.b.repository.revision_tree('rev-1')
347
 
        output = self.get_diff(tree1, tree2)
 
318
        output = get_diff_as_string(tree1, tree2)
348
319
        # the files have the epoch time stamp for the tree in which
349
320
        # they don't exist.
350
321
        self.assertEqualDiff(output, '''\
365
336
    def test_diff_remove_files(self):
366
337
        tree1 = self.b.repository.revision_tree('rev-3')
367
338
        tree2 = self.b.repository.revision_tree('rev-4')
368
 
        output = self.get_diff(tree1, tree2)
 
339
        output = get_diff_as_string(tree1, tree2)
369
340
        # the file has the epoch time stamp for the tree in which
370
341
        # it doesn't exist.
371
342
        self.assertEqualDiff(output, '''\
382
353
        self.wt.rename_one('file1', 'file1b')
383
354
        old_tree = self.b.repository.revision_tree('rev-1')
384
355
        new_tree = self.b.repository.revision_tree('rev-4')
385
 
        out = self.get_diff(old_tree, new_tree, specific_files=['file1b'],
 
356
        out = get_diff_as_string(old_tree, new_tree, specific_files=['file1b'],
386
357
                            working_tree=self.wt)
387
358
        self.assertContainsRe(out, 'file1\t')
388
359
 
394
365
        self.wt.rename_one('file1', 'dir1/file1')
395
366
        old_tree = self.b.repository.revision_tree('rev-1')
396
367
        new_tree = self.b.repository.revision_tree('rev-4')
397
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir1'],
 
368
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir1'],
398
369
                            working_tree=self.wt)
399
370
        self.assertContainsRe(out, 'file1\t')
400
 
        out = self.get_diff(old_tree, new_tree, specific_files=['dir2'],
 
371
        out = get_diff_as_string(old_tree, new_tree, specific_files=['dir2'],
401
372
                            working_tree=self.wt)
402
373
        self.assertNotContainsRe(out, 'file1\t')
403
374
 
404
375
 
405
 
 
406
 
class TestShowDiffTrees(TestShowDiffTreesHelper):
 
376
class TestShowDiffTrees(tests.TestCaseWithTransport):
407
377
    """Direct tests for show_diff_trees"""
408
378
 
409
379
    def test_modified_file(self):
414
384
        tree.commit('one', rev_id='rev-1')
415
385
 
416
386
        self.build_tree_contents([('tree/file', 'new contents\n')])
417
 
        d = self.get_diff(tree.basis_tree(), tree)
 
387
        d = get_diff_as_string(tree.basis_tree(), tree)
418
388
        self.assertContainsRe(d, "=== modified file 'file'\n")
419
389
        self.assertContainsRe(d, '--- old/file\t')
420
390
        self.assertContainsRe(d, '\\+\\+\\+ new/file\t')
431
401
 
432
402
        tree.rename_one('dir', 'other')
433
403
        self.build_tree_contents([('tree/other/file', 'new contents\n')])
434
 
        d = self.get_diff(tree.basis_tree(), tree)
 
404
        d = get_diff_as_string(tree.basis_tree(), tree)
435
405
        self.assertContainsRe(d, "=== renamed directory 'dir' => 'other'\n")
436
406
        self.assertContainsRe(d, "=== modified file 'other/file'\n")
437
407
        # XXX: This is technically incorrect, because it used to be at another
450
420
        tree.commit('one', rev_id='rev-1')
451
421
 
452
422
        tree.rename_one('dir', 'newdir')
453
 
        d = self.get_diff(tree.basis_tree(), tree)
 
423
        d = get_diff_as_string(tree.basis_tree(), tree)
454
424
        # Renaming a directory should be a single "you renamed this dir" even
455
425
        # when there are files inside.
456
426
        self.assertEqual(d, "=== renamed directory 'dir' => 'newdir'\n")
463
433
        tree.commit('one', rev_id='rev-1')
464
434
 
465
435
        tree.rename_one('file', 'newname')
466
 
        d = self.get_diff(tree.basis_tree(), tree)
 
436
        d = get_diff_as_string(tree.basis_tree(), tree)
467
437
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
468
438
        # We shouldn't have a --- or +++ line, because there is no content
469
439
        # change
478
448
 
479
449
        tree.rename_one('file', 'newname')
480
450
        self.build_tree_contents([('tree/newname', 'new contents\n')])
481
 
        d = self.get_diff(tree.basis_tree(), tree)
 
451
        d = get_diff_as_string(tree.basis_tree(), tree)
482
452
        self.assertContainsRe(d, "=== renamed file 'file' => 'newname'\n")
483
453
        self.assertContainsRe(d, '--- old/file\t')
484
454
        self.assertContainsRe(d, '\\+\\+\\+ new/newname\t')
508
478
        tree.rename_one('c', 'new-c')
509
479
        tree.rename_one('d', 'new-d')
510
480
 
511
 
        d = self.get_diff(tree.basis_tree(), tree)
 
481
        d = get_diff_as_string(tree.basis_tree(), tree)
512
482
 
513
483
        self.assertContainsRe(d, r"file 'a'.*\(properties changed:"
514
484
                                  ".*\+x to -x.*\)")
521
491
        self.assertNotContainsRe(d, r"file 'e'")
522
492
        self.assertNotContainsRe(d, r"file 'f'")
523
493
 
524
 
 
525
494
    def test_binary_unicode_filenames(self):
526
495
        """Test that contents of files are *not* encoded in UTF-8 when there
527
496
        is a binary file in the diff.
528
497
        """
529
498
        # See https://bugs.launchpad.net/bugs/110092.
530
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
499
        self.requireFeature(features.UnicodeFilenameFeature)
531
500
 
532
501
        # This bug isn't triggered with cStringIO.
533
502
        from StringIO import StringIO
552
521
 
553
522
    def test_unicode_filename(self):
554
523
        """Test when the filename are unicode."""
555
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
524
        self.requireFeature(features.UnicodeFilenameFeature)
556
525
 
557
526
        alpha, omega = u'\u03b1', u'\u03c9'
558
527
        autf8, outf8 = alpha.encode('utf8'), omega.encode('utf8')
573
542
        tree.add(['add_'+alpha], ['file-id'])
574
543
        self.build_tree_contents([('tree/mod_'+alpha, 'contents_mod\n')])
575
544
 
576
 
        d = self.get_diff(tree.basis_tree(), tree)
 
545
        d = get_diff_as_string(tree.basis_tree(), tree)
577
546
        self.assertContainsRe(d,
578
547
                "=== renamed file 'ren_%s' => 'ren_%s'\n"%(autf8, outf8))
579
548
        self.assertContainsRe(d, "=== added file 'add_%s'"%autf8)
580
549
        self.assertContainsRe(d, "=== modified file 'mod_%s'"%autf8)
581
550
        self.assertContainsRe(d, "=== removed file 'del_%s'"%autf8)
582
551
 
 
552
    def test_unicode_filename_path_encoding(self):
 
553
        """Test for bug #382699: unicode filenames on Windows should be shown
 
554
        in user encoding.
 
555
        """
 
556
        self.requireFeature(features.UnicodeFilenameFeature)
 
557
        # The word 'test' in Russian
 
558
        _russian_test = u'\u0422\u0435\u0441\u0442'
 
559
        directory = _russian_test + u'/'
 
560
        test_txt = _russian_test + u'.txt'
 
561
        u1234 = u'\u1234.txt'
 
562
 
 
563
        tree = self.make_branch_and_tree('.')
 
564
        self.build_tree_contents([
 
565
            (test_txt, 'foo\n'),
 
566
            (u1234, 'foo\n'),
 
567
            (directory, None),
 
568
            ])
 
569
        tree.add([test_txt, u1234, directory])
 
570
 
 
571
        sio = StringIO()
 
572
        diff.show_diff_trees(tree.basis_tree(), tree, sio,
 
573
            path_encoding='cp1251')
 
574
 
 
575
        output = subst_dates(sio.getvalue())
 
576
        shouldbe = ('''\
 
577
=== added directory '%(directory)s'
 
578
=== added file '%(test_txt)s'
 
579
--- a/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
580
+++ b/%(test_txt)s\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
581
@@ -0,0 +1,1 @@
 
582
+foo
 
583
 
 
584
=== added file '?.txt'
 
585
--- a/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
586
+++ b/?.txt\tYYYY-MM-DD HH:MM:SS +ZZZZ
 
587
@@ -0,0 +1,1 @@
 
588
+foo
 
589
 
 
590
''' % {'directory': _russian_test.encode('cp1251'),
 
591
       'test_txt': test_txt.encode('cp1251'),
 
592
      })
 
593
        self.assertEqualDiff(output, shouldbe)
 
594
 
583
595
 
584
596
class DiffWasIs(diff.DiffPath):
585
597
 
678
690
             ' \@\@\n-old\n\+new\n\n')
679
691
 
680
692
    def test_diff_kind_change(self):
681
 
        self.requireFeature(tests.SymlinkFeature)
 
693
        self.requireFeature(features.SymlinkFeature)
682
694
        self.build_tree_contents([('old-tree/olddir/',),
683
695
                                  ('old-tree/olddir/oldfile', 'old\n')])
684
696
        self.old_tree.add('olddir')
1142
1154
 
1143
1155
class TestPatienceDiffLib_c(TestPatienceDiffLib):
1144
1156
 
1145
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1157
    _test_needs_features = [features.compiled_patiencediff_feature]
1146
1158
 
1147
1159
    def setUp(self):
1148
1160
        super(TestPatienceDiffLib_c, self).setUp()
1238
1250
 
1239
1251
class TestPatienceDiffLibFiles_c(TestPatienceDiffLibFiles):
1240
1252
 
1241
 
    _test_needs_features = [compiled_patiencediff_feature]
 
1253
    _test_needs_features = [features.compiled_patiencediff_feature]
1242
1254
 
1243
1255
    def setUp(self):
1244
1256
        super(TestPatienceDiffLibFiles_c, self).setUp()
1250
1262
class TestUsingCompiledIfAvailable(tests.TestCase):
1251
1263
 
1252
1264
    def test_PatienceSequenceMatcher(self):
1253
 
        if compiled_patiencediff_feature.available():
 
1265
        if features.compiled_patiencediff_feature.available():
1254
1266
            from bzrlib._patiencediff_c import PatienceSequenceMatcher_c
1255
1267
            self.assertIs(PatienceSequenceMatcher_c,
1256
1268
                          patiencediff.PatienceSequenceMatcher)
1260
1272
                          patiencediff.PatienceSequenceMatcher)
1261
1273
 
1262
1274
    def test_unique_lcs(self):
1263
 
        if compiled_patiencediff_feature.available():
 
1275
        if features.compiled_patiencediff_feature.available():
1264
1276
            from bzrlib._patiencediff_c import unique_lcs_c
1265
1277
            self.assertIs(unique_lcs_c,
1266
1278
                          patiencediff.unique_lcs)
1270
1282
                          patiencediff.unique_lcs)
1271
1283
 
1272
1284
    def test_recurse_matches(self):
1273
 
        if compiled_patiencediff_feature.available():
 
1285
        if features.compiled_patiencediff_feature.available():
1274
1286
            from bzrlib._patiencediff_c import recurse_matches_c
1275
1287
            self.assertIs(recurse_matches_c,
1276
1288
                          patiencediff.recurse_matches)
1298
1310
                         diff_obj._get_command('old-path', 'new-path'))
1299
1311
 
1300
1312
    def test_from_string_path_with_backslashes(self):
1301
 
        self.requireFeature(test_win32utils.BackslashDirSeparatorFeature)
 
1313
        self.requireFeature(features.backslashdir_feature)
1302
1314
        tool = 'C:\\Tools\\Diff.exe'
1303
1315
        diff_obj = diff.DiffFromTool.from_string(tool, None, None, None)
1304
1316
        self.addCleanup(diff_obj.finish)
1326
1338
                         ' on this machine', str(e))
1327
1339
 
1328
1340
    def test_prepare_files_creates_paths_readable_by_windows_tool(self):
1329
 
        self.requireFeature(AttribFeature)
 
1341
        self.requireFeature(features.AttribFeature)
1330
1342
        output = StringIO()
1331
1343
        tree = self.make_branch_and_tree('tree')
1332
1344
        self.build_tree_contents([('tree/file', 'content')])
1391
1403
        diff_obj._prepare_files('file2-id', 'oldname2', 'newname2')
1392
1404
 
1393
1405
 
 
1406
class TestDiffFromToolEncodedFilename(tests.TestCaseWithTransport):
 
1407
 
 
1408
    def test_encodable_filename(self):
 
1409
        # Just checks file path for external diff tool.
 
1410
        # We cannot change CPython's internal encoding used by os.exec*.
 
1411
        import sys
 
1412
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1413
                                    None, None, None)
 
1414
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1415
            encoding = scenario['encoding']
 
1416
            dirname  = scenario['info']['directory']
 
1417
            filename = scenario['info']['filename']
 
1418
 
 
1419
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1420
            relpath = dirname + u'/' + filename
 
1421
            fullpath = diffobj._safe_filename('safe', relpath)
 
1422
            self.assertEqual(
 
1423
                    fullpath,
 
1424
                    fullpath.encode(encoding).decode(encoding)
 
1425
                    )
 
1426
            self.assert_(fullpath.startswith(diffobj._root + '/safe'))
 
1427
 
 
1428
    def test_unencodable_filename(self):
 
1429
        import sys
 
1430
        diffobj = diff.DiffFromTool(['dummy', '@old_path', '@new_path'],
 
1431
                                    None, None, None)
 
1432
        for _, scenario in EncodingAdapter.encoding_scenarios:
 
1433
            encoding = scenario['encoding']
 
1434
            dirname  = scenario['info']['directory']
 
1435
            filename = scenario['info']['filename']
 
1436
 
 
1437
            if encoding == 'iso-8859-1':
 
1438
                encoding = 'iso-8859-2'
 
1439
            else:
 
1440
                encoding = 'iso-8859-1'
 
1441
 
 
1442
            self.overrideAttr(diffobj, '_fenc', lambda: encoding)
 
1443
            relpath = dirname + u'/' + filename
 
1444
            fullpath = diffobj._safe_filename('safe', relpath)
 
1445
            self.assertEqual(
 
1446
                    fullpath,
 
1447
                    fullpath.encode(encoding).decode(encoding)
 
1448
                    )
 
1449
            self.assert_(fullpath.startswith(diffobj._root + '/safe'))
 
1450
 
 
1451
 
1394
1452
class TestGetTreesAndBranchesToDiffLocked(tests.TestCaseWithTransport):
1395
1453
 
1396
1454
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1397
 
        """Call get_trees_and_branches_to_diff_locked.  Overridden by
1398
 
        TestGetTreesAndBranchesToDiff.
1399
 
        """
 
1455
        """Call get_trees_and_branches_to_diff_locked."""
1400
1456
        return diff.get_trees_and_branches_to_diff_locked(
1401
1457
            path_list, revision_specs, old_url, new_url, self.addCleanup)
1402
1458
 
1439
1495
        self.assertEqual(tree.branch.base, new_branch.base)
1440
1496
        self.assertIs(None, specific_files)
1441
1497
        self.assertEqual(tree.basedir, extra_trees[0].basedir)
1442
 
 
1443
 
 
1444
 
class TestGetTreesAndBranchesToDiff(TestGetTreesAndBranchesToDiffLocked):
1445
 
    """Apply the tests for get_trees_and_branches_to_diff_locked to the
1446
 
    deprecated get_trees_and_branches_to_diff function.
1447
 
    """
1448
 
 
1449
 
    def call_gtabtd(self, path_list, revision_specs, old_url, new_url):
1450
 
        return self.applyDeprecated(
1451
 
            deprecated_in((2, 2, 0)), diff.get_trees_and_branches_to_diff,
1452
 
            path_list, revision_specs, old_url, new_url)
1453