/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

merge bzr.dev.

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