/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Robert Collins
  • Date: 2007-04-23 02:29:35 UTC
  • mfrom: (2441 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2442.
  • Revision ID: robertc@robertcollins.net-20070423022935-9hhongamvk6bfdso
Resolve conflicts with bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    osutils,
27
27
    )
28
28
from bzrlib.memorytree import MemoryTree
29
 
from bzrlib.tests import TestCase, TestCaseWithTransport
 
29
from bzrlib.osutils import has_symlinks
 
30
from bzrlib.tests import (
 
31
        TestCase,
 
32
        TestCaseWithTransport,
 
33
        TestSkipped,
 
34
        )
30
35
 
31
36
 
32
37
# TODO:
97
102
         b/g      g-file
98
103
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
99
104
 
100
 
        # 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.
101
108
        """
102
109
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
103
110
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
166
173
            state.save()
167
174
        finally:
168
175
            state.unlock()
169
 
        del state # Callers should unlock
 
176
        del state
170
177
        state = dirstate.DirState.on_file('dirstate')
171
178
        state.lock_read()
172
179
        try:
174
181
        finally:
175
182
            state.unlock()
176
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
177
346
 
178
347
class TestTreeToDirState(TestCaseWithDirState):
179
348
 
201
370
             ])])
202
371
        state = dirstate.DirState.from_tree(tree, 'dirstate')
203
372
        self.check_state_with_reopen(expected_result, state)
204
 
        state._validate()
 
373
        state.lock_read()
 
374
        try:
 
375
            state._validate()
 
376
        finally:
 
377
            state.unlock()
205
378
 
206
379
    def test_2_parents_empty_to_dirstate(self):
207
380
        # create a parent by doing a commit
218
391
             ])])
219
392
        state = dirstate.DirState.from_tree(tree, 'dirstate')
220
393
        self.check_state_with_reopen(expected_result, state)
221
 
        state._validate()
 
394
        state.lock_read()
 
395
        try:
 
396
            state._validate()
 
397
        finally:
 
398
            state.unlock()
222
399
 
223
400
    def test_empty_unknowns_are_ignored_to_dirstate(self):
224
401
        """We should be able to create a dirstate for an empty tree."""
362
539
        finally:
363
540
            state.unlock()
364
541
 
 
542
    def test_can_save_in_read_lock(self):
 
543
        self.build_tree(['a-file'])
 
544
        state = dirstate.DirState.initialize('dirstate')
 
545
        try:
 
546
            # No stat and no sha1 sum.
 
547
            state.add('a-file', 'a-file-id', 'file', None, '')
 
548
            state.save()
 
549
        finally:
 
550
            state.unlock()
 
551
 
 
552
        # Now open in readonly mode
 
553
        state = dirstate.DirState.on_file('dirstate')
 
554
        state.lock_read()
 
555
        try:
 
556
            entry = state._get_entry(0, path_utf8='a-file')
 
557
            # The current sha1 sum should be empty
 
558
            self.assertEqual('', entry[1][0][1])
 
559
            # We should have a real entry.
 
560
            self.assertNotEqual((None, None), entry)
 
561
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
 
562
            # We should have gotten a real sha1
 
563
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
 
564
                             sha1sum)
 
565
 
 
566
            # The dirblock has been updated
 
567
            self.assertEqual(sha1sum, entry[1][0][1])
 
568
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
569
                             state._dirblock_state)
 
570
 
 
571
            del entry
 
572
            # Now, since we are the only one holding a lock, we should be able
 
573
            # to save and have it written to disk
 
574
            state.save()
 
575
        finally:
 
576
            state.unlock()
 
577
 
 
578
        # Re-open the file, and ensure that the state has been updated.
 
579
        state = dirstate.DirState.on_file('dirstate')
 
580
        state.lock_read()
 
581
        try:
 
582
            entry = state._get_entry(0, path_utf8='a-file')
 
583
            self.assertEqual(sha1sum, entry[1][0][1])
 
584
        finally:
 
585
            state.unlock()
 
586
 
 
587
    def test_save_fails_quietly_if_locked(self):
 
588
        """If dirstate is locked, save will fail without complaining."""
 
589
        self.build_tree(['a-file'])
 
590
        state = dirstate.DirState.initialize('dirstate')
 
591
        try:
 
592
            # No stat and no sha1 sum.
 
593
            state.add('a-file', 'a-file-id', 'file', None, '')
 
594
            state.save()
 
595
        finally:
 
596
            state.unlock()
 
597
 
 
598
        state = dirstate.DirState.on_file('dirstate')
 
599
        state.lock_read()
 
600
        try:
 
601
            entry = state._get_entry(0, path_utf8='a-file')
 
602
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
 
603
            # We should have gotten a real sha1
 
604
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
 
605
                             sha1sum)
 
606
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
607
                             state._dirblock_state)
 
608
 
 
609
            # Now, before we try to save, grab another dirstate, and take out a
 
610
            # read lock.
 
611
            # TODO: jam 20070315 Ideally this would be locked by another
 
612
            #       process. To make sure the file is really OS locked.
 
613
            state2 = dirstate.DirState.on_file('dirstate')
 
614
            state2.lock_read()
 
615
            try:
 
616
                # This won't actually write anything, because it couldn't grab
 
617
                # a write lock. But it shouldn't raise an error, either.
 
618
                # TODO: jam 20070315 We should probably distinguish between
 
619
                #       being dirty because of 'update_entry'. And dirty
 
620
                #       because of real modification. So that save() *does*
 
621
                #       raise a real error if it fails when we have real
 
622
                #       modifications.
 
623
                state.save()
 
624
            finally:
 
625
                state2.unlock()
 
626
        finally:
 
627
            state.unlock()
 
628
        
 
629
        # The file on disk should not be modified.
 
630
        state = dirstate.DirState.on_file('dirstate')
 
631
        state.lock_read()
 
632
        try:
 
633
            entry = state._get_entry(0, path_utf8='a-file')
 
634
            self.assertEqual('', entry[1][0][1])
 
635
        finally:
 
636
            state.unlock()
 
637
 
365
638
 
366
639
class TestDirStateInitialize(TestCaseWithDirState):
367
640
 
375
648
        try:
376
649
            self.assertIsInstance(state, dirstate.DirState)
377
650
            lines = state.get_lines()
378
 
            self.assertFileEqual(''.join(state.get_lines()),
379
 
                'dirstate')
380
 
            self.check_state_with_reopen(expected_result, state)
381
 
        except:
 
651
        finally:
382
652
            state.unlock()
383
 
            raise
 
653
        # On win32 you can't read from a locked file, even within the same
 
654
        # process. So we have to unlock and release before we check the file
 
655
        # contents.
 
656
        self.assertFileEqual(''.join(lines), 'dirstate')
 
657
        state.lock_read() # check_state_with_reopen will unlock
 
658
        self.check_state_with_reopen(expected_result, state)
384
659
 
385
660
 
386
661
class TestDirStateManipulations(TestCaseWithDirState):
432
707
        finally:
433
708
            state.unlock()
434
709
        state = dirstate.DirState.on_file('dirstate')
435
 
        state._validate()
436
710
        state.lock_read()
437
711
        try:
 
712
            state._validate()
438
713
            self.assertEqual(expected_rows, list(state._iter_entries()))
439
714
        finally:
440
715
            state.unlock()
684
959
    def test_add_symlink_to_root_no_parents_all_data(self):
685
960
        # The most trivial addition of a symlink when there are no parents and
686
961
        # its in the root and all data about the file is supplied
687
 
        ## TODO: windows: dont fail this test. Also, how are symlinks meant to
688
 
        # be represented on windows.
 
962
        # bzr doesn't support fake symlinks on windows, yet.
 
963
        if not has_symlinks():
 
964
            raise TestSkipped("No symlink support")
689
965
        os.symlink('target', 'a link')
690
966
        stat = os.lstat('a link')
691
967
        expected_entries = [
1201
1477
    def test_update_entry_symlink(self):
1202
1478
        """Update entry should read symlinks."""
1203
1479
        if not osutils.has_symlinks():
1204
 
            return # PlatformDeficiency / TestSkipped
 
1480
            # PlatformDeficiency / TestSkipped
 
1481
            raise TestSkipped("No symlink support")
1205
1482
        state, entry = self.get_state_with_a()
1206
1483
        state.save()
1207
1484
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1286
1563
        This should not be called if this platform does not have symlink
1287
1564
        support.
1288
1565
        """
 
1566
        # caller should care about skipping test on platforms without symlinks
1289
1567
        os.symlink('path/to/foo', 'a')
1290
1568
 
1291
1569
        stat_value = os.lstat('a')
1320
1598
 
1321
1599
    def test_update_missing_symlink(self):
1322
1600
        if not osutils.has_symlinks():
1323
 
            return # PlatformDeficiency / TestSkipped
 
1601
            # PlatformDeficiency / TestSkipped
 
1602
            raise TestSkipped("No symlink support")
1324
1603
        state, entry = self.get_state_with_a()
1325
1604
        packed_stat = self.create_and_test_symlink(state, entry)
1326
1605
        os.remove('a')
1341
1620
    def test_update_file_to_symlink(self):
1342
1621
        """File becomes a symlink"""
1343
1622
        if not osutils.has_symlinks():
1344
 
            return # PlatformDeficiency / TestSkipped
 
1623
            # PlatformDeficiency / TestSkipped
 
1624
            raise TestSkipped("No symlink support")
1345
1625
        state, entry = self.get_state_with_a()
1346
1626
        self.create_and_test_file(state, entry)
1347
1627
        os.remove('a')
1357
1637
    def test_update_dir_to_symlink(self):
1358
1638
        """Directory becomes a symlink"""
1359
1639
        if not osutils.has_symlinks():
1360
 
            return # PlatformDeficiency / TestSkipped
 
1640
            # PlatformDeficiency / TestSkipped
 
1641
            raise TestSkipped("No symlink support")
1361
1642
        state, entry = self.get_state_with_a()
1362
1643
        self.create_and_test_dir(state, entry)
1363
1644
        os.rmdir('a')
1365
1646
 
1366
1647
    def test_update_symlink_to_file(self):
1367
1648
        """Symlink becomes a file"""
 
1649
        if not has_symlinks():
 
1650
            raise TestSkipped("No symlink support")
1368
1651
        state, entry = self.get_state_with_a()
1369
1652
        self.create_and_test_symlink(state, entry)
1370
1653
        os.remove('a')
1372
1655
 
1373
1656
    def test_update_symlink_to_dir(self):
1374
1657
        """Symlink becomes a directory"""
 
1658
        if not has_symlinks():
 
1659
            raise TestSkipped("No symlink support")
1375
1660
        state, entry = self.get_state_with_a()
1376
1661
        self.create_and_test_symlink(state, entry)
1377
1662
        os.remove('a')
1452
1737
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1453
1738
 
1454
1739
 
1455
 
class TestBisect(TestCaseWithTransport):
 
1740
class TestBisect(TestCaseWithDirState):
1456
1741
    """Test the ability to bisect into the disk format."""
1457
1742
 
1458
 
    def create_basic_dirstate(self):
1459
 
        """Create a dirstate with a few files and directories.
1460
 
 
1461
 
            a
1462
 
            b/
1463
 
              c
1464
 
              d/
1465
 
                e
1466
 
            f
1467
 
        """
1468
 
        tree = self.make_branch_and_tree('tree')
1469
 
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'f']
1470
 
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'f-id']
1471
 
        self.build_tree(['tree/' + p for p in paths])
1472
 
        tree.set_root_id('TREE_ROOT')
1473
 
        tree.add([p.rstrip('/') for p in paths], file_ids)
1474
 
        tree.commit('initial', rev_id='rev-1')
1475
 
        revision_id = 'rev-1'
1476
 
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
1477
 
        t = self.get_transport().clone('tree')
1478
 
        a_text = t.get_bytes('a')
1479
 
        a_sha = osutils.sha_string(a_text)
1480
 
        a_len = len(a_text)
1481
 
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
1482
 
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
1483
 
        c_text = t.get_bytes('b/c')
1484
 
        c_sha = osutils.sha_string(c_text)
1485
 
        c_len = len(c_text)
1486
 
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
1487
 
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
1488
 
        e_text = t.get_bytes('b/d/e')
1489
 
        e_sha = osutils.sha_string(e_text)
1490
 
        e_len = len(e_text)
1491
 
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
1492
 
        f_text = t.get_bytes('f')
1493
 
        f_sha = osutils.sha_string(f_text)
1494
 
        f_len = len(f_text)
1495
 
        null_stat = dirstate.DirState.NULLSTAT
1496
 
        expected = {
1497
 
            '':(('', '', 'TREE_ROOT'), [
1498
 
                  ('d', '', 0, False, null_stat),
1499
 
                  ('d', '', 0, False, revision_id),
1500
 
                ]),
1501
 
            'a':(('', 'a', 'a-id'), [
1502
 
                   ('f', '', 0, False, null_stat),
1503
 
                   ('f', a_sha, a_len, False, revision_id),
1504
 
                 ]),
1505
 
            'b':(('', 'b', 'b-id'), [
1506
 
                  ('d', '', 0, False, null_stat),
1507
 
                  ('d', '', 0, False, revision_id),
1508
 
                 ]),
1509
 
            'b/c':(('b', 'c', 'c-id'), [
1510
 
                    ('f', '', 0, False, null_stat),
1511
 
                    ('f', c_sha, c_len, False, revision_id),
1512
 
                   ]),
1513
 
            'b/d':(('b', 'd', 'd-id'), [
1514
 
                    ('d', '', 0, False, null_stat),
1515
 
                    ('d', '', 0, False, revision_id),
1516
 
                   ]),
1517
 
            'b/d/e':(('b/d', 'e', 'e-id'), [
1518
 
                      ('f', '', 0, False, null_stat),
1519
 
                      ('f', e_sha, e_len, False, revision_id),
1520
 
                     ]),
1521
 
            'f':(('', 'f', 'f-id'), [
1522
 
                  ('f', '', 0, False, null_stat),
1523
 
                  ('f', f_sha, f_len, False, revision_id),
1524
 
                 ]),
1525
 
        }
1526
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
1527
 
        try:
1528
 
            state.save()
1529
 
        finally:
1530
 
            state.unlock()
1531
 
        # Use a different object, to make sure nothing is pre-cached in memory.
1532
 
        state = dirstate.DirState.on_file('dirstate')
1533
 
        state.lock_read()
1534
 
        self.addCleanup(state.unlock)
1535
 
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
1536
 
                         state._dirblock_state)
1537
 
        # This is code is only really tested if we actually have to make more
1538
 
        # than one read, so set the page size to something smaller.
1539
 
        # We want it to contain about 2.2 records, so that we have a couple
1540
 
        # records that we can read per attempt
1541
 
        state._bisect_page_size = 200
1542
 
        return tree, state, expected
1543
 
 
1544
 
    def create_duplicated_dirstate(self):
1545
 
        """Create a dirstate with a deleted and added entries.
1546
 
 
1547
 
        This grabs a basic_dirstate, and then removes and re adds every entry
1548
 
        with a new file id.
1549
 
        """
1550
 
        tree, state, expected = self.create_basic_dirstate()
1551
 
        # Now we will just remove and add every file so we get an extra entry
1552
 
        # per entry. Unversion in reverse order so we handle subdirs
1553
 
        tree.unversion(['f-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
1554
 
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f'],
1555
 
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'f-id2'])
1556
 
 
1557
 
        # Update the expected dictionary.
1558
 
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'f']:
1559
 
            orig = expected[path]
1560
 
            path2 = path + '2'
1561
 
            # This record was deleted in the current tree
1562
 
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
1563
 
                                        orig[1][1]])
1564
 
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
1565
 
            # And didn't exist in the basis tree
1566
 
            expected[path2] = (new_key, [orig[1][0],
1567
 
                                         dirstate.DirState.NULL_PARENT_DETAILS])
1568
 
 
1569
 
        # We will replace the 'dirstate' file underneath 'state', but that is
1570
 
        # okay as lock as we unlock 'state' first.
1571
 
        state.unlock()
1572
 
        try:
1573
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1574
 
            try:
1575
 
                new_state.save()
1576
 
            finally:
1577
 
                new_state.unlock()
1578
 
        finally:
1579
 
            # But we need to leave state in a read-lock because we already have
1580
 
            # a cleanup scheduled
1581
 
            state.lock_read()
1582
 
        return tree, state, expected
1583
 
 
1584
 
    def create_renamed_dirstate(self):
1585
 
        """Create a dirstate with a few internal renames.
1586
 
 
1587
 
        This takes the basic dirstate, and moves the paths around.
1588
 
        """
1589
 
        tree, state, expected = self.create_basic_dirstate()
1590
 
        # Rename a file
1591
 
        tree.rename_one('a', 'b/g')
1592
 
        # And a directory
1593
 
        tree.rename_one('b/d', 'h')
1594
 
 
1595
 
        old_a = expected['a']
1596
 
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
1597
 
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
1598
 
                                                ('r', 'a', 0, False, '')])
1599
 
        old_d = expected['b/d']
1600
 
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
1601
 
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
1602
 
                                             ('r', 'b/d', 0, False, '')])
1603
 
 
1604
 
        old_e = expected['b/d/e']
1605
 
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
1606
 
                             old_e[1][1]])
1607
 
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
1608
 
                                                ('r', 'b/d/e', 0, False, '')])
1609
 
 
1610
 
        state.unlock()
1611
 
        try:
1612
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
1613
 
            try:
1614
 
                new_state.save()
1615
 
            finally:
1616
 
                new_state.unlock()
1617
 
        finally:
1618
 
            state.lock_read()
1619
 
        return tree, state, expected
1620
1743
 
1621
1744
    def assertBisect(self, expected_map, map_keys, state, paths):
1622
1745
        """Assert that bisecting for paths returns the right result.
1929
2052
        for path in paths:
1930
2053
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
1931
2054
 
 
2055
 
 
2056
class TestDirstateValidation(TestCaseWithDirState):
 
2057
 
 
2058
    def test_validate_correct_dirstate(self):
 
2059
        state = self.create_complex_dirstate()
 
2060
        state._validate()
 
2061
        state.unlock()
 
2062
        # and make sure we can also validate with a read lock
 
2063
        state.lock_read()
 
2064
        try:
 
2065
            state._validate()
 
2066
        finally:
 
2067
            state.unlock()
 
2068
 
 
2069
    def test_dirblock_not_sorted(self):
 
2070
        tree, state, expected = self.create_renamed_dirstate()
 
2071
        state._read_dirblocks_if_needed()
 
2072
        last_dirblock = state._dirblocks[-1]
 
2073
        # we're appending to the dirblock, but this name comes before some of
 
2074
        # the existing names; that's wrong
 
2075
        last_dirblock[1].append(
 
2076
            (('h', 'aaaa', 'a-id'),
 
2077
             [('a', '', 0, False, ''),
 
2078
              ('a', '', 0, False, '')]))
 
2079
        e = self.assertRaises(AssertionError,
 
2080
            state._validate)
 
2081
        self.assertContainsRe(str(e), 'not sorted')
 
2082
 
 
2083
    def test_dirblock_name_mismatch(self):
 
2084
        tree, state, expected = self.create_renamed_dirstate()
 
2085
        state._read_dirblocks_if_needed()
 
2086
        last_dirblock = state._dirblocks[-1]
 
2087
        # add an entry with the wrong directory name
 
2088
        last_dirblock[1].append(
 
2089
            (('', 'z', 'a-id'),
 
2090
             [('a', '', 0, False, ''),
 
2091
              ('a', '', 0, False, '')]))
 
2092
        e = self.assertRaises(AssertionError,
 
2093
            state._validate)
 
2094
        self.assertContainsRe(str(e),
 
2095
            "doesn't match directory name")
 
2096
 
 
2097
    def test_dirblock_missing_rename(self):
 
2098
        tree, state, expected = self.create_renamed_dirstate()
 
2099
        state._read_dirblocks_if_needed()
 
2100
        last_dirblock = state._dirblocks[-1]
 
2101
        # make another entry for a-id, without a correct 'r' pointer to
 
2102
        # the real occurrence in the working tree
 
2103
        last_dirblock[1].append(
 
2104
            (('h', 'z', 'a-id'),
 
2105
             [('a', '', 0, False, ''),
 
2106
              ('a', '', 0, False, '')]))
 
2107
        e = self.assertRaises(AssertionError,
 
2108
            state._validate)
 
2109
        self.assertContainsRe(str(e),
 
2110
            'file a-id is absent in row')