480
474
self.assertFinished(client)
483
class TestBzrDirOpen(TestRemote):
485
def make_fake_client_and_transport(self, path='quack'):
486
transport = MemoryTransport()
487
transport.mkdir(path)
488
transport = transport.clone(path)
489
client = FakeClient(transport.base)
490
return client, transport
492
def test_absent(self):
493
client, transport = self.make_fake_client_and_transport()
494
client.add_expected_call(
495
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
496
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
497
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
498
self.assertFinished(client)
500
def test_present_without_workingtree(self):
501
client, transport = self.make_fake_client_and_transport()
502
client.add_expected_call(
503
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
504
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
505
_client=client, _force_probe=True)
506
self.assertIsInstance(bd, RemoteBzrDir)
507
self.assertFalse(bd.has_workingtree())
508
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
509
self.assertFinished(client)
511
def test_present_with_workingtree(self):
512
client, transport = self.make_fake_client_and_transport()
513
client.add_expected_call(
514
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
515
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
516
_client=client, _force_probe=True)
517
self.assertIsInstance(bd, RemoteBzrDir)
518
self.assertTrue(bd.has_workingtree())
519
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
520
self.assertFinished(client)
522
def test_backwards_compat(self):
523
client, transport = self.make_fake_client_and_transport()
524
client.add_expected_call(
525
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
526
client.add_expected_call(
527
'BzrDir.open', ('quack/',), 'success', ('yes',))
528
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
529
_client=client, _force_probe=True)
530
self.assertIsInstance(bd, RemoteBzrDir)
531
self.assertFinished(client)
533
def test_backwards_compat_hpss_v2(self):
534
client, transport = self.make_fake_client_and_transport()
535
# Monkey-patch fake client to simulate real-world behaviour with v2
536
# server: upon first RPC call detect the protocol version, and because
537
# the version is 2 also do _remember_remote_is_before((1, 6)) before
538
# continuing with the RPC.
539
orig_check_call = client._check_call
540
def check_call(method, args):
541
client._medium._protocol_version = 2
542
client._medium._remember_remote_is_before((1, 6))
543
client._check_call = orig_check_call
544
client._check_call(method, args)
545
client._check_call = check_call
546
client.add_expected_call(
547
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
548
client.add_expected_call(
549
'BzrDir.open', ('quack/',), 'success', ('yes',))
550
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
551
_client=client, _force_probe=True)
552
self.assertIsInstance(bd, RemoteBzrDir)
553
self.assertFinished(client)
556
477
class TestBzrDirOpenBranch(TestRemote):
558
479
def test_backwards_compat(self):
2430
2219
self.assertEqual([], client._calls)
2433
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2434
"""Base class for Repository.insert_stream and .insert_stream_1.19
2438
def checkInsertEmptyStream(self, repo, client):
2439
"""Insert an empty stream, checking the result.
2441
This checks that there are no resume_tokens or missing_keys, and that
2442
the client is finished.
2444
sink = repo._get_sink()
2445
fmt = repository.RepositoryFormat.get_default_format()
2446
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2447
self.assertEqual([], resume_tokens)
2448
self.assertEqual(set(), missing_keys)
2449
self.assertFinished(client)
2452
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2453
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2456
This test case is very similar to TestRepositoryInsertStream_1_19.
2460
TestRemoteRepository.setUp(self)
2461
self.disable_verb('Repository.insert_stream_1.19')
2463
def test_unlocked_repo(self):
2464
transport_path = 'quack'
2465
repo, client = self.setup_fake_client_and_repository(transport_path)
2466
client.add_expected_call(
2467
'Repository.insert_stream_1.19', ('quack/', ''),
2468
'unknown', ('Repository.insert_stream_1.19',))
2469
client.add_expected_call(
2470
'Repository.insert_stream', ('quack/', ''),
2472
client.add_expected_call(
2473
'Repository.insert_stream', ('quack/', ''),
2475
self.checkInsertEmptyStream(repo, client)
2477
def test_locked_repo_with_no_lock_token(self):
2478
transport_path = 'quack'
2479
repo, client = self.setup_fake_client_and_repository(transport_path)
2480
client.add_expected_call(
2481
'Repository.lock_write', ('quack/', ''),
2482
'success', ('ok', ''))
2483
client.add_expected_call(
2484
'Repository.insert_stream_1.19', ('quack/', ''),
2485
'unknown', ('Repository.insert_stream_1.19',))
2486
client.add_expected_call(
2487
'Repository.insert_stream', ('quack/', ''),
2489
client.add_expected_call(
2490
'Repository.insert_stream', ('quack/', ''),
2493
self.checkInsertEmptyStream(repo, client)
2495
def test_locked_repo_with_lock_token(self):
2496
transport_path = 'quack'
2497
repo, client = self.setup_fake_client_and_repository(transport_path)
2498
client.add_expected_call(
2499
'Repository.lock_write', ('quack/', ''),
2500
'success', ('ok', 'a token'))
2501
client.add_expected_call(
2502
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2503
'unknown', ('Repository.insert_stream_1.19',))
2504
client.add_expected_call(
2505
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2507
client.add_expected_call(
2508
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2511
self.checkInsertEmptyStream(repo, client)
2513
def test_stream_with_inventory_deltas(self):
2514
"""'inventory-deltas' substreams cannot be sent to the
2515
Repository.insert_stream verb, because not all servers that implement
2516
that verb will accept them. So when one is encountered the RemoteSink
2517
immediately stops using that verb and falls back to VFS insert_stream.
2519
transport_path = 'quack'
2520
repo, client = self.setup_fake_client_and_repository(transport_path)
2521
client.add_expected_call(
2522
'Repository.insert_stream_1.19', ('quack/', ''),
2523
'unknown', ('Repository.insert_stream_1.19',))
2524
client.add_expected_call(
2525
'Repository.insert_stream', ('quack/', ''),
2527
client.add_expected_call(
2528
'Repository.insert_stream', ('quack/', ''),
2530
# Create a fake real repository for insert_stream to fall back on, so
2531
# that we can directly see the records the RemoteSink passes to the
2536
def insert_stream(self, stream, src_format, resume_tokens):
2537
for substream_kind, substream in stream:
2538
self.records.append(
2539
(substream_kind, [record.key for record in substream]))
2540
return ['fake tokens'], ['fake missing keys']
2541
fake_real_sink = FakeRealSink()
2542
class FakeRealRepository:
2543
def _get_sink(self):
2544
return fake_real_sink
2545
def is_in_write_group(self):
2547
def refresh_data(self):
2549
repo._real_repository = FakeRealRepository()
2550
sink = repo._get_sink()
2551
fmt = repository.RepositoryFormat.get_default_format()
2552
stream = self.make_stream_with_inv_deltas(fmt)
2553
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2554
# Every record from the first inventory delta should have been sent to
2556
expected_records = [
2557
('inventory-deltas', [('rev2',), ('rev3',)]),
2558
('texts', [('some-rev', 'some-file')])]
2559
self.assertEqual(expected_records, fake_real_sink.records)
2560
# The return values from the real sink's insert_stream are propagated
2561
# back to the original caller.
2562
self.assertEqual(['fake tokens'], resume_tokens)
2563
self.assertEqual(['fake missing keys'], missing_keys)
2564
self.assertFinished(client)
2566
def make_stream_with_inv_deltas(self, fmt):
2567
"""Make a simple stream with an inventory delta followed by more
2568
records and more substreams to test that all records and substreams
2569
from that point on are used.
2571
This sends, in order:
2572
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2574
* texts substream: (some-rev, some-file)
2576
# Define a stream using generators so that it isn't rewindable.
2577
inv = inventory.Inventory(revision_id='rev1')
2578
inv.root.revision = 'rev1'
2579
def stream_with_inv_delta():
2580
yield ('inventories', inventories_substream())
2581
yield ('inventory-deltas', inventory_delta_substream())
2583
versionedfile.FulltextContentFactory(
2584
('some-rev', 'some-file'), (), None, 'content')])
2585
def inventories_substream():
2586
# An empty inventory fulltext. This will be streamed normally.
2587
text = fmt._serializer.write_inventory_to_string(inv)
2588
yield versionedfile.FulltextContentFactory(
2589
('rev1',), (), None, text)
2590
def inventory_delta_substream():
2591
# An inventory delta. This can't be streamed via this verb, so it
2592
# will trigger a fallback to VFS insert_stream.
2593
entry = inv.make_entry(
2594
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2595
entry.revision = 'ghost'
2596
delta = [(None, 'newdir', 'newdir-id', entry)]
2597
serializer = inventory_delta.InventoryDeltaSerializer(
2598
versioned_root=True, tree_references=False)
2599
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2600
yield versionedfile.ChunkedContentFactory(
2601
('rev2',), (('rev1',)), None, lines)
2603
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2604
yield versionedfile.ChunkedContentFactory(
2605
('rev3',), (('rev1',)), None, lines)
2606
return stream_with_inv_delta()
2609
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2611
def test_unlocked_repo(self):
2612
transport_path = 'quack'
2613
repo, client = self.setup_fake_client_and_repository(transport_path)
2614
client.add_expected_call(
2615
'Repository.insert_stream_1.19', ('quack/', ''),
2617
client.add_expected_call(
2618
'Repository.insert_stream_1.19', ('quack/', ''),
2620
self.checkInsertEmptyStream(repo, client)
2622
def test_locked_repo_with_no_lock_token(self):
2623
transport_path = 'quack'
2624
repo, client = self.setup_fake_client_and_repository(transport_path)
2625
client.add_expected_call(
2626
'Repository.lock_write', ('quack/', ''),
2627
'success', ('ok', ''))
2628
client.add_expected_call(
2629
'Repository.insert_stream_1.19', ('quack/', ''),
2631
client.add_expected_call(
2632
'Repository.insert_stream_1.19', ('quack/', ''),
2635
self.checkInsertEmptyStream(repo, client)
2637
def test_locked_repo_with_lock_token(self):
2638
transport_path = 'quack'
2639
repo, client = self.setup_fake_client_and_repository(transport_path)
2640
client.add_expected_call(
2641
'Repository.lock_write', ('quack/', ''),
2642
'success', ('ok', 'a token'))
2643
client.add_expected_call(
2644
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2646
client.add_expected_call(
2647
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2650
self.checkInsertEmptyStream(repo, client)
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)
2653
2281
class TestRepositoryTarball(TestRemoteRepository):