474
481
self.assertFinished(client)
484
class TestBzrDirOpen(TestRemote):
486
def make_fake_client_and_transport(self, path='quack'):
487
transport = MemoryTransport()
488
transport.mkdir(path)
489
transport = transport.clone(path)
490
client = FakeClient(transport.base)
491
return client, transport
493
def test_absent(self):
494
client, transport = self.make_fake_client_and_transport()
495
client.add_expected_call(
496
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
497
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
498
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
499
self.assertFinished(client)
501
def test_present_without_workingtree(self):
502
client, transport = self.make_fake_client_and_transport()
503
client.add_expected_call(
504
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
505
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
506
_client=client, _force_probe=True)
507
self.assertIsInstance(bd, RemoteBzrDir)
508
self.assertFalse(bd.has_workingtree())
509
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
510
self.assertFinished(client)
512
def test_present_with_workingtree(self):
513
client, transport = self.make_fake_client_and_transport()
514
client.add_expected_call(
515
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
516
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
517
_client=client, _force_probe=True)
518
self.assertIsInstance(bd, RemoteBzrDir)
519
self.assertTrue(bd.has_workingtree())
520
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
521
self.assertFinished(client)
523
def test_backwards_compat(self):
524
client, transport = self.make_fake_client_and_transport()
525
client.add_expected_call(
526
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
527
client.add_expected_call(
528
'BzrDir.open', ('quack/',), 'success', ('yes',))
529
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
530
_client=client, _force_probe=True)
531
self.assertIsInstance(bd, RemoteBzrDir)
532
self.assertFinished(client)
534
def test_backwards_compat_hpss_v2(self):
535
client, transport = self.make_fake_client_and_transport()
536
# Monkey-patch fake client to simulate real-world behaviour with v2
537
# server: upon first RPC call detect the protocol version, and because
538
# the version is 2 also do _remember_remote_is_before((1, 6)) before
539
# continuing with the RPC.
540
orig_check_call = client._check_call
541
def check_call(method, args):
542
client._medium._protocol_version = 2
543
client._medium._remember_remote_is_before((1, 6))
544
client._check_call = orig_check_call
545
client._check_call(method, args)
546
client._check_call = check_call
547
client.add_expected_call(
548
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
549
client.add_expected_call(
550
'BzrDir.open', ('quack/',), 'success', ('yes',))
551
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
552
_client=client, _force_probe=True)
553
self.assertIsInstance(bd, RemoteBzrDir)
554
self.assertFinished(client)
477
557
class TestBzrDirOpenBranch(TestRemote):
479
559
def test_backwards_compat(self):
2219
2431
self.assertEqual([], client._calls)
2222
class TestRepositoryInsertStream(TestRemoteRepository):
2224
def test_unlocked_repo(self):
2225
transport_path = 'quack'
2226
repo, client = self.setup_fake_client_and_repository(transport_path)
2227
client.add_expected_call(
2228
'Repository.insert_stream', ('quack/', ''),
2230
client.add_expected_call(
2231
'Repository.insert_stream', ('quack/', ''),
2233
sink = repo._get_sink()
2234
fmt = repository.RepositoryFormat.get_default_format()
2235
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2236
self.assertEqual([], resume_tokens)
2237
self.assertEqual(set(), missing_keys)
2238
self.assertFinished(client)
2240
def test_locked_repo_with_no_lock_token(self):
2241
transport_path = 'quack'
2242
repo, client = self.setup_fake_client_and_repository(transport_path)
2243
client.add_expected_call(
2244
'Repository.lock_write', ('quack/', ''),
2245
'success', ('ok', ''))
2246
client.add_expected_call(
2247
'Repository.insert_stream', ('quack/', ''),
2249
client.add_expected_call(
2250
'Repository.insert_stream', ('quack/', ''),
2253
sink = repo._get_sink()
2254
fmt = repository.RepositoryFormat.get_default_format()
2255
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2256
self.assertEqual([], resume_tokens)
2257
self.assertEqual(set(), missing_keys)
2258
self.assertFinished(client)
2260
def test_locked_repo_with_lock_token(self):
2261
transport_path = 'quack'
2262
repo, client = self.setup_fake_client_and_repository(transport_path)
2263
client.add_expected_call(
2264
'Repository.lock_write', ('quack/', ''),
2265
'success', ('ok', 'a token'))
2266
client.add_expected_call(
2267
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2269
client.add_expected_call(
2270
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2273
sink = repo._get_sink()
2274
fmt = repository.RepositoryFormat.get_default_format()
2275
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2276
self.assertEqual([], resume_tokens)
2277
self.assertEqual(set(), missing_keys)
2278
self.assertFinished(client)
2434
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2435
"""Base class for Repository.insert_stream and .insert_stream_1.19
2439
def checkInsertEmptyStream(self, repo, client):
2440
"""Insert an empty stream, checking the result.
2442
This checks that there are no resume_tokens or missing_keys, and that
2443
the client is finished.
2445
sink = repo._get_sink()
2446
fmt = repository.RepositoryFormat.get_default_format()
2447
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2448
self.assertEqual([], resume_tokens)
2449
self.assertEqual(set(), missing_keys)
2450
self.assertFinished(client)
2453
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2454
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2457
This test case is very similar to TestRepositoryInsertStream_1_19.
2461
TestRemoteRepository.setUp(self)
2462
self.disable_verb('Repository.insert_stream_1.19')
2464
def test_unlocked_repo(self):
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
client.add_expected_call(
2468
'Repository.insert_stream_1.19', ('quack/', ''),
2469
'unknown', ('Repository.insert_stream_1.19',))
2470
client.add_expected_call(
2471
'Repository.insert_stream', ('quack/', ''),
2473
client.add_expected_call(
2474
'Repository.insert_stream', ('quack/', ''),
2476
self.checkInsertEmptyStream(repo, client)
2478
def test_locked_repo_with_no_lock_token(self):
2479
transport_path = 'quack'
2480
repo, client = self.setup_fake_client_and_repository(transport_path)
2481
client.add_expected_call(
2482
'Repository.lock_write', ('quack/', ''),
2483
'success', ('ok', ''))
2484
client.add_expected_call(
2485
'Repository.insert_stream_1.19', ('quack/', ''),
2486
'unknown', ('Repository.insert_stream_1.19',))
2487
client.add_expected_call(
2488
'Repository.insert_stream', ('quack/', ''),
2490
client.add_expected_call(
2491
'Repository.insert_stream', ('quack/', ''),
2494
self.checkInsertEmptyStream(repo, client)
2496
def test_locked_repo_with_lock_token(self):
2497
transport_path = 'quack'
2498
repo, client = self.setup_fake_client_and_repository(transport_path)
2499
client.add_expected_call(
2500
'Repository.lock_write', ('quack/', ''),
2501
'success', ('ok', 'a token'))
2502
client.add_expected_call(
2503
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2504
'unknown', ('Repository.insert_stream_1.19',))
2505
client.add_expected_call(
2506
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2508
client.add_expected_call(
2509
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2512
self.checkInsertEmptyStream(repo, client)
2514
def test_stream_with_inventory_deltas(self):
2515
"""'inventory-deltas' substreams cannot be sent to the
2516
Repository.insert_stream verb, because not all servers that implement
2517
that verb will accept them. So when one is encountered the RemoteSink
2518
immediately stops using that verb and falls back to VFS insert_stream.
2520
transport_path = 'quack'
2521
repo, client = self.setup_fake_client_and_repository(transport_path)
2522
client.add_expected_call(
2523
'Repository.insert_stream_1.19', ('quack/', ''),
2524
'unknown', ('Repository.insert_stream_1.19',))
2525
client.add_expected_call(
2526
'Repository.insert_stream', ('quack/', ''),
2528
client.add_expected_call(
2529
'Repository.insert_stream', ('quack/', ''),
2531
# Create a fake real repository for insert_stream to fall back on, so
2532
# that we can directly see the records the RemoteSink passes to the
2537
def insert_stream(self, stream, src_format, resume_tokens):
2538
for substream_kind, substream in stream:
2539
self.records.append(
2540
(substream_kind, [record.key for record in substream]))
2541
return ['fake tokens'], ['fake missing keys']
2542
fake_real_sink = FakeRealSink()
2543
class FakeRealRepository:
2544
def _get_sink(self):
2545
return fake_real_sink
2546
def is_in_write_group(self):
2548
def refresh_data(self):
2550
repo._real_repository = FakeRealRepository()
2551
sink = repo._get_sink()
2552
fmt = repository.RepositoryFormat.get_default_format()
2553
stream = self.make_stream_with_inv_deltas(fmt)
2554
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2555
# Every record from the first inventory delta should have been sent to
2557
expected_records = [
2558
('inventory-deltas', [('rev2',), ('rev3',)]),
2559
('texts', [('some-rev', 'some-file')])]
2560
self.assertEqual(expected_records, fake_real_sink.records)
2561
# The return values from the real sink's insert_stream are propagated
2562
# back to the original caller.
2563
self.assertEqual(['fake tokens'], resume_tokens)
2564
self.assertEqual(['fake missing keys'], missing_keys)
2565
self.assertFinished(client)
2567
def make_stream_with_inv_deltas(self, fmt):
2568
"""Make a simple stream with an inventory delta followed by more
2569
records and more substreams to test that all records and substreams
2570
from that point on are used.
2572
This sends, in order:
2573
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2575
* texts substream: (some-rev, some-file)
2577
# Define a stream using generators so that it isn't rewindable.
2578
inv = inventory.Inventory(revision_id='rev1')
2579
inv.root.revision = 'rev1'
2580
def stream_with_inv_delta():
2581
yield ('inventories', inventories_substream())
2582
yield ('inventory-deltas', inventory_delta_substream())
2584
versionedfile.FulltextContentFactory(
2585
('some-rev', 'some-file'), (), None, 'content')])
2586
def inventories_substream():
2587
# An empty inventory fulltext. This will be streamed normally.
2588
text = fmt._serializer.write_inventory_to_string(inv)
2589
yield versionedfile.FulltextContentFactory(
2590
('rev1',), (), None, text)
2591
def inventory_delta_substream():
2592
# An inventory delta. This can't be streamed via this verb, so it
2593
# will trigger a fallback to VFS insert_stream.
2594
entry = inv.make_entry(
2595
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2596
entry.revision = 'ghost'
2597
delta = [(None, 'newdir', 'newdir-id', entry)]
2598
serializer = inventory_delta.InventoryDeltaSerializer(
2599
versioned_root=True, tree_references=False)
2600
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2601
yield versionedfile.ChunkedContentFactory(
2602
('rev2',), (('rev1',)), None, lines)
2604
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2605
yield versionedfile.ChunkedContentFactory(
2606
('rev3',), (('rev1',)), None, lines)
2607
return stream_with_inv_delta()
2610
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2612
def test_unlocked_repo(self):
2613
transport_path = 'quack'
2614
repo, client = self.setup_fake_client_and_repository(transport_path)
2615
client.add_expected_call(
2616
'Repository.insert_stream_1.19', ('quack/', ''),
2618
client.add_expected_call(
2619
'Repository.insert_stream_1.19', ('quack/', ''),
2621
self.checkInsertEmptyStream(repo, client)
2623
def test_locked_repo_with_no_lock_token(self):
2624
transport_path = 'quack'
2625
repo, client = self.setup_fake_client_and_repository(transport_path)
2626
client.add_expected_call(
2627
'Repository.lock_write', ('quack/', ''),
2628
'success', ('ok', ''))
2629
client.add_expected_call(
2630
'Repository.insert_stream_1.19', ('quack/', ''),
2632
client.add_expected_call(
2633
'Repository.insert_stream_1.19', ('quack/', ''),
2636
self.checkInsertEmptyStream(repo, client)
2638
def test_locked_repo_with_lock_token(self):
2639
transport_path = 'quack'
2640
repo, client = self.setup_fake_client_and_repository(transport_path)
2641
client.add_expected_call(
2642
'Repository.lock_write', ('quack/', ''),
2643
'success', ('ok', 'a token'))
2644
client.add_expected_call(
2645
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2647
client.add_expected_call(
2648
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2651
self.checkInsertEmptyStream(repo, client)
2281
2654
class TestRepositoryTarball(TestRemoteRepository):