~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:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2006, 2007 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
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,
 
26
    repository,
30
27
    revision as _mod_revision,
31
 
    symbol_versioning,
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
40
36
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
41
37
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
42
38
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
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
43
44
from bzrlib.repofmt import knitrepo
 
45
from bzrlib.osutils import sha_file, sha_string
44
46
from bzrlib.tests import (
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestCaseWithTransport,
 
51
    TestSkipped,
45
52
    test_read_bundle,
46
53
    test_commit,
47
54
    )
48
55
from bzrlib.transform import TreeTransform
49
 
from bzrlib.tests import (
50
 
    features,
51
 
    )
52
 
 
53
 
 
54
 
def get_text(vf, key):
55
 
    """Get the fulltext for a given revision id that is present in the vf"""
56
 
    stream = vf.get_record_stream([key], 'unordered', True)
57
 
    record = stream.next()
58
 
    return record.get_bytes_as('fulltext')
59
 
 
60
 
 
61
 
def get_inventory_text(repo, revision_id):
62
 
    """Get the fulltext for the inventory at revision id"""
63
 
    repo.lock_read()
64
 
    try:
65
 
        return get_text(repo.inventories, (revision_id,))
66
 
    finally:
67
 
        repo.unlock()
68
56
 
69
57
 
70
58
class MockTree(object):
71
 
 
72
59
    def __init__(self):
73
60
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
74
61
        object.__init__(self)
79
66
 
80
67
    inventory = property(lambda x:x)
81
68
 
82
 
    def all_file_ids(self):
83
 
        return set(self.paths.keys())
 
69
    def __iter__(self):
 
70
        return self.paths.iterkeys()
84
71
 
85
72
    def __getitem__(self, file_id):
86
73
        if file_id == self.root.file_id:
116
103
            ie = InventoryDirectory(file_id, name, parent_id)
117
104
        elif kind == 'file':
118
105
            ie = InventoryFile(file_id, name, parent_id)
119
 
            ie.text_sha1 = text_sha_1
120
 
            ie.text_size = text_size
121
106
        elif kind == 'symlink':
122
107
            ie = InventoryLink(file_id, name, parent_id)
123
108
        else:
124
 
            raise errors.BzrError('unknown kind %r' % kind)
 
109
            raise BzrError('unknown kind %r' % kind)
 
110
        ie.text_sha1 = text_sha_1
 
111
        ie.text_size = text_size
125
112
        return ie
126
113
 
127
114
    def add_dir(self, file_id, path):
128
115
        self.paths[file_id] = path
129
116
        self.ids[path] = file_id
130
 
 
 
117
    
131
118
    def add_file(self, file_id, path, contents):
132
119
        self.add_dir(file_id, path)
133
120
        self.contents[file_id] = contents
147
134
        result.seek(0,0)
148
135
        return result
149
136
 
150
 
    def get_file_revision(self, file_id):
151
 
        return self.inventory[file_id].revision
152
 
 
153
137
    def contents_stats(self, file_id):
154
138
        if file_id not in self.contents:
155
139
            return None, None
156
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
140
        text_sha1 = sha_file(self.get_file(file_id))
157
141
        return text_sha1, len(self.contents[file_id])
158
142
 
159
143
 
160
 
class BTreeTester(tests.TestCase):
 
144
class BTreeTester(TestCase):
161
145
    """A simple unittest tester for the BundleTree class."""
162
146
 
163
147
    def make_tree_1(self):
167
151
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
168
152
        mtree.add_dir("d", "grandparent/alt_parent")
169
153
        return BundleTree(mtree, ''), mtree
170
 
 
 
154
        
171
155
    def test_renames(self):
172
156
        """Ensure that file renames have the proper effect on children"""
173
157
        btree = self.make_tree_1()[0]
174
158
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
175
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
159
        self.assertEqual(btree.old_path("grandparent/parent"), 
176
160
                         "grandparent/parent")
177
161
        self.assertEqual(btree.old_path("grandparent/parent/file"),
178
162
                         "grandparent/parent/file")
218
202
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
219
203
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
220
204
 
221
 
        btree.note_rename("grandparent/parent/file",
 
205
        btree.note_rename("grandparent/parent/file", 
222
206
                          "grandparent2/parent2/file2")
223
207
        self.assertEqual(btree.id2path("a"), "grandparent2")
224
208
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
233
217
    def test_moves(self):
234
218
        """Ensure that file moves have the proper effect on children"""
235
219
        btree = self.make_tree_1()[0]
236
 
        btree.note_rename("grandparent/parent/file",
 
220
        btree.note_rename("grandparent/parent/file", 
237
221
                          "grandparent/alt_parent/file")
238
222
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
239
223
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
241
225
 
242
226
    def unified_diff(self, old, new):
243
227
        out = StringIO()
244
 
        diff.internal_diff("old", old, "new", new, out)
 
228
        internal_diff("old", old, "new", new, out)
245
229
        out.seek(0,0)
246
230
        return out.read()
247
231
 
248
232
    def make_tree_2(self):
249
233
        btree = self.make_tree_1()[0]
250
 
        btree.note_rename("grandparent/parent/file",
 
234
        btree.note_rename("grandparent/parent/file", 
251
235
                          "grandparent/alt_parent/file")
252
236
        self.assertTrue(btree.id2path("e") is None)
253
237
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
282
266
    def make_tree_3(self):
283
267
        btree, mtree = self.make_tree_1()
284
268
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
285
 
        btree.note_rename("grandparent/parent/file",
 
269
        btree.note_rename("grandparent/parent/file", 
286
270
                          "grandparent/alt_parent/file")
287
 
        btree.note_rename("grandparent/parent/topping",
 
271
        btree.note_rename("grandparent/parent/topping", 
288
272
                          "grandparent/alt_parent/stopping")
289
273
        return btree
290
274
 
329
313
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
330
314
        btree.note_deletion("grandparent/parent/file")
331
315
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
332
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
316
        btree.note_last_changed("grandparent/alt_parent/fool", 
333
317
                                "revisionidiguess")
334
318
        self.assertEqual(self.sorted_ids(btree),
335
319
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
336
320
 
337
321
 
338
 
class BundleTester1(tests.TestCaseWithTransport):
 
322
class BundleTester1(TestCaseWithTransport):
339
323
 
340
324
    def test_mismatched_bundle(self):
341
325
        format = bzrdir.BzrDirMetaFormat1()
342
326
        format.repository_format = knitrepo.RepositoryFormatKnit3()
343
327
        serializer = BundleSerializerV08('0.8')
344
328
        b = self.make_branch('.', format=format)
345
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
329
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
346
330
                          b.repository, [], {}, StringIO())
347
331
 
348
332
    def test_matched_bundle(self):
368
352
        format = bzrdir.BzrDirMetaFormat1()
369
353
        format.repository_format = knitrepo.RepositoryFormatKnit1()
370
354
        target = self.make_branch('target', format=format)
371
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
355
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
372
356
                          target.repository, read_bundle(text))
373
357
 
374
358
 
382
366
    def make_branch_and_tree(self, path, format=None):
383
367
        if format is None:
384
368
            format = self.bzrdir_format()
385
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
386
 
            self, path, format)
 
369
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
387
370
 
388
371
    def make_branch(self, path, format=None):
389
372
        if format is None:
390
373
            format = self.bzrdir_format()
391
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
374
        return TestCaseWithTransport.make_branch(self, path, format)
392
375
 
393
376
    def create_bundle_text(self, base_rev_id, rev_id):
394
377
        bundle_txt = StringIO()
395
 
        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, 
396
379
                               bundle_txt, format=self.format)
397
380
        bundle_txt.seek(0)
398
 
        self.assertEqual(bundle_txt.readline(),
 
381
        self.assertEqual(bundle_txt.readline(), 
399
382
                         '# Bazaar revision bundle v%s\n' % self.format)
400
383
        self.assertEqual(bundle_txt.readline(), '#\n')
401
384
 
409
392
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
410
393
        Make sure that the text generated is valid, and that it
411
394
        can be applied against the base, and generate the same information.
412
 
 
413
 
        :return: The in-memory bundle
 
395
        
 
396
        :return: The in-memory bundle 
414
397
        """
415
398
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
416
399
 
417
 
        # This should also validate the generated bundle
 
400
        # This should also validate the generated bundle 
418
401
        bundle = read_bundle(bundle_txt)
419
402
        repository = self.b1.repository
420
403
        for bundle_rev in bundle.real_revisions:
424
407
            # it
425
408
            branch_rev = repository.get_revision(bundle_rev.revision_id)
426
409
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
427
 
                      'timestamp', 'timezone', 'message', 'committer',
 
410
                      'timestamp', 'timezone', 'message', 'committer', 
428
411
                      'parent_ids', 'properties'):
429
 
                self.assertEqual(getattr(branch_rev, a),
 
412
                self.assertEqual(getattr(branch_rev, a), 
430
413
                                 getattr(bundle_rev, a))
431
 
            self.assertEqual(len(branch_rev.parent_ids),
 
414
            self.assertEqual(len(branch_rev.parent_ids), 
432
415
                             len(bundle_rev.parent_ids))
433
 
        self.assertEqual(rev_ids,
 
416
        self.assertEqual(rev_ids, 
434
417
                         [r.revision_id for r in bundle.real_revisions])
435
418
        self.valid_apply_bundle(base_rev_id, bundle,
436
419
                                   checkout_dir=checkout_dir)
440
423
    def get_invalid_bundle(self, base_rev_id, rev_id):
441
424
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
442
425
        Munge the text so that it's invalid.
443
 
 
 
426
        
444
427
        :return: The in-memory bundle
445
428
        """
446
429
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
447
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
430
        new_text = bundle_txt.getvalue().replace('executable:no', 
448
431
                                               'executable:yes')
449
432
        bundle_txt = StringIO(new_text)
450
433
        bundle = read_bundle(bundle_txt)
451
434
        self.valid_apply_bundle(base_rev_id, bundle)
452
 
        return bundle
 
435
        return bundle 
453
436
 
454
437
    def test_non_bundle(self):
455
 
        self.assertRaises(errors.NotABundle,
456
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
438
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
457
439
 
458
440
    def test_malformed(self):
459
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
441
        self.assertRaises(BadBundle, read_bundle, 
460
442
                          StringIO('# Bazaar revision bundle v'))
461
443
 
462
444
    def test_crlf_bundle(self):
463
445
        try:
464
446
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
465
 
        except errors.BadBundle:
 
447
        except BadBundle:
466
448
            # It is currently permitted for bundles with crlf line endings to
467
449
            # make read_bundle raise a BadBundle, but this should be fixed.
468
450
            # Anything else, especially NotABundle, is an error.
497
479
                                 % (ancestor,))
498
480
 
499
481
                # Now check that the file contents are all correct
500
 
                for inventory_id in old.all_file_ids():
 
482
                for inventory_id in old:
501
483
                    try:
502
484
                        old_file = old.get_file(inventory_id)
503
 
                    except errors.NoSuchFile:
 
485
                    except NoSuchFile:
504
486
                        continue
505
487
                    if old_file is None:
506
488
                        continue
511
493
                old.unlock()
512
494
        if not _mod_revision.is_null(rev_id):
513
495
            rh = self.b1.revision_history()
514
 
            self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
515
 
                tree.branch.set_revision_history, rh[:rh.index(rev_id)+1])
 
496
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
516
497
            tree.update()
517
498
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
518
499
            self.assertFalse(delta.has_changed(),
537
518
        self.assertIs(repository.has_revision(base_rev_id), True)
538
519
        for rev in info.real_revisions:
539
520
            self.assert_(not repository.has_revision(rev.revision_id),
540
 
                'Revision {%s} present before applying bundle'
 
521
                'Revision {%s} present before applying bundle' 
541
522
                % rev.revision_id)
542
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
523
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
543
524
 
544
525
        for rev in info.real_revisions:
545
526
            self.assert_(repository.has_revision(rev.revision_id),
546
 
                'Missing revision {%s} after applying bundle'
 
527
                'Missing revision {%s} after applying bundle' 
547
528
                % rev.revision_id)
548
529
 
549
530
        self.assert_(to_tree.branch.repository.has_revision(info.target))
555
536
        rev = info.real_revisions[-1]
556
537
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
557
538
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
558
 
 
 
539
        
559
540
        # TODO: make sure the target tree is identical to base tree
560
541
        #       we might also check the working tree.
561
542
 
580
561
        self.tree1 = self.make_branch_and_tree('b1')
581
562
        self.b1 = self.tree1.branch
582
563
 
583
 
        self.build_tree_contents([('b1/one', 'one\n')])
584
 
        self.tree1.add('one', 'one-id')
585
 
        self.tree1.set_root_id('root-id')
 
564
        open('b1/one', 'wb').write('one\n')
 
565
        self.tree1.add('one')
586
566
        self.tree1.commit('add one', rev_id='a@cset-0-1')
587
567
 
588
568
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
599
579
                , 'b1/sub/sub/'
600
580
                , 'b1/sub/sub/nonempty.txt'
601
581
                ])
602
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
603
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
582
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
583
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
604
584
        tt = TreeTransform(self.tree1)
605
585
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
606
586
        tt.apply()
622
602
 
623
603
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
624
604
 
625
 
        # Check a rollup bundle
 
605
        # Check a rollup bundle 
626
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
627
607
 
628
608
        # Now delete entries
636
616
        tt.set_executability(False, trans_id)
637
617
        tt.apply()
638
618
        self.tree1.commit('removed', rev_id='a@cset-0-3')
639
 
 
 
619
        
640
620
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
641
 
        self.assertRaises((errors.TestamentMismatch,
642
 
            errors.VersionedFileInvalidChecksum,
643
 
            errors.BadBundle), self.get_invalid_bundle,
 
621
        self.assertRaises((TestamentMismatch,
 
622
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
644
623
            'a@cset-0-2', 'a@cset-0-3')
645
 
        # Check a rollup bundle
 
624
        # Check a rollup bundle 
646
625
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
647
626
 
648
627
        # Now move the directory
650
629
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
651
630
 
652
631
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
653
 
        # Check a rollup bundle
 
632
        # Check a rollup bundle 
654
633
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
655
634
 
656
635
        # Modified files
658
637
        open('b1/sub/dir/ pre space', 'ab').write(
659
638
             '\r\nAdding some\r\nDOS format lines\r\n')
660
639
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
661
 
        self.tree1.rename_one('sub/dir/ pre space',
 
640
        self.tree1.rename_one('sub/dir/ pre space', 
662
641
                              'sub/ start space')
663
642
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
664
643
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
670
649
                          verbose=False)
671
650
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
672
651
        other = self.get_checkout('a@cset-0-5')
673
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
674
 
                                       'a@cset-0-5')
675
 
        tree2_inv = get_inventory_text(other.branch.repository,
676
 
                                       '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')
677
655
        self.assertEqualDiff(tree1_inv, tree2_inv)
678
656
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
679
657
        other.commit('rename file', rev_id='a@cset-0-6b')
682
660
                          verbose=False)
683
661
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
684
662
 
685
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
686
 
        link_id = 'link-1'
687
 
 
688
 
        self.requireFeature(features.SymlinkFeature)
 
663
    def test_symlink_bundle(self):
 
664
        self.requireFeature(SymlinkFeature)
689
665
        self.tree1 = self.make_branch_and_tree('b1')
690
666
        self.b1 = self.tree1.branch
691
 
 
692
667
        tt = TreeTransform(self.tree1)
693
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
668
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
694
669
        tt.apply()
695
670
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
696
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
697
 
        if getattr(bundle ,'revision_tree', None) is not None:
698
 
            # Not all bundle formats supports revision_tree
699
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
700
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
701
 
 
 
671
        self.get_valid_bundle('null:', 'l@cset-0-1')
702
672
        tt = TreeTransform(self.tree1)
703
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
673
        trans_id = tt.trans_id_tree_file_id('link-1')
704
674
        tt.adjust_path('link2', tt.root, trans_id)
705
675
        tt.delete_contents(trans_id)
706
 
        tt.create_symlink(new_link_target, trans_id)
 
676
        tt.create_symlink('mars', trans_id)
707
677
        tt.apply()
708
678
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
709
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
 
        if getattr(bundle ,'revision_tree', None) is not None:
711
 
            # Not all bundle formats supports revision_tree
712
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
713
 
            self.assertEqual(new_link_target,
714
 
                             bund_tree.get_symlink_target(link_id))
715
 
 
 
679
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
716
680
        tt = TreeTransform(self.tree1)
717
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
681
        trans_id = tt.trans_id_tree_file_id('link-1')
718
682
        tt.delete_contents(trans_id)
719
683
        tt.create_symlink('jupiter', trans_id)
720
684
        tt.apply()
721
685
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
722
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
723
 
 
 
686
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
724
687
        tt = TreeTransform(self.tree1)
725
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
688
        trans_id = tt.trans_id_tree_file_id('link-1')
726
689
        tt.delete_contents(trans_id)
727
690
        tt.apply()
728
691
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
729
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
730
 
 
731
 
    def test_symlink_bundle(self):
732
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
733
 
 
734
 
    def test_unicode_symlink_bundle(self):
735
 
        self.requireFeature(features.UnicodeFilenameFeature)
736
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
737
 
                                  u'bar/\N{Euro Sign}foo',
738
 
                                  u'mars\N{Euro Sign}')
 
692
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
739
693
 
740
694
    def test_binary_bundle(self):
741
695
        self.tree1 = self.make_branch_and_tree('b1')
742
696
        self.b1 = self.tree1.branch
743
697
        tt = TreeTransform(self.tree1)
744
 
 
 
698
        
745
699
        # Add
746
700
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
747
701
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
839
793
        return bundle_file.getvalue()
840
794
 
841
795
    def test_unicode_bundle(self):
842
 
        self.requireFeature(features.UnicodeFilenameFeature)
843
796
        # Handle international characters
844
797
        os.mkdir('b1')
845
 
        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")
846
802
 
847
803
        self.tree1 = self.make_branch_and_tree('b1')
848
804
        self.b1 = self.tree1.branch
852
808
            u'William Dod\xe9\n').encode('utf-8'))
853
809
        f.close()
854
810
 
855
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
811
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
856
812
        self.tree1.commit(u'i18n commit from William Dod\xe9',
857
813
                          rev_id='i18n-1', committer=u'William Dod\xe9')
858
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
 
859
829
        # Add
860
830
        bundle = self.get_valid_bundle('null:', 'i18n-1')
861
831
 
862
832
        # Modified
863
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
833
        f = open(u'b1/with Dod\xe9', 'wb')
864
834
        f.write(u'Modified \xb5\n'.encode('utf8'))
865
835
        f.close()
866
836
        self.tree1.commit(u'modified', rev_id='i18n-2')
867
837
 
868
838
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
869
 
 
 
839
        
870
840
        # Renamed
871
 
        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')
872
842
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
873
843
                          committer=u'Erik B\xe5gfors')
874
844
 
875
845
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
876
846
 
877
847
        # Removed
878
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
848
        self.tree1.remove([u'B\xe5gfors'])
879
849
        self.tree1.commit(u'removed', rev_id='i18n-4')
880
850
 
881
851
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
886
856
 
887
857
    def test_whitespace_bundle(self):
888
858
        if sys.platform in ('win32', 'cygwin'):
889
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
890
 
                                    ' with tabs or trailing spaces')
 
859
            raise TestSkipped('Windows doesn\'t support filenames'
 
860
                              ' with tabs or trailing spaces')
891
861
        self.tree1 = self.make_branch_and_tree('b1')
892
862
        self.b1 = self.tree1.branch
893
863
 
918
888
        self.tree1.commit('removed', rev_id='white-4')
919
889
 
920
890
        bundle = self.get_valid_bundle('white-3', 'white-4')
921
 
 
 
891
        
922
892
        # Now test a complet roll-up
923
893
        bundle = self.get_valid_bundle('null:', 'white-4')
924
894
 
937
907
                          timezone=19800, timestamp=1152544886.0)
938
908
 
939
909
        bundle = self.get_valid_bundle('null:', 'tz-1')
940
 
 
 
910
        
941
911
        rev = bundle.revisions[0]
942
912
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
943
913
        self.assertEqual(19800, rev.timezone)
1045
1015
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1046
1016
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1047
1017
        bundle.install_revisions(repo)
1048
 
        inv_text = repo._get_inventory_xml('rev2')
 
1018
        inv_text = repo.get_inventory_xml('rev2')
1049
1019
        self.assertNotContainsRe(inv_text, 'format="5"')
1050
1020
        self.assertContainsRe(inv_text, 'format="7"')
1051
1021
 
1071
1041
 
1072
1042
    def test_inv_hash_across_serializers(self):
1073
1043
        repo = self.make_repo_with_installed_revisions()
1074
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1075
 
        xml = repo._get_inventory_xml('rev2')
1076
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
 
1044
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
 
1045
        xml = repo.get_inventory_xml('rev2')
 
1046
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
1077
1047
 
1078
1048
    def test_across_models_incompatible(self):
1079
1049
        tree = self.make_simple_tree('dirstate-with-subtree')
1082
1052
        try:
1083
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1084
1054
        except errors.IncompatibleBundleFormat:
1085
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1086
1056
        repo = self.make_repository('repo', format='knit')
1087
1057
        bundle.install_revisions(repo)
1088
1058
 
1109
1079
        try:
1110
1080
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1111
1081
        except errors.IncompatibleBundleFormat:
1112
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1082
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1113
1083
        if isinstance(bundle, v09.BundleInfo09):
1114
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1084
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1115
1085
        repo = self.make_repository('repo', format='knit')
1116
1086
        self.assertRaises(errors.IncompatibleRevision,
1117
1087
                          bundle.install_revisions, repo)
1124
1094
        try:
1125
1095
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1126
1096
        except ValueError:
1127
 
            raise tests.TestSkipped(
1128
 
                "Repository doesn't support revision ids with slashes")
 
1097
            raise TestSkipped("Repository doesn't support revision ids with"
 
1098
                              " slashes")
1129
1099
        bundle = self.get_valid_bundle('null:', 'rev/id')
1130
1100
 
1131
1101
    def test_skip_file(self):
1147
1117
        self.tree1.commit('rev3', rev_id='rev3')
1148
1118
        bundle = self.get_valid_bundle('reva', 'rev3')
1149
1119
        if getattr(bundle, 'get_bundle_reader', None) is None:
1150
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1120
            raise TestSkipped('Bundle format cannot provide reader')
1151
1121
        # be sure that file1 comes before file2
1152
1122
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1153
1123
            if f == 'file3-id':
1156
1126
        bundle.install_revisions(target.branch.repository)
1157
1127
 
1158
1128
 
1159
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1129
class V08BundleTester(BundleTester, TestCaseWithTransport):
1160
1130
 
1161
1131
    format = '0.8'
1162
1132
 
1295
1265
        return format
1296
1266
 
1297
1267
 
1298
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1268
class V4BundleTester(BundleTester, TestCaseWithTransport):
1299
1269
 
1300
1270
    format = '4'
1301
1271
 
1303
1273
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1304
1274
        Make sure that the text generated is valid, and that it
1305
1275
        can be applied against the base, and generate the same information.
1306
 
 
1307
 
        :return: The in-memory bundle
 
1276
        
 
1277
        :return: The in-memory bundle 
1308
1278
        """
1309
1279
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1310
1280
 
1311
 
        # This should also validate the generated bundle
 
1281
        # This should also validate the generated bundle 
1312
1282
        bundle = read_bundle(bundle_txt)
1313
1283
        repository = self.b1.repository
1314
1284
        for bundle_rev in bundle.real_revisions:
1318
1288
            # it
1319
1289
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1320
1290
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1321
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1291
                      'timestamp', 'timezone', 'message', 'committer', 
1322
1292
                      'parent_ids', 'properties'):
1323
 
                self.assertEqual(getattr(branch_rev, a),
 
1293
                self.assertEqual(getattr(branch_rev, a), 
1324
1294
                                 getattr(bundle_rev, a))
1325
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1295
            self.assertEqual(len(branch_rev.parent_ids), 
1326
1296
                             len(bundle_rev.parent_ids))
1327
1297
        self.assertEqual(set(rev_ids),
1328
1298
                         set([r.revision_id for r in bundle.real_revisions]))
1342
1312
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1343
1313
        new_text = new_text.replace('<file file_id="exe-1"',
1344
1314
                                    '<file executable="y" file_id="exe-1"')
1345
 
        new_text = new_text.replace('B260', 'B275')
 
1315
        new_text = new_text.replace('B222', 'B237')
1346
1316
        bundle_txt = StringIO()
1347
1317
        bundle_txt.write(serializer._get_bundle_header('4'))
1348
1318
        bundle_txt.write('\n')
1354
1324
 
1355
1325
    def create_bundle_text(self, base_rev_id, rev_id):
1356
1326
        bundle_txt = StringIO()
1357
 
        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, 
1358
1328
                               bundle_txt, format=self.format)
1359
1329
        bundle_txt.seek(0)
1360
 
        self.assertEqual(bundle_txt.readline(),
 
1330
        self.assertEqual(bundle_txt.readline(), 
1361
1331
                         '# Bazaar revision bundle v%s\n' % self.format)
1362
1332
        self.assertEqual(bundle_txt.readline(), '#\n')
1363
1333
        rev = self.b1.repository.get_revision(rev_id)
1385
1355
        install_bundle(target_repo, serializer.read(s))
1386
1356
        target_repo.lock_read()
1387
1357
        self.addCleanup(target_repo.unlock)
1388
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1389
 
        repo_texts = dict((i, ''.join(content)) for i, content
1390
 
                          in target_repo.iter_files_bytes(
1391
 
                                [('fileid-2', 'rev1', '1'),
1392
 
                                 ('fileid-2', 'rev2', '2')]))
1393
1358
        self.assertEqual({'1':'contents1\nstatic\n',
1394
 
                          '2':'contents2\nstatic\n'},
1395
 
                         repo_texts)
 
1359
            '2':'contents2\nstatic\n'},
 
1360
            dict(target_repo.iter_files_bytes(
 
1361
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
1396
1362
        rtree = target_repo.revision_tree('rev2')
1397
1363
        inventory_vf = target_repo.inventories
1398
1364
        # If the inventory store has a graph, it must match the revision graph.
1418
1384
        branch = tree_a.branch
1419
1385
        repo_a = branch.repository
1420
1386
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1421
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
1387
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
1422
1388
        try:
1423
1389
            from bzrlib.testament import Testament
1424
1390
            # monkey patch gpg signing mechanism
1448
1414
        install_bundle(repo_b, serializer.read(s))
1449
1415
 
1450
1416
 
1451
 
class V4_2aBundleTester(V4BundleTester):
 
1417
class V4WeaveBundleTester(V4BundleTester):
1452
1418
 
1453
1419
    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)
 
1420
        return 'metaweave'
1643
1421
 
1644
1422
 
1645
1423
class MungedBundleTester(object):
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.
1820
1598
            def look_up(self, name, url):
1821
1599
                return 'source'
1822
1600
        directories.register('foo:', FooService, 'Testing directory service')
1823
 
        self.addCleanup(directories.remove, 'foo:')
 
1601
        self.addCleanup(lambda: directories.remove('foo:'))
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
 
        self.start_server(server)
1834
 
        url = server.get_url() + 'infinite-loop'
1835
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1836
 
 
1837
 
    def test_smart_server_connection_reset(self):
1838
 
        """If a smart server connection fails during the attempt to read a
1839
 
        bundle, then the ConnectionReset error should be propagated.
1840
 
        """
1841
 
        # Instantiate a server that will provoke a ConnectionReset
1842
 
        sock_server = _DisconnectingTCPServer()
1843
 
        self.start_server(sock_server)
1844
 
        # We don't really care what the url is since the server will close the
1845
 
        # connection without interpreting it
1846
 
        url = sock_server.get_url()
1847
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1848
 
 
1849
 
 
1850
 
class _DisconnectingTCPServer(object):
1851
 
    """A TCP server that immediately closes any connection made to it."""
1852
 
 
1853
 
    def start_server(self):
1854
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1855
 
        self.sock.bind(('127.0.0.1', 0))
1856
 
        self.sock.listen(1)
1857
 
        self.port = self.sock.getsockname()[1]
1858
 
        self.thread = threading.Thread(
1859
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1860
 
            target=self.accept_and_close)
1861
 
        self.thread.start()
1862
 
 
1863
 
    def accept_and_close(self):
1864
 
        conn, addr = self.sock.accept()
1865
 
        conn.shutdown(socket.SHUT_RDWR)
1866
 
        conn.close()
1867
 
 
1868
 
    def get_url(self):
1869
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1870
 
 
1871
 
    def stop_server(self):
1872
 
        try:
1873
 
            # make sure the thread dies by connecting to the listening socket,
1874
 
            # just in case the test failed to do so.
1875
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1876
 
            conn.connect(self.sock.getsockname())
1877
 
            conn.close()
1878
 
        except socket.error:
1879
 
            pass
1880
 
        self.sock.close()
1881
 
        self.thread.join()