~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_inv.py

  • Committer: Robert Collins
  • Date: 2006-02-17 04:17:09 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060217041709-7251eb701f69ca7e
Review feedback.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
from bzrlib.selftest import TestCase
 
17
from cStringIO import StringIO
 
18
import os
18
19
 
19
 
from bzrlib.inventory import Inventory, InventoryEntry
 
20
from bzrlib.branch import Branch
 
21
import bzrlib.errors as errors
 
22
from bzrlib.diff import internal_diff
 
23
from bzrlib.inventory import Inventory, ROOT_ID
 
24
import bzrlib.inventory as inventory
 
25
from bzrlib.osutils import has_symlinks, rename, pathjoin
 
26
from bzrlib.tests import TestCase, TestCaseWithTransport
20
27
 
21
28
 
22
29
class TestInventory(TestCase):
23
30
 
24
31
    def test_is_within(self):
25
32
        from bzrlib.osutils import is_inside_any
26
 
        
27
 
        for dirs, fn in [(['src', 'doc'], 'src/foo.c'),
28
 
                         (['src'], 'src/foo.c'),
 
33
 
 
34
        SRC_FOO_C = pathjoin('src', 'foo.c')
 
35
        for dirs, fn in [(['src', 'doc'], SRC_FOO_C),
 
36
                         (['src'], SRC_FOO_C),
29
37
                         (['src'], 'src'),
30
38
                         ]:
31
39
            self.assert_(is_inside_any(dirs, fn))
49
57
        self.assertEqual(inv.path2id('src/bye.c'), 'bye-id')
50
58
        
51
59
        self.assert_('src-id' in inv)
 
60
 
 
61
 
 
62
    def test_version(self):
 
63
        """Inventory remembers the text's version."""
 
64
        inv = Inventory()
 
65
        ie = inv.add_path('foo.txt', 'file')
 
66
        ## XXX
 
67
 
 
68
 
 
69
class TestInventoryEntry(TestCase):
 
70
 
 
71
    def test_file_kind_character(self):
 
72
        file = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
 
73
        self.assertEqual(file.kind_character(), '')
 
74
 
 
75
    def test_dir_kind_character(self):
 
76
        dir = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
 
77
        self.assertEqual(dir.kind_character(), '/')
 
78
 
 
79
    def test_link_kind_character(self):
 
80
        dir = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
 
81
        self.assertEqual(dir.kind_character(), '')
 
82
 
 
83
    def test_dir_detect_changes(self):
 
84
        left = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
 
85
        left.text_sha1 = 123
 
86
        left.executable = True
 
87
        left.symlink_target='foo'
 
88
        right = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
 
89
        right.text_sha1 = 321
 
90
        right.symlink_target='bar'
 
91
        self.assertEqual((False, False), left.detect_changes(right))
 
92
        self.assertEqual((False, False), right.detect_changes(left))
 
93
 
 
94
    def test_file_detect_changes(self):
 
95
        left = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
 
96
        left.text_sha1 = 123
 
97
        right = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
 
98
        right.text_sha1 = 123
 
99
        self.assertEqual((False, False), left.detect_changes(right))
 
100
        self.assertEqual((False, False), right.detect_changes(left))
 
101
        left.executable = True
 
102
        self.assertEqual((False, True), left.detect_changes(right))
 
103
        self.assertEqual((False, True), right.detect_changes(left))
 
104
        right.text_sha1 = 321
 
105
        self.assertEqual((True, True), left.detect_changes(right))
 
106
        self.assertEqual((True, True), right.detect_changes(left))
 
107
 
 
108
    def test_symlink_detect_changes(self):
 
109
        left = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
 
110
        left.text_sha1 = 123
 
111
        left.executable = True
 
112
        left.symlink_target='foo'
 
113
        right = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
 
114
        right.text_sha1 = 321
 
115
        right.symlink_target='foo'
 
116
        self.assertEqual((False, False), left.detect_changes(right))
 
117
        self.assertEqual((False, False), right.detect_changes(left))
 
118
        left.symlink_target = 'different'
 
119
        self.assertEqual((True, False), left.detect_changes(right))
 
120
        self.assertEqual((True, False), right.detect_changes(left))
 
121
 
 
122
    def test_file_has_text(self):
 
123
        file = inventory.InventoryFile('123', 'hello.c', ROOT_ID)
 
124
        self.failUnless(file.has_text())
 
125
 
 
126
    def test_directory_has_text(self):
 
127
        dir = inventory.InventoryDirectory('123', 'hello.c', ROOT_ID)
 
128
        self.failIf(dir.has_text())
 
129
 
 
130
    def test_link_has_text(self):
 
131
        link = inventory.InventoryLink('123', 'hello.c', ROOT_ID)
 
132
        self.failIf(link.has_text())
 
133
 
 
134
 
 
135
class TestEntryDiffing(TestCaseWithTransport):
 
136
 
 
137
    def setUp(self):
 
138
        super(TestEntryDiffing, self).setUp()
 
139
        self.wt = self.make_branch_and_tree('.')
 
140
        self.branch = self.wt.branch
 
141
        print >> open('file', 'wb'), 'foo'
 
142
        self.wt.add(['file'], ['fileid'])
 
143
        if has_symlinks():
 
144
            os.symlink('target1', 'symlink')
 
145
            self.wt.add(['symlink'], ['linkid'])
 
146
        self.wt.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.repository.revision_tree('1')
 
152
        self.inv_1 = self.branch.repository.get_inventory('1')
 
153
        self.file_1 = self.inv_1['fileid']
 
154
        self.tree_2 = self.wt
 
155
        self.inv_2 = self.tree_2.read_working_inventory()
 
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)
 
167
        self.assertEqual(output.getvalue(), "--- old_label\t\n"
 
168
                                            "+++ /dev/null\t\n"
 
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)
 
179
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
 
180
                                            "+++ new_label\t\n"
 
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)
 
191
        self.assertEqual(output.getvalue(), "--- /dev/null\t\n"
 
192
                                            "+++ new_label\t\n"
 
193
                                            "@@ -1,1 +1,1 @@\n"
 
194
                                            "-foo\n"
 
195
                                            "+bar\n"
 
196
                                            "\n")
 
197
        
 
198
    def test_link_diff_deleted(self):
 
199
        if not has_symlinks():
 
200
            return
 
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):
 
210
        if not has_symlinks():
 
211
            return
 
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):
 
221
        if not has_symlinks():
 
222
            return
 
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")
 
230
 
 
231
 
 
232
class TestSnapshot(TestCaseWithTransport):
 
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.wt = self.make_branch_and_tree('.')
 
243
        self.branch = self.wt.branch
 
244
        self.build_tree(['subdir/', 'subdir/file'], line_endings='binary')
 
245
        self.wt.add(['subdir', 'subdir/file'],
 
246
                                       ['dirid', 'fileid'])
 
247
        if has_symlinks():
 
248
            pass
 
249
        self.wt.commit('message_1', rev_id = '1')
 
250
        self.tree_1 = self.branch.repository.revision_tree('1')
 
251
        self.inv_1 = self.branch.repository.get_inventory('1')
 
252
        self.file_1 = self.inv_1['fileid']
 
253
        self.file_active = self.wt.inventory['fileid']
 
254
 
 
255
    def test_snapshot_new_revision(self):
 
256
        # This tests that a simple commit with no parents makes a new
 
257
        # revision value in the inventory entry
 
258
        self.file_active.snapshot('2', 'subdir/file', {}, self.wt, 
 
259
                                  self.branch.repository.weave_store,
 
260
                                  self.branch.get_transaction())
 
261
        # expected outcome - file_1 has a revision id of '2', and we can get
 
262
        # its text of 'file contents' out of the weave.
 
263
        self.assertEqual(self.file_1.revision, '1')
 
264
        self.assertEqual(self.file_active.revision, '2')
 
265
        # this should be a separate test probably, but lets check it once..
 
266
        lines = self.branch.repository.weave_store.get_lines('fileid','2',
 
267
            self.branch.get_transaction())
 
268
        self.assertEqual(lines, ['contents of subdir/file\n'])
 
269
 
 
270
    def test_snapshot_unchanged(self):
 
271
        #This tests that a simple commit does not make a new entry for
 
272
        # an unchanged inventory entry
 
273
        self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
 
274
                                  self.wt, 
 
275
                                  self.branch.repository.weave_store,
 
276
                                  self.branch.get_transaction())
 
277
        self.assertEqual(self.file_1.revision, '1')
 
278
        self.assertEqual(self.file_active.revision, '1')
 
279
        self.assertRaises(errors.WeaveError,
 
280
                          self.branch.repository.weave_store.get_lines, 
 
281
                          'fileid', '2', self.branch.get_transaction())
 
282
 
 
283
    def test_snapshot_merge_identical_different_revid(self):
 
284
        # This tests that a commit with two identical parents, one of which has
 
285
        # a different revision id, results in a new revision id in the entry.
 
286
        # 1->other, commit a merge of other against 1, results in 2.
 
287
        other_ie = inventory.InventoryFile('fileid', 'newname', self.file_1.parent_id)
 
288
        other_ie = inventory.InventoryFile('fileid', 'file', self.file_1.parent_id)
 
289
        other_ie.revision = '1'
 
290
        other_ie.text_sha1 = self.file_1.text_sha1
 
291
        other_ie.text_size = self.file_1.text_size
 
292
        self.assertEqual(self.file_1, other_ie)
 
293
        other_ie.revision = 'other'
 
294
        self.assertNotEqual(self.file_1, other_ie)
 
295
        self.branch.repository.weave_store.add_identical_text('fileid', '1', 
 
296
            'other', ['1'], self.branch.get_transaction())
 
297
        self.file_active.snapshot('2', 'subdir/file', 
 
298
                                  {'1':self.file_1, 'other':other_ie},
 
299
                                  self.wt, 
 
300
                                  self.branch.repository.weave_store,
 
301
                                  self.branch.get_transaction())
 
302
        self.assertEqual(self.file_active.revision, '2')
 
303
 
 
304
    def test_snapshot_changed(self):
 
305
        # This tests that a commit with one different parent results in a new
 
306
        # revision id in the entry.
 
307
        self.file_active.name='newname'
 
308
        rename('subdir/file', 'subdir/newname')
 
309
        self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
 
310
                                  self.wt,
 
311
                                  self.branch.repository.weave_store,
 
312
                                  self.branch.get_transaction())
 
313
        # expected outcome - file_1 has a revision id of '2'
 
314
        self.assertEqual(self.file_active.revision, '2')
 
315
 
 
316
 
 
317
class TestPreviousHeads(TestCaseWithTransport):
 
318
 
 
319
    def setUp(self):
 
320
        # we want several inventories, that respectively
 
321
        # give use the following scenarios:
 
322
        # A) fileid not in any inventory (A),
 
323
        # B) fileid present in one inventory (B) and (A,B)
 
324
        # C) fileid present in two inventories, and they
 
325
        #   are not mutual descendents (B, C)
 
326
        # D) fileid present in two inventories and one is
 
327
        #   a descendent of the other. (B, D)
 
328
        super(TestPreviousHeads, self).setUp()
 
329
        self.wt = self.make_branch_and_tree('.')
 
330
        self.branch = self.wt.branch
 
331
        self.build_tree(['file'])
 
332
        self.wt.commit('new branch', allow_pointless=True, rev_id='A')
 
333
        self.inv_A = self.branch.repository.get_inventory('A')
 
334
        self.wt.add(['file'], ['fileid'])
 
335
        self.wt.commit('add file', rev_id='B')
 
336
        self.inv_B = self.branch.repository.get_inventory('B')
 
337
        self.branch.lock_write()
 
338
        try:
 
339
            self.branch.control_files.put_utf8('revision-history', 'A\n')
 
340
        finally:
 
341
            self.branch.unlock()
 
342
        self.assertEqual(self.branch.revision_history(), ['A'])
 
343
        self.wt.commit('another add of file', rev_id='C')
 
344
        self.inv_C = self.branch.repository.get_inventory('C')
 
345
        self.wt.add_pending_merge('B')
 
346
        self.wt.commit('merge in B', rev_id='D')
 
347
        self.inv_D = self.branch.repository.get_inventory('D')
 
348
        self.file_active = self.wt.inventory['fileid']
 
349
        self.weave = self.branch.repository.weave_store.get_weave('fileid',
 
350
            self.branch.get_transaction())
 
351
        
 
352
    def get_previous_heads(self, inventories):
 
353
        return self.file_active.find_previous_heads(inventories, self.weave)
 
354
        
 
355
    def test_fileid_in_no_inventory(self):
 
356
        self.assertEqual({}, self.get_previous_heads([self.inv_A]))
 
357
 
 
358
    def test_fileid_in_one_inventory(self):
 
359
        self.assertEqual({'B':self.inv_B['fileid']},
 
360
                         self.get_previous_heads([self.inv_B]))
 
361
        self.assertEqual({'B':self.inv_B['fileid']},
 
362
                         self.get_previous_heads([self.inv_A, self.inv_B]))
 
363
        self.assertEqual({'B':self.inv_B['fileid']},
 
364
                         self.get_previous_heads([self.inv_B, self.inv_A]))
 
365
 
 
366
    def test_fileid_in_two_inventories_gives_both_entries(self):
 
367
        self.assertEqual({'B':self.inv_B['fileid'],
 
368
                          'C':self.inv_C['fileid']},
 
369
                          self.get_previous_heads([self.inv_B, self.inv_C]))
 
370
        self.assertEqual({'B':self.inv_B['fileid'],
 
371
                          'C':self.inv_C['fileid']},
 
372
                          self.get_previous_heads([self.inv_C, self.inv_B]))
 
373
 
 
374
    def test_fileid_in_two_inventories_already_merged_gives_head(self):
 
375
        self.assertEqual({'D':self.inv_D['fileid']},
 
376
                         self.get_previous_heads([self.inv_B, self.inv_D]))
 
377
        self.assertEqual({'D':self.inv_D['fileid']},
 
378
                         self.get_previous_heads([self.inv_D, self.inv_B]))
 
379
 
 
380
    # TODO: test two inventories with the same file revision 
 
381
 
 
382
 
 
383
class TestExecutable(TestCaseWithTransport):
 
384
 
 
385
    def test_stays_executable(self):
 
386
        basic_inv = """<inventory format="5">
 
387
<file file_id="a-20051208024829-849e76f7968d7a86" name="a" executable="yes" />
 
388
<file file_id="b-20051208024829-849e76f7968d7a86" name="b" />
 
389
</inventory>
 
390
"""
 
391
        wt = self.make_branch_and_tree('b1')
 
392
        b = wt.branch
 
393
        open('b1/a', 'wb').write('a test\n')
 
394
        open('b1/b', 'wb').write('b test\n')
 
395
        os.chmod('b1/a', 0755)
 
396
        os.chmod('b1/b', 0644)
 
397
        # Manually writing the inventory, to ensure that
 
398
        # the executable="yes" entry is set for 'a' and not for 'b'
 
399
        open('b1/.bzr/inventory', 'wb').write(basic_inv)
 
400
 
 
401
        a_id = "a-20051208024829-849e76f7968d7a86"
 
402
        b_id = "b-20051208024829-849e76f7968d7a86"
 
403
        wt = wt.bzrdir.open_workingtree()
 
404
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
405
 
 
406
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
407
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
408
 
 
409
        wt.commit('adding a,b', rev_id='r1')
 
410
 
 
411
        rev_tree = b.repository.revision_tree('r1')
 
412
        self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
 
413
        self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
 
414
 
 
415
        self.failUnless(rev_tree.inventory[a_id].executable)
 
416
        self.failIf(rev_tree.inventory[b_id].executable)
 
417
 
 
418
        # Make sure the entries are gone
 
419
        os.remove('b1/a')
 
420
        os.remove('b1/b')
 
421
        self.failIf(wt.has_id(a_id))
 
422
        self.failIf(wt.has_filename('a'))
 
423
        self.failIf(wt.has_id(b_id))
 
424
        self.failIf(wt.has_filename('b'))
 
425
 
 
426
        # Make sure that revert is able to bring them back,
 
427
        # and sets 'a' back to being executable
 
428
 
 
429
        wt.revert(['a', 'b'], rev_tree, backups=False)
 
430
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
431
 
 
432
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
433
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
434
 
 
435
        # Now remove them again, and make sure that after a
 
436
        # commit, they are still marked correctly
 
437
        os.remove('b1/a')
 
438
        os.remove('b1/b')
 
439
        wt.commit('removed', rev_id='r2')
 
440
 
 
441
        self.assertEqual([], [cn for cn,ie in wt.inventory.iter_entries()])
 
442
        self.failIf(wt.has_id(a_id))
 
443
        self.failIf(wt.has_filename('a'))
 
444
        self.failIf(wt.has_id(b_id))
 
445
        self.failIf(wt.has_filename('b'))
 
446
 
 
447
        # Now revert back to the previous commit
 
448
        wt.revert([], rev_tree, backups=False)
 
449
        self.assertEqual(['a', 'b'], [cn for cn,ie in wt.inventory.iter_entries()])
 
450
 
 
451
        self.failUnless(wt.is_executable(a_id), "'a' lost the execute bit")
 
452
        self.failIf(wt.is_executable(b_id), "'b' gained an execute bit")
 
453
 
 
454
        # Now make sure that 'bzr branch' also preserves the
 
455
        # executable bit
 
456
        # TODO: Maybe this should be a blackbox test
 
457
        d2 = b.bzrdir.clone('b2', revision_id='r1')
 
458
        t2 = d2.open_workingtree()
 
459
        b2 = t2.branch
 
460
        self.assertEquals('r1', b2.last_revision())
 
461
 
 
462
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
463
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
464
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
465
 
 
466
        # Make sure pull will delete the files
 
467
        t2.pull(b)
 
468
        self.assertEquals('r2', b2.last_revision())
 
469
        self.assertEqual([], [cn for cn,ie in t2.inventory.iter_entries()])
 
470
 
 
471
        # Now commit the changes on the first branch
 
472
        # so that the second branch can pull the changes
 
473
        # and make sure that the executable bit has been copied
 
474
        wt.commit('resurrected', rev_id='r3')
 
475
 
 
476
        t2.pull(b)
 
477
        self.assertEquals('r3', b2.last_revision())
 
478
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
479
 
 
480
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
481
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")