~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Ian Clatworthy
  • Date: 2007-08-13 14:16:53 UTC
  • mto: (2733.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 2734.
  • Revision ID: ian.clatworthy@internode.on.net-20070813141653-3cbrp00xowq58zv1
Added mini tutorial

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
 
20
import tempfile
22
21
 
23
22
from bzrlib import (
24
23
    bzrdir,
25
 
    diff,
26
24
    errors,
27
25
    inventory,
28
 
    merge,
29
 
    osutils,
30
26
    repository,
31
27
    revision as _mod_revision,
32
 
    tests,
33
28
    treebuilder,
34
29
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
 
30
from bzrlib.bzrdir import BzrDir
36
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
32
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
 
from bzrlib.directory_service import directories
40
33
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
36
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
37
from bzrlib.branch import Branch
 
38
from bzrlib.diff import internal_diff
 
39
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
40
                           NoSuchFile,)
 
41
from bzrlib.merge import Merge3Merger
45
42
from bzrlib.repofmt import knitrepo
46
 
from bzrlib.tests import (
47
 
    test_read_bundle,
48
 
    test_commit,
49
 
    )
 
43
from bzrlib.osutils import has_symlinks, sha_file
 
44
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
45
                          TestCase, TestSkipped, test_commit)
50
46
from bzrlib.transform import TreeTransform
 
47
from bzrlib.workingtree import WorkingTree
51
48
 
52
49
 
53
50
class MockTree(object):
101
98
        elif kind == 'symlink':
102
99
            ie = InventoryLink(file_id, name, parent_id)
103
100
        else:
104
 
            raise errors.BzrError('unknown kind %r' % kind)
 
101
            raise BzrError('unknown kind %r' % kind)
105
102
        ie.text_sha1 = text_sha_1
106
103
        ie.text_size = text_size
107
104
        return ie
109
106
    def add_dir(self, file_id, path):
110
107
        self.paths[file_id] = path
111
108
        self.ids[path] = file_id
112
 
 
 
109
    
113
110
    def add_file(self, file_id, path, contents):
114
111
        self.add_dir(file_id, path)
115
112
        self.contents[file_id] = contents
132
129
    def contents_stats(self, file_id):
133
130
        if file_id not in self.contents:
134
131
            return None, None
135
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
132
        text_sha1 = sha_file(self.get_file(file_id))
136
133
        return text_sha1, len(self.contents[file_id])
137
134
 
138
135
 
139
 
class BTreeTester(tests.TestCase):
 
136
class BTreeTester(TestCase):
140
137
    """A simple unittest tester for the BundleTree class."""
141
138
 
142
139
    def make_tree_1(self):
146
143
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
147
144
        mtree.add_dir("d", "grandparent/alt_parent")
148
145
        return BundleTree(mtree, ''), mtree
149
 
 
 
146
        
150
147
    def test_renames(self):
151
148
        """Ensure that file renames have the proper effect on children"""
152
149
        btree = self.make_tree_1()[0]
153
150
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
154
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
151
        self.assertEqual(btree.old_path("grandparent/parent"), 
155
152
                         "grandparent/parent")
156
153
        self.assertEqual(btree.old_path("grandparent/parent/file"),
157
154
                         "grandparent/parent/file")
164
161
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
165
162
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
166
163
 
167
 
        self.assertTrue(btree.path2id("grandparent2") is None)
168
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
169
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
164
        assert btree.path2id("grandparent2") is None
 
165
        assert btree.path2id("grandparent2/parent") is None
 
166
        assert btree.path2id("grandparent2/parent/file") is None
170
167
 
171
168
        btree.note_rename("grandparent", "grandparent2")
172
 
        self.assertTrue(btree.old_path("grandparent") is None)
173
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
174
 
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
 
169
        assert btree.old_path("grandparent") is None
 
170
        assert btree.old_path("grandparent/parent") is None
 
171
        assert btree.old_path("grandparent/parent/file") is None
175
172
 
176
173
        self.assertEqual(btree.id2path("a"), "grandparent2")
177
174
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
181
178
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
182
179
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
183
180
 
184
 
        self.assertTrue(btree.path2id("grandparent") is None)
185
 
        self.assertTrue(btree.path2id("grandparent/parent") is None)
186
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
181
        assert btree.path2id("grandparent") is None
 
182
        assert btree.path2id("grandparent/parent") is None
 
183
        assert btree.path2id("grandparent/parent/file") is None
187
184
 
188
185
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
189
186
        self.assertEqual(btree.id2path("a"), "grandparent2")
194
191
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
195
192
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
196
193
 
197
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
198
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
194
        assert btree.path2id("grandparent2/parent") is None
 
195
        assert btree.path2id("grandparent2/parent/file") is None
199
196
 
200
 
        btree.note_rename("grandparent/parent/file",
 
197
        btree.note_rename("grandparent/parent/file", 
201
198
                          "grandparent2/parent2/file2")
202
199
        self.assertEqual(btree.id2path("a"), "grandparent2")
203
200
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
207
204
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
208
205
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
209
206
 
210
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
207
        assert btree.path2id("grandparent2/parent2/file") is None
211
208
 
212
209
    def test_moves(self):
213
210
        """Ensure that file moves have the proper effect on children"""
214
211
        btree = self.make_tree_1()[0]
215
 
        btree.note_rename("grandparent/parent/file",
 
212
        btree.note_rename("grandparent/parent/file", 
216
213
                          "grandparent/alt_parent/file")
217
214
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
218
215
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
219
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
216
        assert btree.path2id("grandparent/parent/file") is None
220
217
 
221
218
    def unified_diff(self, old, new):
222
219
        out = StringIO()
223
 
        diff.internal_diff("old", old, "new", new, out)
 
220
        internal_diff("old", old, "new", new, out)
224
221
        out.seek(0,0)
225
222
        return out.read()
226
223
 
227
224
    def make_tree_2(self):
228
225
        btree = self.make_tree_1()[0]
229
 
        btree.note_rename("grandparent/parent/file",
 
226
        btree.note_rename("grandparent/parent/file", 
230
227
                          "grandparent/alt_parent/file")
231
 
        self.assertTrue(btree.id2path("e") is None)
232
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
228
        assert btree.id2path("e") is None
 
229
        assert btree.path2id("grandparent/parent/file") is None
233
230
        btree.note_id("e", "grandparent/parent/file")
234
231
        return btree
235
232
 
261
258
    def make_tree_3(self):
262
259
        btree, mtree = self.make_tree_1()
263
260
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
264
 
        btree.note_rename("grandparent/parent/file",
 
261
        btree.note_rename("grandparent/parent/file", 
265
262
                          "grandparent/alt_parent/file")
266
 
        btree.note_rename("grandparent/parent/topping",
 
263
        btree.note_rename("grandparent/parent/topping", 
267
264
                          "grandparent/alt_parent/stopping")
268
265
        return btree
269
266
 
293
290
        btree = self.make_tree_1()[0]
294
291
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
295
292
        btree.note_deletion("grandparent/parent/file")
296
 
        self.assertTrue(btree.id2path("c") is None)
297
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
293
        assert btree.id2path("c") is None
 
294
        assert btree.path2id("grandparent/parent/file") is None
298
295
 
299
296
    def sorted_ids(self, tree):
300
297
        ids = list(tree)
308
305
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
309
306
        btree.note_deletion("grandparent/parent/file")
310
307
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
311
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
308
        btree.note_last_changed("grandparent/alt_parent/fool", 
312
309
                                "revisionidiguess")
313
310
        self.assertEqual(self.sorted_ids(btree),
314
311
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
315
312
 
316
313
 
317
 
class BundleTester1(tests.TestCaseWithTransport):
 
314
class BundleTester1(TestCaseWithTransport):
318
315
 
319
316
    def test_mismatched_bundle(self):
320
317
        format = bzrdir.BzrDirMetaFormat1()
321
318
        format.repository_format = knitrepo.RepositoryFormatKnit3()
322
319
        serializer = BundleSerializerV08('0.8')
323
320
        b = self.make_branch('.', format=format)
324
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
321
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
325
322
                          b.repository, [], {}, StringIO())
326
323
 
327
324
    def test_matched_bundle(self):
347
344
        format = bzrdir.BzrDirMetaFormat1()
348
345
        format.repository_format = knitrepo.RepositoryFormatKnit1()
349
346
        target = self.make_branch('target', format=format)
350
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
347
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
351
348
                          target.repository, read_bundle(text))
352
349
 
353
350
 
361
358
    def make_branch_and_tree(self, path, format=None):
362
359
        if format is None:
363
360
            format = self.bzrdir_format()
364
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
365
 
            self, path, format)
 
361
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
366
362
 
367
363
    def make_branch(self, path, format=None):
368
364
        if format is None:
369
365
            format = self.bzrdir_format()
370
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
366
        return TestCaseWithTransport.make_branch(self, path, format)
371
367
 
372
368
    def create_bundle_text(self, base_rev_id, rev_id):
373
369
        bundle_txt = StringIO()
374
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
370
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
375
371
                               bundle_txt, format=self.format)
376
372
        bundle_txt.seek(0)
377
 
        self.assertEqual(bundle_txt.readline(),
 
373
        self.assertEqual(bundle_txt.readline(), 
378
374
                         '# Bazaar revision bundle v%s\n' % self.format)
379
375
        self.assertEqual(bundle_txt.readline(), '#\n')
380
376
 
388
384
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
389
385
        Make sure that the text generated is valid, and that it
390
386
        can be applied against the base, and generate the same information.
391
 
 
392
 
        :return: The in-memory bundle
 
387
        
 
388
        :return: The in-memory bundle 
393
389
        """
394
390
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
395
391
 
396
 
        # This should also validate the generated bundle
 
392
        # This should also validate the generated bundle 
397
393
        bundle = read_bundle(bundle_txt)
398
394
        repository = self.b1.repository
399
395
        for bundle_rev in bundle.real_revisions:
403
399
            # it
404
400
            branch_rev = repository.get_revision(bundle_rev.revision_id)
405
401
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
406
 
                      'timestamp', 'timezone', 'message', 'committer',
 
402
                      'timestamp', 'timezone', 'message', 'committer', 
407
403
                      'parent_ids', 'properties'):
408
 
                self.assertEqual(getattr(branch_rev, a),
 
404
                self.assertEqual(getattr(branch_rev, a), 
409
405
                                 getattr(bundle_rev, a))
410
 
            self.assertEqual(len(branch_rev.parent_ids),
 
406
            self.assertEqual(len(branch_rev.parent_ids), 
411
407
                             len(bundle_rev.parent_ids))
412
 
        self.assertEqual(rev_ids,
 
408
        self.assertEqual(rev_ids, 
413
409
                         [r.revision_id for r in bundle.real_revisions])
414
410
        self.valid_apply_bundle(base_rev_id, bundle,
415
411
                                   checkout_dir=checkout_dir)
419
415
    def get_invalid_bundle(self, base_rev_id, rev_id):
420
416
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
421
417
        Munge the text so that it's invalid.
422
 
 
 
418
        
423
419
        :return: The in-memory bundle
424
420
        """
425
421
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
426
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
422
        new_text = bundle_txt.getvalue().replace('executable:no', 
427
423
                                               'executable:yes')
428
424
        bundle_txt = StringIO(new_text)
429
425
        bundle = read_bundle(bundle_txt)
430
426
        self.valid_apply_bundle(base_rev_id, bundle)
431
 
        return bundle
 
427
        return bundle 
432
428
 
433
429
    def test_non_bundle(self):
434
 
        self.assertRaises(errors.NotABundle,
435
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
430
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
436
431
 
437
432
    def test_malformed(self):
438
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
433
        self.assertRaises(BadBundle, read_bundle, 
439
434
                          StringIO('# Bazaar revision bundle v'))
440
435
 
441
436
    def test_crlf_bundle(self):
442
437
        try:
443
438
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
444
 
        except errors.BadBundle:
 
439
        except BadBundle:
445
440
            # It is currently permitted for bundles with crlf line endings to
446
441
            # make read_bundle raise a BadBundle, but this should be fixed.
447
442
            # Anything else, especially NotABundle, is an error.
452
447
        """
453
448
 
454
449
        if checkout_dir is None:
455
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
450
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
456
451
        else:
457
452
            if not os.path.exists(checkout_dir):
458
453
                os.mkdir(checkout_dir)
461
456
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
462
457
                                 format=self.format)
463
458
        s.seek(0)
464
 
        self.assertIsInstance(s.getvalue(), str)
 
459
        assert isinstance(s.getvalue(), str), (
 
460
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
465
461
        install_bundle(tree.branch.repository, read_bundle(s))
466
462
        for ancestor in ancestors:
467
463
            old = self.b1.repository.revision_tree(ancestor)
468
464
            new = tree.branch.repository.revision_tree(ancestor)
469
 
            old.lock_read()
470
 
            new.lock_read()
471
 
            try:
472
 
                # Check that there aren't any inventory level changes
473
 
                delta = new.changes_from(old)
474
 
                self.assertFalse(delta.has_changed(),
475
 
                                 'Revision %s not copied correctly.'
476
 
                                 % (ancestor,))
477
 
 
478
 
                # Now check that the file contents are all correct
479
 
                for inventory_id in old:
480
 
                    try:
481
 
                        old_file = old.get_file(inventory_id)
482
 
                    except errors.NoSuchFile:
483
 
                        continue
484
 
                    if old_file is None:
485
 
                        continue
486
 
                    self.assertEqual(old_file.read(),
487
 
                                     new.get_file(inventory_id).read())
488
 
            finally:
489
 
                new.unlock()
490
 
                old.unlock()
 
465
 
 
466
            # Check that there aren't any inventory level changes
 
467
            delta = new.changes_from(old)
 
468
            self.assertFalse(delta.has_changed(),
 
469
                             'Revision %s not copied correctly.'
 
470
                             % (ancestor,))
 
471
 
 
472
            # Now check that the file contents are all correct
 
473
            for inventory_id in old:
 
474
                try:
 
475
                    old_file = old.get_file(inventory_id)
 
476
                except NoSuchFile:
 
477
                    continue
 
478
                if old_file is None:
 
479
                    continue
 
480
                self.assertEqual(old_file.read(),
 
481
                                 new.get_file(inventory_id).read())
491
482
        if not _mod_revision.is_null(rev_id):
492
483
            rh = self.b1.revision_history()
493
484
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
502
493
        sure everything matches the builtin branch.
503
494
        """
504
495
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
505
 
        to_tree.lock_write()
506
 
        try:
507
 
            self._valid_apply_bundle(base_rev_id, info, to_tree)
508
 
        finally:
509
 
            to_tree.unlock()
510
 
 
511
 
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
512
496
        original_parents = to_tree.get_parent_ids()
513
497
        repository = to_tree.branch.repository
514
498
        original_parents = to_tree.get_parent_ids()
515
499
        self.assertIs(repository.has_revision(base_rev_id), True)
516
500
        for rev in info.real_revisions:
517
501
            self.assert_(not repository.has_revision(rev.revision_id),
518
 
                'Revision {%s} present before applying bundle'
 
502
                'Revision {%s} present before applying bundle' 
519
503
                % rev.revision_id)
520
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
504
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
521
505
 
522
506
        for rev in info.real_revisions:
523
507
            self.assert_(repository.has_revision(rev.revision_id),
524
 
                'Missing revision {%s} after applying bundle'
 
508
                'Missing revision {%s} after applying bundle' 
525
509
                % rev.revision_id)
526
510
 
527
511
        self.assert_(to_tree.branch.repository.has_revision(info.target))
533
517
        rev = info.real_revisions[-1]
534
518
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
535
519
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
536
 
 
 
520
        
537
521
        # TODO: make sure the target tree is identical to base tree
538
522
        #       we might also check the working tree.
539
523
 
599
583
 
600
584
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
601
585
 
602
 
        # Check a rollup bundle
 
586
        # Check a rollup bundle 
603
587
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
604
588
 
605
589
        # Now delete entries
613
597
        tt.set_executability(False, trans_id)
614
598
        tt.apply()
615
599
        self.tree1.commit('removed', rev_id='a@cset-0-3')
616
 
 
 
600
        
617
601
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
618
 
        self.assertRaises((errors.TestamentMismatch,
 
602
        self.assertRaises((TestamentMismatch,
619
603
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
620
604
            'a@cset-0-2', 'a@cset-0-3')
621
 
        # Check a rollup bundle
 
605
        # Check a rollup bundle 
622
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
623
607
 
624
608
        # Now move the directory
626
610
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
627
611
 
628
612
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
629
 
        # Check a rollup bundle
 
613
        # Check a rollup bundle 
630
614
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
631
615
 
632
616
        # Modified files
634
618
        open('b1/sub/dir/ pre space', 'ab').write(
635
619
             '\r\nAdding some\r\nDOS format lines\r\n')
636
620
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
637
 
        self.tree1.rename_one('sub/dir/ pre space',
 
621
        self.tree1.rename_one('sub/dir/ pre space', 
638
622
                              'sub/ start space')
639
623
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
640
624
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
657
641
                          verbose=False)
658
642
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
659
643
 
660
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
661
 
        link_id = 'link-1'
662
 
 
663
 
        self.requireFeature(tests.SymlinkFeature)
 
644
    def test_symlink_bundle(self):
 
645
        if not has_symlinks():
 
646
            raise TestSkipped("No symlink support")
664
647
        self.tree1 = self.make_branch_and_tree('b1')
665
648
        self.b1 = self.tree1.branch
666
 
 
667
649
        tt = TreeTransform(self.tree1)
668
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
650
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
669
651
        tt.apply()
670
652
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
671
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
672
 
        if getattr(bundle ,'revision_tree', None) is not None:
673
 
            # Not all bundle formats supports revision_tree
674
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
675
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
676
 
 
 
653
        self.get_valid_bundle('null:', 'l@cset-0-1')
677
654
        tt = TreeTransform(self.tree1)
678
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
655
        trans_id = tt.trans_id_tree_file_id('link-1')
679
656
        tt.adjust_path('link2', tt.root, trans_id)
680
657
        tt.delete_contents(trans_id)
681
 
        tt.create_symlink(new_link_target, trans_id)
 
658
        tt.create_symlink('mars', trans_id)
682
659
        tt.apply()
683
660
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
684
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
685
 
        if getattr(bundle ,'revision_tree', None) is not None:
686
 
            # Not all bundle formats supports revision_tree
687
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
688
 
            self.assertEqual(new_link_target,
689
 
                             bund_tree.get_symlink_target(link_id))
690
 
 
 
661
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
691
662
        tt = TreeTransform(self.tree1)
692
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
663
        trans_id = tt.trans_id_tree_file_id('link-1')
693
664
        tt.delete_contents(trans_id)
694
665
        tt.create_symlink('jupiter', trans_id)
695
666
        tt.apply()
696
667
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
697
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
698
 
 
 
668
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
699
669
        tt = TreeTransform(self.tree1)
700
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
670
        trans_id = tt.trans_id_tree_file_id('link-1')
701
671
        tt.delete_contents(trans_id)
702
672
        tt.apply()
703
673
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
704
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
705
 
 
706
 
    def test_symlink_bundle(self):
707
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
708
 
 
709
 
    def test_unicode_symlink_bundle(self):
710
 
        self.requireFeature(tests.UnicodeFilenameFeature)
711
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
712
 
                                  u'bar/\N{Euro Sign}foo',
713
 
                                  u'mars\N{Euro Sign}')
 
674
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
714
675
 
715
676
    def test_binary_bundle(self):
716
677
        self.tree1 = self.make_branch_and_tree('b1')
717
678
        self.b1 = self.tree1.branch
718
679
        tt = TreeTransform(self.tree1)
719
 
 
 
680
        
720
681
        # Add
721
682
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
722
683
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
814
775
        return bundle_file.getvalue()
815
776
 
816
777
    def test_unicode_bundle(self):
817
 
        self.requireFeature(tests.UnicodeFilenameFeature)
818
778
        # Handle international characters
819
779
        os.mkdir('b1')
820
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
780
        try:
 
781
            f = open(u'b1/with Dod\xe9', 'wb')
 
782
        except UnicodeEncodeError:
 
783
            raise TestSkipped("Filesystem doesn't support unicode")
821
784
 
822
785
        self.tree1 = self.make_branch_and_tree('b1')
823
786
        self.b1 = self.tree1.branch
827
790
            u'William Dod\xe9\n').encode('utf-8'))
828
791
        f.close()
829
792
 
830
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
793
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
831
794
        self.tree1.commit(u'i18n commit from William Dod\xe9',
832
795
                          rev_id='i18n-1', committer=u'William Dod\xe9')
833
796
 
 
797
        if sys.platform == 'darwin':
 
798
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
799
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
800
                             sorted(os.listdir(u'b1')))
 
801
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
802
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
803
                             delta.removed)
 
804
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
805
                              " combining characters.")
 
806
 
834
807
        # Add
835
808
        bundle = self.get_valid_bundle('null:', 'i18n-1')
836
809
 
837
810
        # Modified
838
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
811
        f = open(u'b1/with Dod\xe9', 'wb')
839
812
        f.write(u'Modified \xb5\n'.encode('utf8'))
840
813
        f.close()
841
814
        self.tree1.commit(u'modified', rev_id='i18n-2')
842
815
 
843
816
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
844
 
 
 
817
        
845
818
        # Renamed
846
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
819
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
847
820
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
848
821
                          committer=u'Erik B\xe5gfors')
849
822
 
850
823
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
851
824
 
852
825
        # Removed
853
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
826
        self.tree1.remove([u'B\xe5gfors'])
854
827
        self.tree1.commit(u'removed', rev_id='i18n-4')
855
828
 
856
829
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
861
834
 
862
835
    def test_whitespace_bundle(self):
863
836
        if sys.platform in ('win32', 'cygwin'):
864
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
865
 
                                    ' with tabs or trailing spaces')
 
837
            raise TestSkipped('Windows doesn\'t support filenames'
 
838
                              ' with tabs or trailing spaces')
866
839
        self.tree1 = self.make_branch_and_tree('b1')
867
840
        self.b1 = self.tree1.branch
868
841
 
893
866
        self.tree1.commit('removed', rev_id='white-4')
894
867
 
895
868
        bundle = self.get_valid_bundle('white-3', 'white-4')
896
 
 
 
869
        
897
870
        # Now test a complet roll-up
898
871
        bundle = self.get_valid_bundle('null:', 'white-4')
899
872
 
912
885
                          timezone=19800, timestamp=1152544886.0)
913
886
 
914
887
        bundle = self.get_valid_bundle('null:', 'tz-1')
915
 
 
 
888
        
916
889
        rev = bundle.revisions[0]
917
890
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
918
891
        self.assertEqual(19800, rev.timezone)
1024
997
        self.assertNotContainsRe(inv_text, 'format="5"')
1025
998
        self.assertContainsRe(inv_text, 'format="7"')
1026
999
 
1027
 
    def make_repo_with_installed_revisions(self):
1028
 
        tree = self.make_simple_tree('knit')
1029
 
        tree.commit('hello', rev_id='rev1')
1030
 
        tree.commit('hello', rev_id='rev2')
1031
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1032
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1033
 
        bundle.install_revisions(repo)
1034
 
        return repo
1035
 
 
1036
1000
    def test_across_models(self):
1037
 
        repo = self.make_repo_with_installed_revisions()
 
1001
        tree = self.make_simple_tree('knit')
 
1002
        tree.commit('hello', rev_id='rev1')
 
1003
        tree.commit('hello', rev_id='rev2')
 
1004
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1005
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1006
        bundle.install_revisions(repo)
1038
1007
        inv = repo.get_inventory('rev2')
1039
1008
        self.assertEqual('rev2', inv.root.revision)
1040
 
        root_id = inv.root.file_id
1041
 
        repo.lock_read()
1042
 
        self.addCleanup(repo.unlock)
1043
 
        self.assertEqual({(root_id, 'rev1'):(),
1044
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1045
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1046
 
 
1047
 
    def test_inv_hash_across_serializers(self):
1048
 
        repo = self.make_repo_with_installed_revisions()
1049
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1050
 
        xml = repo.get_inventory_xml('rev2')
1051
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
 
1009
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1010
                                             repo.get_transaction())
 
1011
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1052
1012
 
1053
1013
    def test_across_models_incompatible(self):
1054
1014
        tree = self.make_simple_tree('dirstate-with-subtree')
1057
1017
        try:
1058
1018
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1059
1019
        except errors.IncompatibleBundleFormat:
1060
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1020
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1061
1021
        repo = self.make_repository('repo', format='knit')
1062
1022
        bundle.install_revisions(repo)
1063
1023
 
1084
1044
        try:
1085
1045
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1086
1046
        except errors.IncompatibleBundleFormat:
1087
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1047
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1088
1048
        if isinstance(bundle, v09.BundleInfo09):
1089
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1049
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1090
1050
        repo = self.make_repository('repo', format='knit')
1091
1051
        self.assertRaises(errors.IncompatibleRevision,
1092
1052
                          bundle.install_revisions, repo)
1099
1059
        try:
1100
1060
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1101
1061
        except ValueError:
1102
 
            raise tests.TestSkipped(
1103
 
                "Repository doesn't support revision ids with slashes")
 
1062
            raise TestSkipped("Repository doesn't support revision ids with"
 
1063
                              " slashes")
1104
1064
        bundle = self.get_valid_bundle('null:', 'rev/id')
1105
1065
 
1106
 
    def test_skip_file(self):
1107
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1108
 
        self.tree1 = self.make_branch_and_tree('tree')
1109
 
        self.b1 = self.tree1.branch
1110
 
        # rev1 is not present in bundle, done by fetch
1111
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1112
 
        self.tree1.add('file2', 'file2-id')
1113
 
        self.tree1.commit('rev1', rev_id='reva')
1114
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1115
 
        # rev2 is present in bundle, and done by fetch
1116
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1117
 
        self.tree1.add('file3', 'file3-id')
1118
 
        self.tree1.commit('rev2')
1119
 
        # Updating file2 should not cause an attempt to add to file1's vf
1120
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1121
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1122
 
        self.tree1.commit('rev3', rev_id='rev3')
1123
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1124
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1125
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
1126
 
        # be sure that file1 comes before file2
1127
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1128
 
            if f == 'file3-id':
1129
 
                break
1130
 
            self.assertNotEqual(f, 'file2-id')
1131
 
        bundle.install_revisions(target.branch.repository)
1132
 
 
1133
 
 
1134
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1066
 
 
1067
class V08BundleTester(BundleTester, TestCaseWithTransport):
1135
1068
 
1136
1069
    format = '0.8'
1137
1070
 
1270
1203
        return format
1271
1204
 
1272
1205
 
1273
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1206
class V4BundleTester(BundleTester, TestCaseWithTransport):
1274
1207
 
1275
1208
    format = '4'
1276
1209
 
1278
1211
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1279
1212
        Make sure that the text generated is valid, and that it
1280
1213
        can be applied against the base, and generate the same information.
1281
 
 
1282
 
        :return: The in-memory bundle
 
1214
        
 
1215
        :return: The in-memory bundle 
1283
1216
        """
1284
1217
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1285
1218
 
1286
 
        # This should also validate the generated bundle
 
1219
        # This should also validate the generated bundle 
1287
1220
        bundle = read_bundle(bundle_txt)
1288
1221
        repository = self.b1.repository
1289
1222
        for bundle_rev in bundle.real_revisions:
1293
1226
            # it
1294
1227
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1295
1228
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1296
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1229
                      'timestamp', 'timezone', 'message', 'committer', 
1297
1230
                      'parent_ids', 'properties'):
1298
 
                self.assertEqual(getattr(branch_rev, a),
 
1231
                self.assertEqual(getattr(branch_rev, a), 
1299
1232
                                 getattr(bundle_rev, a))
1300
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1233
            self.assertEqual(len(branch_rev.parent_ids), 
1301
1234
                             len(bundle_rev.parent_ids))
1302
1235
        self.assertEqual(set(rev_ids),
1303
1236
                         set([r.revision_id for r in bundle.real_revisions]))
1317
1250
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1318
1251
        new_text = new_text.replace('<file file_id="exe-1"',
1319
1252
                                    '<file executable="y" file_id="exe-1"')
1320
 
        new_text = new_text.replace('B222', 'B237')
 
1253
        new_text = new_text.replace('B372', 'B387')
1321
1254
        bundle_txt = StringIO()
1322
1255
        bundle_txt.write(serializer._get_bundle_header('4'))
1323
1256
        bundle_txt.write('\n')
1329
1262
 
1330
1263
    def create_bundle_text(self, base_rev_id, rev_id):
1331
1264
        bundle_txt = StringIO()
1332
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1265
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1333
1266
                               bundle_txt, format=self.format)
1334
1267
        bundle_txt.seek(0)
1335
 
        self.assertEqual(bundle_txt.readline(),
 
1268
        self.assertEqual(bundle_txt.readline(), 
1336
1269
                         '# Bazaar revision bundle v%s\n' % self.format)
1337
1270
        self.assertEqual(bundle_txt.readline(), '#\n')
1338
1271
        rev = self.b1.repository.get_revision(rev_id)
1358
1291
        tree2 = self.make_branch_and_tree('target')
1359
1292
        target_repo = tree2.branch.repository
1360
1293
        install_bundle(target_repo, serializer.read(s))
1361
 
        target_repo.lock_read()
1362
 
        self.addCleanup(target_repo.unlock)
1363
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1364
 
        repo_texts = dict((i, ''.join(content)) for i, content
1365
 
                          in target_repo.iter_files_bytes(
1366
 
                                [('fileid-2', 'rev1', '1'),
1367
 
                                 ('fileid-2', 'rev2', '2')]))
1368
 
        self.assertEqual({'1':'contents1\nstatic\n',
1369
 
                          '2':'contents2\nstatic\n'},
1370
 
                         repo_texts)
 
1294
        vf = target_repo.weave_store.get_weave('fileid-2',
 
1295
            target_repo.get_transaction())
 
1296
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
 
1297
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
1371
1298
        rtree = target_repo.revision_tree('rev2')
1372
 
        inventory_vf = target_repo.inventories
1373
 
        # If the inventory store has a graph, it must match the revision graph.
1374
 
        self.assertSubset(
1375
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1376
 
            [None, (('rev1',),)])
 
1299
        inventory_vf = target_repo.get_inventory_weave()
 
1300
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1377
1301
        self.assertEqual('changed file',
1378
1302
                         target_repo.get_revision('rev2').message)
1379
1303
 
1481
1405
        self.check_valid(bundle)
1482
1406
 
1483
1407
 
1484
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1408
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1485
1409
 
1486
1410
    format = '0.9'
1487
1411
 
1519
1443
        self.check_valid(bundle)
1520
1444
 
1521
1445
 
1522
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1446
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1523
1447
 
1524
1448
    format = '4'
1525
1449
 
1526
1450
 
1527
 
class TestBundleWriterReader(tests.TestCase):
 
1451
class TestBundleWriterReader(TestCase):
1528
1452
 
1529
1453
    def test_roundtrip_record(self):
1530
1454
        fileobj = StringIO()
1535
1459
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1536
1460
        writer.end()
1537
1461
        fileobj.seek(0)
1538
 
        reader = v4.BundleReader(fileobj, stream_input=True)
1539
 
        record_iter = reader.iter_records()
1540
 
        record = record_iter.next()
1541
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1542
 
            'info', None, None), record)
1543
 
        record = record_iter.next()
1544
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1545
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1546
 
                          record)
1547
 
 
1548
 
    def test_roundtrip_record_memory_hungry(self):
1549
 
        fileobj = StringIO()
1550
 
        writer = v4.BundleWriter(fileobj)
1551
 
        writer.begin()
1552
 
        writer.add_info_record(foo='bar')
1553
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1554
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1555
 
        writer.end()
1556
 
        fileobj.seek(0)
1557
 
        reader = v4.BundleReader(fileobj, stream_input=False)
1558
 
        record_iter = reader.iter_records()
 
1462
        record_iter = v4.BundleReader(fileobj).iter_records()
1559
1463
        record = record_iter.next()
1560
1464
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1561
1465
            'info', None, None), record)
1592
1496
        record = record_iter.next()
1593
1497
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1594
1498
            'info', None, None), record)
1595
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1596
 
 
1597
 
 
1598
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1599
 
 
1600
 
    def test_read_mergeable_skips_local(self):
1601
 
        """A local bundle named like the URL should not be read.
1602
 
        """
1603
 
        out, wt = test_read_bundle.create_bundle_file(self)
1604
 
        class FooService(object):
1605
 
            """A directory service that always returns source"""
1606
 
 
1607
 
            def look_up(self, name, url):
1608
 
                return 'source'
1609
 
        directories.register('foo:', FooService, 'Testing directory service')
1610
 
        self.addCleanup(lambda: directories.remove('foo:'))
1611
 
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1612
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1613
 
                          'foo:bar')
1614
 
 
1615
 
    def test_infinite_redirects_are_not_a_bundle(self):
1616
 
        """If a URL causes TooManyRedirections then NotABundle is raised.
1617
 
        """
1618
 
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1619
 
        server = RedirectingMemoryServer()
1620
 
        server.setUp()
1621
 
        url = server.get_url() + 'infinite-loop'
1622
 
        self.addCleanup(server.tearDown)
1623
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1624
 
 
1625
 
    def test_smart_server_connection_reset(self):
1626
 
        """If a smart server connection fails during the attempt to read a
1627
 
        bundle, then the ConnectionReset error should be propagated.
1628
 
        """
1629
 
        # Instantiate a server that will provoke a ConnectionReset
1630
 
        sock_server = _DisconnectingTCPServer()
1631
 
        sock_server.setUp()
1632
 
        self.addCleanup(sock_server.tearDown)
1633
 
        # We don't really care what the url is since the server will close the
1634
 
        # connection without interpreting it
1635
 
        url = sock_server.get_url()
1636
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1637
 
 
1638
 
 
1639
 
class _DisconnectingTCPServer(object):
1640
 
    """A TCP server that immediately closes any connection made to it."""
1641
 
 
1642
 
    def setUp(self):
1643
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1644
 
        self.sock.bind(('127.0.0.1', 0))
1645
 
        self.sock.listen(1)
1646
 
        self.port = self.sock.getsockname()[1]
1647
 
        self.thread = threading.Thread(
1648
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1649
 
            target=self.accept_and_close)
1650
 
        self.thread.start()
1651
 
 
1652
 
    def accept_and_close(self):
1653
 
        conn, addr = self.sock.accept()
1654
 
        conn.shutdown(socket.SHUT_RDWR)
1655
 
        conn.close()
1656
 
 
1657
 
    def get_url(self):
1658
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1659
 
 
1660
 
    def tearDown(self):
1661
 
        try:
1662
 
            # make sure the thread dies by connecting to the listening socket,
1663
 
            # just in case the test failed to do so.
1664
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1665
 
            conn.connect(self.sock.getsockname())
1666
 
            conn.close()
1667
 
        except socket.error:
1668
 
            pass
1669
 
        self.sock.close()
1670
 
        self.thread.join()
1671
 
 
 
1499
        self.assertRaises(BadBundle, record_iter.next)