/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2220.2.2 by Martin Pool
Add tag command and basic implementation
1
# Copyright (C) 2006, 2007 Canonical Ltd
1685.1.63 by Martin Pool
Small Transport fixups
2
#
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
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.
1685.1.63 by Martin Pool
Small Transport fixups
7
#
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
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.
1685.1.63 by Martin Pool
Small Transport fixups
12
#
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
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
16
17
"""Tests for the Repository facility that are not interface tests.
18
19
For interface tests see tests/repository_implementations/*.py.
20
21
For concrete class tests see this file, and for storage formats tests
22
also see this file.
23
"""
24
2592.3.193 by Robert Collins
Move hash tracking of new packs into NewPack.
25
import md5
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
26
from stat import S_ISDIR
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
27
from StringIO import StringIO
28
1556.1.4 by Robert Collins
Add a new format for what will become knit, and the surrounding logic to upgrade repositories within metadirs, and tests for the same.
29
import bzrlib
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
30
from bzrlib.errors import (NotBranchError,
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
31
                           NoSuchFile,
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
32
                           UnknownFormatError,
33
                           UnsupportedFormatError,
34
                           )
2592.3.192 by Robert Collins
Move new revision index management to NewPack.
35
from bzrlib.index import GraphIndex, InMemoryGraphIndex
2241.1.1 by Martin Pool
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary
36
from bzrlib.repository import RepositoryFormat
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
37
from bzrlib.smart import server
2670.3.5 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
38
from bzrlib.tests import (
39
    TestCase,
40
    TestCaseWithTransport,
41
    test_knit,
42
    )
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
43
from bzrlib.transport import get_transport
44
from bzrlib.transport.memory import MemoryServer
2535.3.53 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
45
from bzrlib.util import bencode
2241.1.1 by Martin Pool
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary
46
from bzrlib import (
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
47
    bzrdir,
48
    errors,
2535.3.57 by Andrew Bennetts
Perform some sanity checking of data streams rather than blindly inserting them into our repository.
49
    inventory,
3146.6.1 by Aaron Bentley
InterDifferingSerializer shows a progress bar
50
    progress,
2241.1.1 by Martin Pool
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary
51
    repository,
2535.3.57 by Andrew Bennetts
Perform some sanity checking of data streams rather than blindly inserting them into our repository.
52
    revision as _mod_revision,
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
53
    symbol_versioning,
2241.1.1 by Martin Pool
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary
54
    upgrade,
55
    workingtree,
56
    )
2592.3.173 by Robert Collins
Basic implementation of all_packs.
57
from bzrlib.repofmt import knitrepo, weaverepo, pack_repo
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
58
59
60
class TestDefaultFormat(TestCase):
61
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
62
    def test_get_set_default_format(self):
2204.5.3 by Aaron Bentley
zap old repository default handling
63
        old_default = bzrdir.format_registry.get('default')
64
        private_default = old_default().repository_format.__class__
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
65
        old_format = repository.RepositoryFormat.get_default_format()
1910.2.33 by Aaron Bentley
Fix default format test
66
        self.assertTrue(isinstance(old_format, private_default))
2204.5.3 by Aaron Bentley
zap old repository default handling
67
        def make_sample_bzrdir():
68
            my_bzrdir = bzrdir.BzrDirMetaFormat1()
69
            my_bzrdir.repository_format = SampleRepositoryFormat()
70
            return my_bzrdir
71
        bzrdir.format_registry.remove('default')
72
        bzrdir.format_registry.register('sample', make_sample_bzrdir, '')
73
        bzrdir.format_registry.set_default('sample')
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
74
        # creating a repository should now create an instrumented dir.
75
        try:
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
76
            # the default branch format is used by the meta dir format
77
            # which is not the default bzrdir format at this point
1685.1.63 by Martin Pool
Small Transport fixups
78
            dir = bzrdir.BzrDirMetaFormat1().initialize('memory:///')
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
79
            result = dir.create_repository()
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
80
            self.assertEqual(result, 'A bzr repository dir')
2241.1.1 by Martin Pool
Change RepositoryFormat to use a Registry rather than ad-hoc dictionary
81
        finally:
2204.5.3 by Aaron Bentley
zap old repository default handling
82
            bzrdir.format_registry.remove('default')
2363.5.14 by Aaron Bentley
Prevent repository.get_set_default_format from corrupting inventory
83
            bzrdir.format_registry.remove('sample')
2204.5.3 by Aaron Bentley
zap old repository default handling
84
            bzrdir.format_registry.register('default', old_default, '')
85
        self.assertIsInstance(repository.RepositoryFormat.get_default_format(),
86
                              old_format.__class__)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
87
88
89
class SampleRepositoryFormat(repository.RepositoryFormat):
90
    """A sample format
91
92
    this format is initializable, unsupported to aid in testing the 
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
93
    open and open(unsupported=True) routines.
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
94
    """
95
96
    def get_format_string(self):
97
        """See RepositoryFormat.get_format_string()."""
98
        return "Sample .bzr repository format."
99
1534.6.1 by Robert Collins
allow API creation of shared repositories
100
    def initialize(self, a_bzrdir, shared=False):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
101
        """Initialize a repository in a BzrDir"""
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
102
        t = a_bzrdir.get_repository_transport(self)
1955.3.13 by John Arbash Meinel
Run the full test suite, and fix up any deprecation warnings.
103
        t.put_bytes('format', self.get_format_string())
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
104
        return 'A bzr repository dir'
105
106
    def is_supported(self):
107
        return False
108
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
109
    def open(self, a_bzrdir, _found=False):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
110
        return "opened repository."
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
111
112
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
113
class TestRepositoryFormat(TestCaseWithTransport):
114
    """Tests for the Repository format detection used by the bzr meta dir facility.BzrBranchFormat facility."""
115
116
    def test_find_format(self):
117
        # is the right format object found for a repository?
118
        # create a branch with a few known format objects.
119
        # this is not quite the same as 
120
        self.build_tree(["foo/", "bar/"])
121
        def check_format(format, url):
122
            dir = format._matchingbzrdir.initialize(url)
123
            format.initialize(dir)
124
            t = get_transport(url)
125
            found_format = repository.RepositoryFormat.find_format(dir)
126
            self.failUnless(isinstance(found_format, format.__class__))
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
127
        check_format(weaverepo.RepositoryFormat7(), "bar")
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
128
        
129
    def test_find_format_no_repository(self):
130
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
131
        self.assertRaises(errors.NoRepositoryPresent,
132
                          repository.RepositoryFormat.find_format,
133
                          dir)
134
135
    def test_find_format_unknown_format(self):
136
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
137
        SampleRepositoryFormat().initialize(dir)
138
        self.assertRaises(UnknownFormatError,
139
                          repository.RepositoryFormat.find_format,
140
                          dir)
141
142
    def test_register_unregister_format(self):
143
        format = SampleRepositoryFormat()
144
        # make a control dir
145
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
146
        # make a repo
147
        format.initialize(dir)
148
        # register a format for it.
149
        repository.RepositoryFormat.register_format(format)
150
        # which repository.Open will refuse (not supported)
151
        self.assertRaises(UnsupportedFormatError, repository.Repository.open, self.get_url())
152
        # but open(unsupported) will work
153
        self.assertEqual(format.open(dir), "opened repository.")
154
        # unregister the format
155
        repository.RepositoryFormat.unregister_format(format)
156
157
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
158
class TestFormat6(TestCaseWithTransport):
159
160
    def test_no_ancestry_weave(self):
161
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
162
        repo = weaverepo.RepositoryFormat6().initialize(control)
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
163
        # We no longer need to create the ancestry.weave file
164
        # since it is *never* used.
165
        self.assertRaises(NoSuchFile,
166
                          control.transport.get,
167
                          'ancestry.weave')
168
2818.4.2 by Robert Collins
Review feedback.
169
    def test_exposed_versioned_files_are_marked_dirty(self):
170
        control = bzrdir.BzrDirFormat6().initialize(self.get_url())
171
        repo = weaverepo.RepositoryFormat6().initialize(control)
172
        repo.lock_write()
173
        inv = repo.get_inventory_weave()
174
        repo.unlock()
175
        self.assertRaises(errors.OutSideTransaction,
176
            inv.add_lines, 'foo', [], [])
177
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
178
179
class TestFormat7(TestCaseWithTransport):
180
    
181
    def test_disk_layout(self):
182
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
183
        repo = weaverepo.RepositoryFormat7().initialize(control)
1534.5.3 by Robert Collins
Make format 4/5/6 branches share a single LockableFiles instance across wt/branch/repository.
184
        # in case of side effects of locking.
185
        repo.lock_write()
186
        repo.unlock()
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
187
        # we want:
188
        # format 'Bazaar-NG Repository format 7'
189
        # lock ''
190
        # inventory.weave == empty_weave
191
        # empty revision-store directory
192
        # empty weaves directory
193
        t = control.get_repository_transport(None)
194
        self.assertEqualDiff('Bazaar-NG Repository format 7',
195
                             t.get('format').read())
196
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
197
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
198
        self.assertEqualDiff('# bzr weave file v5\n'
199
                             'w\n'
200
                             'W\n',
201
                             t.get('inventory.weave').read())
1534.6.1 by Robert Collins
allow API creation of shared repositories
202
203
    def test_shared_disk_layout(self):
204
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
205
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
1534.6.1 by Robert Collins
allow API creation of shared repositories
206
        # we want:
207
        # format 'Bazaar-NG Repository format 7'
208
        # inventory.weave == empty_weave
209
        # empty revision-store directory
210
        # empty weaves directory
211
        # a 'shared-storage' marker file.
1553.5.49 by Martin Pool
Use LockDirs for repo format 7
212
        # lock is not present when unlocked
1534.6.1 by Robert Collins
allow API creation of shared repositories
213
        t = control.get_repository_transport(None)
214
        self.assertEqualDiff('Bazaar-NG Repository format 7',
215
                             t.get('format').read())
216
        self.assertEqualDiff('', t.get('shared-storage').read())
217
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
218
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
219
        self.assertEqualDiff('# bzr weave file v5\n'
220
                             'w\n'
221
                             'W\n',
222
                             t.get('inventory.weave').read())
1553.5.49 by Martin Pool
Use LockDirs for repo format 7
223
        self.assertFalse(t.has('branch-lock'))
224
1553.5.56 by Martin Pool
Format 7 repo now uses LockDir!
225
    def test_creates_lockdir(self):
1553.5.49 by Martin Pool
Use LockDirs for repo format 7
226
        """Make sure it appears to be controlled by a LockDir existence"""
227
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
228
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
1553.5.49 by Martin Pool
Use LockDirs for repo format 7
229
        t = control.get_repository_transport(None)
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
230
        # TODO: Should check there is a 'lock' toplevel directory, 
231
        # regardless of contents
232
        self.assertFalse(t.has('lock/held/info'))
1553.5.49 by Martin Pool
Use LockDirs for repo format 7
233
        repo.lock_write()
1658.1.4 by Martin Pool
Quieten warning from TestFormat7.test_creates_lockdir about failing to unlock
234
        try:
235
            self.assertTrue(t.has('lock/held/info'))
236
        finally:
237
            # unlock so we don't get a warning about failing to do so
238
            repo.unlock()
1553.5.56 by Martin Pool
Format 7 repo now uses LockDir!
239
240
    def test_uses_lockdir(self):
241
        """repo format 7 actually locks on lockdir"""
242
        base_url = self.get_url()
243
        control = bzrdir.BzrDirMetaFormat1().initialize(base_url)
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
244
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
1553.5.56 by Martin Pool
Format 7 repo now uses LockDir!
245
        t = control.get_repository_transport(None)
246
        repo.lock_write()
247
        repo.unlock()
248
        del repo
249
        # make sure the same lock is created by opening it
250
        repo = repository.Repository.open(base_url)
251
        repo.lock_write()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
252
        self.assertTrue(t.has('lock/held/info'))
1553.5.56 by Martin Pool
Format 7 repo now uses LockDir!
253
        repo.unlock()
1553.5.58 by Martin Pool
Change LockDirs to format "lock-name/held/info"
254
        self.assertFalse(t.has('lock/held/info'))
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
255
256
    def test_shared_no_tree_disk_layout(self):
257
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
258
        repo = weaverepo.RepositoryFormat7().initialize(control, shared=True)
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
259
        repo.set_make_working_trees(False)
260
        # we want:
261
        # format 'Bazaar-NG Repository format 7'
262
        # lock ''
263
        # inventory.weave == empty_weave
264
        # empty revision-store directory
265
        # empty weaves directory
266
        # a 'shared-storage' marker file.
267
        t = control.get_repository_transport(None)
268
        self.assertEqualDiff('Bazaar-NG Repository format 7',
269
                             t.get('format').read())
1553.5.56 by Martin Pool
Format 7 repo now uses LockDir!
270
        ## self.assertEqualDiff('', t.get('lock').read())
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
271
        self.assertEqualDiff('', t.get('shared-storage').read())
272
        self.assertEqualDiff('', t.get('no-working-trees').read())
273
        repo.set_make_working_trees(True)
274
        self.assertFalse(t.has('no-working-trees'))
275
        self.assertTrue(S_ISDIR(t.stat('revision-store').st_mode))
276
        self.assertTrue(S_ISDIR(t.stat('weaves').st_mode))
277
        self.assertEqualDiff('# bzr weave file v5\n'
278
                             'w\n'
279
                             'W\n',
280
                             t.get('inventory.weave').read())
1534.1.27 by Robert Collins
Start InterRepository with InterRepository.get.
281
2818.4.2 by Robert Collins
Review feedback.
282
    def test_exposed_versioned_files_are_marked_dirty(self):
283
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
284
        repo = weaverepo.RepositoryFormat7().initialize(control)
285
        repo.lock_write()
286
        inv = repo.get_inventory_weave()
287
        repo.unlock()
288
        self.assertRaises(errors.OutSideTransaction,
289
            inv.add_lines, 'foo', [], [])
290
1534.1.27 by Robert Collins
Start InterRepository with InterRepository.get.
291
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
292
class TestFormatKnit1(TestCaseWithTransport):
293
    
294
    def test_disk_layout(self):
295
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.6 by Martin Pool
Move Knit repositories into the submodule bzrlib.repofmt.knitrepo and
296
        repo = knitrepo.RepositoryFormatKnit1().initialize(control)
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
297
        # in case of side effects of locking.
298
        repo.lock_write()
299
        repo.unlock()
300
        # we want:
301
        # format 'Bazaar-NG Knit Repository Format 1'
1553.5.62 by Martin Pool
Add tests that MetaDir repositories use LockDirs
302
        # lock: is a directory
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
303
        # inventory.weave == empty_weave
304
        # empty revision-store directory
305
        # empty weaves directory
306
        t = control.get_repository_transport(None)
307
        self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
308
                             t.get('format').read())
1553.5.57 by Martin Pool
[merge] sync from bzr.dev
309
        # XXX: no locks left when unlocked at the moment
310
        # self.assertEqualDiff('', t.get('lock').read())
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
311
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
312
        self.check_knits(t)
313
1654.1.3 by Robert Collins
Refactor repository knit tests slightly to remove duplication - add a assertHasKnit method.
314
    def assertHasKnit(self, t, knit_name):
315
        """Assert that knit_name exists on t."""
1666.1.7 by Robert Collins
Update repository format check to read knit correct header
316
        self.assertEqualDiff('# bzr knit index 8\n',
1654.1.3 by Robert Collins
Refactor repository knit tests slightly to remove duplication - add a assertHasKnit method.
317
                             t.get(knit_name + '.kndx').read())
318
        # no default content
319
        self.assertTrue(t.has(knit_name + '.knit'))
320
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
321
    def check_knits(self, t):
322
        """check knit content for a repository."""
1654.1.3 by Robert Collins
Refactor repository knit tests slightly to remove duplication - add a assertHasKnit method.
323
        self.assertHasKnit(t, 'inventory')
324
        self.assertHasKnit(t, 'revisions')
325
        self.assertHasKnit(t, 'signatures')
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
326
327
    def test_shared_disk_layout(self):
328
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.6 by Martin Pool
Move Knit repositories into the submodule bzrlib.repofmt.knitrepo and
329
        repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
330
        # we want:
331
        # format 'Bazaar-NG Knit Repository Format 1'
1553.5.62 by Martin Pool
Add tests that MetaDir repositories use LockDirs
332
        # lock: is a directory
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
333
        # inventory.weave == empty_weave
334
        # empty revision-store directory
335
        # empty weaves directory
336
        # a 'shared-storage' marker file.
337
        t = control.get_repository_transport(None)
338
        self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
339
                             t.get('format').read())
1553.5.57 by Martin Pool
[merge] sync from bzr.dev
340
        # XXX: no locks left when unlocked at the moment
341
        # self.assertEqualDiff('', t.get('lock').read())
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
342
        self.assertEqualDiff('', t.get('shared-storage').read())
343
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
344
        self.check_knits(t)
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
345
346
    def test_shared_no_tree_disk_layout(self):
347
        control = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
2241.1.6 by Martin Pool
Move Knit repositories into the submodule bzrlib.repofmt.knitrepo and
348
        repo = knitrepo.RepositoryFormatKnit1().initialize(control, shared=True)
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
349
        repo.set_make_working_trees(False)
350
        # we want:
351
        # format 'Bazaar-NG Knit Repository Format 1'
352
        # lock ''
353
        # inventory.weave == empty_weave
354
        # empty revision-store directory
355
        # empty weaves directory
356
        # a 'shared-storage' marker file.
357
        t = control.get_repository_transport(None)
358
        self.assertEqualDiff('Bazaar-NG Knit Repository Format 1',
359
                             t.get('format').read())
1553.5.57 by Martin Pool
[merge] sync from bzr.dev
360
        # XXX: no locks left when unlocked at the moment
361
        # self.assertEqualDiff('', t.get('lock').read())
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
362
        self.assertEqualDiff('', t.get('shared-storage').read())
363
        self.assertEqualDiff('', t.get('no-working-trees').read())
364
        repo.set_make_working_trees(True)
365
        self.assertFalse(t.has('no-working-trees'))
366
        self.assertTrue(S_ISDIR(t.stat('knits').st_mode))
1563.2.35 by Robert Collins
cleanup deprecation warnings and finish conversion so the inventory is knit based too.
367
        self.check_knits(t)
1556.1.3 by Robert Collins
Rearrangment of Repository logic to be less type code driven, and bugfix InterRepository.missing_revision_ids
368
2818.4.2 by Robert Collins
Review feedback.
369
    def test_exposed_versioned_files_are_marked_dirty(self):
370
        format = bzrdir.BzrDirMetaFormat1()
371
        format.repository_format = knitrepo.RepositoryFormatKnit1()
372
        repo = self.make_repository('.', format=format)
373
        repo.lock_write()
374
        inv = repo.get_inventory_weave()
375
        repo.unlock()
376
        self.assertRaises(errors.OutSideTransaction,
377
            inv.add_lines, 'foo', [], [])
378
2917.2.1 by John Arbash Meinel
Fix bug #152360. The xml5 serializer should be using
379
    def test_deserialise_sets_root_revision(self):
380
        """We must have a inventory.root.revision
381
382
        Old versions of the XML5 serializer did not set the revision_id for
383
        the whole inventory. So we grab the one from the expected text. Which
384
        is valid when the api is not being abused.
385
        """
386
        repo = self.make_repository('.',
387
                format=bzrdir.format_registry.get('knit')())
388
        inv_xml = '<inventory format="5">\n</inventory>\n'
389
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
390
        self.assertEqual('test-rev-id', inv.root.revision)
391
392
    def test_deserialise_uses_global_revision_id(self):
393
        """If it is set, then we re-use the global revision id"""
394
        repo = self.make_repository('.',
395
                format=bzrdir.format_registry.get('knit')())
396
        inv_xml = ('<inventory format="5" revision_id="other-rev-id">\n'
397
                   '</inventory>\n')
398
        # Arguably, the deserialise_inventory should detect a mismatch, and
399
        # raise an error, rather than silently using one revision_id over the
400
        # other.
401
        inv = repo.deserialise_inventory('test-rev-id', inv_xml)
402
        self.assertEqual('other-rev-id', inv.root.revision)
403
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
404
2535.3.53 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
405
class KnitRepositoryStreamTests(test_knit.KnitTests):
406
    """Tests for knitrepo._get_stream_as_bytes."""
407
408
    def test_get_stream_as_bytes(self):
409
        # Make a simple knit
410
        k1 = self.make_test_knit()
411
        k1.add_lines('text-a', [], test_knit.split_lines(test_knit.TEXT_1))
412
        
413
        # Serialise it, check the output.
414
        bytes = knitrepo._get_stream_as_bytes(k1, ['text-a'])
415
        data = bencode.bdecode(bytes)
416
        format, record = data
417
        self.assertEqual('knit-plain', format)
418
        self.assertEqual(['text-a', ['fulltext'], []], record[:3])
419
        self.assertRecordContentEqual(k1, 'text-a', record[3])
420
421
    def test_get_stream_as_bytes_all(self):
422
        """Get a serialised data stream for all the records in a knit.
423
424
        Much like test_get_stream_all, except for get_stream_as_bytes.
425
        """
426
        k1 = self.make_test_knit()
427
        # Insert the same data as BasicKnitTests.test_knit_join, as they seem
428
        # to cover a range of cases (no parents, one parent, multiple parents).
429
        test_data = [
430
            ('text-a', [], test_knit.TEXT_1),
431
            ('text-b', ['text-a'], test_knit.TEXT_1),
432
            ('text-c', [], test_knit.TEXT_1),
433
            ('text-d', ['text-c'], test_knit.TEXT_1),
434
            ('text-m', ['text-b', 'text-d'], test_knit.TEXT_1),
435
           ]
3023.2.3 by Martin Pool
Update tests for new ordering of results from get_data_stream - the order is not defined by the interface, but is stable
436
        # This test is actually a bit strict as the order in which they're
437
        # returned is not defined.  This matches the current (deterministic)
438
        # behaviour.
2535.3.53 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
439
        expected_data_list = [
440
            # version, options, parents
441
            ('text-a', ['fulltext'], []),
442
            ('text-b', ['line-delta'], ['text-a']),
3023.2.3 by Martin Pool
Update tests for new ordering of results from get_data_stream - the order is not defined by the interface, but is stable
443
            ('text-m', ['line-delta'], ['text-b', 'text-d']),
2535.3.53 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
444
            ('text-c', ['fulltext'], []),
445
            ('text-d', ['line-delta'], ['text-c']),
446
            ]
447
        for version_id, parents, lines in test_data:
448
            k1.add_lines(version_id, parents, test_knit.split_lines(lines))
449
450
        bytes = knitrepo._get_stream_as_bytes(
3023.2.3 by Martin Pool
Update tests for new ordering of results from get_data_stream - the order is not defined by the interface, but is stable
451
            k1, ['text-a', 'text-b', 'text-m', 'text-c', 'text-d', ])
2535.3.53 by Andrew Bennetts
Remove get_stream_as_bytes from KnitVersionedFile's API, make it a function in knitrepo.py instead.
452
453
        data = bencode.bdecode(bytes)
454
        format = data.pop(0)
455
        self.assertEqual('knit-plain', format)
456
457
        for expected, actual in zip(expected_data_list, data):
458
            expected_version = expected[0]
459
            expected_options = expected[1]
460
            expected_parents = expected[2]
461
            version, options, parents, bytes = actual
462
            self.assertEqual(expected_version, version)
463
            self.assertEqual(expected_options, options)
464
            self.assertEqual(expected_parents, parents)
465
            self.assertRecordContentEqual(k1, version, bytes)
466
467
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
468
class DummyRepository(object):
469
    """A dummy repository for testing."""
470
471
    _serializer = None
472
473
    def supports_rich_root(self):
474
        return False
475
476
477
class InterDummy(repository.InterRepository):
478
    """An inter-repository optimised code path for DummyRepository.
479
480
    This is for use during testing where we use DummyRepository as repositories
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
481
    so that none of the default regsitered inter-repository classes will
2818.4.2 by Robert Collins
Review feedback.
482
    MATCH.
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
483
    """
484
485
    @staticmethod
486
    def is_compatible(repo_source, repo_target):
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
487
        """InterDummy is compatible with DummyRepository."""
488
        return (isinstance(repo_source, DummyRepository) and 
489
            isinstance(repo_target, DummyRepository))
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
490
491
1534.1.27 by Robert Collins
Start InterRepository with InterRepository.get.
492
class TestInterRepository(TestCaseWithTransport):
493
494
    def test_get_default_inter_repository(self):
495
        # test that the InterRepository.get(repo_a, repo_b) probes
496
        # for a inter_repo class where is_compatible(repo_a, repo_b) returns
497
        # true and returns a default inter_repo otherwise.
498
        # This also tests that the default registered optimised interrepository
499
        # classes do not barf inappropriately when a surprising repository type
500
        # is handed to them.
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
501
        dummy_a = DummyRepository()
502
        dummy_b = DummyRepository()
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
503
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
504
505
    def assertGetsDefaultInterRepository(self, repo_a, repo_b):
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
506
        """Asserts that InterRepository.get(repo_a, repo_b) -> the default.
507
        
508
        The effective default is now InterSameDataRepository because there is
509
        no actual sane default in the presence of incompatible data models.
510
        """
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
511
        inter_repo = repository.InterRepository.get(repo_a, repo_b)
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
512
        self.assertEqual(repository.InterSameDataRepository,
1534.1.27 by Robert Collins
Start InterRepository with InterRepository.get.
513
                         inter_repo.__class__)
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
514
        self.assertEqual(repo_a, inter_repo.source)
515
        self.assertEqual(repo_b, inter_repo.target)
516
517
    def test_register_inter_repository_class(self):
518
        # test that a optimised code path provider - a
519
        # InterRepository subclass can be registered and unregistered
520
        # and that it is correctly selected when given a repository
521
        # pair that it returns true on for the is_compatible static method
522
        # check
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
523
        dummy_a = DummyRepository()
524
        dummy_b = DummyRepository()
525
        repo = self.make_repository('.')
526
        # hack dummies to look like repo somewhat.
527
        dummy_a._serializer = repo._serializer
528
        dummy_b._serializer = repo._serializer
529
        repository.InterRepository.register_optimiser(InterDummy)
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
530
        try:
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
531
            # we should get the default for something InterDummy returns False
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
532
            # to
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
533
            self.assertFalse(InterDummy.is_compatible(dummy_a, repo))
534
            self.assertGetsDefaultInterRepository(dummy_a, repo)
535
            # and we should get an InterDummy for a pair it 'likes'
536
            self.assertTrue(InterDummy.is_compatible(dummy_a, dummy_b))
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
537
            inter_repo = repository.InterRepository.get(dummy_a, dummy_b)
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
538
            self.assertEqual(InterDummy, inter_repo.__class__)
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
539
            self.assertEqual(dummy_a, inter_repo.source)
540
            self.assertEqual(dummy_b, inter_repo.target)
541
        finally:
2305.2.3 by Andrew Bennetts
Bring across test_repository improvements from the hpss branch to fix the last test failures.
542
            repository.InterRepository.unregister_optimiser(InterDummy)
1534.1.28 by Robert Collins
Allow for optimised InterRepository selection.
543
        # now we should get the default InterRepository object again.
544
        self.assertGetsDefaultInterRepository(dummy_a, dummy_b)
1534.1.33 by Robert Collins
Move copy_content_into into InterRepository and InterWeaveRepo, and disable the default codepath test as we have optimised paths for all current combinations.
545
2241.1.17 by Martin Pool
Restore old InterWeave tests
546
547
class TestInterWeaveRepo(TestCaseWithTransport):
548
549
    def test_is_compatible_and_registered(self):
550
        # InterWeaveRepo is compatible when either side
551
        # is a format 5/6/7 branch
2241.1.20 by mbp at sourcefrog
update tests for new locations of weave repos
552
        from bzrlib.repofmt import knitrepo, weaverepo
553
        formats = [weaverepo.RepositoryFormat5(),
554
                   weaverepo.RepositoryFormat6(),
555
                   weaverepo.RepositoryFormat7()]
556
        incompatible_formats = [weaverepo.RepositoryFormat4(),
557
                                knitrepo.RepositoryFormatKnit1(),
2241.1.17 by Martin Pool
Restore old InterWeave tests
558
                                ]
559
        repo_a = self.make_repository('a')
560
        repo_b = self.make_repository('b')
561
        is_compatible = repository.InterWeaveRepo.is_compatible
562
        for source in incompatible_formats:
563
            # force incompatible left then right
564
            repo_a._format = source
565
            repo_b._format = formats[0]
566
            self.assertFalse(is_compatible(repo_a, repo_b))
567
            self.assertFalse(is_compatible(repo_b, repo_a))
568
        for source in formats:
569
            repo_a._format = source
570
            for target in formats:
571
                repo_b._format = target
572
                self.assertTrue(is_compatible(repo_a, repo_b))
573
        self.assertEqual(repository.InterWeaveRepo,
574
                         repository.InterRepository.get(repo_a,
575
                                                        repo_b).__class__)
576
1556.1.4 by Robert Collins
Add a new format for what will become knit, and the surrounding logic to upgrade repositories within metadirs, and tests for the same.
577
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
578
class TestInterRemoteToOther(TestCaseWithTransport):
579
580
    def make_remote_repository(self, path, backing_format=None):
581
        """Make a RemoteRepository object backed by a real repository that will
582
        be created at the given path."""
583
        self.make_repository(path, format=backing_format)
584
        smart_server = server.SmartTCPServer_for_testing()
585
        smart_server.setUp()
586
        remote_transport = get_transport(smart_server.get_url()).clone(path)
587
        self.addCleanup(smart_server.tearDown)
588
        remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
589
        remote_repo = remote_bzrdir.open_repository()
590
        return remote_repo
591
592
    def test_is_compatible_same_format(self):
593
        """InterRemoteToOther is compatible with a remote repository and a
594
        second repository that have the same format."""
595
        local_repo = self.make_repository('local')
596
        remote_repo = self.make_remote_repository('remote')
2535.3.62 by Andrew Bennetts
Cosmetic changes.
597
        is_compatible = repository.InterRemoteToOther.is_compatible
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
598
        self.assertTrue(
2535.3.62 by Andrew Bennetts
Cosmetic changes.
599
            is_compatible(remote_repo, local_repo),
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
600
            "InterRemoteToOther(%r, %r) is false" % (remote_repo, local_repo))
601
          
602
    def test_is_incompatible_different_format(self):
603
        local_repo = self.make_repository('local', 'dirstate')
604
        remote_repo = self.make_remote_repository('a', 'dirstate-with-subtree')
2535.3.62 by Andrew Bennetts
Cosmetic changes.
605
        is_compatible = repository.InterRemoteToOther.is_compatible
2535.3.65 by Andrew Bennetts
Correct test failure caused by typo.
606
        self.assertFalse(
2535.3.62 by Andrew Bennetts
Cosmetic changes.
607
            is_compatible(remote_repo, local_repo),
2535.3.65 by Andrew Bennetts
Correct test failure caused by typo.
608
            "InterRemoteToOther(%r, %r) is true" % (local_repo, remote_repo))
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
609
610
    def test_is_incompatible_different_format_both_remote(self):
2535.3.66 by Andrew Bennetts
Tidy a couple more long lines.
611
        remote_repo_a = self.make_remote_repository(
612
            'a', 'dirstate-with-subtree')
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
613
        remote_repo_b = self.make_remote_repository('b', 'dirstate')
2535.3.62 by Andrew Bennetts
Cosmetic changes.
614
        is_compatible = repository.InterRemoteToOther.is_compatible
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
615
        self.assertFalse(
2535.3.62 by Andrew Bennetts
Cosmetic changes.
616
            is_compatible(remote_repo_a, remote_repo_b),
2535.3.66 by Andrew Bennetts
Tidy a couple more long lines.
617
            "InterRemoteToOther(%r, %r) is true"
618
            % (remote_repo_a, remote_repo_b))
2535.3.41 by Andrew Bennetts
Add tests for InterRemoteToOther.is_compatible.
619
620
1556.1.4 by Robert Collins
Add a new format for what will become knit, and the surrounding logic to upgrade repositories within metadirs, and tests for the same.
621
class TestRepositoryConverter(TestCaseWithTransport):
622
623
    def test_convert_empty(self):
624
        t = get_transport(self.get_url('.'))
625
        t.mkdir('repository')
626
        repo_dir = bzrdir.BzrDirMetaFormat1().initialize('repository')
2241.1.4 by Martin Pool
Moved old weave-based repository formats into bzrlib.repofmt.weaverepo.
627
        repo = weaverepo.RepositoryFormat7().initialize(repo_dir)
2241.1.6 by Martin Pool
Move Knit repositories into the submodule bzrlib.repofmt.knitrepo and
628
        target_format = knitrepo.RepositoryFormatKnit1()
1556.1.4 by Robert Collins
Add a new format for what will become knit, and the surrounding logic to upgrade repositories within metadirs, and tests for the same.
629
        converter = repository.CopyConverter(target_format)
1594.1.3 by Robert Collins
Fixup pb usage to use nested_progress_bar.
630
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
631
        try:
632
            converter.convert(repo, pb)
633
        finally:
634
            pb.finished()
1556.1.4 by Robert Collins
Add a new format for what will become knit, and the surrounding logic to upgrade repositories within metadirs, and tests for the same.
635
        repo = repo_dir.open_repository()
636
        self.assertTrue(isinstance(target_format, repo._format.__class__))
1843.2.5 by Aaron Bentley
Add test of _unescape_xml
637
638
639
class TestMisc(TestCase):
640
    
641
    def test_unescape_xml(self):
642
        """We get some kind of error when malformed entities are passed"""
643
        self.assertRaises(KeyError, repository._unescape_xml, 'foo&bar;') 
1910.2.13 by Aaron Bentley
Start work on converter
644
645
2255.2.211 by Robert Collins
Remove knit2 repository format- it has never been supported.
646
class TestRepositoryFormatKnit3(TestCaseWithTransport):
1910.2.13 by Aaron Bentley
Start work on converter
647
648
    def test_convert(self):
649
        """Ensure the upgrade adds weaves for roots"""
1910.2.35 by Aaron Bentley
Better fix for convesion test
650
        format = bzrdir.BzrDirMetaFormat1()
2241.1.6 by Martin Pool
Move Knit repositories into the submodule bzrlib.repofmt.knitrepo and
651
        format.repository_format = knitrepo.RepositoryFormatKnit1()
1910.2.35 by Aaron Bentley
Better fix for convesion test
652
        tree = self.make_branch_and_tree('.', format)
1910.2.13 by Aaron Bentley
Start work on converter
653
        tree.commit("Dull commit", rev_id="dull")
654
        revision_tree = tree.branch.repository.revision_tree('dull')
655
        self.assertRaises(errors.NoSuchFile, revision_tree.get_file_lines,
656
            revision_tree.inventory.root.file_id)
657
        format = bzrdir.BzrDirMetaFormat1()
2255.2.211 by Robert Collins
Remove knit2 repository format- it has never been supported.
658
        format.repository_format = knitrepo.RepositoryFormatKnit3()
1910.2.13 by Aaron Bentley
Start work on converter
659
        upgrade.Convert('.', format)
1910.2.27 by Aaron Bentley
Fixed conversion test
660
        tree = workingtree.WorkingTree.open('.')
1910.2.13 by Aaron Bentley
Start work on converter
661
        revision_tree = tree.branch.repository.revision_tree('dull')
662
        revision_tree.get_file_lines(revision_tree.inventory.root.file_id)
1910.2.27 by Aaron Bentley
Fixed conversion test
663
        tree.commit("Another dull commit", rev_id='dull2')
664
        revision_tree = tree.branch.repository.revision_tree('dull2')
665
        self.assertEqual('dull', revision_tree.inventory.root.revision)
2220.2.2 by Martin Pool
Add tag command and basic implementation
666
2818.4.2 by Robert Collins
Review feedback.
667
    def test_exposed_versioned_files_are_marked_dirty(self):
668
        format = bzrdir.BzrDirMetaFormat1()
669
        format.repository_format = knitrepo.RepositoryFormatKnit3()
670
        repo = self.make_repository('.', format=format)
671
        repo.lock_write()
672
        inv = repo.get_inventory_weave()
673
        repo.unlock()
674
        self.assertRaises(errors.OutSideTransaction,
675
            inv.add_lines, 'foo', [], [])
2535.3.70 by Andrew Bennetts
Merge from bzr.dev.
676
2535.3.57 by Andrew Bennetts
Perform some sanity checking of data streams rather than blindly inserting them into our repository.
677
678
class TestWithBrokenRepo(TestCaseWithTransport):
2592.3.214 by Robert Collins
Merge bzr.dev.
679
    """These tests seem to be more appropriate as interface tests?"""
2535.3.57 by Andrew Bennetts
Perform some sanity checking of data streams rather than blindly inserting them into our repository.
680
681
    def make_broken_repository(self):
682
        # XXX: This function is borrowed from Aaron's "Reconcile can fix bad
683
        # parent references" branch which is due to land in bzr.dev soon.  Once
684
        # it does, this duplication should be removed.
685
        repo = self.make_repository('broken-repo')
686
        cleanups = []
687
        try:
688
            repo.lock_write()
689
            cleanups.append(repo.unlock)
690
            repo.start_write_group()
691
            cleanups.append(repo.commit_write_group)
692
            # make rev1a: A well-formed revision, containing 'file1'
693
            inv = inventory.Inventory(revision_id='rev1a')
694
            inv.root.revision = 'rev1a'
695
            self.add_file(repo, inv, 'file1', 'rev1a', [])
696
            repo.add_inventory('rev1a', inv, [])
697
            revision = _mod_revision.Revision('rev1a',
698
                committer='jrandom@example.com', timestamp=0,
699
                inventory_sha1='', timezone=0, message='foo', parent_ids=[])
700
            repo.add_revision('rev1a',revision, inv)
701
702
            # make rev1b, which has no Revision, but has an Inventory, and
703
            # file1
704
            inv = inventory.Inventory(revision_id='rev1b')
705
            inv.root.revision = 'rev1b'
706
            self.add_file(repo, inv, 'file1', 'rev1b', [])
707
            repo.add_inventory('rev1b', inv, [])
708
709
            # make rev2, with file1 and file2
710
            # file2 is sane
711
            # file1 has 'rev1b' as an ancestor, even though this is not
712
            # mentioned by 'rev1a', making it an unreferenced ancestor
713
            inv = inventory.Inventory()
714
            self.add_file(repo, inv, 'file1', 'rev2', ['rev1a', 'rev1b'])
715
            self.add_file(repo, inv, 'file2', 'rev2', [])
716
            self.add_revision(repo, 'rev2', inv, ['rev1a'])
717
718
            # make ghost revision rev1c
719
            inv = inventory.Inventory()
720
            self.add_file(repo, inv, 'file2', 'rev1c', [])
721
722
            # make rev3 with file2
723
            # file2 refers to 'rev1c', which is a ghost in this repository, so
724
            # file2 cannot have rev1c as its ancestor.
725
            inv = inventory.Inventory()
726
            self.add_file(repo, inv, 'file2', 'rev3', ['rev1c'])
727
            self.add_revision(repo, 'rev3', inv, ['rev1c'])
728
            return repo
729
        finally:
730
            for cleanup in reversed(cleanups):
731
                cleanup()
732
733
    def add_revision(self, repo, revision_id, inv, parent_ids):
734
        inv.revision_id = revision_id
735
        inv.root.revision = revision_id
736
        repo.add_inventory(revision_id, inv, parent_ids)
737
        revision = _mod_revision.Revision(revision_id,
738
            committer='jrandom@example.com', timestamp=0, inventory_sha1='',
739
            timezone=0, message='foo', parent_ids=parent_ids)
740
        repo.add_revision(revision_id,revision, inv)
741
742
    def add_file(self, repo, inv, filename, revision, parents):
743
        file_id = filename + '-id'
744
        entry = inventory.InventoryFile(file_id, filename, 'TREE_ROOT')
745
        entry.revision = revision
2535.3.70 by Andrew Bennetts
Merge from bzr.dev.
746
        entry.text_size = 0
2535.3.57 by Andrew Bennetts
Perform some sanity checking of data streams rather than blindly inserting them into our repository.
747
        inv.add(entry)
748
        vf = repo.weave_store.get_weave_or_empty(file_id,
749
                                                 repo.get_transaction())
750
        vf.add_lines(revision, parents, ['line\n'])
751
752
    def test_insert_from_broken_repo(self):
753
        """Inserting a data stream from a broken repository won't silently
754
        corrupt the target repository.
755
        """
756
        broken_repo = self.make_broken_repository()
757
        empty_repo = self.make_repository('empty-repo')
758
        stream = broken_repo.get_data_stream(['rev1a', 'rev2', 'rev3'])
2592.3.214 by Robert Collins
Merge bzr.dev.
759
        empty_repo.lock_write()
760
        self.addCleanup(empty_repo.unlock)
761
        empty_repo.start_write_group()
762
        try:
763
            self.assertRaises(
764
                errors.KnitCorrupt, empty_repo.insert_data_stream, stream)
765
        finally:
766
            empty_repo.abort_write_group()
767
768
2939.2.1 by Ian Clatworthy
use 'knitpack' naming instead of 'experimental' for pack formats
769
class TestKnitPackNoSubtrees(TestCaseWithTransport):
2592.3.24 by Robert Collins
Knit1 disk layout specified.
770
771
    def get_format(self):
3010.3.3 by Martin Pool
Merge trunk
772
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
2592.3.24 by Robert Collins
Knit1 disk layout specified.
773
774
    def test_disk_layout(self):
775
        format = self.get_format()
2592.3.36 by Robert Collins
Change the revision index name to NAME.rix.
776
        repo = self.make_repository('.', format=format)
2592.3.24 by Robert Collins
Knit1 disk layout specified.
777
        # in case of side effects of locking.
778
        repo.lock_write()
779
        repo.unlock()
780
        t = repo.bzrdir.get_repository_transport(None)
781
        self.check_format(t)
782
        # XXX: no locks left when unlocked at the moment
783
        # self.assertEqualDiff('', t.get('lock').read())
784
        self.check_databases(t)
785
786
    def check_format(self, t):
2939.2.5 by Ian Clatworthy
review feedback from lifeless
787
        self.assertEqualDiff(
2939.2.7 by Ian Clatworthy
fix strings used in on-disk unit tests
788
            "Bazaar pack repository format 1 (needs bzr 0.92)\n",
2592.3.24 by Robert Collins
Knit1 disk layout specified.
789
                             t.get('format').read())
790
791
    def assertHasKndx(self, t, knit_name):
792
        """Assert that knit_name exists on t."""
793
        self.assertEqualDiff('# bzr knit index 8\n',
794
                             t.get(knit_name + '.kndx').read())
795
796
    def assertHasNoKndx(self, t, knit_name):
797
        """Assert that knit_name has no index on t."""
798
        self.assertFalse(t.has(knit_name + '.kndx'))
799
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
800
    def assertHasNoKnit(self, t, knit_name):
2592.3.24 by Robert Collins
Knit1 disk layout specified.
801
        """Assert that knit_name exists on t."""
802
        # no default content
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
803
        self.assertFalse(t.has(knit_name + '.knit'))
2592.3.24 by Robert Collins
Knit1 disk layout specified.
804
805
    def check_databases(self, t):
806
        """check knit content for a repository."""
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
807
        # check conversion worked
2592.3.61 by Robert Collins
Remove inventory.kndx.
808
        self.assertHasNoKndx(t, 'inventory')
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
809
        self.assertHasNoKnit(t, 'inventory')
2592.3.24 by Robert Collins
Knit1 disk layout specified.
810
        self.assertHasNoKndx(t, 'revisions')
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
811
        self.assertHasNoKnit(t, 'revisions')
2592.3.39 by Robert Collins
Fugly version to remove signatures.kndx
812
        self.assertHasNoKndx(t, 'signatures')
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
813
        self.assertHasNoKnit(t, 'signatures')
814
        self.assertFalse(t.has('knits'))
2592.3.24 by Robert Collins
Knit1 disk layout specified.
815
        # revision-indexes file-container directory
2592.3.83 by Robert Collins
Merge bzr.dev, dropping FileNames for a trivial GraphIndex layer.
816
        self.assertEqual([],
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
817
            list(GraphIndex(t, 'pack-names', None).iter_all_entries()))
2592.3.81 by Robert Collins
Merge FileName allocation change, leading to hash based pack file names.
818
        self.assertTrue(S_ISDIR(t.stat('packs').st_mode))
819
        self.assertTrue(S_ISDIR(t.stat('upload').st_mode))
2592.3.219 by Robert Collins
Review feedback.
820
        self.assertTrue(S_ISDIR(t.stat('indices').st_mode))
821
        self.assertTrue(S_ISDIR(t.stat('obsolete_packs').st_mode))
2592.3.24 by Robert Collins
Knit1 disk layout specified.
822
823
    def test_shared_disk_layout(self):
824
        format = self.get_format()
2592.3.36 by Robert Collins
Change the revision index name to NAME.rix.
825
        repo = self.make_repository('.', shared=True, format=format)
2592.3.24 by Robert Collins
Knit1 disk layout specified.
826
        # we want:
827
        t = repo.bzrdir.get_repository_transport(None)
828
        self.check_format(t)
829
        # XXX: no locks left when unlocked at the moment
830
        # self.assertEqualDiff('', t.get('lock').read())
2592.3.219 by Robert Collins
Review feedback.
831
        # We should have a 'shared-storage' marker file.
2592.3.24 by Robert Collins
Knit1 disk layout specified.
832
        self.assertEqualDiff('', t.get('shared-storage').read())
833
        self.check_databases(t)
834
835
    def test_shared_no_tree_disk_layout(self):
836
        format = self.get_format()
2592.3.36 by Robert Collins
Change the revision index name to NAME.rix.
837
        repo = self.make_repository('.', shared=True, format=format)
2592.3.24 by Robert Collins
Knit1 disk layout specified.
838
        repo.set_make_working_trees(False)
839
        # we want:
840
        t = repo.bzrdir.get_repository_transport(None)
841
        self.check_format(t)
842
        # XXX: no locks left when unlocked at the moment
843
        # self.assertEqualDiff('', t.get('lock').read())
2592.3.219 by Robert Collins
Review feedback.
844
        # We should have a 'shared-storage' marker file.
2592.3.24 by Robert Collins
Knit1 disk layout specified.
845
        self.assertEqualDiff('', t.get('shared-storage').read())
2592.3.219 by Robert Collins
Review feedback.
846
        # We should have a marker for the no-working-trees flag.
2592.3.24 by Robert Collins
Knit1 disk layout specified.
847
        self.assertEqualDiff('', t.get('no-working-trees').read())
2592.3.219 by Robert Collins
Review feedback.
848
        # The marker should go when we toggle the setting.
2592.3.24 by Robert Collins
Knit1 disk layout specified.
849
        repo.set_make_working_trees(True)
850
        self.assertFalse(t.has('no-working-trees'))
851
        self.check_databases(t)
2592.3.25 by Robert Collins
experimental-subtrees layout defined.
852
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
853
    def test_adding_revision_creates_pack_indices(self):
2592.3.118 by Robert Collins
Record the size of the index files in the pack-names index.
854
        format = self.get_format()
855
        tree = self.make_branch_and_tree('.', format=format)
856
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
857
        self.assertEqual([],
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
858
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
2592.3.118 by Robert Collins
Record the size of the index files in the pack-names index.
859
        tree.commit('foobarbaz')
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
860
        index = GraphIndex(trans, 'pack-names', None)
2592.3.219 by Robert Collins
Review feedback.
861
        index_nodes = list(index.iter_all_entries())
862
        self.assertEqual(1, len(index_nodes))
863
        node = index_nodes[0]
2592.3.118 by Robert Collins
Record the size of the index files in the pack-names index.
864
        name = node[1][0]
865
        # the pack sizes should be listed in the index
866
        pack_value = node[2]
867
        sizes = [int(digits) for digits in pack_value.split(' ')]
868
        for size, suffix in zip(sizes, ['.rix', '.iix', '.tix', '.six']):
869
            stat = trans.stat('indices/%s%s' % (name, suffix))
870
            self.assertEqual(size, stat.st_size)
2592.3.60 by Robert Collins
Nuke per-fileid indices for a single unified index.
871
2592.3.52 by Robert Collins
Stop allocating new names unless new data has been inserted.
872
    def test_pulling_nothing_leads_to_no_new_names(self):
873
        format = self.get_format()
874
        tree1 = self.make_branch_and_tree('1', format=format)
875
        tree2 = self.make_branch_and_tree('2', format=format)
876
        tree1.branch.repository.fetch(tree2.branch.repository)
877
        trans = tree1.branch.repository.bzrdir.get_repository_transport(None)
2592.3.83 by Robert Collins
Merge bzr.dev, dropping FileNames for a trivial GraphIndex layer.
878
        self.assertEqual([],
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
879
            list(GraphIndex(trans, 'pack-names', None).iter_all_entries()))
2592.3.39 by Robert Collins
Fugly version to remove signatures.kndx
880
2592.3.84 by Robert Collins
Start of autopacking logic.
881
    def test_commit_across_pack_shape_boundary_autopacks(self):
882
        format = self.get_format()
883
        tree = self.make_branch_and_tree('.', format=format)
884
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
885
        # This test could be a little cheaper by replacing the packs
886
        # attribute on the repository to allow a different pack distribution
2592.3.219 by Robert Collins
Review feedback.
887
        # and max packs policy - so we are checking the policy is honoured
2592.3.84 by Robert Collins
Start of autopacking logic.
888
        # in the test. But for now 11 commits is not a big deal in a single
889
        # test.
890
        for x in range(9):
891
            tree.commit('commit %s' % x)
892
        # there should be 9 packs:
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
893
        index = GraphIndex(trans, 'pack-names', None)
2592.3.84 by Robert Collins
Start of autopacking logic.
894
        self.assertEqual(9, len(list(index.iter_all_entries())))
2948.1.1 by Robert Collins
* Obsolete packs are now cleaned up by pack and autopack operations.
895
        # insert some files in obsolete_packs which should be removed by pack.
896
        trans.put_bytes('obsolete_packs/foo', '123')
897
        trans.put_bytes('obsolete_packs/bar', '321')
2592.3.84 by Robert Collins
Start of autopacking logic.
898
        # committing one more should coalesce to 1 of 10.
899
        tree.commit('commit triggering pack')
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
900
        index = GraphIndex(trans, 'pack-names', None)
2592.3.84 by Robert Collins
Start of autopacking logic.
901
        self.assertEqual(1, len(list(index.iter_all_entries())))
902
        # packing should not damage data
903
        tree = tree.bzrdir.open_workingtree()
904
        check_result = tree.branch.repository.check(
905
            [tree.branch.last_revision()])
2948.1.1 by Robert Collins
* Obsolete packs are now cleaned up by pack and autopack operations.
906
        # We should have 50 (10x5) files in the obsolete_packs directory.
907
        obsolete_files = list(trans.list_dir('obsolete_packs'))
908
        self.assertFalse('foo' in obsolete_files)
909
        self.assertFalse('bar' in obsolete_files)
910
        self.assertEqual(50, len(obsolete_files))
2592.3.84 by Robert Collins
Start of autopacking logic.
911
        # XXX: Todo check packs obsoleted correctly - old packs and indices
912
        # in the obsolete_packs directory.
913
        large_pack_name = list(index.iter_all_entries())[0][1][0]
914
        # finally, committing again should not touch the large pack.
915
        tree.commit('commit not triggering pack')
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
916
        index = GraphIndex(trans, 'pack-names', None)
2592.3.84 by Robert Collins
Start of autopacking logic.
917
        self.assertEqual(2, len(list(index.iter_all_entries())))
918
        pack_names = [node[1][0] for node in index.iter_all_entries()]
919
        self.assertTrue(large_pack_name in pack_names)
920
2592.3.86 by Robert Collins
Implement the pack commands for knit repositories.
921
    def test_pack_after_two_commits_packs_everything(self):
922
        format = self.get_format()
923
        tree = self.make_branch_and_tree('.', format=format)
924
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
925
        tree.commit('start')
926
        tree.commit('more work')
927
        tree.branch.repository.pack()
2592.3.219 by Robert Collins
Review feedback.
928
        # there should be 1 pack:
2592.3.175 by Robert Collins
Update for GraphIndex constructor changes.
929
        index = GraphIndex(trans, 'pack-names', None)
2592.3.86 by Robert Collins
Implement the pack commands for knit repositories.
930
        self.assertEqual(1, len(list(index.iter_all_entries())))
931
        self.assertEqual(2, len(tree.branch.repository.all_revision_ids()))
932
3070.1.1 by Robert Collins
* ``bzr pack`` now orders revision texts in topological order, with newest
933
    def test_pack_layout(self):
934
        format = self.get_format()
935
        tree = self.make_branch_and_tree('.', format=format)
936
        trans = tree.branch.repository.bzrdir.get_repository_transport(None)
937
        tree.commit('start', rev_id='1')
938
        tree.commit('more work', rev_id='2')
939
        tree.branch.repository.pack()
940
        tree.lock_read()
941
        self.addCleanup(tree.unlock)
942
        pack = tree.branch.repository._pack_collection.get_pack_by_name(
943
            tree.branch.repository._pack_collection.names()[0])
944
        # revision access tends to be tip->ancestor, so ordering that way on 
945
        # disk is a good idea.
946
        for _1, key, val, refs in pack.revision_index.iter_all_entries():
947
            if key == ('1',):
948
                pos_1 = int(val[1:].split()[0])
949
            else:
950
                pos_2 = int(val[1:].split()[0])
951
        self.assertTrue(pos_2 < pos_1)
952
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
953
    def test_pack_repositories_support_multiple_write_locks(self):
954
        format = self.get_format()
955
        self.make_repository('.', shared=True, format=format)
956
        r1 = repository.Repository.open('.')
957
        r2 = repository.Repository.open('.')
958
        r1.lock_write()
959
        self.addCleanup(r1.unlock)
960
        r2.lock_write()
961
        r2.unlock()
962
963
    def _add_text(self, repo, fileid):
964
        """Add a text to the repository within a write group."""
965
        vf =repo.weave_store.get_weave(fileid, repo.get_transaction())
966
        vf.add_lines('samplerev+' + fileid, [], [])
967
968
    def test_concurrent_writers_merge_new_packs(self):
969
        format = self.get_format()
970
        self.make_repository('.', shared=True, format=format)
971
        r1 = repository.Repository.open('.')
972
        r2 = repository.Repository.open('.')
973
        r1.lock_write()
974
        try:
975
            # access enough data to load the names list
976
            list(r1.all_revision_ids())
977
            r2.lock_write()
978
            try:
979
                # access enough data to load the names list
980
                list(r2.all_revision_ids())
981
                r1.start_write_group()
982
                try:
983
                    r2.start_write_group()
984
                    try:
985
                        self._add_text(r1, 'fileidr1')
986
                        self._add_text(r2, 'fileidr2')
987
                    except:
988
                        r2.abort_write_group()
989
                        raise
990
                except:
991
                    r1.abort_write_group()
992
                    raise
993
                # both r1 and r2 have open write groups with data in them
994
                # created while the other's write group was open.
995
                # Commit both which requires a merge to the pack-names.
996
                try:
997
                    r1.commit_write_group()
998
                except:
2592.3.219 by Robert Collins
Review feedback.
999
                    r1.abort_write_group()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1000
                    r2.abort_write_group()
1001
                    raise
1002
                r2.commit_write_group()
2592.3.213 by Robert Collins
Retain packs and indices in memory within a lock, even when write groups are entered and exited.
1003
                # tell r1 to reload from disk
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1004
                r1._pack_collection.reset()
2592.3.219 by Robert Collins
Review feedback.
1005
                # Now both repositories should know about both names
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1006
                r1._pack_collection.ensure_loaded()
1007
                r2._pack_collection.ensure_loaded()
1008
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1009
                self.assertEqual(2, len(r1._pack_collection.names()))
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1010
            finally:
2939.1.1 by Martin Pool
Fix up mismatched lock/unlock pairs.
1011
                r2.unlock()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1012
        finally:
2939.1.1 by Martin Pool
Fix up mismatched lock/unlock pairs.
1013
            r1.unlock()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1014
1015
    def test_concurrent_writer_second_preserves_dropping_a_pack(self):
1016
        format = self.get_format()
1017
        self.make_repository('.', shared=True, format=format)
1018
        r1 = repository.Repository.open('.')
1019
        r2 = repository.Repository.open('.')
1020
        # add a pack to drop
1021
        r1.lock_write()
1022
        try:
1023
            r1.start_write_group()
1024
            try:
1025
                self._add_text(r1, 'fileidr1')
1026
            except:
1027
                r1.abort_write_group()
1028
                raise
1029
            else:
1030
                r1.commit_write_group()
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1031
            r1._pack_collection.ensure_loaded()
1032
            name_to_drop = r1._pack_collection.all_packs()[0].name
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1033
        finally:
1034
            r1.unlock()
1035
        r1.lock_write()
1036
        try:
1037
            # access enough data to load the names list
1038
            list(r1.all_revision_ids())
1039
            r2.lock_write()
1040
            try:
1041
                # access enough data to load the names list
1042
                list(r2.all_revision_ids())
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1043
                r1._pack_collection.ensure_loaded()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1044
                try:
1045
                    r2.start_write_group()
1046
                    try:
1047
                        # in r1, drop the pack
2592.3.236 by Martin Pool
Make RepositoryPackCollection.remove_pack_from_memory private
1048
                        r1._pack_collection._remove_pack_from_memory(
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1049
                            r1._pack_collection.get_pack_by_name(name_to_drop))
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1050
                        # in r2, add a pack
1051
                        self._add_text(r2, 'fileidr2')
1052
                    except:
1053
                        r2.abort_write_group()
1054
                        raise
1055
                except:
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1056
                    r1._pack_collection.reset()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1057
                    raise
1058
                # r1 has a changed names list, and r2 an open write groups with
1059
                # changes.
2592.3.208 by Robert Collins
Start refactoring the knit-pack thunking to be clearer.
1060
                # save r1, and then commit the r2 write group, which requires a
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1061
                # merge to the pack-names, which should not reinstate
1062
                # name_to_drop
1063
                try:
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1064
                    r1._pack_collection._save_pack_names()
1065
                    r1._pack_collection.reset()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1066
                except:
1067
                    r2.abort_write_group()
1068
                    raise
1069
                try:
1070
                    r2.commit_write_group()
1071
                except:
1072
                    r2.abort_write_group()
1073
                    raise
1074
                # Now both repositories should now about just one name.
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1075
                r1._pack_collection.ensure_loaded()
1076
                r2._pack_collection.ensure_loaded()
1077
                self.assertEqual(r1._pack_collection.names(), r2._pack_collection.names())
1078
                self.assertEqual(1, len(r1._pack_collection.names()))
1079
                self.assertFalse(name_to_drop in r1._pack_collection.names())
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1080
            finally:
2939.1.1 by Martin Pool
Fix up mismatched lock/unlock pairs.
1081
                r2.unlock()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1082
        finally:
2939.1.1 by Martin Pool
Fix up mismatched lock/unlock pairs.
1083
            r1.unlock()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1084
1085
    def test_lock_write_does_not_physically_lock(self):
1086
        repo = self.make_repository('.', format=self.get_format())
1087
        repo.lock_write()
1088
        self.addCleanup(repo.unlock)
1089
        self.assertFalse(repo.get_physical_lock_status())
1090
1091
    def prepare_for_break_lock(self):
2592.3.219 by Robert Collins
Review feedback.
1092
        # Setup the global ui factory state so that a break-lock method call
1093
        # will find usable input in the input stream.
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1094
        old_factory = bzrlib.ui.ui_factory
1095
        def restoreFactory():
1096
            bzrlib.ui.ui_factory = old_factory
1097
        self.addCleanup(restoreFactory)
1098
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
1099
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
1100
1101
    def test_break_lock_breaks_physical_lock(self):
1102
        repo = self.make_repository('.', format=self.get_format())
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1103
        repo._pack_collection.lock_names()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1104
        repo2 = repository.Repository.open('.')
1105
        self.assertTrue(repo.get_physical_lock_status())
1106
        self.prepare_for_break_lock()
1107
        repo2.break_lock()
1108
        self.assertFalse(repo.get_physical_lock_status())
1109
2592.3.240 by Martin Pool
Rename RepositoryPackCollection.release_names to _unlock_names
1110
    def test_broken_physical_locks_error_on__unlock_names_lock(self):
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1111
        repo = self.make_repository('.', format=self.get_format())
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1112
        repo._pack_collection.lock_names()
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1113
        self.assertTrue(repo.get_physical_lock_status())
1114
        repo2 = repository.Repository.open('.')
1115
        self.prepare_for_break_lock()
1116
        repo2.break_lock()
2592.3.240 by Martin Pool
Rename RepositoryPackCollection.release_names to _unlock_names
1117
        self.assertRaises(errors.LockBroken, repo._pack_collection._unlock_names)
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1118
2949.1.2 by Robert Collins
* Fetch with pack repositories will no longer read the entire history graph.
1119
    def test_fetch_without_find_ghosts_ignores_ghosts(self):
1120
        # we want two repositories at this point:
1121
        # one with a revision that is a ghost in the other
1122
        # repository.
1123
        # 'ghost' is present in has_ghost, 'ghost' is absent in 'missing_ghost'.
1124
        # 'references' is present in both repositories, and 'tip' is present
1125
        # just in has_ghost.
1126
        # has_ghost       missing_ghost
1127
        #------------------------------
1128
        # 'ghost'             -
1129
        # 'references'    'references'
1130
        # 'tip'               -
1131
        # In this test we fetch 'tip' which should not fetch 'ghost'
1132
        has_ghost = self.make_repository('has_ghost', format=self.get_format())
1133
        missing_ghost = self.make_repository('missing_ghost',
1134
            format=self.get_format())
1135
1136
        def add_commit(repo, revision_id, parent_ids):
1137
            repo.lock_write()
1138
            repo.start_write_group()
1139
            inv = inventory.Inventory(revision_id=revision_id)
1140
            inv.root.revision = revision_id
1141
            root_id = inv.root.file_id
1142
            sha1 = repo.add_inventory(revision_id, inv, [])
1143
            vf = repo.weave_store.get_weave_or_empty(root_id,
1144
                repo.get_transaction())
1145
            vf.add_lines(revision_id, [], [])
1146
            rev = bzrlib.revision.Revision(timestamp=0,
1147
                                           timezone=None,
1148
                                           committer="Foo Bar <foo@example.com>",
1149
                                           message="Message",
1150
                                           inventory_sha1=sha1,
1151
                                           revision_id=revision_id)
1152
            rev.parent_ids = parent_ids
1153
            repo.add_revision(revision_id, rev)
1154
            repo.commit_write_group()
1155
            repo.unlock()
1156
        add_commit(has_ghost, 'ghost', [])
1157
        add_commit(has_ghost, 'references', ['ghost'])
1158
        add_commit(missing_ghost, 'references', ['ghost'])
1159
        add_commit(has_ghost, 'tip', ['references'])
1160
        missing_ghost.fetch(has_ghost, 'tip')
1161
        # missing ghost now has tip and not ghost.
1162
        rev = missing_ghost.get_revision('tip')
1163
        inv = missing_ghost.get_inventory('tip')
1164
        self.assertRaises(errors.NoSuchRevision,
1165
            missing_ghost.get_revision, 'ghost')
1166
        self.assertRaises(errors.RevisionNotPresent,
1167
            missing_ghost.get_inventory, 'ghost')
1168
2592.3.188 by Robert Collins
Allow pack repositories to have multiple writers active at one time, for greater concurrency.
1169
2939.2.1 by Ian Clatworthy
use 'knitpack' naming instead of 'experimental' for pack formats
1170
class TestKnitPackSubtrees(TestKnitPackNoSubtrees):
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1171
1172
    def get_format(self):
2939.2.5 by Ian Clatworthy
review feedback from lifeless
1173
        return bzrdir.format_registry.make_bzrdir(
3010.3.3 by Martin Pool
Merge trunk
1174
            'pack-0.92-subtree')
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1175
1176
    def check_format(self, t):
2939.2.5 by Ian Clatworthy
review feedback from lifeless
1177
        self.assertEqualDiff(
2939.2.7 by Ian Clatworthy
fix strings used in on-disk unit tests
1178
            "Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n",
2939.2.5 by Ian Clatworthy
review feedback from lifeless
1179
            t.get('format').read())
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1180
1181
2592.3.84 by Robert Collins
Start of autopacking logic.
1182
class TestRepositoryPackCollection(TestCaseWithTransport):
1183
1184
    def get_format(self):
3010.3.3 by Martin Pool
Merge trunk
1185
        return bzrdir.format_registry.make_bzrdir('pack-0.92')
2592.3.84 by Robert Collins
Start of autopacking logic.
1186
1187
    def test__max_pack_count(self):
2592.3.219 by Robert Collins
Review feedback.
1188
        """The maximum pack count is a function of the number of revisions."""
2592.3.84 by Robert Collins
Start of autopacking logic.
1189
        format = self.get_format()
1190
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1191
        packs = repo._pack_collection
2592.3.84 by Robert Collins
Start of autopacking logic.
1192
        # no revisions - one pack, so that we can have a revision free repo
1193
        # without it blowing up
1194
        self.assertEqual(1, packs._max_pack_count(0))
1195
        # after that the sum of the digits, - check the first 1-9
1196
        self.assertEqual(1, packs._max_pack_count(1))
1197
        self.assertEqual(2, packs._max_pack_count(2))
1198
        self.assertEqual(3, packs._max_pack_count(3))
1199
        self.assertEqual(4, packs._max_pack_count(4))
1200
        self.assertEqual(5, packs._max_pack_count(5))
1201
        self.assertEqual(6, packs._max_pack_count(6))
1202
        self.assertEqual(7, packs._max_pack_count(7))
1203
        self.assertEqual(8, packs._max_pack_count(8))
1204
        self.assertEqual(9, packs._max_pack_count(9))
1205
        # check the boundary cases with two digits for the next decade
1206
        self.assertEqual(1, packs._max_pack_count(10))
1207
        self.assertEqual(2, packs._max_pack_count(11))
1208
        self.assertEqual(10, packs._max_pack_count(19))
1209
        self.assertEqual(2, packs._max_pack_count(20))
1210
        self.assertEqual(3, packs._max_pack_count(21))
1211
        # check some arbitrary big numbers
1212
        self.assertEqual(25, packs._max_pack_count(112894))
1213
1214
    def test_pack_distribution_zero(self):
1215
        format = self.get_format()
1216
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1217
        packs = repo._pack_collection
2592.3.84 by Robert Collins
Start of autopacking logic.
1218
        self.assertEqual([0], packs.pack_distribution(0))
3052.1.6 by John Arbash Meinel
Change the lock check to raise ObjectNotLocked.
1219
1220
    def test_ensure_loaded_unlocked(self):
1221
        format = self.get_format()
1222
        repo = self.make_repository('.', format=format)
1223
        self.assertRaises(errors.ObjectNotLocked,
1224
                          repo._pack_collection.ensure_loaded)
1225
2592.3.84 by Robert Collins
Start of autopacking logic.
1226
    def test_pack_distribution_one_to_nine(self):
1227
        format = self.get_format()
1228
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1229
        packs = repo._pack_collection
2592.3.84 by Robert Collins
Start of autopacking logic.
1230
        self.assertEqual([1],
1231
            packs.pack_distribution(1))
1232
        self.assertEqual([1, 1],
1233
            packs.pack_distribution(2))
1234
        self.assertEqual([1, 1, 1],
1235
            packs.pack_distribution(3))
1236
        self.assertEqual([1, 1, 1, 1],
1237
            packs.pack_distribution(4))
1238
        self.assertEqual([1, 1, 1, 1, 1],
1239
            packs.pack_distribution(5))
1240
        self.assertEqual([1, 1, 1, 1, 1, 1],
1241
            packs.pack_distribution(6))
1242
        self.assertEqual([1, 1, 1, 1, 1, 1, 1],
1243
            packs.pack_distribution(7))
1244
        self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1],
1245
            packs.pack_distribution(8))
1246
        self.assertEqual([1, 1, 1, 1, 1, 1, 1, 1, 1],
1247
            packs.pack_distribution(9))
1248
1249
    def test_pack_distribution_stable_at_boundaries(self):
1250
        """When there are multi-rev packs the counts are stable."""
1251
        format = self.get_format()
1252
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1253
        packs = repo._pack_collection
2592.3.84 by Robert Collins
Start of autopacking logic.
1254
        # in 10s:
1255
        self.assertEqual([10], packs.pack_distribution(10))
1256
        self.assertEqual([10, 1], packs.pack_distribution(11))
1257
        self.assertEqual([10, 10], packs.pack_distribution(20))
1258
        self.assertEqual([10, 10, 1], packs.pack_distribution(21))
1259
        # 100s
1260
        self.assertEqual([100], packs.pack_distribution(100))
1261
        self.assertEqual([100, 1], packs.pack_distribution(101))
1262
        self.assertEqual([100, 10, 1], packs.pack_distribution(111))
1263
        self.assertEqual([100, 100], packs.pack_distribution(200))
1264
        self.assertEqual([100, 100, 1], packs.pack_distribution(201))
1265
        self.assertEqual([100, 100, 10, 1], packs.pack_distribution(211))
1266
2592.3.85 by Robert Collins
Finish autopack corner cases.
1267
    def test_plan_pack_operations_2009_revisions_skip_all_packs(self):
1268
        format = self.get_format()
1269
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1270
        packs = repo._pack_collection
2592.3.85 by Robert Collins
Finish autopack corner cases.
1271
        existing_packs = [(2000, "big"), (9, "medium")]
1272
        # rev count - 2009 -> 2x1000 + 9x1
1273
        pack_operations = packs.plan_autopack_combinations(
1274
            existing_packs, [1000, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1])
1275
        self.assertEqual([], pack_operations)
1276
1277
    def test_plan_pack_operations_2010_revisions_skip_all_packs(self):
1278
        format = self.get_format()
1279
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1280
        packs = repo._pack_collection
2592.3.85 by Robert Collins
Finish autopack corner cases.
1281
        existing_packs = [(2000, "big"), (9, "medium"), (1, "single")]
1282
        # rev count - 2010 -> 2x1000 + 1x10
1283
        pack_operations = packs.plan_autopack_combinations(
1284
            existing_packs, [1000, 1000, 10])
1285
        self.assertEqual([], pack_operations)
1286
1287
    def test_plan_pack_operations_2010_combines_smallest_two(self):
1288
        format = self.get_format()
1289
        repo = self.make_repository('.', format=format)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1290
        packs = repo._pack_collection
2592.3.85 by Robert Collins
Finish autopack corner cases.
1291
        existing_packs = [(1999, "big"), (9, "medium"), (1, "single2"),
1292
            (1, "single1")]
1293
        # rev count - 2010 -> 2x1000 + 1x10 (3)
1294
        pack_operations = packs.plan_autopack_combinations(
1295
            existing_packs, [1000, 1000, 10])
1296
        self.assertEqual([[2, ["single2", "single1"]], [0, []]], pack_operations)
1297
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1298
    def test_all_packs_none(self):
1299
        format = self.get_format()
1300
        tree = self.make_branch_and_tree('.', format=format)
1301
        tree.lock_read()
1302
        self.addCleanup(tree.unlock)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1303
        packs = tree.branch.repository._pack_collection
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1304
        packs.ensure_loaded()
1305
        self.assertEqual([], packs.all_packs())
1306
1307
    def test_all_packs_one(self):
1308
        format = self.get_format()
1309
        tree = self.make_branch_and_tree('.', format=format)
1310
        tree.commit('start')
1311
        tree.lock_read()
1312
        self.addCleanup(tree.unlock)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1313
        packs = tree.branch.repository._pack_collection
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1314
        packs.ensure_loaded()
2592.3.176 by Robert Collins
Various pack refactorings.
1315
        self.assertEqual([
1316
            packs.get_pack_by_name(packs.names()[0])],
1317
            packs.all_packs())
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1318
1319
    def test_all_packs_two(self):
1320
        format = self.get_format()
1321
        tree = self.make_branch_and_tree('.', format=format)
1322
        tree.commit('start')
1323
        tree.commit('continue')
1324
        tree.lock_read()
1325
        self.addCleanup(tree.unlock)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1326
        packs = tree.branch.repository._pack_collection
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1327
        packs.ensure_loaded()
1328
        self.assertEqual([
2592.3.176 by Robert Collins
Various pack refactorings.
1329
            packs.get_pack_by_name(packs.names()[0]),
1330
            packs.get_pack_by_name(packs.names()[1]),
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1331
            ], packs.all_packs())
1332
2592.3.176 by Robert Collins
Various pack refactorings.
1333
    def test_get_pack_by_name(self):
1334
        format = self.get_format()
1335
        tree = self.make_branch_and_tree('.', format=format)
1336
        tree.commit('start')
1337
        tree.lock_read()
1338
        self.addCleanup(tree.unlock)
2592.3.232 by Martin Pool
Disambiguate two member variables called _packs into _packs_by_name and _pack_collection
1339
        packs = tree.branch.repository._pack_collection
2592.3.176 by Robert Collins
Various pack refactorings.
1340
        packs.ensure_loaded()
1341
        name = packs.names()[0]
1342
        pack_1 = packs.get_pack_by_name(name)
1343
        # the pack should be correctly initialised
1344
        rev_index = GraphIndex(packs._index_transport, name + '.rix',
1345
            packs._names[name][0])
1346
        inv_index = GraphIndex(packs._index_transport, name + '.iix',
1347
            packs._names[name][1])
1348
        txt_index = GraphIndex(packs._index_transport, name + '.tix',
1349
            packs._names[name][2])
1350
        sig_index = GraphIndex(packs._index_transport, name + '.six',
1351
            packs._names[name][3])
2592.3.191 by Robert Collins
Give Pack responsibility for index naming, and two concrete classes - NewPack for new packs and ExistingPack for packs we read from disk.
1352
        self.assertEqual(pack_repo.ExistingPack(packs._pack_transport,
2592.3.219 by Robert Collins
Review feedback.
1353
            name, rev_index, inv_index, txt_index, sig_index), pack_1)
2592.3.176 by Robert Collins
Various pack refactorings.
1354
        # and the same instance should be returned on successive calls.
1355
        self.assertTrue(pack_1 is packs.get_pack_by_name(name))
1356
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1357
1358
class TestPack(TestCaseWithTransport):
1359
    """Tests for the Pack object."""
1360
1361
    def assertCurrentlyEqual(self, left, right):
1362
        self.assertTrue(left == right)
1363
        self.assertTrue(right == left)
1364
        self.assertFalse(left != right)
1365
        self.assertFalse(right != left)
1366
1367
    def assertCurrentlyNotEqual(self, left, right):
1368
        self.assertFalse(left == right)
1369
        self.assertFalse(right == left)
1370
        self.assertTrue(left != right)
1371
        self.assertTrue(right != left)
1372
1373
    def test___eq____ne__(self):
2592.3.191 by Robert Collins
Give Pack responsibility for index naming, and two concrete classes - NewPack for new packs and ExistingPack for packs we read from disk.
1374
        left = pack_repo.ExistingPack('', '', '', '', '', '')
1375
        right = pack_repo.ExistingPack('', '', '', '', '', '')
2592.3.173 by Robert Collins
Basic implementation of all_packs.
1376
        self.assertCurrentlyEqual(left, right)
1377
        # change all attributes and ensure equality changes as we do.
1378
        left.revision_index = 'a'
1379
        self.assertCurrentlyNotEqual(left, right)
1380
        right.revision_index = 'a'
1381
        self.assertCurrentlyEqual(left, right)
1382
        left.inventory_index = 'a'
1383
        self.assertCurrentlyNotEqual(left, right)
1384
        right.inventory_index = 'a'
1385
        self.assertCurrentlyEqual(left, right)
1386
        left.text_index = 'a'
1387
        self.assertCurrentlyNotEqual(left, right)
1388
        right.text_index = 'a'
1389
        self.assertCurrentlyEqual(left, right)
1390
        left.signature_index = 'a'
1391
        self.assertCurrentlyNotEqual(left, right)
1392
        right.signature_index = 'a'
1393
        self.assertCurrentlyEqual(left, right)
1394
        left.name = 'a'
1395
        self.assertCurrentlyNotEqual(left, right)
1396
        right.name = 'a'
1397
        self.assertCurrentlyEqual(left, right)
1398
        left.transport = 'a'
1399
        self.assertCurrentlyNotEqual(left, right)
1400
        right.transport = 'a'
1401
        self.assertCurrentlyEqual(left, right)
2592.3.179 by Robert Collins
Generate the revision_index_map for packing during the core operation, from the pack objects.
1402
1403
    def test_file_name(self):
2592.3.191 by Robert Collins
Give Pack responsibility for index naming, and two concrete classes - NewPack for new packs and ExistingPack for packs we read from disk.
1404
        pack = pack_repo.ExistingPack('', 'a_name', '', '', '', '')
2592.3.179 by Robert Collins
Generate the revision_index_map for packing during the core operation, from the pack objects.
1405
        self.assertEqual('a_name.pack', pack.file_name())
2592.3.192 by Robert Collins
Move new revision index management to NewPack.
1406
1407
1408
class TestNewPack(TestCaseWithTransport):
1409
    """Tests for pack_repo.NewPack."""
1410
2592.3.193 by Robert Collins
Move hash tracking of new packs into NewPack.
1411
    def test_new_instance_attributes(self):
2592.3.194 by Robert Collins
Output the revision index from NewPack.finish
1412
        upload_transport = self.get_transport('upload')
1413
        pack_transport = self.get_transport('pack')
1414
        index_transport = self.get_transport('index')
1415
        upload_transport.mkdir('.')
1416
        pack = pack_repo.NewPack(upload_transport, index_transport,
1417
            pack_transport)
2592.3.192 by Robert Collins
Move new revision index management to NewPack.
1418
        self.assertIsInstance(pack.revision_index, InMemoryGraphIndex)
2592.3.195 by Robert Collins
Move some inventory index logic to NewPack.
1419
        self.assertIsInstance(pack.inventory_index, InMemoryGraphIndex)
2592.3.193 by Robert Collins
Move hash tracking of new packs into NewPack.
1420
        self.assertIsInstance(pack._hash, type(md5.new()))
2592.3.194 by Robert Collins
Output the revision index from NewPack.finish
1421
        self.assertTrue(pack.upload_transport is upload_transport)
1422
        self.assertTrue(pack.index_transport is index_transport)
1423
        self.assertTrue(pack.pack_transport is pack_transport)
1424
        self.assertEqual(None, pack.index_sizes)
1425
        self.assertEqual(20, len(pack.random_name))
1426
        self.assertIsInstance(pack.random_name, str)
1427
        self.assertIsInstance(pack.start_time, float)
2951.1.2 by Robert Collins
Partial refactoring of pack_repo to create a Packer object for packing.
1428
1429
1430
class TestPacker(TestCaseWithTransport):
1431
    """Tests for the packs repository Packer class."""
2951.1.10 by Robert Collins
Peer review feedback with Ian.
1432
1433
    # To date, this class has been factored out and nothing new added to it;
1434
    # thus there are not yet any tests.
3146.6.1 by Aaron Bentley
InterDifferingSerializer shows a progress bar
1435
1436
1437
class TestInterDifferingSerializer(TestCaseWithTransport):
1438
1439
    def test_progress_bar(self):
1440
        tree = self.make_branch_and_tree('tree')
1441
        tree.commit('rev1', rev_id='rev-1')
1442
        tree.commit('rev2', rev_id='rev-2')
1443
        tree.commit('rev3', rev_id='rev-3')
1444
        repo = self.make_repository('repo')
1445
        inter_repo = repository.InterDifferingSerializer(
1446
            tree.branch.repository, repo)
1447
        pb = progress.InstrumentedProgress(to_file=StringIO())
1448
        pb.never_throttle = True
1449
        inter_repo.fetch('rev-1', pb)
1450
        self.assertEqual('Transferring revisions', pb.last_msg)
1451
        self.assertEqual(1, pb.last_cnt)
1452
        self.assertEqual(1, pb.last_total)
1453
        inter_repo.fetch('rev-3', pb)
1454
        self.assertEqual(2, pb.last_cnt)
1455
        self.assertEqual(2, pb.last_total)