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 bzrlib.inventory_delta.
19
See doc/developer/inventory.txt for more information.
22
from cStringIO import StringIO
29
from bzrlib.inventory import Inventory
30
from bzrlib.revision import NULL_REVISION
31
from bzrlib.tests import TestCase
33
### DO NOT REFLOW THESE TEXTS. NEW LINES ARE SIGNIFICANT. ###
34
empty_lines = """format: bzr inventory delta v1 (bzr 1.14)
41
root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
43
version: entry-version
46
None\x00/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir
50
root_change_lines = """format: bzr inventory delta v1 (bzr 1.14)
55
/\x00an-id\x00\x00different-version\x00dir
58
corrupt_parent_lines = """format: bzr inventory delta v1 (bzr 1.14)
62
tree_references: false
63
/\x00an-id\x00\x00different-version\x00dir
66
root_only_unversioned = """format: bzr inventory delta v1 (bzr 1.14)
68
version: entry-version
70
tree_references: false
71
None\x00/\x00TREE_ROOT\x00\x00null:\x00dir
74
reference_lines = """format: bzr inventory delta v1 (bzr 1.14)
76
version: entry-version
79
None\x00/\x00TREE_ROOT\x00\x00a@e\xc3\xa5ample.com--2004\x00dir
80
None\x00/foo\x00id\x00TREE_ROOT\x00changed\x00tree\x00subtree-version
83
change_tree_lines = """format: bzr inventory delta v1 (bzr 1.14)
87
tree_references: false
88
/foo\x00id\x00TREE_ROOT\x00changed-twice\x00tree\x00subtree-version2
92
class TestDeserialization(TestCase):
93
"""Test InventoryDeltaSerializer.parse_text_bytes."""
95
def test_parse_no_bytes(self):
96
serializer = inventory_delta.InventoryDeltaSerializer(
97
versioned_root=True, tree_references=True)
98
err = self.assertRaises(
99
errors.BzrError, serializer.parse_text_bytes, '')
100
self.assertContainsRe(str(err), 'unknown format')
102
def test_parse_bad_format(self):
103
serializer = inventory_delta.InventoryDeltaSerializer(
104
versioned_root=True, tree_references=True)
105
err = self.assertRaises(errors.BzrError,
106
serializer.parse_text_bytes, 'format: foo\n')
107
self.assertContainsRe(str(err), 'unknown format')
109
def test_parse_no_parent(self):
110
serializer = inventory_delta.InventoryDeltaSerializer(
111
versioned_root=True, tree_references=True)
112
err = self.assertRaises(errors.BzrError,
113
serializer.parse_text_bytes,
114
'format: bzr inventory delta v1 (bzr 1.14)\n')
115
self.assertContainsRe(str(err), 'missing parent: marker')
117
def test_parse_no_version(self):
118
serializer = inventory_delta.InventoryDeltaSerializer(
119
versioned_root=True, tree_references=True)
120
err = self.assertRaises(errors.BzrError,
121
serializer.parse_text_bytes,
122
'format: bzr inventory delta v1 (bzr 1.14)\n'
124
self.assertContainsRe(str(err), 'missing version: marker')
126
def test_parse_duplicate_key_errors(self):
127
serializer = inventory_delta.InventoryDeltaSerializer(
128
versioned_root=True, tree_references=True)
129
double_root_lines = \
130
"""format: bzr inventory delta v1 (bzr 1.14)
134
tree_references: true
135
None\x00/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
136
None\x00/\x00an-id\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
138
err = self.assertRaises(errors.BzrError,
139
serializer.parse_text_bytes, double_root_lines)
140
self.assertContainsRe(str(err), 'duplicate file id')
142
def test_parse_versioned_root_only(self):
143
serializer = inventory_delta.InventoryDeltaSerializer(
144
versioned_root=True, tree_references=True)
145
parse_result = serializer.parse_text_bytes(root_only_lines)
146
expected_entry = inventory.make_entry(
147
'directory', u'', None, 'an-id')
148
expected_entry.revision = 'a@e\xc3\xa5ample.com--2004'
150
('null:', 'entry-version', [(None, '/', 'an-id', expected_entry)]),
153
def test_parse_special_revid_not_valid_last_mod(self):
154
serializer = inventory_delta.InventoryDeltaSerializer(
155
versioned_root=False, tree_references=True)
156
root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
159
versioned_root: false
160
tree_references: true
161
None\x00/\x00TREE_ROOT\x00\x00null:\x00dir\x00\x00
163
err = self.assertRaises(errors.BzrError,
164
serializer.parse_text_bytes, root_only_lines)
165
self.assertContainsRe(str(err), 'special revisionid found')
167
def test_parse_versioned_root_versioned_disabled(self):
168
serializer = inventory_delta.InventoryDeltaSerializer(
169
versioned_root=False, tree_references=True)
170
root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
173
versioned_root: false
174
tree_references: true
175
None\x00/\x00TREE_ROOT\x00\x00a@e\xc3\xa5ample.com--2004\x00dir\x00\x00
177
err = self.assertRaises(errors.BzrError,
178
serializer.parse_text_bytes, root_only_lines)
179
self.assertContainsRe(str(err), 'Versioned root found')
181
def test_parse_unique_root_id_root_versioned_disabled(self):
182
serializer = inventory_delta.InventoryDeltaSerializer(
183
versioned_root=False, tree_references=True)
184
root_only_lines = """format: bzr inventory delta v1 (bzr 1.14)
187
versioned_root: false
188
tree_references: true
189
None\x00/\x00an-id\x00\x00null:\x00dir\x00\x00
191
err = self.assertRaises(errors.BzrError,
192
serializer.parse_text_bytes, root_only_lines)
193
self.assertContainsRe(str(err), 'Versioned root found')
195
def test_parse_unversioned_root_versioning_enabled(self):
196
serializer = inventory_delta.InventoryDeltaSerializer(
197
versioned_root=True, tree_references=True)
198
err = self.assertRaises(errors.BzrError,
199
serializer.parse_text_bytes, root_only_unversioned)
200
self.assertContainsRe(
201
str(err), 'serialized versioned_root flag is wrong: False')
203
def test_parse_tree_when_disabled(self):
204
serializer = inventory_delta.InventoryDeltaSerializer(
205
versioned_root=True, tree_references=False)
206
err = self.assertRaises(errors.BzrError,
207
serializer.parse_text_bytes, reference_lines)
208
self.assertContainsRe(
209
str(err), 'serialized tree_references flag is wrong: True')
212
class TestSerialization(TestCase):
213
"""Tests for InventoryDeltaSerializer.delta_to_lines."""
215
def test_empty_delta_to_lines(self):
216
old_inv = Inventory(None)
217
new_inv = Inventory(None)
218
delta = new_inv._make_delta(old_inv)
219
serializer = inventory_delta.InventoryDeltaSerializer(
220
versioned_root=True, tree_references=True)
221
self.assertEqual(StringIO(empty_lines).readlines(),
222
serializer.delta_to_lines(NULL_REVISION, NULL_REVISION, delta))
224
def test_root_only_to_lines(self):
225
old_inv = Inventory(None)
226
new_inv = Inventory(None)
227
root = new_inv.make_entry('directory', '', None, 'an-id')
228
root.revision = 'a@e\xc3\xa5ample.com--2004'
230
delta = new_inv._make_delta(old_inv)
231
serializer = inventory_delta.InventoryDeltaSerializer(
232
versioned_root=True, tree_references=True)
233
self.assertEqual(StringIO(root_only_lines).readlines(),
234
serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
236
def test_unversioned_root(self):
237
old_inv = Inventory(None)
238
new_inv = Inventory(None)
239
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
241
delta = new_inv._make_delta(old_inv)
242
serializer = inventory_delta.InventoryDeltaSerializer(
243
versioned_root=False, tree_references=False)
244
self.assertEqual(StringIO(root_only_unversioned).readlines(),
245
serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
247
def test_unversioned_non_root_errors(self):
248
old_inv = Inventory(None)
249
new_inv = Inventory(None)
250
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
251
root.revision = 'a@e\xc3\xa5ample.com--2004'
253
non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
254
new_inv.add(non_root)
255
delta = new_inv._make_delta(old_inv)
256
serializer = inventory_delta.InventoryDeltaSerializer(
257
versioned_root=True, tree_references=True)
258
err = self.assertRaises(errors.BzrError,
259
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
260
self.assertEqual(str(err), 'no version for fileid id')
262
def test_richroot_unversioned_root_errors(self):
263
old_inv = Inventory(None)
264
new_inv = Inventory(None)
265
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
267
delta = new_inv._make_delta(old_inv)
268
serializer = inventory_delta.InventoryDeltaSerializer(
269
versioned_root=True, tree_references=True)
270
err = self.assertRaises(errors.BzrError,
271
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
272
self.assertEqual(str(err), 'no version for fileid TREE_ROOT')
274
def test_nonrichroot_versioned_root_errors(self):
275
old_inv = Inventory(None)
276
new_inv = Inventory(None)
277
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
278
root.revision = 'a@e\xc3\xa5ample.com--2004'
280
delta = new_inv._make_delta(old_inv)
281
serializer = inventory_delta.InventoryDeltaSerializer(
282
versioned_root=False, tree_references=True)
283
err = self.assertRaises(errors.BzrError,
284
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
285
self.assertEqual(str(err), 'Version present for / in TREE_ROOT')
287
def test_nonrichroot_non_TREE_ROOT_id_errors(self):
288
old_inv = Inventory(None)
289
new_inv = Inventory(None)
290
root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
292
delta = new_inv._make_delta(old_inv)
293
serializer = inventory_delta.InventoryDeltaSerializer(
294
versioned_root=False, tree_references=True)
295
err = self.assertRaises(errors.BzrError,
296
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
298
str(err), 'file_id my-rich-root-id is not TREE_ROOT for /')
300
def test_unknown_kind_errors(self):
301
old_inv = Inventory(None)
302
new_inv = Inventory(None)
303
root = new_inv.make_entry('directory', '', None, 'my-rich-root-id')
304
root.revision = 'changed'
306
non_root = new_inv.make_entry('directory', 'foo', root.file_id, 'id')
307
non_root.revision = 'changed'
308
non_root.kind = 'strangelove'
309
new_inv.add(non_root)
310
delta = new_inv._make_delta(old_inv)
311
serializer = inventory_delta.InventoryDeltaSerializer(
312
versioned_root=True, tree_references=True)
313
# we expect keyerror because there is little value wrapping this.
314
# This test aims to prove that it errors more than how it errors.
315
err = self.assertRaises(KeyError,
316
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
317
self.assertEqual(('strangelove',), err.args)
319
def test_tree_reference_disabled(self):
320
old_inv = Inventory(None)
321
new_inv = Inventory(None)
322
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
323
root.revision = 'a@e\xc3\xa5ample.com--2004'
325
non_root = new_inv.make_entry(
326
'tree-reference', 'foo', root.file_id, 'id')
327
non_root.revision = 'changed'
328
non_root.reference_revision = 'subtree-version'
329
new_inv.add(non_root)
330
delta = new_inv._make_delta(old_inv)
331
serializer = inventory_delta.InventoryDeltaSerializer(
332
versioned_root=True, tree_references=False)
333
# we expect keyerror because there is little value wrapping this.
334
# This test aims to prove that it errors more than how it errors.
335
err = self.assertRaises(KeyError,
336
serializer.delta_to_lines, NULL_REVISION, 'entry-version', delta)
337
self.assertEqual(('tree-reference',), err.args)
339
def test_tree_reference_enabled(self):
340
old_inv = Inventory(None)
341
new_inv = Inventory(None)
342
root = new_inv.make_entry('directory', '', None, 'TREE_ROOT')
343
root.revision = 'a@e\xc3\xa5ample.com--2004'
345
non_root = new_inv.make_entry(
346
'tree-reference', 'foo', root.file_id, 'id')
347
non_root.revision = 'changed'
348
non_root.reference_revision = 'subtree-version'
349
new_inv.add(non_root)
350
delta = new_inv._make_delta(old_inv)
351
serializer = inventory_delta.InventoryDeltaSerializer(
352
versioned_root=True, tree_references=True)
353
self.assertEqual(StringIO(reference_lines).readlines(),
354
serializer.delta_to_lines(NULL_REVISION, 'entry-version', delta))
356
def test_to_inventory_root_id_versioned_not_permitted(self):
357
delta = [(None, '/', 'TREE_ROOT', inventory.make_entry(
358
'directory', '', None, 'TREE_ROOT'))]
359
serializer = inventory_delta.InventoryDeltaSerializer(False, True)
361
errors.BzrError, serializer.delta_to_lines, 'old-version',
362
'new-version', delta)
364
def test_to_inventory_root_id_not_versioned(self):
365
delta = [(None, '/', 'an-id', inventory.make_entry(
366
'directory', '', None, 'an-id'))]
367
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
369
errors.BzrError, serializer.delta_to_lines, 'old-version',
370
'new-version', delta)
372
def test_to_inventory_has_tree_not_meant_to(self):
373
make_entry = inventory.make_entry
374
tree_ref = make_entry('tree-reference', 'foo', 'changed-in', 'ref-id')
375
tree_ref.reference_revision = 'ref-revision'
378
make_entry('directory', '', 'changed-in', 'an-id')),
379
(None, '/foo', 'ref-id', tree_ref)
380
# a file that followed the root move
382
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
383
self.assertRaises(errors.BzrError, serializer.delta_to_lines,
384
'old-version', 'new-version', delta)
386
def test_to_inventory_torture(self):
387
def make_entry(kind, name, parent_id, file_id, **attrs):
388
entry = inventory.make_entry(kind, name, parent_id, file_id)
389
for name, value in attrs.items():
390
setattr(entry, name, value)
392
# this delta is crafted to have all the following:
396
# - files moved after parent dir was renamed
397
# - files with and without exec bit
400
(None, '', 'new-root-id',
401
make_entry('directory', '', None, 'new-root-id',
402
revision='changed-in')),
404
('', 'old-root', 'TREE_ROOT',
405
make_entry('directory', 'subdir-now', 'new-root-id',
406
'TREE_ROOT', revision='moved-root')),
407
# a file that followed the root move
408
('under-old-root', 'old-root/under-old-root', 'moved-id',
409
make_entry('file', 'under-old-root', 'TREE_ROOT', 'moved-id',
410
revision='old-rev', executable=False, text_size=30,
411
text_sha1='some-sha')),
413
('old-file', None, 'deleted-id', None),
414
# a tree reference moved to the new root
415
('ref', 'ref', 'ref-id',
416
make_entry('tree-reference', 'ref', 'new-root-id', 'ref-id',
417
reference_revision='tree-reference-id',
418
revision='new-rev')),
419
# a symlink now in a deep dir
420
('dir/link', 'old-root/dir/link', 'link-id',
421
make_entry('symlink', 'link', 'deep-id', 'link-id',
422
symlink_target='target', revision='new-rev')),
424
('dir', 'old-root/dir', 'deep-id',
425
make_entry('directory', 'dir', 'TREE_ROOT', 'deep-id',
426
revision='new-rev')),
427
# a file with an exec bit set
428
(None, 'configure', 'exec-id',
429
make_entry('file', 'configure', 'new-root-id', 'exec-id',
430
executable=True, text_size=30, text_sha1='some-sha',
431
revision='old-rev')),
433
serializer = inventory_delta.InventoryDeltaSerializer(True, True)
434
lines = serializer.delta_to_lines(NULL_REVISION, 'something', delta)
435
expected = """format: bzr inventory delta v1 (bzr 1.14)
439
tree_references: true
440
/\x00/old-root\x00TREE_ROOT\x00new-root-id\x00moved-root\x00dir
441
/dir\x00/old-root/dir\x00deep-id\x00TREE_ROOT\x00new-rev\x00dir
442
/dir/link\x00/old-root/dir/link\x00link-id\x00deep-id\x00new-rev\x00link\x00target
443
/old-file\x00None\x00deleted-id\x00\x00null:\x00deleted\x00\x00
444
/ref\x00/ref\x00ref-id\x00new-root-id\x00new-rev\x00tree\x00tree-reference-id
445
/under-old-root\x00/old-root/under-old-root\x00moved-id\x00TREE_ROOT\x00old-rev\x00file\x0030\x00\x00some-sha
446
None\x00/\x00new-root-id\x00\x00changed-in\x00dir
447
None\x00/configure\x00exec-id\x00new-root-id\x00old-rev\x00file\x0030\x00Y\x00some-sha
449
serialized = ''.join(lines)
450
self.assertIsInstance(serialized, str)
451
self.assertEqual(expected, serialized)
454
class TestContent(TestCase):
455
"""Test serialization of the content part of a line."""
458
entry = inventory.make_entry('directory', 'a dir', None)
459
self.assertEqual('dir', inventory_delta._directory_content(entry))
461
def test_file_0_short_sha(self):
462
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
463
file_entry.text_sha1 = ''
464
file_entry.text_size = 0
465
self.assertEqual('file\x000\x00\x00',
466
inventory_delta._file_content(file_entry))
468
def test_file_10_foo(self):
469
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
470
file_entry.text_sha1 = 'foo'
471
file_entry.text_size = 10
472
self.assertEqual('file\x0010\x00\x00foo',
473
inventory_delta._file_content(file_entry))
475
def test_file_executable(self):
476
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
477
file_entry.executable = True
478
file_entry.text_sha1 = 'foo'
479
file_entry.text_size = 10
480
self.assertEqual('file\x0010\x00Y\x00foo',
481
inventory_delta._file_content(file_entry))
483
def test_file_without_size(self):
484
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
485
file_entry.text_sha1 = 'foo'
486
self.assertRaises(errors.BzrError,
487
inventory_delta._file_content, file_entry)
489
def test_file_without_sha1(self):
490
file_entry = inventory.make_entry('file', 'a file', None, 'file-id')
491
file_entry.text_size = 10
492
self.assertRaises(errors.BzrError,
493
inventory_delta._file_content, file_entry)
495
def test_link_empty_target(self):
496
entry = inventory.make_entry('symlink', 'a link', None)
497
entry.symlink_target = ''
498
self.assertEqual('link\x00',
499
inventory_delta._link_content(entry))
501
def test_link_unicode_target(self):
502
entry = inventory.make_entry('symlink', 'a link', None)
503
entry.symlink_target = ' \xc3\xa5'.decode('utf8')
504
self.assertEqual('link\x00 \xc3\xa5',
505
inventory_delta._link_content(entry))
507
def test_link_space_target(self):
508
entry = inventory.make_entry('symlink', 'a link', None)
509
entry.symlink_target = ' '
510
self.assertEqual('link\x00 ',
511
inventory_delta._link_content(entry))
513
def test_link_no_target(self):
514
entry = inventory.make_entry('symlink', 'a link', None)
515
self.assertRaises(errors.BzrError,
516
inventory_delta._link_content, entry)
518
def test_reference_null(self):
519
entry = inventory.make_entry('tree-reference', 'a tree', None)
520
entry.reference_revision = NULL_REVISION
521
self.assertEqual('tree\x00null:',
522
inventory_delta._reference_content(entry))
524
def test_reference_revision(self):
525
entry = inventory.make_entry('tree-reference', 'a tree', None)
526
entry.reference_revision = 'foo@\xc3\xa5b-lah'
527
self.assertEqual('tree\x00foo@\xc3\xa5b-lah',
528
inventory_delta._reference_content(entry))
530
def test_reference_no_reference(self):
531
entry = inventory.make_entry('tree-reference', 'a tree', None)
532
self.assertRaises(errors.BzrError,
533
inventory_delta._reference_content, entry)