~bzr-pqm/bzr/bzr.dev

963 by Martin Pool
- add the start of a test for inventory file-id matching
1
# Copyright (C) 2005 by 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
17
from cStringIO import StringIO
1185.1.40 by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch.
18
import os
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
19
20
from bzrlib.branch import Branch
1411 by Robert Collins
use weave ancestry to determine inventory entry previous heads, prevent propogating 'I did a merge' merges.
21
from bzrlib.clone import copy_branch
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
22
import bzrlib.errors as errors
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
23
from bzrlib.diff import internal_diff
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
24
from bzrlib.inventory import Inventory, ROOT_ID
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
25
import bzrlib.inventory as inventory
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
26
from bzrlib.osutils import has_symlinks, rename
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
27
from bzrlib.selftest import TestCase, TestCaseInTempDir
963 by Martin Pool
- add the start of a test for inventory file-id matching
28
969 by Martin Pool
- Add less-sucky is_within_any
29
1102 by Martin Pool
- merge test refactoring from robertc
30
class TestInventory(TestCase):
31
32
    def test_is_within(self):
968 by Martin Pool
- add some passing tests for is_inside_any
33
        from bzrlib.osutils import is_inside_any
1185.1.40 by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch.
34
35
        SRC_FOO_C = os.path.join('src', 'foo.c')
36
        for dirs, fn in [(['src', 'doc'], SRC_FOO_C),
37
                         (['src'], SRC_FOO_C),
968 by Martin Pool
- add some passing tests for is_inside_any
38
                         (['src'], 'src'),
39
                         ]:
40
            self.assert_(is_inside_any(dirs, fn))
41
            
969 by Martin Pool
- Add less-sucky is_within_any
42
        for dirs, fn in [(['src'], 'srccontrol'),
43
                         (['src'], 'srccontrol/foo')]:
44
            self.assertFalse(is_inside_any(dirs, fn))
45
            
1102 by Martin Pool
- merge test refactoring from robertc
46
    def test_ids(self):
963 by Martin Pool
- add the start of a test for inventory file-id matching
47
        """Test detection of files within selected directories."""
48
        inv = Inventory()
49
        
50
        for args in [('src', 'directory', 'src-id'), 
51
                     ('doc', 'directory', 'doc-id'), 
52
                     ('src/hello.c', 'file'),
53
                     ('src/bye.c', 'file', 'bye-id'),
54
                     ('Makefile', 'file')]:
55
            inv.add_path(*args)
56
            
57
        self.assertEqual(inv.path2id('src'), 'src-id')
58
        self.assertEqual(inv.path2id('src/bye.c'), 'bye-id')
59
        
60
        self.assert_('src-id' in inv)
1180 by Martin Pool
- start splitting code for xml (de)serialization away from objects
61
62
63
    def test_version(self):
64
        """Inventory remembers the text's version."""
65
        inv = Inventory()
66
        ie = inv.add_path('foo.txt', 'file')
67
        ## XXX
68
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
69
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
70
class TestInventoryEntry(TestCase):
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
71
72
    def test_file_kind_character(self):
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
73
        file = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
74
        self.assertEqual(file.kind_character(), '')
75
76
    def test_dir_kind_character(self):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
77
        dir = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
78
        self.assertEqual(dir.kind_character(), '/')
79
80
    def test_link_kind_character(self):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
81
        dir = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
82
        self.assertEqual(dir.kind_character(), '')
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
83
84
    def test_dir_detect_changes(self):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
85
        left = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
86
        left.text_sha1 = 123
87
        left.executable = True
88
        left.symlink_target='foo'
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
89
        right = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
90
        right.text_sha1 = 321
91
        right.symlink_target='bar'
92
        self.assertEqual((False, False), left.detect_changes(right))
93
        self.assertEqual((False, False), right.detect_changes(left))
94
95
    def test_file_detect_changes(self):
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
96
        left = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
97
        left.text_sha1 = 123
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
98
        right = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
99
        right.text_sha1 = 123
100
        self.assertEqual((False, False), left.detect_changes(right))
101
        self.assertEqual((False, False), right.detect_changes(left))
102
        left.executable = True
103
        self.assertEqual((False, True), left.detect_changes(right))
104
        self.assertEqual((False, True), right.detect_changes(left))
105
        right.text_sha1 = 321
106
        self.assertEqual((True, True), left.detect_changes(right))
107
        self.assertEqual((True, True), right.detect_changes(left))
108
109
    def test_symlink_detect_changes(self):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
110
        left = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
111
        left.text_sha1 = 123
112
        left.executable = True
113
        left.symlink_target='foo'
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
114
        right = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
115
        right.text_sha1 = 321
116
        right.symlink_target='foo'
117
        self.assertEqual((False, False), left.detect_changes(right))
118
        self.assertEqual((False, False), right.detect_changes(left))
119
        left.symlink_target = 'different'
120
        self.assertEqual((True, False), left.detect_changes(right))
121
        self.assertEqual((True, False), right.detect_changes(left))
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
122
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
123
    def test_file_has_text(self):
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
124
        file = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
125
        self.failUnless(file.has_text())
126
127
    def test_directory_has_text(self):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
128
        dir = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
129
        self.failIf(dir.has_text())
130
131
    def test_link_has_text(self):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
132
        link = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
133
        self.failIf(link.has_text())
134
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
135
136
class TestEntryDiffing(TestCaseInTempDir):
137
138
    def setUp(self):
139
        super(TestEntryDiffing, self).setUp()
140
        self.branch = Branch.initialize('.')
141
        print >> open('file', 'wb'), 'foo'
142
        self.branch.add(['file'], ['fileid'])
143
        if has_symlinks():
144
            os.symlink('target1', 'symlink')
145
            self.branch.add(['symlink'], ['linkid'])
146
        self.branch.commit('message_1', rev_id = '1')
147
        print >> open('file', 'wb'), 'bar'
148
        if has_symlinks():
149
            os.unlink('symlink')
150
            os.symlink('target2', 'symlink')
151
        self.tree_1 = self.branch.revision_tree('1')
152
        self.inv_1 = self.branch.get_inventory('1')
153
        self.file_1 = self.inv_1['fileid']
154
        self.tree_2 = self.branch.working_tree()
1497 by Robert Collins
Move Branch.read_working_inventory to WorkingTree.
155
        self.inv_2 = self.tree_2.read_working_inventory()
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
156
        self.file_2 = self.inv_2['fileid']
157
        if has_symlinks():
158
            self.link_1 = self.inv_1['linkid']
159
            self.link_2 = self.inv_2['linkid']
160
161
    def test_file_diff_deleted(self):
162
        output = StringIO()
163
        self.file_1.diff(internal_diff, 
164
                          "old_label", self.tree_1,
165
                          "/dev/null", None, None,
166
                          output)
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
167
        self.assertEqual(output.getvalue(), "--- old_label\t\n"
168
                                            "+++ /dev/null\t\n"
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
169
                                            "@@ -1,1 +0,0 @@\n"
170
                                            "-foo\n"
171
                                            "\n")
172
173
    def test_file_diff_added(self):
174
        output = StringIO()
175
        self.file_1.diff(internal_diff, 
176
                          "new_label", self.tree_1,
177
                          "/dev/null", None, None,
178
                          output, reverse=True)
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
179
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
180
                                            "+++ new_label\t\n"
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
181
                                            "@@ -0,0 +1,1 @@\n"
182
                                            "+foo\n"
183
                                            "\n")
184
185
    def test_file_diff_changed(self):
186
        output = StringIO()
187
        self.file_1.diff(internal_diff, 
188
                          "/dev/null", self.tree_1, 
189
                          "new_label", self.file_2, self.tree_2,
190
                          output)
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
191
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
192
                                            "+++ new_label\t\n"
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
193
                                            "@@ -1,1 +1,1 @@\n"
194
                                            "-foo\n"
195
                                            "+bar\n"
196
                                            "\n")
197
        
198
    def test_link_diff_deleted(self):
1431 by Robert Collins
BUGFIX: disable symlink support tests when no symlink support is present on the system.
199
        if not has_symlinks():
200
            return
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
201
        output = StringIO()
202
        self.link_1.diff(internal_diff, 
203
                          "old_label", self.tree_1,
204
                          "/dev/null", None, None,
205
                          output)
206
        self.assertEqual(output.getvalue(),
207
                         "=== target was 'target1'\n")
208
209
    def test_link_diff_added(self):
1431 by Robert Collins
BUGFIX: disable symlink support tests when no symlink support is present on the system.
210
        if not has_symlinks():
211
            return
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
212
        output = StringIO()
213
        self.link_1.diff(internal_diff, 
214
                          "new_label", self.tree_1,
215
                          "/dev/null", None, None,
216
                          output, reverse=True)
217
        self.assertEqual(output.getvalue(),
218
                         "=== target is 'target1'\n")
219
220
    def test_link_diff_changed(self):
1431 by Robert Collins
BUGFIX: disable symlink support tests when no symlink support is present on the system.
221
        if not has_symlinks():
222
            return
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
223
        output = StringIO()
224
        self.link_1.diff(internal_diff, 
225
                          "/dev/null", self.tree_1, 
226
                          "new_label", self.link_2, self.tree_2,
227
                          output)
228
        self.assertEqual(output.getvalue(),
229
                         "=== target changed 'target1' => 'target2'\n")
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
230
231
232
class TestSnapshot(TestCaseInTempDir):
233
234
    def setUp(self):
235
        # for full testing we'll need a branch
236
        # with a subdir to test parent changes.
237
        # and a file, link and dir under that.
238
        # but right now I only need one attribute
239
        # to change, and then test merge patterns
240
        # with fake parent entries.
241
        super(TestSnapshot, self).setUp()
242
        self.branch = Branch.initialize('.')
243
        self.build_tree(['subdir/', 'subdir/file'])
244
        self.branch.add(['subdir', 'subdir/file'], ['dirid', 'fileid'])
245
        if has_symlinks():
246
            pass
247
        self.branch.commit('message_1', rev_id = '1')
248
        self.tree_1 = self.branch.revision_tree('1')
249
        self.inv_1 = self.branch.get_inventory('1')
250
        self.file_1 = self.inv_1['fileid']
251
        self.work_tree = self.branch.working_tree()
252
        self.file_active = self.work_tree.inventory['fileid']
253
254
    def test_snapshot_new_revision(self):
255
        # This tests that a simple commit with no parents makes a new
256
        # revision value in the inventory entry
257
        self.file_active.snapshot('2', 'subdir/file', {}, self.work_tree, 
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
258
                                  self.branch.weave_store,
259
                                  self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
260
        # expected outcome - file_1 has a revision id of '2', and we can get
261
        # its text of 'file contents' out of the weave.
262
        self.assertEqual(self.file_1.revision, '1')
263
        self.assertEqual(self.file_active.revision, '2')
264
        # this should be a separate test probably, but lets check it once..
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
265
        lines = self.branch.weave_store.get_lines('fileid','2',
266
            self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
267
        self.assertEqual(lines, ['contents of subdir/file\n'])
268
269
    def test_snapshot_unchanged(self):
270
        #This tests that a simple commit does not make a new entry for
271
        # an unchanged inventory entry
272
        self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
273
                                  self.work_tree, self.branch.weave_store,
274
                                  self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
275
        self.assertEqual(self.file_1.revision, '1')
276
        self.assertEqual(self.file_active.revision, '1')
277
        self.assertRaises(errors.WeaveError,
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
278
                          self.branch.weave_store.get_lines, 'fileid', '2',
279
                          self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
280
281
    def test_snapshot_merge_identical_different_revid(self):
282
        # This tests that a commit with two identical parents, one of which has
283
        # a different revision id, results in a new revision id in the entry.
1408 by Robert Collins
we do not need revision_trees in commit, parent inventories are sufficient
284
        # 1->other, commit a merge of other against 1, results in 2.
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
285
        other_ie = inventory.InventoryFile('fileid', 'newname', self.file_1.parent_id)
286
        other_ie = inventory.InventoryFile('fileid', 'file', self.file_1.parent_id)
287
        other_ie.revision = '1'
288
        other_ie.text_sha1 = self.file_1.text_sha1
289
        other_ie.text_size = self.file_1.text_size
290
        self.assertEqual(self.file_1, other_ie)
291
        other_ie.revision = 'other'
292
        self.assertNotEqual(self.file_1, other_ie)
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
293
        self.branch.weave_store.add_identical_text('fileid', '1', 'other', ['1'],
294
            self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
295
        self.file_active.snapshot('2', 'subdir/file', 
296
                                  {'1':self.file_1, 'other':other_ie},
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
297
                                  self.work_tree, self.branch.weave_store,
298
                                  self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
299
        self.assertEqual(self.file_active.revision, '2')
300
301
    def test_snapshot_changed(self):
302
        # This tests that a commit with one different parent results in a new
303
        # revision id in the entry.
304
        self.file_active.name='newname'
305
        rename('subdir/file', 'subdir/newname')
306
        self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
307
                                  self.work_tree, 
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
308
                                  self.branch.weave_store,
309
                                  self.branch.get_transaction())
1407 by Robert Collins
define some expected behaviour for inventory_entry.snapshot
310
        # expected outcome - file_1 has a revision id of '2'
311
        self.assertEqual(self.file_active.revision, '2')
1411 by Robert Collins
use weave ancestry to determine inventory entry previous heads, prevent propogating 'I did a merge' merges.
312
313
314
class TestPreviousHeads(TestCaseInTempDir):
315
316
    def setUp(self):
317
        # we want several inventories, that respectively
318
        # give use the following scenarios:
319
        # A) fileid not in any inventory (A),
320
        # B) fileid present in one inventory (B) and (A,B)
321
        # C) fileid present in two inventories, and they
322
        #   are not mutual descendents (B, C)
323
        # D) fileid present in two inventories and one is
324
        #   a descendent of the other. (B, D)
325
        super(TestPreviousHeads, self).setUp()
326
        self.build_tree(['file'])
327
        self.branch = Branch.initialize('.')
328
        self.branch.commit('new branch', allow_pointless=True, rev_id='A')
329
        self.inv_A = self.branch.get_inventory('A')
330
        self.branch.add(['file'], ['fileid'])
331
        self.branch.commit('add file', rev_id='B')
332
        self.inv_B = self.branch.get_inventory('B')
333
        self.branch.put_controlfile('revision-history', 'A\n')
334
        self.assertEqual(self.branch.revision_history(), ['A'])
335
        self.branch.commit('another add of file', rev_id='C')
336
        self.inv_C = self.branch.get_inventory('C')
337
        self.branch.add_pending_merge('B')
338
        self.branch.commit('merge in B', rev_id='D')
339
        self.inv_D = self.branch.get_inventory('D')
340
        self.file_active = self.branch.working_tree().inventory['fileid']
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
341
        self.weave = self.branch.weave_store.get_weave('fileid',
342
            self.branch.get_transaction())
1411 by Robert Collins
use weave ancestry to determine inventory entry previous heads, prevent propogating 'I did a merge' merges.
343
        
344
    def get_previous_heads(self, inventories):
345
        return self.file_active.find_previous_heads(inventories, self.weave)
346
        
347
    def test_fileid_in_no_inventory(self):
348
        self.assertEqual({}, self.get_previous_heads([self.inv_A]))
349
350
    def test_fileid_in_one_inventory(self):
351
        self.assertEqual({'B':self.inv_B['fileid']},
352
                         self.get_previous_heads([self.inv_B]))
353
        self.assertEqual({'B':self.inv_B['fileid']},
354
                         self.get_previous_heads([self.inv_A, self.inv_B]))
355
        self.assertEqual({'B':self.inv_B['fileid']},
356
                         self.get_previous_heads([self.inv_B, self.inv_A]))
357
358
    def test_fileid_in_two_inventories_gives_both_entries(self):
359
        self.assertEqual({'B':self.inv_B['fileid'],
360
                          'C':self.inv_C['fileid']},
361
                          self.get_previous_heads([self.inv_B, self.inv_C]))
362
        self.assertEqual({'B':self.inv_B['fileid'],
363
                          'C':self.inv_C['fileid']},
364
                          self.get_previous_heads([self.inv_C, self.inv_B]))
365
366
    def test_fileid_in_two_inventories_already_merged_gives_head(self):
367
        self.assertEqual({'D':self.inv_D['fileid']},
368
                         self.get_previous_heads([self.inv_B, self.inv_D]))
369
        self.assertEqual({'D':self.inv_D['fileid']},
370
                         self.get_previous_heads([self.inv_D, self.inv_B]))
371
372
    # TODO: test two inventories with the same file revision