1# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2#
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.
7#
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.
12#
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
16
17"""Tests for different inventory implementations"""
18
19# NOTE: Don't import Inventory here, to make sure that we don't accidentally
20# hardcode that when we should be using self.make_inventory
21
22from breezy import (
23    errors,
24    osutils,
25    )
26from breezy.bzr import (
27    inventory,
28    )
29
30from breezy.bzr.inventory import (
31    InventoryDirectory,
32    InventoryEntry,
33    InventoryFile,
34    InventoryLink,
35    TreeReference,
36    )
37
38from breezy.bzr.tests.per_inventory import TestCaseWithInventory
39
40
41class TestInventory(TestCaseWithInventory):
42
43    def make_init_inventory(self):
44        inv = inventory.Inventory(b'tree-root')
45        inv.revision = b'initial-rev'
46        inv.root.revision = b'initial-rev'
47        return self.inv_to_test_inv(inv)
48
49    def make_file(self, file_id, name, parent_id, content=b'content\n',
50                  revision=b'new-test-rev'):
51        ie = InventoryFile(file_id, name, parent_id)
52        ie.text_sha1 = osutils.sha_string(content)
53        ie.text_size = len(content)
54        ie.revision = revision
55        return ie
56
57    def make_link(self, file_id, name, parent_id, target='link-target\n'):
58        ie = InventoryLink(file_id, name, parent_id)
59        ie.symlink_target = target
60        return ie
61
62    def prepare_inv_with_nested_dirs(self):
63        inv = inventory.Inventory(b'tree-root')
64        inv.root.revision = b'revision'
65        for args in [('src', 'directory', b'src-id'),
66                     ('doc', 'directory', b'doc-id'),
67                     ('src/hello.c', 'file', b'hello-id'),
68                     ('src/bye.c', 'file', b'bye-id'),
69                     ('zz', 'file', b'zz-id'),
70                     ('src/sub/', 'directory', b'sub-id'),
71                     ('src/zz.c', 'file', b'zzc-id'),
72                     ('src/sub/a', 'file', b'a-id'),
73                     ('Makefile', 'file', b'makefile-id')]:
74            ie = inv.add_path(*args)
75            ie.revision = b'revision'
76            if args[1] == 'file':
77                ie.text_sha1 = osutils.sha_string(b'content\n')
78                ie.text_size = len(b'content\n')
79        return self.inv_to_test_inv(inv)
80
81
82class TestInventoryCreateByApplyDelta(TestInventory):
83    """A subset of the inventory delta application tests.
84
85    See test_inv which has comprehensive delta application tests for
86    inventories, dirstate, and repository based inventories.
87    """
88
89    def test_add(self):
90        inv = self.make_init_inventory()
91        inv = inv.create_by_apply_delta([
92            (None, "a", b"a-id", self.make_file(b'a-id', 'a', b'tree-root')),
93            ], b'new-test-rev')
94        self.assertEqual('a', inv.id2path(b'a-id'))
95
96    def test_delete(self):
97        inv = self.make_init_inventory()
98        inv = inv.create_by_apply_delta([
99            (None, "a", b"a-id", self.make_file(b'a-id', 'a', b'tree-root')),
100            ], b'new-rev-1')
101        self.assertEqual('a', inv.id2path(b'a-id'))
102        inv = inv.create_by_apply_delta([
103            ("a", None, b"a-id", None),
104            ], b'new-rev-2')
105        self.assertRaises(errors.NoSuchId, inv.id2path, b'a-id')
106
107    def test_rename(self):
108        inv = self.make_init_inventory()
109        inv = inv.create_by_apply_delta([
110            (None, "a", b"a-id", self.make_file(b'a-id', 'a', b'tree-root')),
111            ], b'new-rev-1')
112        self.assertEqual('a', inv.id2path(b'a-id'))
113        a_ie = inv.get_entry(b'a-id')
114        b_ie = self.make_file(a_ie.file_id, "b", a_ie.parent_id)
115        inv = inv.create_by_apply_delta(
116            [("a", "b", b"a-id", b_ie)], b'new-rev-2')
117        self.assertEqual("b", inv.id2path(b'a-id'))
118
119    def test_illegal(self):
120        # A file-id cannot appear in a delta more than once
121        inv = self.make_init_inventory()
122        self.assertRaises(errors.InconsistentDelta, inv.create_by_apply_delta, [
123            (None, "a", b"id-1", self.make_file(b'id-1', 'a', b'tree-root')),
124            (None, "b", b"id-1", self.make_file(b'id-1', 'b', b'tree-root')),
125            ], b'new-rev-1')
126
127
128class TestInventoryReads(TestInventory):
129
130    def test_is_root(self):
131        """Ensure our root-checking code is accurate."""
132        inv = self.make_init_inventory()
133        self.assertTrue(inv.is_root(b'tree-root'))
134        self.assertFalse(inv.is_root(b'booga'))
135        ie = inv.get_entry(b'tree-root').copy()
136        ie.file_id = b'booga'
137        inv = inv.create_by_apply_delta([("", None, b"tree-root", None),
138                                         (None, "", b"booga", ie)], b'new-rev-2')
139        self.assertFalse(inv.is_root(b'TREE_ROOT'))
140        self.assertTrue(inv.is_root(b'booga'))
141
142    def test_ids(self):
143        """Test detection of files within selected directories."""
144        inv = inventory.Inventory(b'TREE_ROOT')
145        inv.root.revision = b'revision'
146        for args in [('src', 'directory', b'src-id'),
147                     ('doc', 'directory', b'doc-id'),
148                     ('src/hello.c', 'file'),
149                     ('src/bye.c', 'file', b'bye-id'),
150                     ('Makefile', 'file')]:
151            ie = inv.add_path(*args)
152            ie.revision = b'revision'
153            if args[1] == 'file':
154                ie.text_sha1 = osutils.sha_string(b'content\n')
155                ie.text_size = len(b'content\n')
156        inv = self.inv_to_test_inv(inv)
157        self.assertEqual(inv.path2id('src'), b'src-id')
158        self.assertEqual(inv.path2id('src/bye.c'), b'bye-id')
159
160    def test_get_entry_by_path_partial(self):
161        inv = inventory.Inventory(b'TREE_ROOT')
162        inv.root.revision = b'revision'
163        for args in [('src', 'directory', b'src-id'),
164                     ('doc', 'directory', b'doc-id'),
165                     ('src/hello.c', 'file'),
166                     ('src/bye.c', 'file', b'bye-id'),
167                     ('Makefile', 'file'),
168                     ('external', 'tree-reference', b'other-root')]:
169            ie = inv.add_path(*args)
170            ie.revision = b'revision'
171            if args[1] == 'file':
172                ie.text_sha1 = osutils.sha_string(b'content\n')
173                ie.text_size = len(b'content\n')
174            if args[1] == 'tree-reference':
175                ie.reference_revision = b'reference'
176        inv = self.inv_to_test_inv(inv)
177
178        # Standard lookups
179        ie, resolved, remaining = inv.get_entry_by_path_partial('')
180        self.assertEqual((ie.file_id, resolved, remaining), (b'TREE_ROOT', [], []))
181        ie, resolved, remaining = inv.get_entry_by_path_partial('src')
182        self.assertEqual((ie.file_id, resolved, remaining), (b'src-id', ['src'], []))
183        ie, resolved, remaining = inv.get_entry_by_path_partial('src/bye.c')
184        self.assertEqual((ie.file_id, resolved, remaining), (b'bye-id', ['src', 'bye.c'], []))
185
186        # Paths in the external tree
187        ie, resolved, remaining = inv.get_entry_by_path_partial('external')
188        self.assertEqual((ie.file_id, resolved, remaining), (b'other-root', ['external'], []))
189        ie, resolved, remaining = inv.get_entry_by_path_partial('external/blah')
190        self.assertEqual((ie.file_id, resolved, remaining), (b'other-root', ['external'], ['blah']))
191
192        # Nonexistant paths
193        ie, resolved, remaining = inv.get_entry_by_path_partial('foo.c')
194        self.assertEqual((ie, resolved, remaining), (None, None, None))
195
196    def test_non_directory_children(self):
197        """Test path2id when a parent directory has no children"""
198        inv = inventory.Inventory(b'tree-root')
199        inv.add(self.make_file(b'file-id', 'file', b'tree-root'))
200        inv.add(self.make_link(b'link-id', 'link', b'tree-root'))
201        self.assertIs(None, inv.path2id('file/subfile'))
202        self.assertIs(None, inv.path2id('link/subfile'))
203
204    def test_is_unmodified(self):
205        f1 = self.make_file(b'file-id', 'file', b'tree-root')
206        f1.revision = b'rev'
207        self.assertTrue(f1.is_unmodified(f1))
208        f2 = self.make_file(b'file-id', 'file', b'tree-root')
209        f2.revision = b'rev'
210        self.assertTrue(f1.is_unmodified(f2))
211        f3 = self.make_file(b'file-id', 'file', b'tree-root')
212        self.assertFalse(f1.is_unmodified(f3))
213        f4 = self.make_file(b'file-id', 'file', b'tree-root')
214        f4.revision = b'rev1'
215        self.assertFalse(f1.is_unmodified(f4))
216
217    def test_iter_entries(self):
218        inv = self.prepare_inv_with_nested_dirs()
219
220        # Test all entries
221        self.assertEqual([
222            ('', b'tree-root'),
223            ('Makefile', b'makefile-id'),
224            ('doc', b'doc-id'),
225            ('src', b'src-id'),
226            ('src/bye.c', b'bye-id'),
227            ('src/hello.c', b'hello-id'),
228            ('src/sub', b'sub-id'),
229            ('src/sub/a', b'a-id'),
230            ('src/zz.c', b'zzc-id'),
231            ('zz', b'zz-id'),
232            ], [(path, ie.file_id) for path, ie in inv.iter_entries()])
233
234        # Test a subdirectory
235        self.assertEqual([
236            ('bye.c', b'bye-id'),
237            ('hello.c', b'hello-id'),
238            ('sub', b'sub-id'),
239            ('sub/a', b'a-id'),
240            ('zz.c', b'zzc-id'),
241            ], [(path, ie.file_id) for path, ie in inv.iter_entries(
242                from_dir=b'src-id')])
243
244        # Test not recursing at the root level
245        self.assertEqual([
246            ('', b'tree-root'),
247            ('Makefile', b'makefile-id'),
248            ('doc', b'doc-id'),
249            ('src', b'src-id'),
250            ('zz', b'zz-id'),
251            ], [(path, ie.file_id) for path, ie in inv.iter_entries(
252                recursive=False)])
253
254        # Test not recursing at a subdirectory level
255        self.assertEqual([
256            ('bye.c', b'bye-id'),
257            ('hello.c', b'hello-id'),
258            ('sub', b'sub-id'),
259            ('zz.c', b'zzc-id'),
260            ], [(path, ie.file_id) for path, ie in inv.iter_entries(
261                from_dir=b'src-id', recursive=False)])
262
263    def test_iter_just_entries(self):
264        inv = self.prepare_inv_with_nested_dirs()
265        self.assertEqual([
266            b'a-id',
267            b'bye-id',
268            b'doc-id',
269            b'hello-id',
270            b'makefile-id',
271            b'src-id',
272            b'sub-id',
273            b'tree-root',
274            b'zz-id',
275            b'zzc-id',
276            ], sorted([ie.file_id for ie in inv.iter_just_entries()]))
277
278    def test_iter_entries_by_dir(self):
279        inv = self. prepare_inv_with_nested_dirs()
280        self.assertEqual([
281            ('', b'tree-root'),
282            ('Makefile', b'makefile-id'),
283            ('doc', b'doc-id'),
284            ('src', b'src-id'),
285            ('zz', b'zz-id'),
286            ('src/bye.c', b'bye-id'),
287            ('src/hello.c', b'hello-id'),
288            ('src/sub', b'sub-id'),
289            ('src/zz.c', b'zzc-id'),
290            ('src/sub/a', b'a-id'),
291            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir()])
292        self.assertEqual([
293            ('', b'tree-root'),
294            ('Makefile', b'makefile-id'),
295            ('doc', b'doc-id'),
296            ('src', b'src-id'),
297            ('zz', b'zz-id'),
298            ('src/bye.c', b'bye-id'),
299            ('src/hello.c', b'hello-id'),
300            ('src/sub', b'sub-id'),
301            ('src/zz.c', b'zzc-id'),
302            ('src/sub/a', b'a-id'),
303            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir(
304                specific_file_ids=(b'a-id', b'zzc-id', b'doc-id', b'tree-root',
305                                   b'hello-id', b'bye-id', b'zz-id', b'src-id', b'makefile-id',
306                                   b'sub-id'))])
307
308        self.assertEqual([
309            ('Makefile', b'makefile-id'),
310            ('doc', b'doc-id'),
311            ('zz', b'zz-id'),
312            ('src/bye.c', b'bye-id'),
313            ('src/hello.c', b'hello-id'),
314            ('src/zz.c', b'zzc-id'),
315            ('src/sub/a', b'a-id'),
316            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir(
317                specific_file_ids=(b'a-id', b'zzc-id', b'doc-id',
318                                   b'hello-id', b'bye-id', b'zz-id', b'makefile-id'))])
319
320        self.assertEqual([
321            ('Makefile', b'makefile-id'),
322            ('src/bye.c', b'bye-id'),
323            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir(
324                specific_file_ids=(b'bye-id', b'makefile-id'))])
325
326        self.assertEqual([
327            ('Makefile', b'makefile-id'),
328            ('src/bye.c', b'bye-id'),
329            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir(
330                specific_file_ids=(b'bye-id', b'makefile-id'))])
331
332        self.assertEqual([
333            ('src/bye.c', b'bye-id'),
334            ], [(path, ie.file_id) for path, ie in inv.iter_entries_by_dir(
335                specific_file_ids=(b'bye-id',))])
336
337
338class TestInventoryFiltering(TestInventory):
339
340    def test_inv_filter_empty(self):
341        inv = self.prepare_inv_with_nested_dirs()
342        new_inv = inv.filter([])
343        self.assertEqual([
344            ('', b'tree-root'),
345            ], [(path, ie.file_id) for path, ie in new_inv.iter_entries()])
346
347    def test_inv_filter_files(self):
348        inv = self.prepare_inv_with_nested_dirs()
349        new_inv = inv.filter([b'zz-id', b'hello-id', b'a-id'])
350        self.assertEqual([
351            ('', b'tree-root'),
352            ('src', b'src-id'),
353            ('src/hello.c', b'hello-id'),
354            ('src/sub', b'sub-id'),
355            ('src/sub/a', b'a-id'),
356            ('zz', b'zz-id'),
357            ], [(path, ie.file_id) for path, ie in new_inv.iter_entries()])
358
359    def test_inv_filter_dirs(self):
360        inv = self.prepare_inv_with_nested_dirs()
361        new_inv = inv.filter([b'doc-id', b'sub-id'])
362        self.assertEqual([
363            ('', b'tree-root'),
364            ('doc', b'doc-id'),
365            ('src', b'src-id'),
366            ('src/sub', b'sub-id'),
367            ('src/sub/a', b'a-id'),
368            ], [(path, ie.file_id) for path, ie in new_inv.iter_entries()])
369
370    def test_inv_filter_files_and_dirs(self):
371        inv = self.prepare_inv_with_nested_dirs()
372        new_inv = inv.filter([b'makefile-id', b'src-id'])
373        self.assertEqual([
374            ('', b'tree-root'),
375            ('Makefile', b'makefile-id'),
376            ('src', b'src-id'),
377            ('src/bye.c', b'bye-id'),
378            ('src/hello.c', b'hello-id'),
379            ('src/sub', b'sub-id'),
380            ('src/sub/a', b'a-id'),
381            ('src/zz.c', b'zzc-id'),
382            ], [(path, ie.file_id) for path, ie in new_inv.iter_entries()])
383
384    def test_inv_filter_entry_not_present(self):
385        inv = self.prepare_inv_with_nested_dirs()
386        new_inv = inv.filter([b'not-present-id'])
387        self.assertEqual([
388            ('', b'tree-root'),
389            ], [(path, ie.file_id) for path, ie in new_inv.iter_entries()])
390