1
# Copyright (C) 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for the journalled inventory logic.
19
See doc/developer/inventory.txt for more information.
22
from cStringIO import StringIO
29
from bzrlib.osutils import sha_string
30
from bzrlib.inventory import Inventory
31
from bzrlib.revision import NULL_REVISION
32
from bzrlib.tests import TestCase
34
### DO NOT REFLOW THESE TEXTS. NEW LINES ARE SIGNIFICANT. ###
35
empty_lines = """format: bzr inventory delta v1 (bzr 1.14)
42
root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
44
version: entry-version
47
/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir
51
root_change_lines = """format: bzr inventory delta v1 (bzr 1.14)
56
/\x00an-id\x00\x00different-version\x00dir
59
corrupt_parent_lines = """format: bzr inventory delta v1 (bzr 1.14)
63
tree_references: false
64
/\x00an-id\x00\x00different-version\x00dir
67
root_only_unversioned = """format: bzr inventory delta v1 (bzr 1.14)
69
version: entry-version
71
tree_references: false
72
/\x00TREE_ROOT\x00\x00null:\x00dir
75
reference_lines = """format: bzr inventory delta v1 (bzr 1.14)
77
version: entry-version
80
/\x00TREE_ROOT\x00\x00a@e\xc3\xa5ample.com--2004\x00dir
81
/foo\x00id\x00TREE_ROOT\x00changed\x00tree\x00subtree-version
84
change_tree_lines = """format: bzr inventory delta v1 (bzr 1.14)
88
tree_references: false
89
/foo\x00id\x00TREE_ROOT\x00changed-twice\x00tree\x00subtree-version2
93
class TestSerializer(TestCase):
94
"""Test journalled inventory serialisation."""
96
def test_empty_delta_to_lines(self):
97
old_inv = Inventory(None)
98
new_inv = Inventory(None)
99
delta = new_inv._make_delta(old_inv)
100
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
101
tree_references=True)
102
self.assertEqual(StringIO(empty_lines).readlines(),
103
journal.delta_to_lines(NULL_REVISION, NULL_REVISION, delta))
105
def test_root_only_to_lines(self):
106
old_inv = Inventory(None)
107
new_inv = Inventory(None)
108
root = new_inv.make_entry('directory', '', None, 'an-id')
109
root.revision = 'a@e\xc3\xa5ample.com--2004'
111
delta = new_inv._make_delta(old_inv)
112
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
113
tree_references=True)
114
self.assertEqual(StringIO(root_only_lines).readlines(),
115
journal.delta_to_lines(NULL_REVISION, 'entry-version', delta))
117
def test_unversioned_root(self):
118
old_inv = Inventory(None)
119
new_inv = Inventory(None)
120
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
122
delta = new_inv._make_delta(old_inv)
123
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
124
tree_references=False)
125
self.assertEqual(StringIO(root_only_unversioned).readlines(),
126
journal.delta_to_lines(NULL_REVISION, 'entry-version', delta))
128
def test_unversioned_non_root_errors(self):
129
old_inv = Inventory(None)
130
new_inv = Inventory(None)
131
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
132
root.revision = 'a@e\xc3\xa5ample.com--2004'
134
non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
135
new_inv.add(non_root)
136
delta = new_inv._make_delta(old_inv)
137
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
138
tree_references=True)
139
self.assertRaises(errors.BzrError,
140
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
142
def test_richroot_unversioned_root_errors(self):
143
old_inv = Inventory(None)
144
new_inv = Inventory(None)
145
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
147
delta = new_inv._make_delta(old_inv)
148
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
149
tree_references=True)
150
self.assertRaises(errors.BzrError,
151
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
153
def test_nonrichroot_versioned_root_errors(self):
154
old_inv = Inventory(None)
155
new_inv = Inventory(None)
156
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
157
root.revision = 'a@e\xc3\xa5ample.com--2004'
159
delta = new_inv._make_delta(old_inv)
160
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
161
tree_references=True)
162
self.assertRaises(errors.BzrError,
163
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
165
def test_nonrichroot_non_TREE_ROOT_id_errors(self):
166
old_inv = Inventory(None)
167
new_inv = Inventory(None)
168
root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
170
delta = new_inv._make_delta(old_inv)
171
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
172
tree_references=True)
173
self.assertRaises(errors.BzrError,
174
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
176
def test_unknown_kind_errors(self):
177
old_inv = Inventory(None)
178
new_inv = Inventory(None)
179
root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
180
root.revision = 'changed'
182
non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
183
non_root.revision = 'changed'
184
non_root.kind = 'strangelove'
185
new_inv.add(non_root)
186
delta = new_inv._make_delta(old_inv)
187
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
188
tree_references=True)
189
# we expect keyerror because there is little value wrapping this.
190
# This test aims to prove that it errors more than how it errors.
191
self.assertRaises(KeyError,
192
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
194
def test_tree_reference_disabled(self):
195
old_inv = Inventory(None)
196
new_inv = Inventory(None)
197
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
198
root.revision = 'a@e\xc3\xa5ample.com--2004'
200
non_root = new_inv.make_entry(
201
'tree-reference', 'foo', root.file_id, 'id')
202
non_root.revision = 'changed'
203
non_root.reference_revision = 'subtree-version'
204
new_inv.add(non_root)
205
delta = new_inv._make_delta(old_inv)
206
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
207
tree_references=False)
208
# we expect keyerror because there is little value wrapping this.
209
# This test aims to prove that it errors more than how it errors.
210
self.assertRaises(KeyError,
211
journal.delta_to_lines, NULL_REVISION, 'entry-version', delta)
213
def test_tree_reference_enabled(self):
214
old_inv = Inventory(None)
215
new_inv = Inventory(None)
216
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
217
root.revision = 'a@e\xc3\xa5ample.com--2004'
219
non_root = new_inv.make_entry(
220
'tree-reference', 'foo', root.file_id, 'id')
221
non_root.revision = 'changed'
222
non_root.reference_revision = 'subtree-version'
223
new_inv.add(non_root)
224
delta = new_inv._make_delta(old_inv)
225
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
226
tree_references=True)
227
self.assertEqual(StringIO(reference_lines).readlines(),
228
journal.delta_to_lines(NULL_REVISION, 'entry-version', delta))
230
def test_parse_no_bytes(self):
231
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
232
tree_references=True)
233
self.assertRaises(errors.BzrError, journal.parse_text_bytes, '')
235
def test_parse_bad_format(self):
236
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
237
tree_references=True)
238
self.assertRaises(errors.BzrError,
239
journal.parse_text_bytes, 'format: foo\n')
241
def test_parse_no_parent(self):
242
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
243
tree_references=True)
244
self.assertRaises(errors.BzrError,
245
journal.parse_text_bytes,
246
'format: bzr journalled inventory v1 (bzr 1.2)\n')
248
def test_parse_no_validator(self):
249
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
250
tree_references=True)
251
self.assertRaises(errors.BzrError,
252
journal.parse_text_bytes,
253
'format: bzr journalled inventory v1 (bzr 1.2)\n'
256
def test_parse_no_version(self):
257
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
258
tree_references=True)
259
self.assertRaises(errors.BzrError,
260
journal.parse_text_bytes,
261
'format: bzr journalled inventory v1 (bzr 1.2)\n'
264
def test_parse_duplicate_key_errors(self):
265
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
266
tree_references=True)
267
double_root_lines = \
268
"""format: bzr journalled inventory v1 (bzr 1.2)
272
/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
273
/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
275
self.assertRaises(errors.BzrError,
276
journal.parse_text_bytes, double_root_lines)
278
def test_parse_versioned_root_only(self):
279
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
280
tree_references=True)
281
parse_result = journal.parse_text_bytes(root_only_lines)
282
expected_entry = inventory.make_entry(
283
'directory', u'', None, 'an-id')
284
expected_entry.revision = 'a@e\xc3\xa5ample.com--2004'
286
('null:', 'entry-version', [(None, '/', 'an-id', expected_entry)]),
289
def test_parse_special_revid_not_valid_last_mod(self):
290
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
291
tree_references=True)
292
root_only_lines = """format: bzr journalled inventory v1 (bzr 1.2)
296
/\x00TREE_ROOT\x00\x00null:\x00dir\x00\x00
298
self.assertRaises(errors.BzrError,
299
journal.parse_text_bytes, root_only_lines)
301
def test_parse_versioned_root_versioned_disabled(self):
302
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
303
tree_references=True)
304
root_only_lines = """format: bzr journalled inventory v1 (bzr 1.2)
308
/\x00TREE_ROOT\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
310
self.assertRaises(errors.BzrError,
311
journal.parse_text_bytes, root_only_lines)
313
def test_parse_unique_root_id_root_versioned_disabled(self):
314
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=False,
315
tree_references=True)
316
root_only_lines = """format: bzr journalled inventory v1 (bzr 1.2)
320
/\x00an-id\x00\x00null:\x00dir\x00\x00
322
self.assertRaises(errors.BzrError,
323
journal.parse_text_bytes, root_only_lines)
325
def test_parse_unversioned_root_versioning_enabled(self):
326
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
327
tree_references=True)
328
self.assertRaises(errors.BzrError,
329
journal.parse_text_bytes, root_only_unversioned)
331
def test_parse_tree_when_disabled(self):
332
journal = inventory_delta.InventoryDeltaSerializer(versioned_root=True,
333
tree_references=False)
334
self.assertRaises(errors.BzrError,
335
journal.parse_text_bytes, reference_lines)
338
class TestJournalEntry(TestCase):
340
def test_to_inventory_root_id_versioned_not_permitted(self):
341
delta = [(None, '/', 'TREE_ROOT', inventory.make_entry(
342
'directory', '', None, 'TREE_ROOT'))]
343
serializer = inventory_delta.InventoryDeltaSerializer(False, True)
345
errors.BzrError, serializer.delta_to_lines, 'old-version',
346
'new-version', delta)
348
def test_to_inventory_root_id_not_versioned(self):
349
delta = [(None, '/', 'an-id', inventory.make_entry(
350
'directory', '', None, 'an-id'))]
351
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
353
errors.BzrError, serializer.delta_to_lines, 'old-version',
354
'new-version', delta)
356
def test_to_inventory_has_tree_not_meant_to(self):
357
make_entry = inventory.make_entry
358
tree_ref = make_entry('tree-reference', 'foo', 'changed-in', 'ref-id')
359
tree_ref.reference_revision = 'ref-revision'
362
make_entry('directory', '', 'changed-in', 'an-id')),
363
(None, '/foo', 'ref-id', tree_ref)
364
# a file that followed the root move
366
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
367
self.assertRaises(errors.BzrError, serializer.delta_to_lines,
368
'old-version', 'new-version', delta)
370
def test_to_inventory_torture(self):
371
def make_entry(kind, name, parent_id, file_id, **attrs):
372
entry = inventory.make_entry(kind, name, parent_id, file_id)
373
for name, value in attrs.items():
374
setattr(entry, name, value)
376
# this delta is crafted to have all the following:
380
# - files moved after parent dir was renamed
381
# - files with and without exec bit
384
(None, '', 'new-root-id',
385
make_entry('directory', '', None, 'new-root-id',
386
revision='changed-in')),
388
('', 'old-root', 'TREE_ROOT',
389
make_entry('directory', 'subdir-now', 'new-root-id',
390
'TREE_ROOT', revision='moved-root')),
391
# a file that followed the root move
392
('under-old-root', 'old-root/under-old-root', 'moved-id',
393
make_entry('file', 'under-old-root', 'TREE_ROOT', 'moved-id',
394
revision='old-rev', executable=False, text_size=30,
395
text_sha1='some-sha')),
397
('old-file', None, 'deleted-id', None),
398
# a tree reference moved to the new root
399
('ref', 'ref', 'ref-id',
400
make_entry('tree-reference', 'ref', 'new-root-id', 'ref-id',
401
reference_revision='tree-reference-id',
402
revision='new-rev')),
403
# a symlink now in a deep dir
404
('dir/link', 'old-root/dir/link', 'link-id',
405
make_entry('symlink', 'link', 'deep-id', 'link-id',
406
symlink_target='target', revision='new-rev')),
408
('dir', 'old-root/dir', 'deep-id',
409
make_entry('directory', 'dir', 'TREE_ROOT', 'deep-id',
410
revision='new-rev')),
411
# a file with an exec bit set
412
(None, 'configure', 'exec-id',
413
make_entry('file', 'configure', 'new-root-id', 'exec-id',
414
executable=True, text_size=30, text_sha1='some-sha',
415
revision='old-rev')),
417
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
418
lines = serializer.delta_to_lines(NULL_REVISION, 'something', delta)
419
expected = """format: bzr inventory delta v1 (bzr 1.14)
423
tree_references: true
424
/\x00new-root-id\x00\x00changed-in\x00dir
425
/configure\x00exec-id\x00new-root-id\x00old-rev\x00file\x0030\x00Y\x00some-sha
426
/old-root\x00TREE_ROOT\x00new-root-id\x00moved-root\x00dir
427
/old-root/dir\x00deep-id\x00TREE_ROOT\x00new-rev\x00dir
428
/old-root/dir/link\x00link-id\x00deep-id\x00new-rev\x00link\x00target
429
/old-root/under-old-root\x00moved-id\x00TREE_ROOT\x00old-rev\x00file\x0030\x00\x00some-sha
430
/ref\x00ref-id\x00new-root-id\x00new-rev\x00tree\x00tree-reference-id
431
None\x00deleted-id\x00\x00null:\x00deleted\x00\x00
433
serialised = ''.join(lines)
434
self.assertIsInstance(serialised, str)
435
serialised = '\n'.join(l.encode('string_escape') for l in serialised.splitlines())
436
expected = '\n'.join(l.encode('string_escape') for l in expected.splitlines())
437
self.assertEqualDiff(expected, serialised)
440
class TestContent(TestCase):
443
entry = inventory.make_entry('directory', 'a dir', None)
444
self.assertEqual('dir', inventory_delta._directory_content(entry))
446
def test_file_0_short_sha(self):
447
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
448
file_entry.text_sha1 = ''
449
file_entry.text_size = 0
450
self.assertEqual('file\x000\x00\x00',
451
inventory_delta._file_content(file_entry))
453
def test_file_10_foo(self):
454
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
455
file_entry.text_sha1 = 'foo'
456
file_entry.text_size = 10
457
self.assertEqual('file\x0010\x00\x00foo',
458
inventory_delta._file_content(file_entry))
460
def test_file_executable(self):
461
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
462
file_entry.executable = True
463
file_entry.text_sha1 = 'foo'
464
file_entry.text_size = 10
465
self.assertEqual('file\x0010\x00Y\x00foo',
466
inventory_delta._file_content(file_entry))
468
def test_file_without_size(self):
469
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
470
file_entry.text_sha1 = 'foo'
471
self.assertRaises(errors.BzrError,
472
inventory_delta._file_content, file_entry)
474
def test_file_without_sha1(self):
475
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
476
file_entry.text_size = 10
477
self.assertRaises(errors.BzrError,
478
inventory_delta._file_content, file_entry)
480
def test_link_empty_target(self):
481
entry = inventory.make_entry('symlink', 'a link', None)
482
entry.symlink_target = ''
483
self.assertEqual('link\x00',
484
inventory_delta._link_content(entry))
486
def test_link_unicode_target(self):
487
entry = inventory.make_entry('symlink', 'a link', None)
488
entry.symlink_target = ' \xc3\xa5'.decode('utf8')
489
self.assertEqual('link\x00 \xc3\xa5',
490
inventory_delta._link_content(entry))
492
def test_link_space_target(self):
493
entry = inventory.make_entry('symlink', 'a link', None)
494
entry.symlink_target = ' '
495
self.assertEqual('link\x00 ',
496
inventory_delta._link_content(entry))
498
def test_link_no_target(self):
499
entry = inventory.make_entry('symlink', 'a link', None)
500
self.assertRaises(errors.BzrError,
501
inventory_delta._link_content, entry)
503
def test_reference_null(self):
504
entry = inventory.make_entry('tree-reference', 'a tree', None)
505
entry.reference_revision = NULL_REVISION
506
self.assertEqual('tree\x00null:',
507
inventory_delta._reference_content(entry))
509
def test_reference_revision(self):
510
entry = inventory.make_entry('tree-reference', 'a tree', None)
511
entry.reference_revision = 'foo@\xc3\xa5b-lah'
512
self.assertEqual('tree\x00foo@\xc3\xa5b-lah',
513
inventory_delta._reference_content(entry))
515
def test_reference_no_reference(self):
516
entry = inventory.make_entry('tree-reference', 'a tree', None)
517
self.assertRaises(errors.BzrError,
518
inventory_delta._reference_content, entry)