~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_inv.py

MergeĀ fromĀ jam-storage.

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, TestCaseInTempDir
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))
57
65
        ie = inv.add_path('foo.txt', 'file')
58
66
        ## XXX
59
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(TestCaseInTempDir):
 
136
 
 
137
    def setUp(self):
 
138
        super(TestEntryDiffing, self).setUp()
 
139
        self.branch = Branch.initialize(u'.')
 
140
        self.wt = self.branch.working_tree()
 
141
        print >> open('file', 'wb'), 'foo'
 
142
        self.branch.working_tree().add(['file'], ['fileid'])
 
143
        if has_symlinks():
 
144
            os.symlink('target1', 'symlink')
 
145
            self.branch.working_tree().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.branch.working_tree()
 
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(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(u'.')
 
243
        self.build_tree(['subdir/', 'subdir/file'], line_endings='binary')
 
244
        self.branch.working_tree().add(['subdir', 'subdir/file'],
 
245
                                       ['dirid', 'fileid'])
 
246
        if has_symlinks():
 
247
            pass
 
248
        self.wt = self.branch.working_tree()
 
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.work_tree = self.branch.working_tree()
 
254
        self.file_active = self.work_tree.inventory['fileid']
 
255
 
 
256
    def test_snapshot_new_revision(self):
 
257
        # This tests that a simple commit with no parents makes a new
 
258
        # revision value in the inventory entry
 
259
        self.file_active.snapshot('2', 'subdir/file', {}, self.work_tree, 
 
260
                                  self.branch.repository.weave_store,
 
261
                                  self.branch.get_transaction())
 
262
        # expected outcome - file_1 has a revision id of '2', and we can get
 
263
        # its text of 'file contents' out of the weave.
 
264
        self.assertEqual(self.file_1.revision, '1')
 
265
        self.assertEqual(self.file_active.revision, '2')
 
266
        # this should be a separate test probably, but lets check it once..
 
267
        lines = self.branch.repository.weave_store.get_lines('fileid','2',
 
268
            self.branch.get_transaction())
 
269
        self.assertEqual(lines, ['contents of subdir/file\n'])
 
270
 
 
271
    def test_snapshot_unchanged(self):
 
272
        #This tests that a simple commit does not make a new entry for
 
273
        # an unchanged inventory entry
 
274
        self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
 
275
                                  self.work_tree, 
 
276
                                  self.branch.repository.weave_store,
 
277
                                  self.branch.get_transaction())
 
278
        self.assertEqual(self.file_1.revision, '1')
 
279
        self.assertEqual(self.file_active.revision, '1')
 
280
        self.assertRaises(errors.WeaveError,
 
281
                          self.branch.repository.weave_store.get_lines, 
 
282
                          'fileid', '2', self.branch.get_transaction())
 
283
 
 
284
    def test_snapshot_merge_identical_different_revid(self):
 
285
        # This tests that a commit with two identical parents, one of which has
 
286
        # a different revision id, results in a new revision id in the entry.
 
287
        # 1->other, commit a merge of other against 1, results in 2.
 
288
        other_ie = inventory.InventoryFile('fileid', 'newname', self.file_1.parent_id)
 
289
        other_ie = inventory.InventoryFile('fileid', 'file', self.file_1.parent_id)
 
290
        other_ie.revision = '1'
 
291
        other_ie.text_sha1 = self.file_1.text_sha1
 
292
        other_ie.text_size = self.file_1.text_size
 
293
        self.assertEqual(self.file_1, other_ie)
 
294
        other_ie.revision = 'other'
 
295
        self.assertNotEqual(self.file_1, other_ie)
 
296
        self.branch.repository.weave_store.add_identical_text('fileid', '1', 
 
297
            'other', ['1'], self.branch.get_transaction())
 
298
        self.file_active.snapshot('2', 'subdir/file', 
 
299
                                  {'1':self.file_1, 'other':other_ie},
 
300
                                  self.work_tree, 
 
301
                                  self.branch.repository.weave_store,
 
302
                                  self.branch.get_transaction())
 
303
        self.assertEqual(self.file_active.revision, '2')
 
304
 
 
305
    def test_snapshot_changed(self):
 
306
        # This tests that a commit with one different parent results in a new
 
307
        # revision id in the entry.
 
308
        self.file_active.name='newname'
 
309
        rename('subdir/file', 'subdir/newname')
 
310
        self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
 
311
                                  self.work_tree, 
 
312
                                  self.branch.repository.weave_store,
 
313
                                  self.branch.get_transaction())
 
314
        # expected outcome - file_1 has a revision id of '2'
 
315
        self.assertEqual(self.file_active.revision, '2')
 
316
 
 
317
 
 
318
class TestPreviousHeads(TestCaseInTempDir):
 
319
 
 
320
    def setUp(self):
 
321
        # we want several inventories, that respectively
 
322
        # give use the following scenarios:
 
323
        # A) fileid not in any inventory (A),
 
324
        # B) fileid present in one inventory (B) and (A,B)
 
325
        # C) fileid present in two inventories, and they
 
326
        #   are not mutual descendents (B, C)
 
327
        # D) fileid present in two inventories and one is
 
328
        #   a descendent of the other. (B, D)
 
329
        super(TestPreviousHeads, self).setUp()
 
330
        self.build_tree(['file'])
 
331
        self.branch = Branch.initialize(u'.')
 
332
        self.wt = self.branch.working_tree()
 
333
        self.wt.commit('new branch', allow_pointless=True, rev_id='A')
 
334
        self.inv_A = self.branch.repository.get_inventory('A')
 
335
        self.wt.add(['file'], ['fileid'])
 
336
        self.wt.commit('add file', rev_id='B')
 
337
        self.inv_B = self.branch.repository.get_inventory('B')
 
338
        self.branch.lock_write()
 
339
        try:
 
340
            self.branch.control_files.put_utf8('revision-history', 'A\n')
 
341
        finally:
 
342
            self.branch.unlock()
 
343
        self.assertEqual(self.branch.revision_history(), ['A'])
 
344
        self.wt.commit('another add of file', rev_id='C')
 
345
        self.inv_C = self.branch.repository.get_inventory('C')
 
346
        self.wt.add_pending_merge('B')
 
347
        self.wt.commit('merge in B', rev_id='D')
 
348
        self.inv_D = self.branch.repository.get_inventory('D')
 
349
        self.file_active = self.branch.working_tree().inventory['fileid']
 
350
        self.weave = self.branch.repository.weave_store.get_weave('fileid',
 
351
            self.branch.get_transaction())
 
352
        
 
353
    def get_previous_heads(self, inventories):
 
354
        return self.file_active.find_previous_heads(inventories, self.weave)
 
355
        
 
356
    def test_fileid_in_no_inventory(self):
 
357
        self.assertEqual({}, self.get_previous_heads([self.inv_A]))
 
358
 
 
359
    def test_fileid_in_one_inventory(self):
 
360
        self.assertEqual({'B':self.inv_B['fileid']},
 
361
                         self.get_previous_heads([self.inv_B]))
 
362
        self.assertEqual({'B':self.inv_B['fileid']},
 
363
                         self.get_previous_heads([self.inv_A, self.inv_B]))
 
364
        self.assertEqual({'B':self.inv_B['fileid']},
 
365
                         self.get_previous_heads([self.inv_B, self.inv_A]))
 
366
 
 
367
    def test_fileid_in_two_inventories_gives_both_entries(self):
 
368
        self.assertEqual({'B':self.inv_B['fileid'],
 
369
                          'C':self.inv_C['fileid']},
 
370
                          self.get_previous_heads([self.inv_B, self.inv_C]))
 
371
        self.assertEqual({'B':self.inv_B['fileid'],
 
372
                          'C':self.inv_C['fileid']},
 
373
                          self.get_previous_heads([self.inv_C, self.inv_B]))
 
374
 
 
375
    def test_fileid_in_two_inventories_already_merged_gives_head(self):
 
376
        self.assertEqual({'D':self.inv_D['fileid']},
 
377
                         self.get_previous_heads([self.inv_B, self.inv_D]))
 
378
        self.assertEqual({'D':self.inv_D['fileid']},
 
379
                         self.get_previous_heads([self.inv_D, self.inv_B]))
 
380
 
 
381
    # TODO: test two inventories with the same file revision 
 
382
 
 
383
 
 
384
class TestExecutable(TestCaseInTempDir):
 
385
 
 
386
    def test_stays_executable(self):
 
387
        basic_inv = """<inventory format="5">
 
388
<file file_id="a-20051208024829-849e76f7968d7a86" name="a" executable="yes" />
 
389
<file file_id="b-20051208024829-849e76f7968d7a86" name="b" />
 
390
</inventory>
 
391
"""
 
392
        os.mkdir('b1')
 
393
        b = Branch.initialize('b1')
 
394
        open('b1/a', 'wb').write('a test\n')
 
395
        open('b1/b', 'wb').write('b test\n')
 
396
        os.chmod('b1/a', 0755)
 
397
        os.chmod('b1/b', 0644)
 
398
        # Manually writing the inventory, to ensure that
 
399
        # the executable="yes" entry is set for 'a' and not for 'b'
 
400
        open('b1/.bzr/inventory', 'wb').write(basic_inv)
 
401
 
 
402
        a_id = "a-20051208024829-849e76f7968d7a86"
 
403
        b_id = "b-20051208024829-849e76f7968d7a86"
 
404
        t = b.working_tree()
 
405
        self.assertEqual(['a', 'b'], [cn for cn,ie in t.inventory.iter_entries()])
 
406
 
 
407
        self.failUnless(t.is_executable(a_id), "'a' lost the execute bit")
 
408
        self.failIf(t.is_executable(b_id), "'b' gained an execute bit")
 
409
 
 
410
        t.commit('adding a,b', rev_id='r1')
 
411
 
 
412
        rev_tree = b.repository.revision_tree('r1')
 
413
        self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
 
414
        self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
 
415
 
 
416
        self.failUnless(rev_tree.inventory[a_id].executable)
 
417
        self.failIf(rev_tree.inventory[b_id].executable)
 
418
 
 
419
        # Make sure the entries are gone
 
420
        os.remove('b1/a')
 
421
        os.remove('b1/b')
 
422
        self.failIf(t.has_id(a_id))
 
423
        self.failIf(t.has_filename('a'))
 
424
        self.failIf(t.has_id(b_id))
 
425
        self.failIf(t.has_filename('b'))
 
426
 
 
427
        # Make sure that revert is able to bring them back,
 
428
        # and sets 'a' back to being executable
 
429
 
 
430
        t.revert(['b1/a', 'b1/b'], rev_tree, backups=False)
 
431
        self.assertEqual(['a', 'b'], [cn for cn,ie in t.inventory.iter_entries()])
 
432
 
 
433
        self.failUnless(t.is_executable(a_id), "'a' lost the execute bit")
 
434
        self.failIf(t.is_executable(b_id), "'b' gained an execute bit")
 
435
 
 
436
        # Now remove them again, and make sure that after a
 
437
        # commit, they are still marked correctly
 
438
        os.remove('b1/a')
 
439
        os.remove('b1/b')
 
440
        t.commit('removed', rev_id='r2')
 
441
 
 
442
        self.assertEqual([], [cn for cn,ie in t.inventory.iter_entries()])
 
443
        self.failIf(t.has_id(a_id))
 
444
        self.failIf(t.has_filename('a'))
 
445
        self.failIf(t.has_id(b_id))
 
446
        self.failIf(t.has_filename('b'))
 
447
 
 
448
        # Now revert back to the previous commit
 
449
        t.revert([], rev_tree, backups=False)
 
450
        # TODO: FIXME: For some reason, after revert, the tree does not 
 
451
        # regenerate its working inventory, so we have to manually delete
 
452
        # the working tree, and create a new one
 
453
        # This seems to happen any time you do a merge operation on the
 
454
        # working tree
 
455
        del t
 
456
        t = b.working_tree()
 
457
 
 
458
        self.assertEqual(['a', 'b'], [cn for cn,ie in t.inventory.iter_entries()])
 
459
 
 
460
        self.failUnless(t.is_executable(a_id), "'a' lost the execute bit")
 
461
        self.failIf(t.is_executable(b_id), "'b' gained an execute bit")
 
462
 
 
463
        # Now make sure that 'bzr branch' also preserves the
 
464
        # executable bit
 
465
        # TODO: Maybe this should be a blackbox test
 
466
        b.clone('b2', revision='r1')
 
467
        b2 = Branch.open('b2')
 
468
        self.assertEquals('r1', b2.last_revision())
 
469
        t2 = b2.working_tree()
 
470
 
 
471
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
472
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
473
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")
 
474
 
 
475
        # Make sure pull will delete the files
 
476
        t2.pull(b)
 
477
        self.assertEquals('r2', b2.last_revision())
 
478
        # FIXME: Same thing here, t2 needs to be recreated
 
479
        del t2
 
480
        t2 = b2.working_tree()
 
481
        self.assertEqual([], [cn for cn,ie in t2.inventory.iter_entries()])
 
482
 
 
483
        # Now commit the changes on the first branch
 
484
        # so that the second branch can pull the changes
 
485
        # and make sure that the executable bit has been copied
 
486
        t.commit('resurrected', rev_id='r3')
 
487
 
 
488
        t2.pull(b)
 
489
        # FIXME: And here
 
490
        del t2
 
491
        t2 = b2.working_tree()
 
492
        self.assertEquals('r3', b2.last_revision())
 
493
        self.assertEqual(['a', 'b'], [cn for cn,ie in t2.inventory.iter_entries()])
 
494
 
 
495
        self.failUnless(t2.is_executable(a_id), "'a' lost the execute bit")
 
496
        self.failIf(t2.is_executable(b_id), "'b' gained an execute bit")