1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
17
"""Tests for the Repository facility that are not interface tests.
19
For interface tests see tests/per_repository/*.py.
21
For concrete class tests see this file, and for storage formats tests
26
from stat import S_ISDIR
27
from StringIO import StringIO
30
from bzrlib.errors import (NotBranchError,
33
UnsupportedFormatError,
35
from bzrlib import graph
36
from bzrlib.index import GraphIndex, InMemoryGraphIndex
37
from bzrlib.repository import RepositoryFormat
38
from bzrlib.smart import server
39
from bzrlib.tests import (
41
TestCaseWithTransport,
45
from bzrlib.transport import (
49
from bzrlib.transport.memory import MemoryServer
50
from bzrlib.util import bencode
57
revision as _mod_revision,
62
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
65
class TestDefaultFormat(TestCase):
67
def test_get_set_default_format(self):
68
old_default = bzrdir.format_registry.get('default')
69
private_default = old_default().repository_format.__class__
70
old_format = repository.RepositoryFormat.get_default_format()
71
self.assertTrue(isinstance(old_format, private_default))
72
def make_sample_bzrdir():
73
my_bzrdir = bzrdir.BzrDirMetaFormat1()
74
my_bzrdir.repository_format = SampleRepositoryFormat()
76
bzrdir.format_registry.remove('default')
77
bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
78
bzrdir.format_registry.set_default('sample')
79
# creating a repository should now create an instrumented dir.
81
# the default branch format is used by the meta dir format
82
# which is not the default bzrdir format at this point
83
dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
84
result = dir.create_repository()
85
self.assertEqual(result, 'A bzr repository dir')
87
bzrdir.format_registry.remove('default')
88
bzrdir.format_registry.remove('sample')
89
bzrdir.format_registry.register('default', old_default, '')
90
self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
94
class SampleRepositoryFormat(repository.RepositoryFormat):
97
this format is initializable, unsupported to aid in testing the
98
open and open(unsupported=True) routines.
101
def get_format_string(self):
102
"""See RepositoryFormat.get_format_string()."""
103
return "Sample .bzr repository format."
105
def initialize(self, a_bzrdir, shared=False):
106
"""Initialize a repository in a BzrDir"""
107
t = a_bzrdir.get_repository_transport(self)
108
t.put_bytes('format', self.get_format_string())
109
return 'A bzr repository dir'
111
def is_supported(self):
114
def open(self, a_bzrdir, _found=False):
115
return "opened repository."
118
class TestRepositoryFormat(TestCaseWithTransport):
119
"""Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
121
def test_find_format(self):
122
# is the right format object found for a repository?
123
# create a branch with a few known format objects.
124
# this is not quite the same as
125
self.build_tree(["foo/", "bar/"])
126
def check_format(format, url):
127
dir = format._matchingbzrdir.initialize(url)
128
format.initialize(dir)
129
t = get_transport(url)
130
found_format = repository.RepositoryFormat.find_format(dir)
131
self.failUnless(isinstance(found_format, format.__class__))
132
check_format(weaverepo.RepositoryFormat7(), "bar")
134
def test_find_format_no_repository(self):
135
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
136
self.assertRaises(errors.NoRepositoryPresent,
137
repository.RepositoryFormat.find_format,
140
def test_find_format_unknown_format(self):
141
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
142
SampleRepositoryFormat().initialize(dir)
143
self.assertRaises(UnknownFormatError,
144
repository.RepositoryFormat.find_format,
147
def test_register_unregister_format(self):
148
format = SampleRepositoryFormat()
150
dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
152
format.initialize(dir)
153
# register a format for it.
154
repository.RepositoryFormat.register_format(format)
155
# which repository.Open will refuse (not supported)
156
self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
157
# but open(unsupported) will work
158
self.assertEqual(format.open(dir), "opened repository.")
159
# unregister the format
160
repository.RepositoryFormat.unregister_format(format)
163
class TestFormat6(TestCaseWithTransport):
165
def test_attribute__fetch_order(self):
166
"""Weaves need topological data insertion."""
167
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
168
repo = weaverepo.RepositoryFormat6().initialize(control)
169
self.assertEqual('topological', repo._fetch_order)
171
def test_attribute__fetch_uses_deltas(self):
172
"""Weaves do not reuse deltas."""
173
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
174
repo = weaverepo.RepositoryFormat6().initialize(control)
175
self.assertEqual(False, repo._fetch_uses_deltas)
177
def test_attribute__fetch_reconcile(self):
178
"""Weave repositories need a reconcile after fetch."""
179
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
180
repo = weaverepo.RepositoryFormat6().initialize(control)
181
self.assertEqual(True, repo._fetch_reconcile)
183
def test_no_ancestry_weave(self):
184
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
185
repo = weaverepo.RepositoryFormat6().initialize(control)
186
# We no longer need to create the ancestry.weave file
187
# since it is *never* used.
188
self.assertRaises(NoSuchFile,
189
control.transport.get,
192
def test_supports_external_lookups(self):
193
control = bzrdir.BzrDirFormat6().initialize(self.get_url())
194
repo = weaverepo.RepositoryFormat6().initialize(control)
195
self.assertFalse(repo._format.supports_external_lookups)
198
class TestFormat7(TestCaseWithTransport):
200
def test_attribute__fetch_order(self):
201
"""Weaves need topological data insertion."""
202
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
203
repo = weaverepo.RepositoryFormat7().initialize(control)
204
self.assertEqual('topological', repo._fetch_order)
206
def test_attribute__fetch_uses_deltas(self):
207
"""Weaves do not reuse deltas."""
208
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
209
repo = weaverepo.RepositoryFormat7().initialize(control)
210
self.assertEqual(False, repo._fetch_uses_deltas)
212
def test_attribute__fetch_reconcile(self):
213
"""Weave repositories need a reconcile after fetch."""
214
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
215
repo = weaverepo.RepositoryFormat7().initialize(control)
216
self.assertEqual(True, repo._fetch_reconcile)
218
def test_disk_layout(self):
219
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
220
repo = weaverepo.RepositoryFormat7().initialize(control)
221
# in case of side effects of locking.
225
# format 'Bazaar-NG Repository format 7'
227
# inventory.weave == empty_weave
228
# empty revision-store directory
229
# empty weaves directory
230
t = control.get_repository_transport(None)
231
self.assertEqualDiff('Bazaar-NG Repository format 7',
232
t.get('format').read())
233
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
234
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
235
self.assertEqualDiff('# bzr weave file v5\n'
238
t.get('inventory.weave').read())
239
# Creating a file with id Foo:Bar results in a non-escaped file name on
241
control.create_branch()
242
tree = control.create_workingtree()
243
tree.add(['foo'], ['Foo:Bar'], ['file'])
244
tree.put_file_bytes_non_atomic('Foo:Bar', 'content\n')
245
tree.commit('first post', rev_id='first')
246
self.assertEqualDiff(
247
'# bzr weave file v5\n'
249
'1 7fe70820e08a1aac0ef224d9c66ab66831cc4ab1\n'
257
t.get('weaves/74/Foo%3ABar.weave').read())
259
def test_shared_disk_layout(self):
260
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
261
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
263
# format 'Bazaar-NG Repository format 7'
264
# inventory.weave == empty_weave
265
# empty revision-store directory
266
# empty weaves directory
267
# a 'shared-storage' marker file.
268
# lock is not present when unlocked
269
t = control.get_repository_transport(None)
270
self.assertEqualDiff('Bazaar-NG Repository format 7',
271
t.get('format').read())
272
self.assertEqualDiff('', t.get('shared-storage').read())
273
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
274
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
275
self.assertEqualDiff('# bzr weave file v5\n'
278
t.get('inventory.weave').read())
279
self.assertFalse(t.has('branch-lock'))
281
def test_creates_lockdir(self):
282
"""Make sure it appears to be controlled by a LockDir existence"""
283
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
284
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
285
t = control.get_repository_transport(None)
286
# TODO: Should check there is a 'lock' toplevel directory,
287
# regardless of contents
288
self.assertFalse(t.has('lock/held/info'))
291
self.assertTrue(t.has('lock/held/info'))
293
# unlock so we don't get a warning about failing to do so
296
def test_uses_lockdir(self):
297
"""repo format 7 actually locks on lockdir"""
298
base_url = self.get_url()
299
control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
300
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
301
t = control.get_repository_transport(None)
305
# make sure the same lock is created by opening it
306
repo = repository.Repository.open(base_url)
308
self.assertTrue(t.has('lock/held/info'))
310
self.assertFalse(t.has('lock/held/info'))
312
def test_shared_no_tree_disk_layout(self):
313
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
314
repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
315
repo.set_make_working_trees(False)
317
# format 'Bazaar-NG Repository format 7'
319
# inventory.weave == empty_weave
320
# empty revision-store directory
321
# empty weaves directory
322
# a 'shared-storage' marker file.
323
t = control.get_repository_transport(None)
324
self.assertEqualDiff('Bazaar-NG Repository format 7',
325
t.get('format').read())
326
## self.assertEqualDiff('', t.get('lock').read())
327
self.assertEqualDiff('', t.get('shared-storage').read())
328
self.assertEqualDiff('', t.get('no-working-trees').read())
329
repo.set_make_working_trees(True)
330
self.assertFalse(t.has('no-working-trees'))
331
self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
332
self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
333
self.assertEqualDiff('# bzr weave file v5\n'
336
t.get('inventory.weave').read())
338
def test_supports_external_lookups(self):
339
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
340
repo = weaverepo.RepositoryFormat7().initialize(control)
341
self.assertFalse(repo._format.supports_external_lookups)
344
class TestFormatKnit1(TestCaseWithTransport):
346
def test_attribute__fetch_order(self):
347
"""Knits need topological data insertion."""
348
repo = self.make_repository('.',
349
format=bzrdir.format_registry.get('knit')())
350
self.assertEqual('topological', repo._fetch_order)
352
def test_attribute__fetch_uses_deltas(self):
353
"""Knits reuse deltas."""
354
repo = self.make_repository('.',
355
format=bzrdir.format_registry.get('knit')())
356
self.assertEqual(True, repo._fetch_uses_deltas)
358
def test_disk_layout(self):
359
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
360
repo = knitrepo.RepositoryFormatKnit1().initialize(control)
361
# in case of side effects of locking.
365
# format 'Bazaar-NG Knit Repository Format 1'
366
# lock: is a directory
367
# inventory.weave == empty_weave
368
# empty revision-store directory
369
# empty weaves directory
370
t = control.get_repository_transport(None)
371
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
372
t.get('format').read())
373
# XXX: no locks left when unlocked at the moment
374
# self.assertEqualDiff('', t.get('lock').read())
375
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
377
# Check per-file knits.
378
branch = control.create_branch()
379
tree = control.create_workingtree()
380
tree.add(['foo'], ['Nasty-IdC:'], ['file'])
381
tree.put_file_bytes_non_atomic('Nasty-IdC:', '')
382
tree.commit('1st post', rev_id='foo')
383
self.assertHasKnit(t, 'knits/e8/%254easty-%2549d%2543%253a',
384
'\nfoo fulltext 0 81 :')
386
def assertHasKnit(self, t, knit_name, extra_content=''):
387
"""Assert that knit_name exists on t."""
388
self.assertEqualDiff('# bzr knit index 8\n' + extra_content,
389
t.get(knit_name + '.kndx').read())
391
def check_knits(self, t):
392
"""check knit content for a repository."""
393
self.assertHasKnit(t, 'inventory')
394
self.assertHasKnit(t, 'revisions')
395
self.assertHasKnit(t, 'signatures')
397
def test_shared_disk_layout(self):
398
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
399
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
401
# format 'Bazaar-NG Knit Repository Format 1'
402
# lock: is a directory
403
# inventory.weave == empty_weave
404
# empty revision-store directory
405
# empty weaves directory
406
# a 'shared-storage' marker file.
407
t = control.get_repository_transport(None)
408
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
409
t.get('format').read())
410
# XXX: no locks left when unlocked at the moment
411
# self.assertEqualDiff('', t.get('lock').read())
412
self.assertEqualDiff('', t.get('shared-storage').read())
413
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
416
def test_shared_no_tree_disk_layout(self):
417
control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
418
repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
419
repo.set_make_working_trees(False)
421
# format 'Bazaar-NG Knit Repository Format 1'
423
# inventory.weave == empty_weave
424
# empty revision-store directory
425
# empty weaves directory
426
# a 'shared-storage' marker file.
427
t = control.get_repository_transport(None)
428
self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
429
t.get('format').read())
430
# XXX: no locks left when unlocked at the moment
431
# self.assertEqualDiff('', t.get('lock').read())
432
self.assertEqualDiff('', t.get('shared-storage').read())
433
self.assertEqualDiff('', t.get('no-working-trees').read())
434
repo.set_make_working_trees(True)
435
self.assertFalse(t.has('no-working-trees'))
436
self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
439
def test_deserialise_sets_root_revision(self):
440
"""We must have a inventory.root.revision
442
Old versions of the XML5 serializer did not set the revision_id for
443
the whole inventory. So we grab the one from the expected text. Which
444
is valid when the api is not being abused.
446
repo = self.make_repository('.',
447
format=bzrdir.format_registry.get('knit')())
448
inv_xml = '<inventory format="5">\n</inventory>\n'
449
inv = repo.deserialise_inventory('test-rev-id', inv_xml)
450
self.assertEqual('test-rev-id', inv.root.revision)
452
def test_deserialise_uses_global_revision_id(self):
453
"""If it is set, then we re-use the global revision id"""
454
repo = self.make_repository('.',
455
format=bzrdir.format_registry.get('knit')())
456
inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
458
# Arguably, the deserialise_inventory should detect a mismatch, and
459
# raise an error, rather than silently using one revision_id over the
461
self.assertRaises(AssertionError, repo.deserialise_inventory,
462
'test-rev-id', inv_xml)
463
inv = repo.deserialise_inventory('other-rev-id', inv_xml)
464
self.assertEqual('other-rev-id', inv.root.revision)
466
def test_supports_external_lookups(self):
467
repo = self.make_repository('.',
468
format=bzrdir.format_registry.get('knit')())
469
self.assertFalse(repo._format.supports_external_lookups)
472
class DummyRepository(object):
473
"""A dummy repository for testing."""
477
def supports_rich_root(self):
481
class InterDummy(repository.InterRepository):
482
"""An inter-repository optimised code path for DummyRepository.
484
This is for use during testing where we use DummyRepository as repositories
485
so that none of the default regsitered inter-repository classes will
490
def is_compatible(repo_source, repo_target):
491
"""InterDummy is compatible with DummyRepository."""
492
return (isinstance(repo_source, DummyRepository) and
493
isinstance(repo_target, DummyRepository))
496
class TestInterRepository(TestCaseWithTransport):
498
def test_get_default_inter_repository(self):
499
# test that the InterRepository.get(repo_a, repo_b) probes
500
# for a inter_repo class where is_compatible(repo_a, repo_b) returns
501
# true and returns a default inter_repo otherwise.
502
# This also tests that the default registered optimised interrepository
503
# classes do not barf inappropriately when a surprising repository type
505
dummy_a = DummyRepository()
506
dummy_b = DummyRepository()
507
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
509
def assertGetsDefaultInterRepository(self, repo_a, repo_b):
510
"""Asserts that InterRepository.get(repo_a, repo_b) -> the default.
512
The effective default is now InterSameDataRepository because there is
513
no actual sane default in the presence of incompatible data models.
515
inter_repo = repository.InterRepository.get(repo_a, repo_b)
516
self.assertEqual(repository.InterSameDataRepository,
517
inter_repo.__class__)
518
self.assertEqual(repo_a, inter_repo.source)
519
self.assertEqual(repo_b, inter_repo.target)
521
def test_register_inter_repository_class(self):
522
# test that a optimised code path provider - a
523
# InterRepository subclass can be registered and unregistered
524
# and that it is correctly selected when given a repository
525
# pair that it returns true on for the is_compatible static method
527
dummy_a = DummyRepository()
528
dummy_b = DummyRepository()
529
repo = self.make_repository('.')
530
# hack dummies to look like repo somewhat.
531
dummy_a._serializer = repo._serializer
532
dummy_b._serializer = repo._serializer
533
repository.InterRepository.register_optimiser(InterDummy)
535
# we should get the default for something InterDummy returns False
537
self.assertFalse(InterDummy.is_compatible(dummy_a, repo))
538
self.assertGetsDefaultInterRepository(dummy_a, repo)
539
# and we should get an InterDummy for a pair it 'likes'
540
self.assertTrue(InterDummy.is_compatible(dummy_a, dummy_b))
541
inter_repo = repository.InterRepository.get(dummy_a, dummy_b)
542
self.assertEqual(InterDummy, inter_repo.__class__)
543
self.assertEqual(dummy_a, inter_repo.source)
544
self.assertEqual(dummy_b, inter_repo.target)
546
repository.InterRepository.unregister_optimiser(InterDummy)
547
# now we should get the default InterRepository object again.
548
self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
551
class TestInterWeaveRepo(TestCaseWithTransport):
553
def test_is_compatible_and_registered(self):
554
# InterWeaveRepo is compatible when either side
555
# is a format 5/6/7 branch
556
from bzrlib.repofmt import knitrepo, weaverepo
557
formats = [weaverepo.RepositoryFormat5(),
558
weaverepo.RepositoryFormat6(),
559
weaverepo.RepositoryFormat7()]
560
incompatible_formats = [weaverepo.RepositoryFormat4(),
561
knitrepo.RepositoryFormatKnit1(),
563
repo_a = self.make_repository('a')
564
repo_b = self.make_repository('b')
565
is_compatible = repository.InterWeaveRepo.is_compatible
566
for source in incompatible_formats:
567
# force incompatible left then right
568
repo_a._format = source
569
repo_b._format = formats[0]
570
self.assertFalse(is_compatible(repo_a, repo_b))
571
self.assertFalse(is_compatible(repo_b, repo_a))
572
for source in formats:
573
repo_a._format = source
574
for target in formats:
575
repo_b._format = target
576
self.assertTrue(is_compatible(repo_a, repo_b))
577
self.assertEqual(repository.InterWeaveRepo,
578
repository.InterRepository.get(repo_a,
582
class TestRepositoryConverter(TestCaseWithTransport):
584
def test_convert_empty(self):
585
t = get_transport(self.get_url('.'))
586
t.mkdir('repository')
587
repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
588
repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
589
target_format = knitrepo.RepositoryFormatKnit1()
590
converter = repository.CopyConverter(target_format)
591
pb = bzrlib.ui.ui_factory.nested_progress_bar()
593
converter.convert(repo, pb)
596
repo = repo_dir.open_repository()
597
self.assertTrue(isinstance(target_format, repo._format.__class__))
600
class TestMisc(TestCase):
602
def test_unescape_xml(self):
603
"""We get some kind of error when malformed entities are passed"""
604
self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;')
607
class TestRepositoryFormatKnit3(TestCaseWithTransport):
609
def test_attribute__fetch_order(self):
610
"""Knits need topological data insertion."""
611
format = bzrdir.BzrDirMetaFormat1()
612
format.repository_format = knitrepo.RepositoryFormatKnit3()
613
repo = self.make_repository('.', format=format)
614
self.assertEqual('topological', repo._fetch_order)
616
def test_attribute__fetch_uses_deltas(self):
617
"""Knits reuse deltas."""
618
format = bzrdir.BzrDirMetaFormat1()
619
format.repository_format = knitrepo.RepositoryFormatKnit3()
620
repo = self.make_repository('.', format=format)
621
self.assertEqual(True, repo._fetch_uses_deltas)
623
def test_convert(self):
624
"""Ensure the upgrade adds weaves for roots"""
625
format = bzrdir.BzrDirMetaFormat1()
626
format.repository_format = knitrepo.RepositoryFormatKnit1()
627
tree = self.make_branch_and_tree('.', format)
628
tree.commit("Dull commit", rev_id="dull")
629
revision_tree = tree.branch.repository.revision_tree('dull')
630
revision_tree.lock_read()
632
self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
633
revision_tree.inventory.root.file_id)
635
revision_tree.unlock()
636
format = bzrdir.BzrDirMetaFormat1()
637
format.repository_format = knitrepo.RepositoryFormatKnit3()
638
upgrade.Convert('.', format)
639
tree = workingtree.WorkingTree.open('.')
640
revision_tree = tree.branch.repository.revision_tree('dull')
641
revision_tree.lock_read()
643
revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
645
revision_tree.unlock()
646
tree.commit("Another dull commit", rev_id='dull2')
647
revision_tree = tree.branch.repository.revision_tree('dull2')
648
revision_tree.lock_read()
649
self.addCleanup(revision_tree.unlock)
650
self.assertEqual('dull', revision_tree.inventory.root.revision)
652
def test_supports_external_lookups(self):
653
format = bzrdir.BzrDirMetaFormat1()
654
format.repository_format = knitrepo.RepositoryFormatKnit3()
655
repo = self.make_repository('.', format=format)
656
self.assertFalse(repo._format.supports_external_lookups)
659
class TestWithBrokenRepo(TestCaseWithTransport):
660
"""These tests seem to be more appropriate as interface tests?"""
662
def make_broken_repository(self):
663
# XXX: This function is borrowed from Aaron's "Reconcile can fix bad
664
# parent references" branch which is due to land in bzr.dev soon. Once
665
# it does, this duplication should be removed.
666
repo = self.make_repository('broken-repo')
670
cleanups.append(repo.unlock)
671
repo.start_write_group()
672
cleanups.append(repo.commit_write_group)
673
# make rev1a: A well-formed revision, containing 'file1'
674
inv = inventory.Inventory(revision_id='rev1a')
675
inv.root.revision = 'rev1a'
676
self.add_file(repo, inv, 'file1', 'rev1a', [])
677
repo.add_inventory('rev1a', inv, [])
678
revision = _mod_revision.Revision('rev1a',
679
committer='jrandom@example.com', timestamp=0,
680
inventory_sha1='', timezone=0, message='foo', parent_ids=[])
681
repo.add_revision('rev1a',revision, inv)
683
# make rev1b, which has no Revision, but has an Inventory, and
685
inv = inventory.Inventory(revision_id='rev1b')
686
inv.root.revision = 'rev1b'
687
self.add_file(repo, inv, 'file1', 'rev1b', [])
688
repo.add_inventory('rev1b', inv, [])
690
# make rev2, with file1 and file2
692
# file1 has 'rev1b' as an ancestor, even though this is not
693
# mentioned by 'rev1a', making it an unreferenced ancestor
694
inv = inventory.Inventory()
695
self.add_file(repo, inv, 'file1', 'rev2', ['rev1a', 'rev1b'])
696
self.add_file(repo, inv, 'file2', 'rev2', [])
697
self.add_revision(repo, 'rev2', inv, ['rev1a'])
699
# make ghost revision rev1c
700
inv = inventory.Inventory()
701
self.add_file(repo, inv, 'file2', 'rev1c', [])
703
# make rev3 with file2
704
# file2 refers to 'rev1c', which is a ghost in this repository, so
705
# file2 cannot have rev1c as its ancestor.
706
inv = inventory.Inventory()
707
self.add_file(repo, inv, 'file2', 'rev3', ['rev1c'])
708
self.add_revision(repo, 'rev3', inv, ['rev1c'])
711
for cleanup in reversed(cleanups):
714
def add_revision(self, repo, revision_id, inv, parent_ids):
715
inv.revision_id = revision_id
716
inv.root.revision = revision_id
717
repo.add_inventory(revision_id, inv, parent_ids)
718
revision = _mod_revision.Revision(revision_id,
719
committer='jrandom@example.com', timestamp=0, inventory_sha1='',
720
timezone=0, message='foo', parent_ids=parent_ids)
721
repo.add_revision(revision_id,revision, inv)
723
def add_file(self, repo, inv, filename, revision, parents):
724
file_id = filename + '-id'
725
entry = inventory.InventoryFile(file_id, filename, 'TREE_ROOT')
726
entry.revision = revision
729
text_key = (file_id, revision)
730
parent_keys = [(file_id, parent) for parent in parents]
731
repo.texts.add_lines(text_key, parent_keys, ['line\n'])
733
def test_insert_from_broken_repo(self):
734
"""Inserting a data stream from a broken repository won't silently
735
corrupt the target repository.
737
broken_repo = self.make_broken_repository()
738
empty_repo = self.make_repository('empty-repo')
739
self.assertRaises(errors.RevisionNotPresent, empty_repo.fetch, broken_repo)
742
class TestRepositoryPackCollection(TestCaseWithTransport):
744
def get_format(self):
745
return bzrdir.format_registry.make_bzrdir('pack-0.92')
748
format = self.get_format()
749
repo = self.make_repository('.', format=format)
750
return repo._pack_collection
752
def test__max_pack_count(self):
753
"""The maximum pack count is a function of the number of revisions."""
754
# no revisions - one pack, so that we can have a revision free repo
755
# without it blowing up
756
packs = self.get_packs()
757
self.assertEqual(1, packs._max_pack_count(0))
758
# after that the sum of the digits, - check the first 1-9
759
self.assertEqual(1, packs._max_pack_count(1))
760
self.assertEqual(2, packs._max_pack_count(2))
761
self.assertEqual(3, packs._max_pack_count(3))
762
self.assertEqual(4, packs._max_pack_count(4))
763
self.assertEqual(5, packs._max_pack_count(5))
764
self.assertEqual(6, packs._max_pack_count(6))
765
self.assertEqual(7, packs._max_pack_count(7))
766
self.assertEqual(8, packs._max_pack_count(8))
767
self.assertEqual(9, packs._max_pack_count(9))
768
# check the boundary cases with two digits for the next decade
769
self.assertEqual(1, packs._max_pack_count(10))
770
self.assertEqual(2, packs._max_pack_count(11))
771
self.assertEqual(10, packs._max_pack_count(19))
772
self.assertEqual(2, packs._max_pack_count(20))
773
self.assertEqual(3, packs._max_pack_count(21))
774
# check some arbitrary big numbers
775
self.assertEqual(25, packs._max_pack_count(112894))
777
def test_pack_distribution_zero(self):
778
packs = self.get_packs()
779
self.assertEqual([0], packs.pack_distribution(0))
781
def test_ensure_loaded_unlocked(self):
782
packs = self.get_packs()
783
self.assertRaises(errors.ObjectNotLocked,
786
def test_pack_distribution_one_to_nine(self):
787
packs = self.get_packs()
788
self.assertEqual([1],
789
packs.pack_distribution(1))
790
self.assertEqual([1, 1],
791
packs.pack_distribution(2))
792
self.assertEqual([1, 1, 1],
793
packs.pack_distribution(3))
794
self.assertEqual([1, 1, 1, 1],
795
packs.pack_distribution(4))
796
self.assertEqual([1, 1, 1, 1, 1],
797
packs.pack_distribution(5))
798
self.assertEqual([1, 1, 1, 1, 1, 1],
799
packs.pack_distribution(6))
800
self.assertEqual([1, 1, 1, 1, 1, 1, 1],
801
packs.pack_distribution(7))
802
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1],
803
packs.pack_distribution(8))
804
self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1, 1],
805
packs.pack_distribution(9))
807
def test_pack_distribution_stable_at_boundaries(self):
808
"""When there are multi-rev packs the counts are stable."""
809
packs = self.get_packs()
811
self.assertEqual([10], packs.pack_distribution(10))
812
self.assertEqual([10, 1], packs.pack_distribution(11))
813
self.assertEqual([10, 10], packs.pack_distribution(20))
814
self.assertEqual([10, 10, 1], packs.pack_distribution(21))
816
self.assertEqual([100], packs.pack_distribution(100))
817
self.assertEqual([100, 1], packs.pack_distribution(101))
818
self.assertEqual([100, 10, 1], packs.pack_distribution(111))
819
self.assertEqual([100, 100], packs.pack_distribution(200))
820
self.assertEqual([100, 100, 1], packs.pack_distribution(201))
821
self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
823
def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
824
packs = self.get_packs()
825
existing_packs = [(2000, "big"), (9, "medium")]
826
# rev count - 2009 -> 2x1000 + 9x1
827
pack_operations = packs.plan_autopack_combinations(
828
existing_packs, [1000, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1])
829
self.assertEqual([], pack_operations)
831
def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
832
packs = self.get_packs()
833
existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
834
# rev count - 2010 -> 2x1000 + 1x10
835
pack_operations = packs.plan_autopack_combinations(
836
existing_packs, [1000, 1000, 10])
837
self.assertEqual([], pack_operations)
839
def test_plan_pack_operations_2010_combines_smallest_two(self):
840
packs = self.get_packs()
841
existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
843
# rev count - 2010 -> 2x1000 + 1x10 (3)
844
pack_operations = packs.plan_autopack_combinations(
845
existing_packs, [1000, 1000, 10])
846
self.assertEqual([[2, ["single2", "single1"]]], pack_operations)
848
def test_plan_pack_operations_creates_a_single_op(self):
849
packs = self.get_packs()
850
existing_packs = [(50, 'a'), (40, 'b'), (30, 'c'), (10, 'd'),
851
(10, 'e'), (6, 'f'), (4, 'g')]
852
# rev count 150 -> 1x100 and 5x10
853
# The two size 10 packs do not need to be touched. The 50, 40, 30 would
854
# be combined into a single 120 size pack, and the 6 & 4 would
855
# becombined into a size 10 pack. However, if we have to rewrite them,
856
# we save a pack file with no increased I/O by putting them into the
858
distribution = packs.pack_distribution(150)
859
pack_operations = packs.plan_autopack_combinations(existing_packs,
861
self.assertEqual([[130, ['a', 'b', 'c', 'f', 'g']]], pack_operations)
863
def test_all_packs_none(self):
864
format = self.get_format()
865
tree = self.make_branch_and_tree('.', format=format)
867
self.addCleanup(tree.unlock)
868
packs = tree.branch.repository._pack_collection
869
packs.ensure_loaded()
870
self.assertEqual([], packs.all_packs())
872
def test_all_packs_one(self):
873
format = self.get_format()
874
tree = self.make_branch_and_tree('.', format=format)
877
self.addCleanup(tree.unlock)
878
packs = tree.branch.repository._pack_collection
879
packs.ensure_loaded()
881
packs.get_pack_by_name(packs.names()[0])],
884
def test_all_packs_two(self):
885
format = self.get_format()
886
tree = self.make_branch_and_tree('.', format=format)
888
tree.commit('continue')
890
self.addCleanup(tree.unlock)
891
packs = tree.branch.repository._pack_collection
892
packs.ensure_loaded()
894
packs.get_pack_by_name(packs.names()[0]),
895
packs.get_pack_by_name(packs.names()[1]),
896
], packs.all_packs())
898
def test_get_pack_by_name(self):
899
format = self.get_format()
900
tree = self.make_branch_and_tree('.', format=format)
903
self.addCleanup(tree.unlock)
904
packs = tree.branch.repository._pack_collection
905
packs.ensure_loaded()
906
name = packs.names()[0]
907
pack_1 = packs.get_pack_by_name(name)
908
# the pack should be correctly initialised
909
sizes = packs._names[name]
910
rev_index = GraphIndex(packs._index_transport, name + '.rix', sizes[0])
911
inv_index = GraphIndex(packs._index_transport, name + '.iix', sizes[1])
912
txt_index = GraphIndex(packs._index_transport, name + '.tix', sizes[2])
913
sig_index = GraphIndex(packs._index_transport, name + '.six', sizes[3])
914
self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
915
name, rev_index, inv_index, txt_index, sig_index), pack_1)
916
# and the same instance should be returned on successive calls.
917
self.assertTrue(pack_1 is packs.get_pack_by_name(name))
920
class TestPack(TestCaseWithTransport):
921
"""Tests for the Pack object."""
923
def assertCurrentlyEqual(self, left, right):
924
self.assertTrue(left == right)
925
self.assertTrue(right == left)
926
self.assertFalse(left != right)
927
self.assertFalse(right != left)
929
def assertCurrentlyNotEqual(self, left, right):
930
self.assertFalse(left == right)
931
self.assertFalse(right == left)
932
self.assertTrue(left != right)
933
self.assertTrue(right != left)
935
def test___eq____ne__(self):
936
left = pack_repo.ExistingPack('', '', '', '', '', '')
937
right = pack_repo.ExistingPack('', '', '', '', '', '')
938
self.assertCurrentlyEqual(left, right)
939
# change all attributes and ensure equality changes as we do.
940
left.revision_index = 'a'
941
self.assertCurrentlyNotEqual(left, right)
942
right.revision_index = 'a'
943
self.assertCurrentlyEqual(left, right)
944
left.inventory_index = 'a'
945
self.assertCurrentlyNotEqual(left, right)
946
right.inventory_index = 'a'
947
self.assertCurrentlyEqual(left, right)
948
left.text_index = 'a'
949
self.assertCurrentlyNotEqual(left, right)
950
right.text_index = 'a'
951
self.assertCurrentlyEqual(left, right)
952
left.signature_index = 'a'
953
self.assertCurrentlyNotEqual(left, right)
954
right.signature_index = 'a'
955
self.assertCurrentlyEqual(left, right)
957
self.assertCurrentlyNotEqual(left, right)
959
self.assertCurrentlyEqual(left, right)
961
self.assertCurrentlyNotEqual(left, right)
962
right.transport = 'a'
963
self.assertCurrentlyEqual(left, right)
965
def test_file_name(self):
966
pack = pack_repo.ExistingPack('', 'a_name', '', '', '', '')
967
self.assertEqual('a_name.pack', pack.file_name())
970
class TestNewPack(TestCaseWithTransport):
971
"""Tests for pack_repo.NewPack."""
973
def test_new_instance_attributes(self):
974
upload_transport = self.get_transport('upload')
975
pack_transport = self.get_transport('pack')
976
index_transport = self.get_transport('index')
977
upload_transport.mkdir('.')
978
pack = pack_repo.NewPack(upload_transport, index_transport,
980
self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
981
self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
982
self.assertIsInstance(pack._hash, type(md5.new()))
983
self.assertTrue(pack.upload_transport is upload_transport)
984
self.assertTrue(pack.index_transport is index_transport)
985
self.assertTrue(pack.pack_transport is pack_transport)
986
self.assertEqual(None, pack.index_sizes)
987
self.assertEqual(20, len(pack.random_name))
988
self.assertIsInstance(pack.random_name, str)
989
self.assertIsInstance(pack.start_time, float)
992
class TestPacker(TestCaseWithTransport):
993
"""Tests for the packs repository Packer class."""
995
# To date, this class has been factored out and nothing new added to it;
996
# thus there are not yet any tests.
999
class TestInterDifferingSerializer(TestCaseWithTransport):
1001
def test_progress_bar(self):
1002
tree = self.make_branch_and_tree('tree')
1003
tree.commit('rev1', rev_id='rev-1')
1004
tree.commit('rev2', rev_id='rev-2')
1005
tree.commit('rev3', rev_id='rev-3')
1006
repo = self.make_repository('repo')
1007
inter_repo = repository.InterDifferingSerializer(
1008
tree.branch.repository, repo)
1009
pb = progress.InstrumentedProgress(to_file=StringIO())
1010
pb.never_throttle = True
1011
inter_repo.fetch('rev-1', pb)
1012
self.assertEqual('Transferring revisions', pb.last_msg)
1013
self.assertEqual(1, pb.last_cnt)
1014
self.assertEqual(1, pb.last_total)
1015
inter_repo.fetch('rev-3', pb)
1016
self.assertEqual(2, pb.last_cnt)
1017
self.assertEqual(2, pb.last_total)