~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Martin Pool
  • Date: 2007-04-04 01:22:11 UTC
  • mfrom: (2393.1.1 bzr.docs)
  • mto: This revision was merged to the branch mainline in revision 2397.
  • Revision ID: mbp@sourcefrog.net-20070404012211-sq269me6bai9m6xk
merge trunk and doc fix from elliot

Show diffs side-by-side

added added

removed removed

Lines of Context:
102
102
         b/g      g-file
103
103
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
104
104
 
105
 
        # Notice that a/e is an empty directory.
 
105
        Notice that a/e is an empty directory.
 
106
 
 
107
        :return: The dirstate, still write-locked.
106
108
        """
107
109
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
108
110
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
179
181
        finally:
180
182
            state.unlock()
181
183
 
 
184
    def create_basic_dirstate(self):
 
185
        """Create a dirstate with a few files and directories.
 
186
 
 
187
            a
 
188
            b/
 
189
              c
 
190
              d/
 
191
                e
 
192
            f
 
193
        """
 
194
        tree = self.make_branch_and_tree('tree')
 
195
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
 
196
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
 
197
        self.build_tree(['tree/' + p for p in paths])
 
198
        tree.set_root_id('TREE_ROOT')
 
199
        tree.add([p.rstrip('/') for p in paths], file_ids)
 
200
        tree.commit('initial', rev_id='rev-1')
 
201
        revision_id = 'rev-1'
 
202
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
 
203
        t = self.get_transport().clone('tree')
 
204
        a_text = t.get_bytes('a')
 
205
        a_sha = osutils.sha_string(a_text)
 
206
        a_len = len(a_text)
 
207
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
 
208
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
 
209
        c_text = t.get_bytes('b/c')
 
210
        c_sha = osutils.sha_string(c_text)
 
211
        c_len = len(c_text)
 
212
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
 
213
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
 
214
        e_text = t.get_bytes('b/d/e')
 
215
        e_sha = osutils.sha_string(e_text)
 
216
        e_len = len(e_text)
 
217
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
 
218
        f_text = t.get_bytes('f')
 
219
        f_sha = osutils.sha_string(f_text)
 
220
        f_len = len(f_text)
 
221
        null_stat = dirstate.DirState.NULLSTAT
 
222
        expected = {
 
223
            '':(('', '', 'TREE_ROOT'), [
 
224
                  ('d', '', 0, False, null_stat),
 
225
                  ('d', '', 0, False, revision_id),
 
226
                ]),
 
227
            'a':(('', 'a', 'a-id'), [
 
228
                   ('f', '', 0, False, null_stat),
 
229
                   ('f', a_sha, a_len, False, revision_id),
 
230
                 ]),
 
231
            'b':(('', 'b', 'b-id'), [
 
232
                  ('d', '', 0, False, null_stat),
 
233
                  ('d', '', 0, False, revision_id),
 
234
                 ]),
 
235
            'b/c':(('b', 'c', 'c-id'), [
 
236
                    ('f', '', 0, False, null_stat),
 
237
                    ('f', c_sha, c_len, False, revision_id),
 
238
                   ]),
 
239
            'b/d':(('b', 'd', 'd-id'), [
 
240
                    ('d', '', 0, False, null_stat),
 
241
                    ('d', '', 0, False, revision_id),
 
242
                   ]),
 
243
            'b/d/e':(('b/d', 'e', 'e-id'), [
 
244
                      ('f', '', 0, False, null_stat),
 
245
                      ('f', e_sha, e_len, False, revision_id),
 
246
                     ]),
 
247
            'f':(('', 'f', 'f-id'), [
 
248
                  ('f', '', 0, False, null_stat),
 
249
                  ('f', f_sha, f_len, False, revision_id),
 
250
                 ]),
 
251
        }
 
252
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
253
        try:
 
254
            state.save()
 
255
        finally:
 
256
            state.unlock()
 
257
        # Use a different object, to make sure nothing is pre-cached in memory.
 
258
        state = dirstate.DirState.on_file('dirstate')
 
259
        state.lock_read()
 
260
        self.addCleanup(state.unlock)
 
261
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
262
                         state._dirblock_state)
 
263
        # This is code is only really tested if we actually have to make more
 
264
        # than one read, so set the page size to something smaller.
 
265
        # We want it to contain about 2.2 records, so that we have a couple
 
266
        # records that we can read per attempt
 
267
        state._bisect_page_size = 200
 
268
        return tree, state, expected
 
269
 
 
270
    def create_duplicated_dirstate(self):
 
271
        """Create a dirstate with a deleted and added entries.
 
272
 
 
273
        This grabs a basic_dirstate, and then removes and re adds every entry
 
274
        with a new file id.
 
275
        """
 
276
        tree, state, expected = self.create_basic_dirstate()
 
277
        # Now we will just remove and add every file so we get an extra entry
 
278
        # per entry. Unversion in reverse order so we handle subdirs
 
279
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
 
280
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
 
281
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
 
282
 
 
283
        # Update the expected dictionary.
 
284
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
 
285
            orig = expected[path]
 
286
            path2 = path + '2'
 
287
            # This record was deleted in the current tree
 
288
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
 
289
                                        orig[1][1]])
 
290
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
 
291
            # And didn't exist in the basis tree
 
292
            expected[path2] = (new_key, [orig[1][0],
 
293
                                         dirstate.DirState.NULL_PARENT_DETAILS])
 
294
 
 
295
        # We will replace the 'dirstate' file underneath 'state', but that is
 
296
        # okay as lock as we unlock 'state' first.
 
297
        state.unlock()
 
298
        try:
 
299
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
300
            try:
 
301
                new_state.save()
 
302
            finally:
 
303
                new_state.unlock()
 
304
        finally:
 
305
            # But we need to leave state in a read-lock because we already have
 
306
            # a cleanup scheduled
 
307
            state.lock_read()
 
308
        return tree, state, expected
 
309
 
 
310
    def create_renamed_dirstate(self):
 
311
        """Create a dirstate with a few internal renames.
 
312
 
 
313
        This takes the basic dirstate, and moves the paths around.
 
314
        """
 
315
        tree, state, expected = self.create_basic_dirstate()
 
316
        # Rename a file
 
317
        tree.rename_one('a', 'b/g')
 
318
        # And a directory
 
319
        tree.rename_one('b/d', 'h')
 
320
 
 
321
        old_a = expected['a']
 
322
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
 
323
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
 
324
                                                ('r', 'a', 0, False, '')])
 
325
        old_d = expected['b/d']
 
326
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
 
327
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
 
328
                                             ('r', 'b/d', 0, False, '')])
 
329
 
 
330
        old_e = expected['b/d/e']
 
331
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
 
332
                             old_e[1][1]])
 
333
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
 
334
                                                ('r', 'b/d/e', 0, False, '')])
 
335
 
 
336
        state.unlock()
 
337
        try:
 
338
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
 
339
            try:
 
340
                new_state.save()
 
341
            finally:
 
342
                new_state.unlock()
 
343
        finally:
 
344
            state.lock_read()
 
345
        return tree, state, expected
182
346
 
183
347
class TestTreeToDirState(TestCaseWithDirState):
184
348
 
206
370
             ])])
207
371
        state = dirstate.DirState.from_tree(tree, 'dirstate')
208
372
        self.check_state_with_reopen(expected_result, state)
209
 
        state._validate()
 
373
        state.lock_read()
 
374
        try:
 
375
            state._validate()
 
376
        finally:
 
377
            state.unlock()
210
378
 
211
379
    def test_2_parents_empty_to_dirstate(self):
212
380
        # create a parent by doing a commit
223
391
             ])])
224
392
        state = dirstate.DirState.from_tree(tree, 'dirstate')
225
393
        self.check_state_with_reopen(expected_result, state)
226
 
        state._validate()
 
394
        state.lock_read()
 
395
        try:
 
396
            state._validate()
 
397
        finally:
 
398
            state.unlock()
227
399
 
228
400
    def test_empty_unknowns_are_ignored_to_dirstate(self):
229
401
        """We should be able to create a dirstate for an empty tree."""
533
705
        finally:
534
706
            state.unlock()
535
707
        state = dirstate.DirState.on_file('dirstate')
536
 
        state._validate()
537
708
        state.lock_read()
538
709
        try:
 
710
            state._validate()
539
711
            self.assertEqual(expected_rows, list(state._iter_entries()))
540
712
        finally:
541
713
            state.unlock()
1563
1735
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1564
1736
 
1565
1737
 
1566
 
class TestBisect(TestCaseWithTransport):
 
1738
class TestBisect(TestCaseWithDirState):
1567
1739
    """Test the ability to bisect into the disk format."""
1568
1740
 
1569
 
    def create_basic_dirstate(self):
1570
 
        """Create a dirstate with a few files and directories.
1571
 
 
1572
 
            a
1573
 
            b/
1574
 
              c
1575
 
              d/
1576
 
                e
1577
 
            f
1578
 
        """
1579
 
        tree = self.make_branch_and_tree('tree')
1580
 
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1581
 
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1582
 
        self.build_tree(['tree/' + p for p in paths])
1583
 
        tree.set_root_id('TREE_ROOT')
1584
 
        tree.add([p.rstrip('/') for p in paths], file_ids)
1585
 
        tree.commit('initial', rev_id='rev-1')
1586
 
        revision_id = 'rev-1'
1587
 
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1588
 
        t = self.get_transport().clone('tree')
1589
 
        a_text = t.get_bytes('a')
1590
 
        a_sha = osutils.sha_string(a_text)
1591
 
        a_len = len(a_text)
1592
 
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1593
 
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1594
 
        c_text = t.get_bytes('b/c')
1595
 
        c_sha = osutils.sha_string(c_text)
1596
 
        c_len = len(c_text)
1597
 
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1598
 
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1599
 
        e_text = t.get_bytes('b/d/e')
1600
 
        e_sha = osutils.sha_string(e_text)
1601
 
        e_len = len(e_text)
1602
 
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1603
 
        f_text = t.get_bytes('f')
1604
 
        f_sha = osutils.sha_string(f_text)
1605
 
        f_len = len(f_text)
1606
 
        null_stat = dirstate.DirState.NULLSTAT
1607
 
        expected = {
1608
 
            '':(('', '', 'TREE_ROOT'), [
1609
 
                  ('d', '', 0, False, null_stat),
1610
 
                  ('d', '', 0, False, revision_id),
1611
 
                ]),
1612
 
            'a':(('', 'a', 'a-id'), [
1613
 
                   ('f', '', 0, False, null_stat),
1614
 
                   ('f', a_sha, a_len, False, revision_id),
1615
 
                 ]),
1616
 
            'b':(('', 'b', 'b-id'), [
1617
 
                  ('d', '', 0, False, null_stat),
1618
 
                  ('d', '', 0, False, revision_id),
1619
 
                 ]),
1620
 
            'b/c':(('b', 'c', 'c-id'), [
1621
 
                    ('f', '', 0, False, null_stat),
1622
 
                    ('f', c_sha, c_len, False, revision_id),
1623
 
                   ]),
1624
 
            'b/d':(('b', 'd', 'd-id'), [
1625
 
                    ('d', '', 0, False, null_stat),
1626
 
                    ('d', '', 0, False, revision_id),
1627
 
                   ]),
1628
 
            'b/d/e':(('b/d', 'e', 'e-id'), [
1629
 
                      ('f', '', 0, False, null_stat),
1630
 
                      ('f', e_sha, e_len, False, revision_id),
1631
 
                     ]),
1632
 
            'f':(('', 'f', 'f-id'), [
1633
 
                  ('f', '', 0, False, null_stat),
1634
 
                  ('f', f_sha, f_len, False, revision_id),
1635
 
                 ]),
1636
 
        }
1637
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
1638
 
        try:
1639
 
            state.save()
1640
 
        finally:
1641
 
            state.unlock()
1642
 
        # Use a different object, to make sure nothing is pre-cached in memory.
1643
 
        state = dirstate.DirState.on_file('dirstate')
1644
 
        state.lock_read()
1645
 
        self.addCleanup(state.unlock)
1646
 
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1647
 
                         state._dirblock_state)
1648
 
        # This is code is only really tested if we actually have to make more
1649
 
        # than one read, so set the page size to something smaller.
1650
 
        # We want it to contain about 2.2 records, so that we have a couple
1651
 
        # records that we can read per attempt
1652
 
        state._bisect_page_size = 200
1653
 
        return tree, state, expected
1654
 
 
1655
 
    def create_duplicated_dirstate(self):
1656
 
        """Create a dirstate with a deleted and added entries.
1657
 
 
1658
 
        This grabs a basic_dirstate, and then removes and re adds every entry
1659
 
        with a new file id.
1660
 
        """
1661
 
        tree, state, expected = self.create_basic_dirstate()
1662
 
        # Now we will just remove and add every file so we get an extra entry
1663
 
        # per entry. Unversion in reverse order so we handle subdirs
1664
 
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1665
 
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1666
 
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1667
 
 
1668
 
        # Update the expected dictionary.
1669
 
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1670
 
            orig = expected[path]
1671
 
            path2 = path + '2'
1672
 
            # This record was deleted in the current tree
1673
 
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1674
 
                                        orig[1][1]])
1675
 
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1676
 
            # And didn't exist in the basis tree
1677
 
            expected[path2] = (new_key, [orig[1][0],
1678
 
                                         dirstate.DirState.NULL_PARENT_DETAILS])
1679
 
 
1680
 
        # We will replace the 'dirstate' file underneath 'state', but that is
1681
 
        # okay as lock as we unlock 'state' first.
1682
 
        state.unlock()
1683
 
        try:
1684
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1685
 
            try:
1686
 
                new_state.save()
1687
 
            finally:
1688
 
                new_state.unlock()
1689
 
        finally:
1690
 
            # But we need to leave state in a read-lock because we already have
1691
 
            # a cleanup scheduled
1692
 
            state.lock_read()
1693
 
        return tree, state, expected
1694
 
 
1695
 
    def create_renamed_dirstate(self):
1696
 
        """Create a dirstate with a few internal renames.
1697
 
 
1698
 
        This takes the basic dirstate, and moves the paths around.
1699
 
        """
1700
 
        tree, state, expected = self.create_basic_dirstate()
1701
 
        # Rename a file
1702
 
        tree.rename_one('a', 'b/g')
1703
 
        # And a directory
1704
 
        tree.rename_one('b/d', 'h')
1705
 
 
1706
 
        old_a = expected['a']
1707
 
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1708
 
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1709
 
                                                ('r', 'a', 0, False, '')])
1710
 
        old_d = expected['b/d']
1711
 
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1712
 
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1713
 
                                             ('r', 'b/d', 0, False, '')])
1714
 
 
1715
 
        old_e = expected['b/d/e']
1716
 
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1717
 
                             old_e[1][1]])
1718
 
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1719
 
                                                ('r', 'b/d/e', 0, False, '')])
1720
 
 
1721
 
        state.unlock()
1722
 
        try:
1723
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1724
 
            try:
1725
 
                new_state.save()
1726
 
            finally:
1727
 
                new_state.unlock()
1728
 
        finally:
1729
 
            state.lock_read()
1730
 
        return tree, state, expected
1731
1741
 
1732
1742
    def assertBisect(self, expected_map, map_keys, state, paths):
1733
1743
        """Assert that bisecting for paths returns the right result.
2040
2050
        for path in paths:
2041
2051
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2042
2052
 
 
2053
 
 
2054
class TestDirstateValidation(TestCaseWithDirState):
 
2055
 
 
2056
    def test_validate_correct_dirstate(self):
 
2057
        state = self.create_complex_dirstate()
 
2058
        state._validate()
 
2059
        state.unlock()
 
2060
        # and make sure we can also validate with a read lock
 
2061
        state.lock_read()
 
2062
        try:
 
2063
            state._validate()
 
2064
        finally:
 
2065
            state.unlock()
 
2066
 
 
2067
    def test_dirblock_not_sorted(self):
 
2068
        tree, state, expected = self.create_renamed_dirstate()
 
2069
        state._read_dirblocks_if_needed()
 
2070
        last_dirblock = state._dirblocks[-1]
 
2071
        # we're appending to the dirblock, but this name comes before some of
 
2072
        # the existing names; that's wrong
 
2073
        last_dirblock[1].append(
 
2074
            (('h', 'aaaa', 'a-id'),
 
2075
             [('a', '', 0, False, ''),
 
2076
              ('a', '', 0, False, '')]))
 
2077
        e = self.assertRaises(AssertionError,
 
2078
            state._validate)
 
2079
        self.assertContainsRe(str(e), 'not sorted')
 
2080
 
 
2081
    def test_dirblock_name_mismatch(self):
 
2082
        tree, state, expected = self.create_renamed_dirstate()
 
2083
        state._read_dirblocks_if_needed()
 
2084
        last_dirblock = state._dirblocks[-1]
 
2085
        # add an entry with the wrong directory name
 
2086
        last_dirblock[1].append(
 
2087
            (('', 'z', 'a-id'),
 
2088
             [('a', '', 0, False, ''),
 
2089
              ('a', '', 0, False, '')]))
 
2090
        e = self.assertRaises(AssertionError,
 
2091
            state._validate)
 
2092
        self.assertContainsRe(str(e),
 
2093
            "doesn't match directory name")
 
2094
 
 
2095
    def test_dirblock_missing_rename(self):
 
2096
        tree, state, expected = self.create_renamed_dirstate()
 
2097
        state._read_dirblocks_if_needed()
 
2098
        last_dirblock = state._dirblocks[-1]
 
2099
        # make another entry for a-id, without a correct 'r' pointer to
 
2100
        # the real occurrence in the working tree
 
2101
        last_dirblock[1].append(
 
2102
            (('h', 'z', 'a-id'),
 
2103
             [('a', '', 0, False, ''),
 
2104
              ('a', '', 0, False, '')]))
 
2105
        e = self.assertRaises(AssertionError,
 
2106
            state._validate)
 
2107
        self.assertContainsRe(str(e),
 
2108
            'file a-id is absent in row')