~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

merge 2.0 branch rev 4647

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
    config,
32
32
    errors,
33
33
    graph,
 
34
    inventory,
 
35
    inventory_delta,
34
36
    pack,
35
37
    remote,
36
38
    repository,
38
40
    tests,
39
41
    treebuilder,
40
42
    urlutils,
 
43
    versionedfile,
41
44
    )
42
45
from bzrlib.branch import Branch
43
46
from bzrlib.bzrdir import BzrDir, BzrDirFormat
58
61
    condition_isinstance,
59
62
    split_suite_by_condition,
60
63
    multiply_tests,
 
64
    KnownFailure,
61
65
    )
62
66
from bzrlib.transport import get_transport, http
63
67
from bzrlib.transport.memory import MemoryTransport
153
157
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
154
158
        self.assertTrue(r._format.supports_external_lookups)
155
159
 
 
160
    def test_remote_branch_set_append_revisions_only(self):
 
161
        # Make a format 1.9 branch, which supports append_revisions_only
 
162
        branch = self.make_branch('branch', format='1.9')
 
163
        config = branch.get_config()
 
164
        branch.set_append_revisions_only(True)
 
165
        self.assertEqual(
 
166
            'True', config.get_user_option('append_revisions_only'))
 
167
        branch.set_append_revisions_only(False)
 
168
        self.assertEqual(
 
169
            'False', config.get_user_option('append_revisions_only'))
 
170
 
 
171
    def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
 
172
        branch = self.make_branch('branch', format='knit')
 
173
        config = branch.get_config()
 
174
        self.assertRaises(
 
175
            errors.UpgradeRequired, branch.set_append_revisions_only, True)
 
176
 
156
177
 
157
178
class FakeProtocol(object):
158
179
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
314
335
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
315
336
        return reference_bzrdir_format.repository_format
316
337
 
317
 
    def disable_verb(self, verb):
318
 
        """Disable a verb for one test."""
319
 
        request_handlers = smart.request.request_handlers
320
 
        orig_method = request_handlers.get(verb)
321
 
        request_handlers.remove(verb)
322
 
        def restoreVerb():
323
 
            request_handlers.register(verb, orig_method)
324
 
        self.addCleanup(restoreVerb)
 
338
    def assertFinished(self, fake_client):
 
339
        """Assert that all of a FakeClient's expected calls have occurred."""
 
340
        fake_client.finished_test()
325
341
 
326
342
 
327
343
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
428
444
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
429
445
        self.assertEqual(expected._repository_format, result._repository_format)
430
446
        self.assertEqual(expected._branch_format, result._branch_format)
431
 
        client.finished_test()
 
447
        self.assertFinished(client)
432
448
 
433
449
    def test_current_server(self):
434
450
        transport = self.get_transport('.')
449
465
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
450
466
        self.assertEqual(None, result._repository_format)
451
467
        self.assertEqual(None, result._branch_format)
452
 
        client.finished_test()
 
468
        self.assertFinished(client)
453
469
 
454
470
 
455
471
class TestBzrDirOpenBranch(TestRemote):
488
504
        result = bzrdir.open_branch()
489
505
        self.assertIsInstance(result, RemoteBranch)
490
506
        self.assertEqual(bzrdir, result.bzrdir)
491
 
        client.finished_test()
 
507
        self.assertFinished(client)
492
508
 
493
509
    def test_branch_missing(self):
494
510
        transport = MemoryTransport()
541
557
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
542
558
            _client=client)
543
559
        result = bzrdir.open_branch()
544
 
        client.finished_test()
 
560
        self.assertFinished(client)
545
561
 
546
562
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
547
563
        reference_format = self.get_repo_format()
645
661
        network_name = reference_format.network_name()
646
662
        client.add_expected_call(
647
663
            'BzrDir.create_repository', ('quack/',
648
 
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
649
 
            'success', ('ok', 'no', 'no', 'no', network_name))
 
664
                'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
665
                'False'),
 
666
            'success', ('ok', 'yes', 'yes', 'yes', network_name))
650
667
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
651
668
            _client=client)
652
669
        repo = a_bzrdir.create_repository()
654
671
        self.assertIsInstance(repo, remote.RemoteRepository)
655
672
        # its format should have the settings from the response
656
673
        format = repo._format
657
 
        self.assertFalse(format.rich_root_data)
658
 
        self.assertFalse(format.supports_tree_reference)
659
 
        self.assertFalse(format.supports_external_lookups)
 
674
        self.assertTrue(format.rich_root_data)
 
675
        self.assertTrue(format.supports_tree_reference)
 
676
        self.assertTrue(format.supports_external_lookups)
660
677
        self.assertEqual(network_name, format.network_name())
661
678
 
662
679
 
761
778
        # transport connected to a real server.
762
779
        result = fmt._initialize_on_transport_ex_rpc(client, 'path',
763
780
            transport, False, False, False, None, None, None, None, False)
764
 
        client.finished_test()
 
781
        self.assertFinished(client)
765
782
 
766
783
    def test_error(self):
767
784
        """Error responses are translated, e.g. 'PermissionDenied' raises the
785
802
            False, False, False, None, None, None, None, False)
786
803
        self.assertEqual('path', err.path)
787
804
        self.assertEqual(': extra info', err.extra)
788
 
        client.finished_test()
 
805
        self.assertFinished(client)
789
806
 
790
807
    def test_error_from_real_server(self):
791
808
        """Integration test for error translation."""
866
883
        transport = transport.clone('quack')
867
884
        branch = self.make_remote_branch(transport, client)
868
885
        result = branch.get_parent()
869
 
        client.finished_test()
 
886
        self.assertFinished(client)
870
887
        self.assertEqual(None, result)
871
888
 
872
889
    def test_parent_relative(self):
898
915
        branch = self.make_remote_branch(transport, client)
899
916
        result = branch.get_parent()
900
917
        self.assertEqual('http://foo/', result)
901
 
        client.finished_test()
 
918
        self.assertFinished(client)
902
919
 
903
920
 
904
921
class TestBranchSetParentLocation(RemoteBranchTestCase):
919
936
        branch._lock_token = 'b'
920
937
        branch._repo_lock_token = 'r'
921
938
        branch._set_parent_location(None)
922
 
        client.finished_test()
 
939
        self.assertFinished(client)
923
940
 
924
941
    def test_parent(self):
925
942
        transport = MemoryTransport()
936
953
        branch._lock_token = 'b'
937
954
        branch._repo_lock_token = 'r'
938
955
        branch._set_parent_location('foo')
939
 
        client.finished_test()
 
956
        self.assertFinished(client)
940
957
 
941
958
    def test_backwards_compat(self):
942
959
        self.setup_smart_server_with_call_log()
974
991
        transport = transport.clone('quack')
975
992
        branch = self.make_remote_branch(transport, client)
976
993
        result = branch.tags.get_tag_dict()
977
 
        client.finished_test()
 
994
        self.assertFinished(client)
978
995
        self.assertEqual({}, result)
979
996
 
980
997
 
994
1011
        transport = transport.clone('quack')
995
1012
        branch = self.make_remote_branch(transport, client)
996
1013
        result = branch.last_revision_info()
997
 
        client.finished_test()
 
1014
        self.assertFinished(client)
998
1015
        self.assertEqual((0, NULL_REVISION), result)
999
1016
 
1000
1017
    def test_non_empty_branch(self):
1075
1092
        branch = bzrdir.open_branch()
1076
1093
        result = branch.get_stacked_on_url()
1077
1094
        self.assertEqual('../base', result)
1078
 
        client.finished_test()
 
1095
        self.assertFinished(client)
1079
1096
        # it's in the fallback list both for the RemoteRepository and its vfs
1080
1097
        # repository
1081
1098
        self.assertEqual(1, len(branch.repository._fallback_repositories))
1108
1125
        branch = bzrdir.open_branch()
1109
1126
        result = branch.get_stacked_on_url()
1110
1127
        self.assertEqual('../base', result)
1111
 
        client.finished_test()
 
1128
        self.assertFinished(client)
1112
1129
        # it's in the fallback list both for the RemoteRepository.
1113
1130
        self.assertEqual(1, len(branch.repository._fallback_repositories))
1114
1131
        # And we haven't had to construct a real repository.
1149
1166
        result = branch.set_revision_history([])
1150
1167
        branch.unlock()
1151
1168
        self.assertEqual(None, result)
1152
 
        client.finished_test()
 
1169
        self.assertFinished(client)
1153
1170
 
1154
1171
    def test_set_nonempty(self):
1155
1172
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1187
1204
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1188
1205
        branch.unlock()
1189
1206
        self.assertEqual(None, result)
1190
 
        client.finished_test()
 
1207
        self.assertFinished(client)
1191
1208
 
1192
1209
    def test_no_such_revision(self):
1193
1210
        transport = MemoryTransport()
1222
1239
        self.assertRaises(
1223
1240
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1224
1241
        branch.unlock()
1225
 
        client.finished_test()
 
1242
        self.assertFinished(client)
1226
1243
 
1227
1244
    def test_tip_change_rejected(self):
1228
1245
        """TipChangeRejected responses cause a TipChangeRejected exception to
1265
1282
        self.assertIsInstance(err.msg, unicode)
1266
1283
        self.assertEqual(rejection_msg_unicode, err.msg)
1267
1284
        branch.unlock()
1268
 
        client.finished_test()
 
1285
        self.assertFinished(client)
1269
1286
 
1270
1287
 
1271
1288
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1381
1398
        self.assertEqual(
1382
1399
            [('set_last_revision_info', 1234, 'a-revision-id')],
1383
1400
            real_branch.calls)
1384
 
        client.finished_test()
 
1401
        self.assertFinished(client)
1385
1402
 
1386
1403
    def test_unexpected_error(self):
1387
1404
        # If the server sends an error the client doesn't understand, it gets
1495
1512
        config = branch._get_config()
1496
1513
        config.set_option('foo', 'bar')
1497
1514
        branch.unlock()
1498
 
        client.finished_test()
 
1515
        self.assertFinished(client)
1499
1516
 
1500
1517
    def test_backwards_compat_set_option(self):
1501
1518
        self.setup_smart_server_with_call_log()
1525
1542
        transport = transport.clone('quack')
1526
1543
        branch = self.make_remote_branch(transport, client)
1527
1544
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1528
 
        client.finished_test()
 
1545
        self.assertFinished(client)
1529
1546
 
1530
1547
 
1531
1548
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2018
2035
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2019
2036
 
2020
2037
 
 
2038
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2039
 
 
2040
    def test_ok(self):
 
2041
        repo, client = self.setup_fake_client_and_repository('quack')
 
2042
        client.add_expected_call(
 
2043
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2044
            'success', ('ok', 'rev-five'))
 
2045
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2046
        self.assertEqual((True, 'rev-five'), result)
 
2047
        self.assertFinished(client)
 
2048
 
 
2049
    def test_history_incomplete(self):
 
2050
        repo, client = self.setup_fake_client_and_repository('quack')
 
2051
        client.add_expected_call(
 
2052
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2053
            'success', ('history-incomplete', 10, 'rev-ten'))
 
2054
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2055
        self.assertEqual((False, (10, 'rev-ten')), result)
 
2056
        self.assertFinished(client)
 
2057
 
 
2058
    def test_history_incomplete_with_fallback(self):
 
2059
        """A 'history-incomplete' response causes the fallback repository to be
 
2060
        queried too, if one is set.
 
2061
        """
 
2062
        # Make a repo with a fallback repo, both using a FakeClient.
 
2063
        format = remote.response_tuple_to_repo_format(
 
2064
            ('yes', 'no', 'yes', 'fake-network-name'))
 
2065
        repo, client = self.setup_fake_client_and_repository('quack')
 
2066
        repo._format = format
 
2067
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2068
            'fallback')
 
2069
        fallback_repo._client = client
 
2070
        repo.add_fallback_repository(fallback_repo)
 
2071
        # First the client should ask the primary repo
 
2072
        client.add_expected_call(
 
2073
            'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
 
2074
            'success', ('history-incomplete', 2, 'rev-two'))
 
2075
        # Then it should ask the fallback, using revno/revid from the
 
2076
        # history-incomplete response as the known revno/revid.
 
2077
        client.add_expected_call(
 
2078
            'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
 
2079
            'success', ('ok', 'rev-one'))
 
2080
        result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
 
2081
        self.assertEqual((True, 'rev-one'), result)
 
2082
        self.assertFinished(client)
 
2083
 
 
2084
    def test_nosuchrevision(self):
 
2085
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2086
        # remote repo.  The client translates that response to NoSuchRevision.
 
2087
        repo, client = self.setup_fake_client_and_repository('quack')
 
2088
        client.add_expected_call(
 
2089
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2090
            'error', ('nosuchrevision', 'rev-foo'))
 
2091
        self.assertRaises(
 
2092
            errors.NoSuchRevision,
 
2093
            repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
 
2094
        self.assertFinished(client)
 
2095
 
 
2096
 
2021
2097
class TestRepositoryIsShared(TestRemoteRepository):
2022
2098
 
2023
2099
    def test_is_shared(self):
2138
2214
        self.assertEqual([], client._calls)
2139
2215
 
2140
2216
 
2141
 
class TestRepositoryInsertStream(TestRemoteRepository):
2142
 
 
2143
 
    def test_unlocked_repo(self):
2144
 
        transport_path = 'quack'
2145
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2146
 
        client.add_expected_call(
2147
 
            'Repository.insert_stream', ('quack/', ''),
2148
 
            'success', ('ok',))
2149
 
        client.add_expected_call(
2150
 
            'Repository.insert_stream', ('quack/', ''),
2151
 
            'success', ('ok',))
2152
 
        sink = repo._get_sink()
2153
 
        fmt = repository.RepositoryFormat.get_default_format()
2154
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2155
 
        self.assertEqual([], resume_tokens)
2156
 
        self.assertEqual(set(), missing_keys)
2157
 
        client.finished_test()
2158
 
 
2159
 
    def test_locked_repo_with_no_lock_token(self):
2160
 
        transport_path = 'quack'
2161
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2162
 
        client.add_expected_call(
2163
 
            'Repository.lock_write', ('quack/', ''),
2164
 
            'success', ('ok', ''))
2165
 
        client.add_expected_call(
2166
 
            'Repository.insert_stream', ('quack/', ''),
2167
 
            'success', ('ok',))
2168
 
        client.add_expected_call(
2169
 
            'Repository.insert_stream', ('quack/', ''),
2170
 
            'success', ('ok',))
2171
 
        repo.lock_write()
2172
 
        sink = repo._get_sink()
2173
 
        fmt = repository.RepositoryFormat.get_default_format()
2174
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2175
 
        self.assertEqual([], resume_tokens)
2176
 
        self.assertEqual(set(), missing_keys)
2177
 
        client.finished_test()
2178
 
 
2179
 
    def test_locked_repo_with_lock_token(self):
2180
 
        transport_path = 'quack'
2181
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2182
 
        client.add_expected_call(
2183
 
            'Repository.lock_write', ('quack/', ''),
2184
 
            'success', ('ok', 'a token'))
2185
 
        client.add_expected_call(
2186
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2187
 
            'success', ('ok',))
2188
 
        client.add_expected_call(
2189
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2190
 
            'success', ('ok',))
2191
 
        repo.lock_write()
2192
 
        sink = repo._get_sink()
2193
 
        fmt = repository.RepositoryFormat.get_default_format()
2194
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2195
 
        self.assertEqual([], resume_tokens)
2196
 
        self.assertEqual(set(), missing_keys)
2197
 
        client.finished_test()
 
2217
class TestRepositoryInsertStreamBase(TestRemoteRepository):
 
2218
    """Base class for Repository.insert_stream and .insert_stream_1.19
 
2219
    tests.
 
2220
    """
 
2221
    
 
2222
    def checkInsertEmptyStream(self, repo, client):
 
2223
        """Insert an empty stream, checking the result.
 
2224
 
 
2225
        This checks that there are no resume_tokens or missing_keys, and that
 
2226
        the client is finished.
 
2227
        """
 
2228
        sink = repo._get_sink()
 
2229
        fmt = repository.RepositoryFormat.get_default_format()
 
2230
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2231
        self.assertEqual([], resume_tokens)
 
2232
        self.assertEqual(set(), missing_keys)
 
2233
        self.assertFinished(client)
 
2234
 
 
2235
 
 
2236
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
 
2237
    """Tests for using Repository.insert_stream verb when the _1.19 variant is
 
2238
    not available.
 
2239
 
 
2240
    This test case is very similar to TestRepositoryInsertStream_1_19.
 
2241
    """
 
2242
 
 
2243
    def setUp(self):
 
2244
        TestRemoteRepository.setUp(self)
 
2245
        self.disable_verb('Repository.insert_stream_1.19')
 
2246
 
 
2247
    def test_unlocked_repo(self):
 
2248
        transport_path = 'quack'
 
2249
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2250
        client.add_expected_call(
 
2251
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2252
            'unknown', ('Repository.insert_stream_1.19',))
 
2253
        client.add_expected_call(
 
2254
            'Repository.insert_stream', ('quack/', ''),
 
2255
            'success', ('ok',))
 
2256
        client.add_expected_call(
 
2257
            'Repository.insert_stream', ('quack/', ''),
 
2258
            'success', ('ok',))
 
2259
        self.checkInsertEmptyStream(repo, client)
 
2260
 
 
2261
    def test_locked_repo_with_no_lock_token(self):
 
2262
        transport_path = 'quack'
 
2263
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2264
        client.add_expected_call(
 
2265
            'Repository.lock_write', ('quack/', ''),
 
2266
            'success', ('ok', ''))
 
2267
        client.add_expected_call(
 
2268
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2269
            'unknown', ('Repository.insert_stream_1.19',))
 
2270
        client.add_expected_call(
 
2271
            'Repository.insert_stream', ('quack/', ''),
 
2272
            'success', ('ok',))
 
2273
        client.add_expected_call(
 
2274
            'Repository.insert_stream', ('quack/', ''),
 
2275
            'success', ('ok',))
 
2276
        repo.lock_write()
 
2277
        self.checkInsertEmptyStream(repo, client)
 
2278
 
 
2279
    def test_locked_repo_with_lock_token(self):
 
2280
        transport_path = 'quack'
 
2281
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2282
        client.add_expected_call(
 
2283
            'Repository.lock_write', ('quack/', ''),
 
2284
            'success', ('ok', 'a token'))
 
2285
        client.add_expected_call(
 
2286
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2287
            'unknown', ('Repository.insert_stream_1.19',))
 
2288
        client.add_expected_call(
 
2289
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2290
            'success', ('ok',))
 
2291
        client.add_expected_call(
 
2292
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2293
            'success', ('ok',))
 
2294
        repo.lock_write()
 
2295
        self.checkInsertEmptyStream(repo, client)
 
2296
 
 
2297
    def test_stream_with_inventory_deltas(self):
 
2298
        """'inventory-deltas' substreams cannot be sent to the
 
2299
        Repository.insert_stream verb, because not all servers that implement
 
2300
        that verb will accept them.  So when one is encountered the RemoteSink
 
2301
        immediately stops using that verb and falls back to VFS insert_stream.
 
2302
        """
 
2303
        transport_path = 'quack'
 
2304
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2305
        client.add_expected_call(
 
2306
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2307
            'unknown', ('Repository.insert_stream_1.19',))
 
2308
        client.add_expected_call(
 
2309
            'Repository.insert_stream', ('quack/', ''),
 
2310
            'success', ('ok',))
 
2311
        client.add_expected_call(
 
2312
            'Repository.insert_stream', ('quack/', ''),
 
2313
            'success', ('ok',))
 
2314
        # Create a fake real repository for insert_stream to fall back on, so
 
2315
        # that we can directly see the records the RemoteSink passes to the
 
2316
        # real sink.
 
2317
        class FakeRealSink:
 
2318
            def __init__(self):
 
2319
                self.records = []
 
2320
            def insert_stream(self, stream, src_format, resume_tokens):
 
2321
                for substream_kind, substream in stream:
 
2322
                    self.records.append(
 
2323
                        (substream_kind, [record.key for record in substream]))
 
2324
                return ['fake tokens'], ['fake missing keys']
 
2325
        fake_real_sink = FakeRealSink()
 
2326
        class FakeRealRepository:
 
2327
            def _get_sink(self):
 
2328
                return fake_real_sink
 
2329
        repo._real_repository = FakeRealRepository()
 
2330
        sink = repo._get_sink()
 
2331
        fmt = repository.RepositoryFormat.get_default_format()
 
2332
        stream = self.make_stream_with_inv_deltas(fmt)
 
2333
        resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
 
2334
        # Every record from the first inventory delta should have been sent to
 
2335
        # the VFS sink.
 
2336
        expected_records = [
 
2337
            ('inventory-deltas', [('rev2',), ('rev3',)]),
 
2338
            ('texts', [('some-rev', 'some-file')])]
 
2339
        self.assertEqual(expected_records, fake_real_sink.records)
 
2340
        # The return values from the real sink's insert_stream are propagated
 
2341
        # back to the original caller.
 
2342
        self.assertEqual(['fake tokens'], resume_tokens)
 
2343
        self.assertEqual(['fake missing keys'], missing_keys)
 
2344
        self.assertFinished(client)
 
2345
 
 
2346
    def make_stream_with_inv_deltas(self, fmt):
 
2347
        """Make a simple stream with an inventory delta followed by more
 
2348
        records and more substreams to test that all records and substreams
 
2349
        from that point on are used.
 
2350
 
 
2351
        This sends, in order:
 
2352
           * inventories substream: rev1, rev2, rev3.  rev2 and rev3 are
 
2353
             inventory-deltas.
 
2354
           * texts substream: (some-rev, some-file)
 
2355
        """
 
2356
        # Define a stream using generators so that it isn't rewindable.
 
2357
        inv = inventory.Inventory(revision_id='rev1')
 
2358
        inv.root.revision = 'rev1'
 
2359
        def stream_with_inv_delta():
 
2360
            yield ('inventories', inventories_substream())
 
2361
            yield ('inventory-deltas', inventory_delta_substream())
 
2362
            yield ('texts', [
 
2363
                versionedfile.FulltextContentFactory(
 
2364
                    ('some-rev', 'some-file'), (), None, 'content')])
 
2365
        def inventories_substream():
 
2366
            # An empty inventory fulltext.  This will be streamed normally.
 
2367
            text = fmt._serializer.write_inventory_to_string(inv)
 
2368
            yield versionedfile.FulltextContentFactory(
 
2369
                ('rev1',), (), None, text)
 
2370
        def inventory_delta_substream():
 
2371
            # An inventory delta.  This can't be streamed via this verb, so it
 
2372
            # will trigger a fallback to VFS insert_stream.
 
2373
            entry = inv.make_entry(
 
2374
                'directory', 'newdir', inv.root.file_id, 'newdir-id')
 
2375
            entry.revision = 'ghost'
 
2376
            delta = [(None, 'newdir', 'newdir-id', entry)]
 
2377
            serializer = inventory_delta.InventoryDeltaSerializer(
 
2378
                versioned_root=True, tree_references=False)
 
2379
            lines = serializer.delta_to_lines('rev1', 'rev2', delta)
 
2380
            yield versionedfile.ChunkedContentFactory(
 
2381
                ('rev2',), (('rev1',)), None, lines)
 
2382
            # Another delta.
 
2383
            lines = serializer.delta_to_lines('rev1', 'rev3', delta)
 
2384
            yield versionedfile.ChunkedContentFactory(
 
2385
                ('rev3',), (('rev1',)), None, lines)
 
2386
        return stream_with_inv_delta()
 
2387
 
 
2388
 
 
2389
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
 
2390
 
 
2391
    def test_unlocked_repo(self):
 
2392
        transport_path = 'quack'
 
2393
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2394
        client.add_expected_call(
 
2395
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2396
            'success', ('ok',))
 
2397
        client.add_expected_call(
 
2398
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2399
            'success', ('ok',))
 
2400
        self.checkInsertEmptyStream(repo, client)
 
2401
 
 
2402
    def test_locked_repo_with_no_lock_token(self):
 
2403
        transport_path = 'quack'
 
2404
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2405
        client.add_expected_call(
 
2406
            'Repository.lock_write', ('quack/', ''),
 
2407
            'success', ('ok', ''))
 
2408
        client.add_expected_call(
 
2409
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2410
            'success', ('ok',))
 
2411
        client.add_expected_call(
 
2412
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2413
            'success', ('ok',))
 
2414
        repo.lock_write()
 
2415
        self.checkInsertEmptyStream(repo, client)
 
2416
 
 
2417
    def test_locked_repo_with_lock_token(self):
 
2418
        transport_path = 'quack'
 
2419
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2420
        client.add_expected_call(
 
2421
            'Repository.lock_write', ('quack/', ''),
 
2422
            'success', ('ok', 'a token'))
 
2423
        client.add_expected_call(
 
2424
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2425
            'success', ('ok',))
 
2426
        client.add_expected_call(
 
2427
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2428
            'success', ('ok',))
 
2429
        repo.lock_write()
 
2430
        self.checkInsertEmptyStream(repo, client)
2198
2431
 
2199
2432
 
2200
2433
class TestRepositoryTarball(TestRemoteRepository):
2285
2518
        client.add_expected_call(
2286
2519
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2287
2520
        repo.autopack()
2288
 
        client.finished_test()
 
2521
        self.assertFinished(client)
2289
2522
 
2290
2523
    def test_ok_with_real_repo(self):
2291
2524
        """When the server returns 'ok' and there is a _real_repository, then
2435
2668
        expected_error = errors.ReadError(path)
2436
2669
        self.assertEqual(expected_error, translated_error)
2437
2670
 
 
2671
    def test_IncompatibleRepositories(self):
 
2672
        translated_error = self.translateTuple(('IncompatibleRepositories',
 
2673
            "repo1", "repo2", "details here"))
 
2674
        expected_error = errors.IncompatibleRepositories("repo1", "repo2",
 
2675
            "details here")
 
2676
        self.assertEqual(expected_error, translated_error)
 
2677
 
2438
2678
    def test_PermissionDenied_no_args(self):
2439
2679
        path = 'a path'
2440
2680
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
2570
2810
        tree1.commit('rev1', rev_id='rev1')
2571
2811
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2572
2812
            ).open_workingtree()
2573
 
        tree2.commit('local changes make me feel good.')
 
2813
        local_tree = tree2.branch.create_checkout('local')
 
2814
        local_tree.commit('local changes make me feel good.')
2574
2815
        branch2 = Branch.open(self.get_url('tree2'))
2575
2816
        branch2.lock_read()
2576
2817
        self.addCleanup(branch2.unlock)
2598
2839
                    result.append(content.key[-1])
2599
2840
        return result
2600
2841
 
2601
 
    def get_ordered_revs(self, format, order):
 
2842
    def get_ordered_revs(self, format, order, branch_factory=None):
2602
2843
        """Get a list of the revisions in a stream to format format.
2603
2844
 
2604
2845
        :param format: The format of the target.
2605
2846
        :param order: the order that target should have requested.
 
2847
        :param branch_factory: A callable to create a trunk and stacked branch
 
2848
            to fetch from. If none, self.prepare_stacked_remote_branch is used.
2606
2849
        :result: The revision ids in the stream, in the order seen,
2607
2850
            the topological order of revisions in the source.
2608
2851
        """
2610
2853
        target_repository_format = unordered_format.repository_format
2611
2854
        # Cross check
2612
2855
        self.assertEqual(order, target_repository_format._fetch_order)
2613
 
        trunk, stacked = self.prepare_stacked_remote_branch()
 
2856
        if branch_factory is None:
 
2857
            branch_factory = self.prepare_stacked_remote_branch
 
2858
        _, stacked = branch_factory()
2614
2859
        source = stacked.repository._get_source(target_repository_format)
2615
2860
        tip = stacked.last_revision()
2616
2861
        revs = stacked.repository.get_ancestry(tip)
2635
2880
        # from the server, then one from the backing branch.
2636
2881
        self.assertLength(2, self.hpss_calls)
2637
2882
 
 
2883
    def test_stacked_on_stacked_get_stream_unordered(self):
 
2884
        # Repository._get_source.get_stream() from a stacked repository which
 
2885
        # is itself stacked yields the full data from all three sources.
 
2886
        def make_stacked_stacked():
 
2887
            _, stacked = self.prepare_stacked_remote_branch()
 
2888
            tree = stacked.bzrdir.sprout('tree3', stacked=True
 
2889
                ).open_workingtree()
 
2890
            local_tree = tree.branch.create_checkout('local-tree3')
 
2891
            local_tree.commit('more local changes are better')
 
2892
            branch = Branch.open(self.get_url('tree3'))
 
2893
            branch.lock_read()
 
2894
            return None, branch
 
2895
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
 
2896
            branch_factory=make_stacked_stacked)
 
2897
        self.assertEqual(set(expected_revs), set(rev_ord))
 
2898
        # Getting unordered results should have made a streaming data request
 
2899
        # from the server, and one from each backing repo
 
2900
        self.assertLength(3, self.hpss_calls)
 
2901
 
2638
2902
    def test_stacked_get_stream_topological(self):
2639
2903
        # Repository._get_source.get_stream() from a stacked repository with
2640
2904
        # topological sorting yields the full data from both stacked and
2641
2905
        # stacked upon sources in topological order.
2642
2906
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2643
2907
        self.assertEqual(expected_revs, rev_ord)
2644
 
        # Getting topological sort requires VFS calls still
2645
 
        self.assertLength(12, self.hpss_calls)
 
2908
        # Getting topological sort requires VFS calls still - one of which is
 
2909
        # pushing up from the bound branch.
 
2910
        self.assertLength(13, self.hpss_calls)
2646
2911
 
2647
2912
    def test_stacked_get_stream_groupcompress(self):
2648
2913
        # Repository._get_source.get_stream() from a stacked repository with