474
449
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
475
450
self.assertEqual(None, result._repository_format)
476
451
self.assertEqual(None, result._branch_format)
477
self.assertFinished(client)
480
class TestBzrDirOpen(TestRemote):
482
def make_fake_client_and_transport(self, path='quack'):
483
transport = MemoryTransport()
484
transport.mkdir(path)
485
transport = transport.clone(path)
486
client = FakeClient(transport.base)
487
return client, transport
489
def test_absent(self):
490
client, transport = self.make_fake_client_and_transport()
491
client.add_expected_call(
492
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
493
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
494
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
495
self.assertFinished(client)
497
def test_present_without_workingtree(self):
498
client, transport = self.make_fake_client_and_transport()
499
client.add_expected_call(
500
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
501
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
502
_client=client, _force_probe=True)
503
self.assertIsInstance(bd, RemoteBzrDir)
504
self.assertFalse(bd.has_workingtree())
505
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
506
self.assertFinished(client)
508
def test_present_with_workingtree(self):
509
client, transport = self.make_fake_client_and_transport()
510
client.add_expected_call(
511
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
512
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
513
_client=client, _force_probe=True)
514
self.assertIsInstance(bd, RemoteBzrDir)
515
self.assertTrue(bd.has_workingtree())
516
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
517
self.assertFinished(client)
519
def test_backwards_compat(self):
520
client, transport = self.make_fake_client_and_transport()
521
client.add_expected_call(
522
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
523
client.add_expected_call(
524
'BzrDir.open', ('quack/',), 'success', ('yes',))
525
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
526
_client=client, _force_probe=True)
527
self.assertIsInstance(bd, RemoteBzrDir)
528
self.assertFinished(client)
452
client.finished_test()
531
455
class TestBzrDirOpenBranch(TestRemote):
822
741
self.assertEqual(network_name, repo._format.network_name())
825
class TestBzrDirFormatInitializeEx(TestRemote):
827
def test_success(self):
828
"""Simple test for typical successful call."""
829
fmt = bzrdir.RemoteBzrDirFormat()
830
default_format_name = BzrDirFormat.get_default_format().network_name()
831
transport = self.get_transport()
832
client = FakeClient(transport.base)
833
client.add_expected_call(
834
'BzrDirFormat.initialize_ex_1.16',
835
(default_format_name, 'path', 'False', 'False', 'False', '',
836
'', '', '', 'False'),
838
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
839
'bzrdir fmt', 'False', '', '', 'repo lock token'))
840
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
841
# it's currently hard to test that without supplying a real remote
842
# transport connected to a real server.
843
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
844
transport, False, False, False, None, None, None, None, False)
845
self.assertFinished(client)
847
def test_error(self):
848
"""Error responses are translated, e.g. 'PermissionDenied' raises the
849
corresponding error from the client.
851
fmt = bzrdir.RemoteBzrDirFormat()
852
default_format_name = BzrDirFormat.get_default_format().network_name()
853
transport = self.get_transport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'BzrDirFormat.initialize_ex_1.16',
857
(default_format_name, 'path', 'False', 'False', 'False', '',
858
'', '', '', 'False'),
860
('PermissionDenied', 'path', 'extra info'))
861
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
862
# it's currently hard to test that without supplying a real remote
863
# transport connected to a real server.
864
err = self.assertRaises(errors.PermissionDenied,
865
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
866
False, False, False, None, None, None, None, False)
867
self.assertEqual('path', err.path)
868
self.assertEqual(': extra info', err.extra)
869
self.assertFinished(client)
871
def test_error_from_real_server(self):
872
"""Integration test for error translation."""
873
transport = self.make_smart_server('foo')
874
transport = transport.clone('no-such-path')
875
fmt = bzrdir.RemoteBzrDirFormat()
876
err = self.assertRaises(errors.NoSuchFile,
877
fmt.initialize_on_transport_ex, transport, create_prefix=False)
880
744
class OldSmartClient(object):
881
745
"""A fake smart client for test_old_version that just returns a version one
882
746
response to the 'hello' (query version) command.
1065
919
transport = transport.clone('quack')
1066
920
branch = self.make_remote_branch(transport, client)
1067
921
result = branch.tags.get_tag_dict()
1068
self.assertFinished(client)
922
client.finished_test()
1069
923
self.assertEqual({}, result)
1072
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1074
def test_trivial(self):
1075
transport = MemoryTransport()
1076
client = FakeClient(transport.base)
1077
client.add_expected_call(
1078
'Branch.get_stacked_on_url', ('quack/',),
1079
'error', ('NotStacked',))
1080
client.add_expected_call(
1081
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1083
transport.mkdir('quack')
1084
transport = transport.clone('quack')
1085
branch = self.make_remote_branch(transport, client)
1086
self.lock_remote_branch(branch)
1087
branch._set_tags_bytes('tags bytes')
1088
self.assertFinished(client)
1089
self.assertEqual('tags bytes', client._calls[-1][-1])
1091
def test_backwards_compatible(self):
1092
transport = MemoryTransport()
1093
client = FakeClient(transport.base)
1094
client.add_expected_call(
1095
'Branch.get_stacked_on_url', ('quack/',),
1096
'error', ('NotStacked',))
1097
client.add_expected_call(
1098
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1099
'unknown', ('Branch.set_tags_bytes',))
1100
transport.mkdir('quack')
1101
transport = transport.clone('quack')
1102
branch = self.make_remote_branch(transport, client)
1103
self.lock_remote_branch(branch)
1104
class StubRealBranch(object):
1107
def _set_tags_bytes(self, bytes):
1108
self.calls.append(('set_tags_bytes', bytes))
1109
real_branch = StubRealBranch()
1110
branch._real_branch = real_branch
1111
branch._set_tags_bytes('tags bytes')
1112
# Call a second time, to exercise the 'remote version already inferred'
1114
branch._set_tags_bytes('tags bytes')
1115
self.assertFinished(client)
1117
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1120
926
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1122
928
def test_empty_branch(self):
2164
1963
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2167
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2170
repo, client = self.setup_fake_client_and_repository('quack')
2171
client.add_expected_call(
2172
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2173
'success', ('ok', 'rev-five'))
2174
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2175
self.assertEqual((True, 'rev-five'), result)
2176
self.assertFinished(client)
2178
def test_history_incomplete(self):
2179
repo, client = self.setup_fake_client_and_repository('quack')
2180
client.add_expected_call(
2181
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2182
'success', ('history-incomplete', 10, 'rev-ten'))
2183
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2184
self.assertEqual((False, (10, 'rev-ten')), result)
2185
self.assertFinished(client)
2187
def test_history_incomplete_with_fallback(self):
2188
"""A 'history-incomplete' response causes the fallback repository to be
2189
queried too, if one is set.
2191
# Make a repo with a fallback repo, both using a FakeClient.
2192
format = remote.response_tuple_to_repo_format(
2193
('yes', 'no', 'yes', 'fake-network-name'))
2194
repo, client = self.setup_fake_client_and_repository('quack')
2195
repo._format = format
2196
fallback_repo, ignored = self.setup_fake_client_and_repository(
2198
fallback_repo._client = client
2199
repo.add_fallback_repository(fallback_repo)
2200
# First the client should ask the primary repo
2201
client.add_expected_call(
2202
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2203
'success', ('history-incomplete', 2, 'rev-two'))
2204
# Then it should ask the fallback, using revno/revid from the
2205
# history-incomplete response as the known revno/revid.
2206
client.add_expected_call(
2207
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2208
'success', ('ok', 'rev-one'))
2209
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2210
self.assertEqual((True, 'rev-one'), result)
2211
self.assertFinished(client)
2213
def test_nosuchrevision(self):
2214
# 'nosuchrevision' is returned when the known-revid is not found in the
2215
# remote repo. The client translates that response to NoSuchRevision.
2216
repo, client = self.setup_fake_client_and_repository('quack')
2217
client.add_expected_call(
2218
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2219
'error', ('nosuchrevision', 'rev-foo'))
2221
errors.NoSuchRevision,
2222
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2223
self.assertFinished(client)
2225
def test_branch_fallback_locking(self):
2226
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2227
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2228
will be invoked, which will fail if the repo is unlocked.
2230
self.setup_smart_server_with_call_log()
2231
tree = self.make_branch_and_memory_tree('.')
2233
rev1 = tree.commit('First')
2234
rev2 = tree.commit('Second')
2236
branch = tree.branch
2237
self.assertFalse(branch.is_locked())
2238
self.reset_smart_call_log()
2239
verb = 'Repository.get_rev_id_for_revno'
2240
self.disable_verb(verb)
2241
self.assertEqual(rev1, branch.get_rev_id(1))
2242
self.assertLength(1, [call for call in self.hpss_calls if
2243
call.call.method == verb])
2246
1966
class TestRepositoryIsShared(TestRemoteRepository):
2248
1968
def test_is_shared(self):
2363
2083
self.assertEqual([], client._calls)
2366
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2367
"""Base class for Repository.insert_stream and .insert_stream_1.19
2371
def checkInsertEmptyStream(self, repo, client):
2372
"""Insert an empty stream, checking the result.
2374
This checks that there are no resume_tokens or missing_keys, and that
2375
the client is finished.
2377
sink = repo._get_sink()
2378
fmt = repository.RepositoryFormat.get_default_format()
2379
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2380
self.assertEqual([], resume_tokens)
2381
self.assertEqual(set(), missing_keys)
2382
self.assertFinished(client)
2385
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2386
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2389
This test case is very similar to TestRepositoryInsertStream_1_19.
2393
TestRemoteRepository.setUp(self)
2394
self.disable_verb('Repository.insert_stream_1.19')
2396
def test_unlocked_repo(self):
2397
transport_path = 'quack'
2398
repo, client = self.setup_fake_client_and_repository(transport_path)
2399
client.add_expected_call(
2400
'Repository.insert_stream_1.19', ('quack/', ''),
2401
'unknown', ('Repository.insert_stream_1.19',))
2402
client.add_expected_call(
2403
'Repository.insert_stream', ('quack/', ''),
2405
client.add_expected_call(
2406
'Repository.insert_stream', ('quack/', ''),
2408
self.checkInsertEmptyStream(repo, client)
2410
def test_locked_repo_with_no_lock_token(self):
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_expected_call(
2414
'Repository.lock_write', ('quack/', ''),
2415
'success', ('ok', ''))
2416
client.add_expected_call(
2417
'Repository.insert_stream_1.19', ('quack/', ''),
2418
'unknown', ('Repository.insert_stream_1.19',))
2419
client.add_expected_call(
2420
'Repository.insert_stream', ('quack/', ''),
2422
client.add_expected_call(
2423
'Repository.insert_stream', ('quack/', ''),
2426
self.checkInsertEmptyStream(repo, client)
2428
def test_locked_repo_with_lock_token(self):
2429
transport_path = 'quack'
2430
repo, client = self.setup_fake_client_and_repository(transport_path)
2431
client.add_expected_call(
2432
'Repository.lock_write', ('quack/', ''),
2433
'success', ('ok', 'a token'))
2434
client.add_expected_call(
2435
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2436
'unknown', ('Repository.insert_stream_1.19',))
2437
client.add_expected_call(
2438
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2440
client.add_expected_call(
2441
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2444
self.checkInsertEmptyStream(repo, client)
2446
def test_stream_with_inventory_deltas(self):
2447
"""'inventory-deltas' substreams cannot be sent to the
2448
Repository.insert_stream verb, because not all servers that implement
2449
that verb will accept them. So when one is encountered the RemoteSink
2450
immediately stops using that verb and falls back to VFS insert_stream.
2452
transport_path = 'quack'
2453
repo, client = self.setup_fake_client_and_repository(transport_path)
2454
client.add_expected_call(
2455
'Repository.insert_stream_1.19', ('quack/', ''),
2456
'unknown', ('Repository.insert_stream_1.19',))
2457
client.add_expected_call(
2458
'Repository.insert_stream', ('quack/', ''),
2460
client.add_expected_call(
2461
'Repository.insert_stream', ('quack/', ''),
2463
# Create a fake real repository for insert_stream to fall back on, so
2464
# that we can directly see the records the RemoteSink passes to the
2469
def insert_stream(self, stream, src_format, resume_tokens):
2470
for substream_kind, substream in stream:
2471
self.records.append(
2472
(substream_kind, [record.key for record in substream]))
2473
return ['fake tokens'], ['fake missing keys']
2474
fake_real_sink = FakeRealSink()
2475
class FakeRealRepository:
2476
def _get_sink(self):
2477
return fake_real_sink
2478
def is_in_write_group(self):
2480
def refresh_data(self):
2482
repo._real_repository = FakeRealRepository()
2483
sink = repo._get_sink()
2484
fmt = repository.RepositoryFormat.get_default_format()
2485
stream = self.make_stream_with_inv_deltas(fmt)
2486
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2487
# Every record from the first inventory delta should have been sent to
2489
expected_records = [
2490
('inventory-deltas', [('rev2',), ('rev3',)]),
2491
('texts', [('some-rev', 'some-file')])]
2492
self.assertEqual(expected_records, fake_real_sink.records)
2493
# The return values from the real sink's insert_stream are propagated
2494
# back to the original caller.
2495
self.assertEqual(['fake tokens'], resume_tokens)
2496
self.assertEqual(['fake missing keys'], missing_keys)
2497
self.assertFinished(client)
2499
def make_stream_with_inv_deltas(self, fmt):
2500
"""Make a simple stream with an inventory delta followed by more
2501
records and more substreams to test that all records and substreams
2502
from that point on are used.
2504
This sends, in order:
2505
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2507
* texts substream: (some-rev, some-file)
2509
# Define a stream using generators so that it isn't rewindable.
2510
inv = inventory.Inventory(revision_id='rev1')
2511
inv.root.revision = 'rev1'
2512
def stream_with_inv_delta():
2513
yield ('inventories', inventories_substream())
2514
yield ('inventory-deltas', inventory_delta_substream())
2516
versionedfile.FulltextContentFactory(
2517
('some-rev', 'some-file'), (), None, 'content')])
2518
def inventories_substream():
2519
# An empty inventory fulltext. This will be streamed normally.
2520
text = fmt._serializer.write_inventory_to_string(inv)
2521
yield versionedfile.FulltextContentFactory(
2522
('rev1',), (), None, text)
2523
def inventory_delta_substream():
2524
# An inventory delta. This can't be streamed via this verb, so it
2525
# will trigger a fallback to VFS insert_stream.
2526
entry = inv.make_entry(
2527
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2528
entry.revision = 'ghost'
2529
delta = [(None, 'newdir', 'newdir-id', entry)]
2530
serializer = inventory_delta.InventoryDeltaSerializer(
2531
versioned_root=True, tree_references=False)
2532
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2533
yield versionedfile.ChunkedContentFactory(
2534
('rev2',), (('rev1',)), None, lines)
2536
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2537
yield versionedfile.ChunkedContentFactory(
2538
('rev3',), (('rev1',)), None, lines)
2539
return stream_with_inv_delta()
2542
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2544
def test_unlocked_repo(self):
2545
transport_path = 'quack'
2546
repo, client = self.setup_fake_client_and_repository(transport_path)
2547
client.add_expected_call(
2548
'Repository.insert_stream_1.19', ('quack/', ''),
2550
client.add_expected_call(
2551
'Repository.insert_stream_1.19', ('quack/', ''),
2553
self.checkInsertEmptyStream(repo, client)
2555
def test_locked_repo_with_no_lock_token(self):
2556
transport_path = 'quack'
2557
repo, client = self.setup_fake_client_and_repository(transport_path)
2558
client.add_expected_call(
2559
'Repository.lock_write', ('quack/', ''),
2560
'success', ('ok', ''))
2561
client.add_expected_call(
2562
'Repository.insert_stream_1.19', ('quack/', ''),
2564
client.add_expected_call(
2565
'Repository.insert_stream_1.19', ('quack/', ''),
2568
self.checkInsertEmptyStream(repo, client)
2570
def test_locked_repo_with_lock_token(self):
2571
transport_path = 'quack'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_expected_call(
2574
'Repository.lock_write', ('quack/', ''),
2575
'success', ('ok', 'a token'))
2576
client.add_expected_call(
2577
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2579
client.add_expected_call(
2580
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2583
self.checkInsertEmptyStream(repo, client)
2086
class TestRepositoryInsertStream(TestRemoteRepository):
2088
def test_unlocked_repo(self):
2089
transport_path = 'quack'
2090
repo, client = self.setup_fake_client_and_repository(transport_path)
2091
client.add_expected_call(
2092
'Repository.insert_stream', ('quack/', ''),
2094
client.add_expected_call(
2095
'Repository.insert_stream', ('quack/', ''),
2097
sink = repo._get_sink()
2098
fmt = repository.RepositoryFormat.get_default_format()
2099
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2100
self.assertEqual([], resume_tokens)
2101
self.assertEqual(set(), missing_keys)
2102
client.finished_test()
2104
def test_locked_repo_with_no_lock_token(self):
2105
transport_path = 'quack'
2106
repo, client = self.setup_fake_client_and_repository(transport_path)
2107
client.add_expected_call(
2108
'Repository.lock_write', ('quack/', ''),
2109
'success', ('ok', ''))
2110
client.add_expected_call(
2111
'Repository.insert_stream', ('quack/', ''),
2113
client.add_expected_call(
2114
'Repository.insert_stream', ('quack/', ''),
2117
sink = repo._get_sink()
2118
fmt = repository.RepositoryFormat.get_default_format()
2119
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2120
self.assertEqual([], resume_tokens)
2121
self.assertEqual(set(), missing_keys)
2122
client.finished_test()
2124
def test_locked_repo_with_lock_token(self):
2125
transport_path = 'quack'
2126
repo, client = self.setup_fake_client_and_repository(transport_path)
2127
client.add_expected_call(
2128
'Repository.lock_write', ('quack/', ''),
2129
'success', ('ok', 'a token'))
2130
client.add_expected_call(
2131
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2133
client.add_expected_call(
2134
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2137
sink = repo._get_sink()
2138
fmt = repository.RepositoryFormat.get_default_format()
2139
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2140
self.assertEqual([], resume_tokens)
2141
self.assertEqual(set(), missing_keys)
2142
client.finished_test()
2586
2145
class TestRepositoryTarball(TestRemoteRepository):