~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-09 15:09:12 UTC
  • mto: This revision was merged to the branch mainline in revision 3699.
  • Revision ID: john@arbash-meinel.com-20080909150912-wyttm8he1zsls2ck
Use the right timing function on win32

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
from cStringIO import StringIO
18
18
import os
19
 
import socket
20
19
import sys
21
 
import threading
22
20
 
23
21
from bzrlib import (
24
22
    bzrdir,
25
 
    diff,
26
23
    errors,
27
24
    inventory,
28
 
    merge,
29
25
    osutils,
30
26
    repository,
31
27
    revision as _mod_revision,
32
 
    tests,
33
28
    treebuilder,
34
29
    )
 
30
from bzrlib.bzrdir import BzrDir
35
31
from bzrlib.bundle import read_mergeable_from_url
36
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
33
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
34
from bzrlib.directory_service import directories
40
35
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
36
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
37
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
38
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
39
from bzrlib.branch import Branch
 
40
from bzrlib.diff import internal_diff
 
41
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
42
                           NoSuchFile,)
 
43
from bzrlib.merge import Merge3Merger
45
44
from bzrlib.repofmt import knitrepo
 
45
from bzrlib.osutils import sha_file, sha_string
46
46
from bzrlib.tests import (
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestCaseWithTransport,
 
51
    TestSkipped,
47
52
    test_read_bundle,
48
53
    test_commit,
49
54
    )
50
55
from bzrlib.transform import TreeTransform
51
56
 
52
57
 
53
 
def get_text(vf, key):
54
 
    """Get the fulltext for a given revision id that is present in the vf"""
55
 
    stream = vf.get_record_stream([key], 'unordered', True)
56
 
    record = stream.next()
57
 
    return record.get_bytes_as('fulltext')
58
 
 
59
 
 
60
 
def get_inventory_text(repo, revision_id):
61
 
    """Get the fulltext for the inventory at revision id"""
62
 
    repo.lock_read()
63
 
    try:
64
 
        return get_text(repo.inventories, (revision_id,))
65
 
    finally:
66
 
        repo.unlock()
67
 
 
68
 
 
69
58
class MockTree(object):
70
59
    def __init__(self):
71
60
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
117
106
        elif kind == 'symlink':
118
107
            ie = InventoryLink(file_id, name, parent_id)
119
108
        else:
120
 
            raise errors.BzrError('unknown kind %r' % kind)
 
109
            raise BzrError('unknown kind %r' % kind)
121
110
        ie.text_sha1 = text_sha_1
122
111
        ie.text_size = text_size
123
112
        return ie
125
114
    def add_dir(self, file_id, path):
126
115
        self.paths[file_id] = path
127
116
        self.ids[path] = file_id
128
 
 
 
117
    
129
118
    def add_file(self, file_id, path, contents):
130
119
        self.add_dir(file_id, path)
131
120
        self.contents[file_id] = contents
148
137
    def contents_stats(self, file_id):
149
138
        if file_id not in self.contents:
150
139
            return None, None
151
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
140
        text_sha1 = sha_file(self.get_file(file_id))
152
141
        return text_sha1, len(self.contents[file_id])
153
142
 
154
143
 
155
 
class BTreeTester(tests.TestCase):
 
144
class BTreeTester(TestCase):
156
145
    """A simple unittest tester for the BundleTree class."""
157
146
 
158
147
    def make_tree_1(self):
162
151
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
163
152
        mtree.add_dir("d", "grandparent/alt_parent")
164
153
        return BundleTree(mtree, ''), mtree
165
 
 
 
154
        
166
155
    def test_renames(self):
167
156
        """Ensure that file renames have the proper effect on children"""
168
157
        btree = self.make_tree_1()[0]
169
158
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
170
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
159
        self.assertEqual(btree.old_path("grandparent/parent"), 
171
160
                         "grandparent/parent")
172
161
        self.assertEqual(btree.old_path("grandparent/parent/file"),
173
162
                         "grandparent/parent/file")
213
202
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
214
203
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
215
204
 
216
 
        btree.note_rename("grandparent/parent/file",
 
205
        btree.note_rename("grandparent/parent/file", 
217
206
                          "grandparent2/parent2/file2")
218
207
        self.assertEqual(btree.id2path("a"), "grandparent2")
219
208
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
228
217
    def test_moves(self):
229
218
        """Ensure that file moves have the proper effect on children"""
230
219
        btree = self.make_tree_1()[0]
231
 
        btree.note_rename("grandparent/parent/file",
 
220
        btree.note_rename("grandparent/parent/file", 
232
221
                          "grandparent/alt_parent/file")
233
222
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
234
223
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
236
225
 
237
226
    def unified_diff(self, old, new):
238
227
        out = StringIO()
239
 
        diff.internal_diff("old", old, "new", new, out)
 
228
        internal_diff("old", old, "new", new, out)
240
229
        out.seek(0,0)
241
230
        return out.read()
242
231
 
243
232
    def make_tree_2(self):
244
233
        btree = self.make_tree_1()[0]
245
 
        btree.note_rename("grandparent/parent/file",
 
234
        btree.note_rename("grandparent/parent/file", 
246
235
                          "grandparent/alt_parent/file")
247
236
        self.assertTrue(btree.id2path("e") is None)
248
237
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
277
266
    def make_tree_3(self):
278
267
        btree, mtree = self.make_tree_1()
279
268
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
280
 
        btree.note_rename("grandparent/parent/file",
 
269
        btree.note_rename("grandparent/parent/file", 
281
270
                          "grandparent/alt_parent/file")
282
 
        btree.note_rename("grandparent/parent/topping",
 
271
        btree.note_rename("grandparent/parent/topping", 
283
272
                          "grandparent/alt_parent/stopping")
284
273
        return btree
285
274
 
324
313
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
325
314
        btree.note_deletion("grandparent/parent/file")
326
315
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
327
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
316
        btree.note_last_changed("grandparent/alt_parent/fool", 
328
317
                                "revisionidiguess")
329
318
        self.assertEqual(self.sorted_ids(btree),
330
319
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
331
320
 
332
321
 
333
 
class BundleTester1(tests.TestCaseWithTransport):
 
322
class BundleTester1(TestCaseWithTransport):
334
323
 
335
324
    def test_mismatched_bundle(self):
336
325
        format = bzrdir.BzrDirMetaFormat1()
337
326
        format.repository_format = knitrepo.RepositoryFormatKnit3()
338
327
        serializer = BundleSerializerV08('0.8')
339
328
        b = self.make_branch('.', format=format)
340
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
329
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
341
330
                          b.repository, [], {}, StringIO())
342
331
 
343
332
    def test_matched_bundle(self):
363
352
        format = bzrdir.BzrDirMetaFormat1()
364
353
        format.repository_format = knitrepo.RepositoryFormatKnit1()
365
354
        target = self.make_branch('target', format=format)
366
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
355
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
367
356
                          target.repository, read_bundle(text))
368
357
 
369
358
 
377
366
    def make_branch_and_tree(self, path, format=None):
378
367
        if format is None:
379
368
            format = self.bzrdir_format()
380
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
381
 
            self, path, format)
 
369
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
382
370
 
383
371
    def make_branch(self, path, format=None):
384
372
        if format is None:
385
373
            format = self.bzrdir_format()
386
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
374
        return TestCaseWithTransport.make_branch(self, path, format)
387
375
 
388
376
    def create_bundle_text(self, base_rev_id, rev_id):
389
377
        bundle_txt = StringIO()
390
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
378
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
391
379
                               bundle_txt, format=self.format)
392
380
        bundle_txt.seek(0)
393
 
        self.assertEqual(bundle_txt.readline(),
 
381
        self.assertEqual(bundle_txt.readline(), 
394
382
                         '# Bazaar revision bundle v%s\n' % self.format)
395
383
        self.assertEqual(bundle_txt.readline(), '#\n')
396
384
 
404
392
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
405
393
        Make sure that the text generated is valid, and that it
406
394
        can be applied against the base, and generate the same information.
407
 
 
408
 
        :return: The in-memory bundle
 
395
        
 
396
        :return: The in-memory bundle 
409
397
        """
410
398
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
411
399
 
412
 
        # This should also validate the generated bundle
 
400
        # This should also validate the generated bundle 
413
401
        bundle = read_bundle(bundle_txt)
414
402
        repository = self.b1.repository
415
403
        for bundle_rev in bundle.real_revisions:
419
407
            # it
420
408
            branch_rev = repository.get_revision(bundle_rev.revision_id)
421
409
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
422
 
                      'timestamp', 'timezone', 'message', 'committer',
 
410
                      'timestamp', 'timezone', 'message', 'committer', 
423
411
                      'parent_ids', 'properties'):
424
 
                self.assertEqual(getattr(branch_rev, a),
 
412
                self.assertEqual(getattr(branch_rev, a), 
425
413
                                 getattr(bundle_rev, a))
426
 
            self.assertEqual(len(branch_rev.parent_ids),
 
414
            self.assertEqual(len(branch_rev.parent_ids), 
427
415
                             len(bundle_rev.parent_ids))
428
 
        self.assertEqual(rev_ids,
 
416
        self.assertEqual(rev_ids, 
429
417
                         [r.revision_id for r in bundle.real_revisions])
430
418
        self.valid_apply_bundle(base_rev_id, bundle,
431
419
                                   checkout_dir=checkout_dir)
435
423
    def get_invalid_bundle(self, base_rev_id, rev_id):
436
424
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
437
425
        Munge the text so that it's invalid.
438
 
 
 
426
        
439
427
        :return: The in-memory bundle
440
428
        """
441
429
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
442
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
430
        new_text = bundle_txt.getvalue().replace('executable:no', 
443
431
                                               'executable:yes')
444
432
        bundle_txt = StringIO(new_text)
445
433
        bundle = read_bundle(bundle_txt)
446
434
        self.valid_apply_bundle(base_rev_id, bundle)
447
 
        return bundle
 
435
        return bundle 
448
436
 
449
437
    def test_non_bundle(self):
450
 
        self.assertRaises(errors.NotABundle,
451
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
438
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
452
439
 
453
440
    def test_malformed(self):
454
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
441
        self.assertRaises(BadBundle, read_bundle, 
455
442
                          StringIO('# Bazaar revision bundle v'))
456
443
 
457
444
    def test_crlf_bundle(self):
458
445
        try:
459
446
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
460
 
        except errors.BadBundle:
 
447
        except BadBundle:
461
448
            # It is currently permitted for bundles with crlf line endings to
462
449
            # make read_bundle raise a BadBundle, but this should be fixed.
463
450
            # Anything else, especially NotABundle, is an error.
495
482
                for inventory_id in old:
496
483
                    try:
497
484
                        old_file = old.get_file(inventory_id)
498
 
                    except errors.NoSuchFile:
 
485
                    except NoSuchFile:
499
486
                        continue
500
487
                    if old_file is None:
501
488
                        continue
531
518
        self.assertIs(repository.has_revision(base_rev_id), True)
532
519
        for rev in info.real_revisions:
533
520
            self.assert_(not repository.has_revision(rev.revision_id),
534
 
                'Revision {%s} present before applying bundle'
 
521
                'Revision {%s} present before applying bundle' 
535
522
                % rev.revision_id)
536
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
523
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
537
524
 
538
525
        for rev in info.real_revisions:
539
526
            self.assert_(repository.has_revision(rev.revision_id),
540
 
                'Missing revision {%s} after applying bundle'
 
527
                'Missing revision {%s} after applying bundle' 
541
528
                % rev.revision_id)
542
529
 
543
530
        self.assert_(to_tree.branch.repository.has_revision(info.target))
549
536
        rev = info.real_revisions[-1]
550
537
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
551
538
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
552
 
 
 
539
        
553
540
        # TODO: make sure the target tree is identical to base tree
554
541
        #       we might also check the working tree.
555
542
 
574
561
        self.tree1 = self.make_branch_and_tree('b1')
575
562
        self.b1 = self.tree1.branch
576
563
 
577
 
        self.build_tree_contents([('b1/one', 'one\n')])
578
 
        self.tree1.add('one', 'one-id')
579
 
        self.tree1.set_root_id('root-id')
 
564
        open('b1/one', 'wb').write('one\n')
 
565
        self.tree1.add('one')
580
566
        self.tree1.commit('add one', rev_id='a@cset-0-1')
581
567
 
582
568
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
593
579
                , 'b1/sub/sub/'
594
580
                , 'b1/sub/sub/nonempty.txt'
595
581
                ])
596
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
597
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
582
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
583
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
598
584
        tt = TreeTransform(self.tree1)
599
585
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
600
586
        tt.apply()
616
602
 
617
603
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
618
604
 
619
 
        # Check a rollup bundle
 
605
        # Check a rollup bundle 
620
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
621
607
 
622
608
        # Now delete entries
630
616
        tt.set_executability(False, trans_id)
631
617
        tt.apply()
632
618
        self.tree1.commit('removed', rev_id='a@cset-0-3')
633
 
 
 
619
        
634
620
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
635
 
        self.assertRaises((errors.TestamentMismatch,
636
 
            errors.VersionedFileInvalidChecksum,
637
 
            errors.BadBundle), self.get_invalid_bundle,
 
621
        self.assertRaises((TestamentMismatch,
 
622
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
638
623
            'a@cset-0-2', 'a@cset-0-3')
639
 
        # Check a rollup bundle
 
624
        # Check a rollup bundle 
640
625
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
641
626
 
642
627
        # Now move the directory
644
629
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
645
630
 
646
631
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
647
 
        # Check a rollup bundle
 
632
        # Check a rollup bundle 
648
633
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
649
634
 
650
635
        # Modified files
652
637
        open('b1/sub/dir/ pre space', 'ab').write(
653
638
             '\r\nAdding some\r\nDOS format lines\r\n')
654
639
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
655
 
        self.tree1.rename_one('sub/dir/ pre space',
 
640
        self.tree1.rename_one('sub/dir/ pre space', 
656
641
                              'sub/ start space')
657
642
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
658
643
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
664
649
                          verbose=False)
665
650
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
651
        other = self.get_checkout('a@cset-0-5')
667
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
668
 
                                       'a@cset-0-5')
669
 
        tree2_inv = get_inventory_text(other.branch.repository,
670
 
                                       'a@cset-0-5')
 
652
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
653
            'a@cset-0-5')
 
654
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
671
655
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
656
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
657
        other.commit('rename file', rev_id='a@cset-0-6b')
676
660
                          verbose=False)
677
661
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
678
662
 
679
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
681
 
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
663
    def test_symlink_bundle(self):
 
664
        self.requireFeature(SymlinkFeature)
683
665
        self.tree1 = self.make_branch_and_tree('b1')
684
666
        self.b1 = self.tree1.branch
685
 
 
686
667
        tt = TreeTransform(self.tree1)
687
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
668
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
688
669
        tt.apply()
689
670
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
690
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
691
 
        if getattr(bundle ,'revision_tree', None) is not None:
692
 
            # Not all bundle formats supports revision_tree
693
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
694
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
695
 
 
 
671
        self.get_valid_bundle('null:', 'l@cset-0-1')
696
672
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
673
        trans_id = tt.trans_id_tree_file_id('link-1')
698
674
        tt.adjust_path('link2', tt.root, trans_id)
699
675
        tt.delete_contents(trans_id)
700
 
        tt.create_symlink(new_link_target, trans_id)
 
676
        tt.create_symlink('mars', trans_id)
701
677
        tt.apply()
702
678
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
703
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
704
 
        if getattr(bundle ,'revision_tree', None) is not None:
705
 
            # Not all bundle formats supports revision_tree
706
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
707
 
            self.assertEqual(new_link_target,
708
 
                             bund_tree.get_symlink_target(link_id))
709
 
 
 
679
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
680
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
681
        trans_id = tt.trans_id_tree_file_id('link-1')
712
682
        tt.delete_contents(trans_id)
713
683
        tt.create_symlink('jupiter', trans_id)
714
684
        tt.apply()
715
685
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
716
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
717
 
 
 
686
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
718
687
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
688
        trans_id = tt.trans_id_tree_file_id('link-1')
720
689
        tt.delete_contents(trans_id)
721
690
        tt.apply()
722
691
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
723
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
724
 
 
725
 
    def test_symlink_bundle(self):
726
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
727
 
 
728
 
    def test_unicode_symlink_bundle(self):
729
 
        self.requireFeature(tests.UnicodeFilenameFeature)
730
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
731
 
                                  u'bar/\N{Euro Sign}foo',
732
 
                                  u'mars\N{Euro Sign}')
 
692
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
733
693
 
734
694
    def test_binary_bundle(self):
735
695
        self.tree1 = self.make_branch_and_tree('b1')
736
696
        self.b1 = self.tree1.branch
737
697
        tt = TreeTransform(self.tree1)
738
 
 
 
698
        
739
699
        # Add
740
700
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
701
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
833
793
        return bundle_file.getvalue()
834
794
 
835
795
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
837
796
        # Handle international characters
838
797
        os.mkdir('b1')
839
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
798
        try:
 
799
            f = open(u'b1/with Dod\xe9', 'wb')
 
800
        except UnicodeEncodeError:
 
801
            raise TestSkipped("Filesystem doesn't support unicode")
840
802
 
841
803
        self.tree1 = self.make_branch_and_tree('b1')
842
804
        self.b1 = self.tree1.branch
846
808
            u'William Dod\xe9\n').encode('utf-8'))
847
809
        f.close()
848
810
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
811
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
850
812
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
813
                          rev_id='i18n-1', committer=u'William Dod\xe9')
852
814
 
 
815
        if sys.platform == 'darwin':
 
816
            from bzrlib.workingtree import WorkingTree3
 
817
            if type(self.tree1) is WorkingTree3:
 
818
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
819
 
 
820
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
821
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
822
                             sorted(os.listdir(u'b1')))
 
823
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
824
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
825
                             delta.removed)
 
826
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
827
                              " combining characters.")
 
828
 
853
829
        # Add
854
830
        bundle = self.get_valid_bundle('null:', 'i18n-1')
855
831
 
856
832
        # Modified
857
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
833
        f = open(u'b1/with Dod\xe9', 'wb')
858
834
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
835
        f.close()
860
836
        self.tree1.commit(u'modified', rev_id='i18n-2')
861
837
 
862
838
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
863
 
 
 
839
        
864
840
        # Renamed
865
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
841
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
866
842
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
867
843
                          committer=u'Erik B\xe5gfors')
868
844
 
869
845
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
870
846
 
871
847
        # Removed
872
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
848
        self.tree1.remove([u'B\xe5gfors'])
873
849
        self.tree1.commit(u'removed', rev_id='i18n-4')
874
850
 
875
851
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
880
856
 
881
857
    def test_whitespace_bundle(self):
882
858
        if sys.platform in ('win32', 'cygwin'):
883
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
884
 
                                    ' with tabs or trailing spaces')
 
859
            raise TestSkipped('Windows doesn\'t support filenames'
 
860
                              ' with tabs or trailing spaces')
885
861
        self.tree1 = self.make_branch_and_tree('b1')
886
862
        self.b1 = self.tree1.branch
887
863
 
912
888
        self.tree1.commit('removed', rev_id='white-4')
913
889
 
914
890
        bundle = self.get_valid_bundle('white-3', 'white-4')
915
 
 
 
891
        
916
892
        # Now test a complet roll-up
917
893
        bundle = self.get_valid_bundle('null:', 'white-4')
918
894
 
931
907
                          timezone=19800, timestamp=1152544886.0)
932
908
 
933
909
        bundle = self.get_valid_bundle('null:', 'tz-1')
934
 
 
 
910
        
935
911
        rev = bundle.revisions[0]
936
912
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
937
913
        self.assertEqual(19800, rev.timezone)
1067
1043
        repo = self.make_repo_with_installed_revisions()
1068
1044
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1069
1045
        xml = repo.get_inventory_xml('rev2')
1070
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
 
1046
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
1071
1047
 
1072
1048
    def test_across_models_incompatible(self):
1073
1049
        tree = self.make_simple_tree('dirstate-with-subtree')
1076
1052
        try:
1077
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1078
1054
        except errors.IncompatibleBundleFormat:
1079
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1080
1056
        repo = self.make_repository('repo', format='knit')
1081
1057
        bundle.install_revisions(repo)
1082
1058
 
1103
1079
        try:
1104
1080
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1105
1081
        except errors.IncompatibleBundleFormat:
1106
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1082
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1107
1083
        if isinstance(bundle, v09.BundleInfo09):
1108
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1084
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1109
1085
        repo = self.make_repository('repo', format='knit')
1110
1086
        self.assertRaises(errors.IncompatibleRevision,
1111
1087
                          bundle.install_revisions, repo)
1118
1094
        try:
1119
1095
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1120
1096
        except ValueError:
1121
 
            raise tests.TestSkipped(
1122
 
                "Repository doesn't support revision ids with slashes")
 
1097
            raise TestSkipped("Repository doesn't support revision ids with"
 
1098
                              " slashes")
1123
1099
        bundle = self.get_valid_bundle('null:', 'rev/id')
1124
1100
 
1125
1101
    def test_skip_file(self):
1141
1117
        self.tree1.commit('rev3', rev_id='rev3')
1142
1118
        bundle = self.get_valid_bundle('reva', 'rev3')
1143
1119
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1120
            raise TestSkipped('Bundle format cannot provide reader')
1145
1121
        # be sure that file1 comes before file2
1146
1122
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1147
1123
            if f == 'file3-id':
1150
1126
        bundle.install_revisions(target.branch.repository)
1151
1127
 
1152
1128
 
1153
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1129
class V08BundleTester(BundleTester, TestCaseWithTransport):
1154
1130
 
1155
1131
    format = '0.8'
1156
1132
 
1289
1265
        return format
1290
1266
 
1291
1267
 
1292
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1268
class V4BundleTester(BundleTester, TestCaseWithTransport):
1293
1269
 
1294
1270
    format = '4'
1295
1271
 
1297
1273
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1298
1274
        Make sure that the text generated is valid, and that it
1299
1275
        can be applied against the base, and generate the same information.
1300
 
 
1301
 
        :return: The in-memory bundle
 
1276
        
 
1277
        :return: The in-memory bundle 
1302
1278
        """
1303
1279
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1304
1280
 
1305
 
        # This should also validate the generated bundle
 
1281
        # This should also validate the generated bundle 
1306
1282
        bundle = read_bundle(bundle_txt)
1307
1283
        repository = self.b1.repository
1308
1284
        for bundle_rev in bundle.real_revisions:
1312
1288
            # it
1313
1289
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1314
1290
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1315
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1291
                      'timestamp', 'timezone', 'message', 'committer', 
1316
1292
                      'parent_ids', 'properties'):
1317
 
                self.assertEqual(getattr(branch_rev, a),
 
1293
                self.assertEqual(getattr(branch_rev, a), 
1318
1294
                                 getattr(bundle_rev, a))
1319
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1295
            self.assertEqual(len(branch_rev.parent_ids), 
1320
1296
                             len(bundle_rev.parent_ids))
1321
1297
        self.assertEqual(set(rev_ids),
1322
1298
                         set([r.revision_id for r in bundle.real_revisions]))
1336
1312
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
1313
        new_text = new_text.replace('<file file_id="exe-1"',
1338
1314
                                    '<file executable="y" file_id="exe-1"')
1339
 
        new_text = new_text.replace('B260', 'B275')
 
1315
        new_text = new_text.replace('B222', 'B237')
1340
1316
        bundle_txt = StringIO()
1341
1317
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
1318
        bundle_txt.write('\n')
1348
1324
 
1349
1325
    def create_bundle_text(self, base_rev_id, rev_id):
1350
1326
        bundle_txt = StringIO()
1351
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1327
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1352
1328
                               bundle_txt, format=self.format)
1353
1329
        bundle_txt.seek(0)
1354
 
        self.assertEqual(bundle_txt.readline(),
 
1330
        self.assertEqual(bundle_txt.readline(), 
1355
1331
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
1332
        self.assertEqual(bundle_txt.readline(), '#\n')
1357
1333
        rev = self.b1.repository.get_revision(rev_id)
1379
1355
        install_bundle(target_repo, serializer.read(s))
1380
1356
        target_repo.lock_read()
1381
1357
        self.addCleanup(target_repo.unlock)
1382
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1383
 
        repo_texts = dict((i, ''.join(content)) for i, content
1384
 
                          in target_repo.iter_files_bytes(
1385
 
                                [('fileid-2', 'rev1', '1'),
1386
 
                                 ('fileid-2', 'rev2', '2')]))
1387
1358
        self.assertEqual({'1':'contents1\nstatic\n',
1388
 
                          '2':'contents2\nstatic\n'},
1389
 
                         repo_texts)
 
1359
            '2':'contents2\nstatic\n'},
 
1360
            dict(target_repo.iter_files_bytes(
 
1361
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
1390
1362
        rtree = target_repo.revision_tree('rev2')
1391
1363
        inventory_vf = target_repo.inventories
1392
1364
        # If the inventory store has a graph, it must match the revision graph.
1448
1420
        return 'metaweave'
1449
1421
 
1450
1422
 
1451
 
class V4_2aBundleTester(V4BundleTester):
1452
 
 
1453
 
    def bzrdir_format(self):
1454
 
        return '2a'
1455
 
 
1456
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1457
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1458
 
        Munge the text so that it's invalid.
1459
 
 
1460
 
        :return: The in-memory bundle
1461
 
        """
1462
 
        from bzrlib.bundle import serializer
1463
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1464
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1465
 
        # We are going to be replacing some text to set the executable bit on a
1466
 
        # file. Make sure the text replacement actually works correctly.
1467
 
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
1468
 
        new_text = new_text.replace('<file file_id="exe-1"',
1469
 
                                    '<file executable="y" file_id="exe-1"')
1470
 
        new_text = new_text.replace('B244', 'B259')
1471
 
        bundle_txt = StringIO()
1472
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1473
 
        bundle_txt.write('\n')
1474
 
        bundle_txt.write(new_text.encode('bz2'))
1475
 
        bundle_txt.seek(0)
1476
 
        bundle = read_bundle(bundle_txt)
1477
 
        self.valid_apply_bundle(base_rev_id, bundle)
1478
 
        return bundle
1479
 
 
1480
 
    def make_merged_branch(self):
1481
 
        builder = self.make_branch_builder('source')
1482
 
        builder.start_series()
1483
 
        builder.build_snapshot('a@cset-0-1', None, [
1484
 
            ('add', ('', 'root-id', 'directory', None)),
1485
 
            ('add', ('file', 'file-id', 'file', 'original content\n')),
1486
 
            ])
1487
 
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
1488
 
            ('modify', ('file-id', 'new-content\n')),
1489
 
            ])
1490
 
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
1491
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1492
 
            ])
1493
 
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
1494
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1495
 
            ])
1496
 
        builder.finish_series()
1497
 
        self.b1 = builder.get_branch()
1498
 
        self.b1.lock_read()
1499
 
        self.addCleanup(self.b1.unlock)
1500
 
 
1501
 
    def make_bundle_just_inventories(self, base_revision_id,
1502
 
                                     target_revision_id,
1503
 
                                     revision_ids):
1504
 
        sio = StringIO()
1505
 
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1506
 
                                         self.b1.repository, sio)
1507
 
        writer.bundle.begin()
1508
 
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
1509
 
        writer.bundle.end()
1510
 
        sio.seek(0)
1511
 
        return sio
1512
 
 
1513
 
    def test_single_inventory_multiple_parents_as_xml(self):
1514
 
        self.make_merged_branch()
1515
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1516
 
                                                ['a@cset-0-3'])
1517
 
        reader = v4.BundleReader(sio, stream_input=False)
1518
 
        records = list(reader.iter_records())
1519
 
        self.assertEqual(1, len(records))
1520
 
        (bytes, metadata, repo_kind, revision_id,
1521
 
         file_id) = records[0]
1522
 
        self.assertIs(None, file_id)
1523
 
        self.assertEqual('a@cset-0-3', revision_id)
1524
 
        self.assertEqual('inventory', repo_kind)
1525
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1526
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1527
 
                          'storage_kind': 'mpdiff',
1528
 
                         }, metadata)
1529
 
        # We should have an mpdiff that takes some lines from both parents.
1530
 
        self.assertEqualDiff(
1531
 
            'i 1\n'
1532
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1533
 
            '\n'
1534
 
            'c 0 1 1 2\n'
1535
 
            'c 1 3 3 2\n', bytes)
1536
 
 
1537
 
    def test_single_inv_no_parents_as_xml(self):
1538
 
        self.make_merged_branch()
1539
 
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1540
 
                                                ['a@cset-0-1'])
1541
 
        reader = v4.BundleReader(sio, stream_input=False)
1542
 
        records = list(reader.iter_records())
1543
 
        self.assertEqual(1, len(records))
1544
 
        (bytes, metadata, repo_kind, revision_id,
1545
 
         file_id) = records[0]
1546
 
        self.assertIs(None, file_id)
1547
 
        self.assertEqual('a@cset-0-1', revision_id)
1548
 
        self.assertEqual('inventory', repo_kind)
1549
 
        self.assertEqual({'parents': [],
1550
 
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1551
 
                          'storage_kind': 'mpdiff',
1552
 
                         }, metadata)
1553
 
        # We should have an mpdiff that takes some lines from both parents.
1554
 
        self.assertEqualDiff(
1555
 
            'i 4\n'
1556
 
            '<inventory format="10" revision_id="a@cset-0-1">\n'
1557
 
            '<directory file_id="root-id" name=""'
1558
 
                ' revision="a@cset-0-1" />\n'
1559
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1560
 
                ' revision="a@cset-0-1"'
1561
 
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1562
 
                ' text_size="17" />\n'
1563
 
            '</inventory>\n'
1564
 
            '\n', bytes)
1565
 
 
1566
 
    def test_multiple_inventories_as_xml(self):
1567
 
        self.make_merged_branch()
1568
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1569
 
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
1570
 
        reader = v4.BundleReader(sio, stream_input=False)
1571
 
        records = list(reader.iter_records())
1572
 
        self.assertEqual(3, len(records))
1573
 
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
1574
 
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
1575
 
                         revision_ids)
1576
 
        metadata_2a = records[0][1]
1577
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1578
 
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1579
 
                          'storage_kind': 'mpdiff',
1580
 
                         }, metadata_2a)
1581
 
        metadata_2b = records[1][1]
1582
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1583
 
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1584
 
                          'storage_kind': 'mpdiff',
1585
 
                         }, metadata_2b)
1586
 
        metadata_3 = records[2][1]
1587
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1588
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1589
 
                          'storage_kind': 'mpdiff',
1590
 
                         }, metadata_3)
1591
 
        bytes_2a = records[0][0]
1592
 
        self.assertEqualDiff(
1593
 
            'i 1\n'
1594
 
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
1595
 
            '\n'
1596
 
            'c 0 1 1 1\n'
1597
 
            'i 1\n'
1598
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1599
 
                ' revision="a@cset-0-2a"'
1600
 
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
1601
 
                ' text_size="12" />\n'
1602
 
            '\n'
1603
 
            'c 0 3 3 1\n', bytes_2a)
1604
 
        bytes_2b = records[1][0]
1605
 
        self.assertEqualDiff(
1606
 
            'i 1\n'
1607
 
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
1608
 
            '\n'
1609
 
            'c 0 1 1 2\n'
1610
 
            'i 1\n'
1611
 
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
1612
 
                ' revision="a@cset-0-2b"'
1613
 
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
1614
 
                ' text_size="14" />\n'
1615
 
            '\n'
1616
 
            'c 0 3 4 1\n', bytes_2b)
1617
 
        bytes_3 = records[2][0]
1618
 
        self.assertEqualDiff(
1619
 
            'i 1\n'
1620
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1621
 
            '\n'
1622
 
            'c 0 1 1 2\n'
1623
 
            'c 1 3 3 2\n', bytes_3)
1624
 
 
1625
 
    def test_creating_bundle_preserves_chk_pages(self):
1626
 
        self.make_merged_branch()
1627
 
        target = self.b1.bzrdir.sprout('target',
1628
 
                                       revision_id='a@cset-0-2a').open_branch()
1629
 
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
1630
 
                                                      'a@cset-0-3')
1631
 
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
1632
 
        bundle = read_bundle(bundle_txt)
1633
 
        target.lock_write()
1634
 
        self.addCleanup(target.unlock)
1635
 
        install_bundle(target.repository, bundle)
1636
 
        inv1 = self.b1.repository.inventories.get_record_stream([
1637
 
            ('a@cset-0-3',)], 'unordered',
1638
 
            True).next().get_bytes_as('fulltext')
1639
 
        inv2 = target.repository.inventories.get_record_stream([
1640
 
            ('a@cset-0-3',)], 'unordered',
1641
 
            True).next().get_bytes_as('fulltext')
1642
 
        self.assertEqualDiff(inv1, inv2)
1643
 
 
1644
 
 
1645
1423
class MungedBundleTester(object):
1646
1424
 
1647
1425
    def build_test_bundle(self):
1694
1472
        self.check_valid(bundle)
1695
1473
 
1696
1474
 
1697
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1475
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1698
1476
 
1699
1477
    format = '0.9'
1700
1478
 
1732
1510
        self.check_valid(bundle)
1733
1511
 
1734
1512
 
1735
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1513
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1736
1514
 
1737
1515
    format = '4'
1738
1516
 
1739
1517
 
1740
 
class TestBundleWriterReader(tests.TestCase):
 
1518
class TestBundleWriterReader(TestCase):
1741
1519
 
1742
1520
    def test_roundtrip_record(self):
1743
1521
        fileobj = StringIO()
1805
1583
        record = record_iter.next()
1806
1584
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
1585
            'info', None, None), record)
1808
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1809
 
 
1810
 
 
1811
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
 
1586
        self.assertRaises(BadBundle, record_iter.next)
 
1587
 
 
1588
 
 
1589
class TestReadMergeableFromUrl(TestCaseWithTransport):
1812
1590
 
1813
1591
    def test_read_mergeable_skips_local(self):
1814
1592
        """A local bundle named like the URL should not be read.
1824
1602
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1825
1603
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1826
1604
                          'foo:bar')
1827
 
 
1828
 
    def test_infinite_redirects_are_not_a_bundle(self):
1829
 
        """If a URL causes TooManyRedirections then NotABundle is raised.
1830
 
        """
1831
 
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1832
 
        server = RedirectingMemoryServer()
1833
 
        server.setUp()
1834
 
        url = server.get_url() + 'infinite-loop'
1835
 
        self.addCleanup(server.tearDown)
1836
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1837
 
 
1838
 
    def test_smart_server_connection_reset(self):
1839
 
        """If a smart server connection fails during the attempt to read a
1840
 
        bundle, then the ConnectionReset error should be propagated.
1841
 
        """
1842
 
        # Instantiate a server that will provoke a ConnectionReset
1843
 
        sock_server = _DisconnectingTCPServer()
1844
 
        sock_server.setUp()
1845
 
        self.addCleanup(sock_server.tearDown)
1846
 
        # We don't really care what the url is since the server will close the
1847
 
        # connection without interpreting it
1848
 
        url = sock_server.get_url()
1849
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1850
 
 
1851
 
 
1852
 
class _DisconnectingTCPServer(object):
1853
 
    """A TCP server that immediately closes any connection made to it."""
1854
 
 
1855
 
    def setUp(self):
1856
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1857
 
        self.sock.bind(('127.0.0.1', 0))
1858
 
        self.sock.listen(1)
1859
 
        self.port = self.sock.getsockname()[1]
1860
 
        self.thread = threading.Thread(
1861
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1862
 
            target=self.accept_and_close)
1863
 
        self.thread.start()
1864
 
 
1865
 
    def accept_and_close(self):
1866
 
        conn, addr = self.sock.accept()
1867
 
        conn.shutdown(socket.SHUT_RDWR)
1868
 
        conn.close()
1869
 
 
1870
 
    def get_url(self):
1871
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1872
 
 
1873
 
    def tearDown(self):
1874
 
        try:
1875
 
            # make sure the thread dies by connecting to the listening socket,
1876
 
            # just in case the test failed to do so.
1877
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1878
 
            conn.connect(self.sock.getsockname())
1879
 
            conn.close()
1880
 
        except socket.error:
1881
 
            pass
1882
 
        self.sock.close()
1883
 
        self.thread.join()
1884