~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-14 20:47:17 UTC
  • mto: (2353.4.2 locking)
  • mto: This revision was merged to the branch mainline in revision 2360.
  • Revision ID: john@arbash-meinel.com-20070314204717-htynwogv97fqr22a
Cleanup errors, and change ReadOnlyLockError to pass around more details.

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.osutils import has_symlinks
30
 
from bzrlib.tests import (
31
 
        TestCase,
32
 
        TestCaseWithTransport,
33
 
        TestSkipped,
34
 
        )
 
29
from bzrlib.tests import TestCase, TestCaseWithTransport
35
30
 
36
31
 
37
32
# TODO:
102
97
         b/g      g-file
103
98
         b/h\xc3\xa5  h-\xc3\xa5-file  #This is u'\xe5' encoded into utf-8
104
99
 
105
 
        Notice that a/e is an empty directory.
106
 
 
107
 
        :return: The dirstate, still write-locked.
 
100
        # Notice that a/e is an empty directory.
108
101
        """
109
102
        packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
110
103
        null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
173
166
            state.save()
174
167
        finally:
175
168
            state.unlock()
176
 
        del state
 
169
        del state # Callers should unlock
177
170
        state = dirstate.DirState.on_file('dirstate')
178
171
        state.lock_read()
179
172
        try:
181
174
        finally:
182
175
            state.unlock()
183
176
 
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
 
            b-c
193
 
            f
194
 
        """
195
 
        tree = self.make_branch_and_tree('tree')
196
 
        paths = ['a', 'b/', 'b/c', 'b/d/', 'b/d/e', 'b-c', 'f']
197
 
        file_ids = ['a-id', 'b-id', 'c-id', 'd-id', 'e-id', 'b-c-id', 'f-id']
198
 
        self.build_tree(['tree/' + p for p in paths])
199
 
        tree.set_root_id('TREE_ROOT')
200
 
        tree.add([p.rstrip('/') for p in paths], file_ids)
201
 
        tree.commit('initial', rev_id='rev-1')
202
 
        revision_id = 'rev-1'
203
 
        # a_packed_stat = dirstate.pack_stat(os.stat('tree/a'))
204
 
        t = self.get_transport('tree')
205
 
        a_text = t.get_bytes('a')
206
 
        a_sha = osutils.sha_string(a_text)
207
 
        a_len = len(a_text)
208
 
        # b_packed_stat = dirstate.pack_stat(os.stat('tree/b'))
209
 
        # c_packed_stat = dirstate.pack_stat(os.stat('tree/b/c'))
210
 
        c_text = t.get_bytes('b/c')
211
 
        c_sha = osutils.sha_string(c_text)
212
 
        c_len = len(c_text)
213
 
        # d_packed_stat = dirstate.pack_stat(os.stat('tree/b/d'))
214
 
        # e_packed_stat = dirstate.pack_stat(os.stat('tree/b/d/e'))
215
 
        e_text = t.get_bytes('b/d/e')
216
 
        e_sha = osutils.sha_string(e_text)
217
 
        e_len = len(e_text)
218
 
        b_c_text = t.get_bytes('b-c')
219
 
        b_c_sha = osutils.sha_string(b_c_text)
220
 
        b_c_len = len(b_c_text)
221
 
        # f_packed_stat = dirstate.pack_stat(os.stat('tree/f'))
222
 
        f_text = t.get_bytes('f')
223
 
        f_sha = osutils.sha_string(f_text)
224
 
        f_len = len(f_text)
225
 
        null_stat = dirstate.DirState.NULLSTAT
226
 
        expected = {
227
 
            '':(('', '', 'TREE_ROOT'), [
228
 
                  ('d', '', 0, False, null_stat),
229
 
                  ('d', '', 0, False, revision_id),
230
 
                ]),
231
 
            'a':(('', 'a', 'a-id'), [
232
 
                   ('f', '', 0, False, null_stat),
233
 
                   ('f', a_sha, a_len, False, revision_id),
234
 
                 ]),
235
 
            'b':(('', 'b', 'b-id'), [
236
 
                  ('d', '', 0, False, null_stat),
237
 
                  ('d', '', 0, False, revision_id),
238
 
                 ]),
239
 
            'b/c':(('b', 'c', 'c-id'), [
240
 
                    ('f', '', 0, False, null_stat),
241
 
                    ('f', c_sha, c_len, False, revision_id),
242
 
                   ]),
243
 
            'b/d':(('b', 'd', 'd-id'), [
244
 
                    ('d', '', 0, False, null_stat),
245
 
                    ('d', '', 0, False, revision_id),
246
 
                   ]),
247
 
            'b/d/e':(('b/d', 'e', 'e-id'), [
248
 
                      ('f', '', 0, False, null_stat),
249
 
                      ('f', e_sha, e_len, False, revision_id),
250
 
                     ]),
251
 
            'b-c':(('', 'b-c', 'b-c-id'), [
252
 
                      ('f', '', 0, False, null_stat),
253
 
                      ('f', b_c_sha, b_c_len, False, revision_id),
254
 
                     ]),
255
 
            'f':(('', 'f', 'f-id'), [
256
 
                  ('f', '', 0, False, null_stat),
257
 
                  ('f', f_sha, f_len, False, revision_id),
258
 
                 ]),
259
 
        }
260
 
        state = dirstate.DirState.from_tree(tree, 'dirstate')
261
 
        try:
262
 
            state.save()
263
 
        finally:
264
 
            state.unlock()
265
 
        # Use a different object, to make sure nothing is pre-cached in memory.
266
 
        state = dirstate.DirState.on_file('dirstate')
267
 
        state.lock_read()
268
 
        self.addCleanup(state.unlock)
269
 
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
270
 
                         state._dirblock_state)
271
 
        # This is code is only really tested if we actually have to make more
272
 
        # than one read, so set the page size to something smaller.
273
 
        # We want it to contain about 2.2 records, so that we have a couple
274
 
        # records that we can read per attempt
275
 
        state._bisect_page_size = 200
276
 
        return tree, state, expected
277
 
 
278
 
    def create_duplicated_dirstate(self):
279
 
        """Create a dirstate with a deleted and added entries.
280
 
 
281
 
        This grabs a basic_dirstate, and then removes and re adds every entry
282
 
        with a new file id.
283
 
        """
284
 
        tree, state, expected = self.create_basic_dirstate()
285
 
        # Now we will just remove and add every file so we get an extra entry
286
 
        # per entry. Unversion in reverse order so we handle subdirs
287
 
        tree.unversion(['f-id', 'b-c-id', 'e-id', 'd-id', 'c-id', 'b-id', 'a-id'])
288
 
        tree.add(['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f'],
289
 
                 ['a-id2', 'b-id2', 'c-id2', 'd-id2', 'e-id2', 'b-c-id2', 'f-id2'])
290
 
 
291
 
        # Update the expected dictionary.
292
 
        for path in ['a', 'b', 'b/c', 'b/d', 'b/d/e', 'b-c', 'f']:
293
 
            orig = expected[path]
294
 
            path2 = path + '2'
295
 
            # This record was deleted in the current tree
296
 
            expected[path] = (orig[0], [dirstate.DirState.NULL_PARENT_DETAILS,
297
 
                                        orig[1][1]])
298
 
            new_key = (orig[0][0], orig[0][1], orig[0][2]+'2')
299
 
            # And didn't exist in the basis tree
300
 
            expected[path2] = (new_key, [orig[1][0],
301
 
                                         dirstate.DirState.NULL_PARENT_DETAILS])
302
 
 
303
 
        # We will replace the 'dirstate' file underneath 'state', but that is
304
 
        # okay as lock as we unlock 'state' first.
305
 
        state.unlock()
306
 
        try:
307
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
308
 
            try:
309
 
                new_state.save()
310
 
            finally:
311
 
                new_state.unlock()
312
 
        finally:
313
 
            # But we need to leave state in a read-lock because we already have
314
 
            # a cleanup scheduled
315
 
            state.lock_read()
316
 
        return tree, state, expected
317
 
 
318
 
    def create_renamed_dirstate(self):
319
 
        """Create a dirstate with a few internal renames.
320
 
 
321
 
        This takes the basic dirstate, and moves the paths around.
322
 
        """
323
 
        tree, state, expected = self.create_basic_dirstate()
324
 
        # Rename a file
325
 
        tree.rename_one('a', 'b/g')
326
 
        # And a directory
327
 
        tree.rename_one('b/d', 'h')
328
 
 
329
 
        old_a = expected['a']
330
 
        expected['a'] = (old_a[0], [('r', 'b/g', 0, False, ''), old_a[1][1]])
331
 
        expected['b/g'] = (('b', 'g', 'a-id'), [old_a[1][0],
332
 
                                                ('r', 'a', 0, False, '')])
333
 
        old_d = expected['b/d']
334
 
        expected['b/d'] = (old_d[0], [('r', 'h', 0, False, ''), old_d[1][1]])
335
 
        expected['h'] = (('', 'h', 'd-id'), [old_d[1][0],
336
 
                                             ('r', 'b/d', 0, False, '')])
337
 
 
338
 
        old_e = expected['b/d/e']
339
 
        expected['b/d/e'] = (old_e[0], [('r', 'h/e', 0, False, ''),
340
 
                             old_e[1][1]])
341
 
        expected['h/e'] = (('h', 'e', 'e-id'), [old_e[1][0],
342
 
                                                ('r', 'b/d/e', 0, False, '')])
343
 
 
344
 
        state.unlock()
345
 
        try:
346
 
            new_state = dirstate.DirState.from_tree(tree, 'dirstate')
347
 
            try:
348
 
                new_state.save()
349
 
            finally:
350
 
                new_state.unlock()
351
 
        finally:
352
 
            state.lock_read()
353
 
        return tree, state, expected
354
 
 
355
177
 
356
178
class TestTreeToDirState(TestCaseWithDirState):
357
179
 
379
201
             ])])
380
202
        state = dirstate.DirState.from_tree(tree, 'dirstate')
381
203
        self.check_state_with_reopen(expected_result, state)
382
 
        state.lock_read()
383
 
        try:
384
 
            state._validate()
385
 
        finally:
386
 
            state.unlock()
 
204
        state._validate()
387
205
 
388
206
    def test_2_parents_empty_to_dirstate(self):
389
207
        # create a parent by doing a commit
400
218
             ])])
401
219
        state = dirstate.DirState.from_tree(tree, 'dirstate')
402
220
        self.check_state_with_reopen(expected_result, state)
403
 
        state.lock_read()
404
 
        try:
405
 
            state._validate()
406
 
        finally:
407
 
            state.unlock()
 
221
        state._validate()
408
222
 
409
223
    def test_empty_unknowns_are_ignored_to_dirstate(self):
410
224
        """We should be able to create a dirstate for an empty tree."""
548
362
        finally:
549
363
            state.unlock()
550
364
 
551
 
    def test_can_save_in_read_lock(self):
552
 
        self.build_tree(['a-file'])
553
 
        state = dirstate.DirState.initialize('dirstate')
554
 
        try:
555
 
            # No stat and no sha1 sum.
556
 
            state.add('a-file', 'a-file-id', 'file', None, '')
557
 
            state.save()
558
 
        finally:
559
 
            state.unlock()
560
 
 
561
 
        # Now open in readonly mode
562
 
        state = dirstate.DirState.on_file('dirstate')
563
 
        state.lock_read()
564
 
        try:
565
 
            entry = state._get_entry(0, path_utf8='a-file')
566
 
            # The current sha1 sum should be empty
567
 
            self.assertEqual('', entry[1][0][1])
568
 
            # We should have a real entry.
569
 
            self.assertNotEqual((None, None), entry)
570
 
            # Make sure everything is old enough
571
 
            state._sha_cutoff_time()
572
 
            state._cutoff_time += 10
573
 
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
574
 
            # We should have gotten a real sha1
575
 
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
576
 
                             sha1sum)
577
 
 
578
 
            # The dirblock has been updated
579
 
            self.assertEqual(sha1sum, entry[1][0][1])
580
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
 
                             state._dirblock_state)
582
 
 
583
 
            del entry
584
 
            # Now, since we are the only one holding a lock, we should be able
585
 
            # to save and have it written to disk
586
 
            state.save()
587
 
        finally:
588
 
            state.unlock()
589
 
 
590
 
        # Re-open the file, and ensure that the state has been updated.
591
 
        state = dirstate.DirState.on_file('dirstate')
592
 
        state.lock_read()
593
 
        try:
594
 
            entry = state._get_entry(0, path_utf8='a-file')
595
 
            self.assertEqual(sha1sum, entry[1][0][1])
596
 
        finally:
597
 
            state.unlock()
598
 
 
599
 
    def test_save_fails_quietly_if_locked(self):
600
 
        """If dirstate is locked, save will fail without complaining."""
601
 
        self.build_tree(['a-file'])
602
 
        state = dirstate.DirState.initialize('dirstate')
603
 
        try:
604
 
            # No stat and no sha1 sum.
605
 
            state.add('a-file', 'a-file-id', 'file', None, '')
606
 
            state.save()
607
 
        finally:
608
 
            state.unlock()
609
 
 
610
 
        state = dirstate.DirState.on_file('dirstate')
611
 
        state.lock_read()
612
 
        try:
613
 
            entry = state._get_entry(0, path_utf8='a-file')
614
 
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
615
 
            # We should have gotten a real sha1
616
 
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
617
 
                             sha1sum)
618
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
 
                             state._dirblock_state)
620
 
 
621
 
            # Now, before we try to save, grab another dirstate, and take out a
622
 
            # read lock.
623
 
            # TODO: jam 20070315 Ideally this would be locked by another
624
 
            #       process. To make sure the file is really OS locked.
625
 
            state2 = dirstate.DirState.on_file('dirstate')
626
 
            state2.lock_read()
627
 
            try:
628
 
                # This won't actually write anything, because it couldn't grab
629
 
                # a write lock. But it shouldn't raise an error, either.
630
 
                # TODO: jam 20070315 We should probably distinguish between
631
 
                #       being dirty because of 'update_entry'. And dirty
632
 
                #       because of real modification. So that save() *does*
633
 
                #       raise a real error if it fails when we have real
634
 
                #       modifications.
635
 
                state.save()
636
 
            finally:
637
 
                state2.unlock()
638
 
        finally:
639
 
            state.unlock()
640
 
        
641
 
        # The file on disk should not be modified.
642
 
        state = dirstate.DirState.on_file('dirstate')
643
 
        state.lock_read()
644
 
        try:
645
 
            entry = state._get_entry(0, path_utf8='a-file')
646
 
            self.assertEqual('', entry[1][0][1])
647
 
        finally:
648
 
            state.unlock()
649
 
 
650
365
 
651
366
class TestDirStateInitialize(TestCaseWithDirState):
652
367
 
660
375
        try:
661
376
            self.assertIsInstance(state, dirstate.DirState)
662
377
            lines = state.get_lines()
663
 
        finally:
 
378
            self.assertFileEqual(''.join(state.get_lines()),
 
379
                'dirstate')
 
380
            self.check_state_with_reopen(expected_result, state)
 
381
        except:
664
382
            state.unlock()
665
 
        # On win32 you can't read from a locked file, even within the same
666
 
        # process. So we have to unlock and release before we check the file
667
 
        # contents.
668
 
        self.assertFileEqual(''.join(lines), 'dirstate')
669
 
        state.lock_read() # check_state_with_reopen will unlock
670
 
        self.check_state_with_reopen(expected_result, state)
 
383
            raise
671
384
 
672
385
 
673
386
class TestDirStateManipulations(TestCaseWithDirState):
700
413
            # This will unlock it
701
414
            self.check_state_with_reopen(expected_result, state)
702
415
 
703
 
    def test_set_state_from_inventory_mixed_paths(self):
704
 
        tree1 = self.make_branch_and_tree('tree1')
705
 
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
706
 
                         'tree1/a/b/foo', 'tree1/a-b/bar'])
707
 
        tree1.lock_write()
708
 
        try:
709
 
            tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
710
 
                      ['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
711
 
            tree1.commit('rev1', rev_id='rev1')
712
 
            root_id = tree1.get_root_id()
713
 
            inv = tree1.inventory
714
 
        finally:
715
 
            tree1.unlock()
716
 
        expected_result1 = [('', '', root_id, 'd'),
717
 
                            ('', 'a', 'a-id', 'd'),
718
 
                            ('', 'a-b', 'a-b-id', 'd'),
719
 
                            ('a', 'b', 'b-id', 'd'),
720
 
                            ('a/b', 'foo', 'foo-id', 'f'),
721
 
                            ('a-b', 'bar', 'bar-id', 'f'),
722
 
                           ]
723
 
        expected_result2 = [('', '', root_id, 'd'),
724
 
                            ('', 'a', 'a-id', 'd'),
725
 
                            ('', 'a-b', 'a-b-id', 'd'),
726
 
                            ('a-b', 'bar', 'bar-id', 'f'),
727
 
                           ]
728
 
        state = dirstate.DirState.initialize('dirstate')
729
 
        try:
730
 
            state.set_state_from_inventory(inv)
731
 
            values = []
732
 
            for entry in state._iter_entries():
733
 
                values.append(entry[0] + entry[1][0][:1])
734
 
            self.assertEqual(expected_result1, values)
735
 
            del inv['b-id']
736
 
            state.set_state_from_inventory(inv)
737
 
            values = []
738
 
            for entry in state._iter_entries():
739
 
                values.append(entry[0] + entry[1][0][:1])
740
 
            self.assertEqual(expected_result2, values)
741
 
        finally:
742
 
            state.unlock()
743
 
 
744
416
    def test_set_path_id_no_parents(self):
745
417
        """The id of a path can be changed trivally with no parents."""
746
418
        state = dirstate.DirState.initialize('dirstate')
760
432
        finally:
761
433
            state.unlock()
762
434
        state = dirstate.DirState.on_file('dirstate')
 
435
        state._validate()
763
436
        state.lock_read()
764
437
        try:
765
 
            state._validate()
766
438
            self.assertEqual(expected_rows, list(state._iter_entries()))
767
439
        finally:
768
440
            state.unlock()
1012
684
    def test_add_symlink_to_root_no_parents_all_data(self):
1013
685
        # The most trivial addition of a symlink when there are no parents and
1014
686
        # its in the root and all data about the file is supplied
1015
 
        # bzr doesn't support fake symlinks on windows, yet.
1016
 
        if not has_symlinks():
1017
 
            raise TestSkipped("No symlink support")
 
687
        ## TODO: windows: dont fail this test. Also, how are symlinks meant to
 
688
        # be represented on windows.
1018
689
        os.symlink('target', 'a link')
1019
690
        stat = os.lstat('a link')
1020
691
        expected_entries = [
1474
1145
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1475
1146
                         link_or_sha1)
1476
1147
 
1477
 
        # The dirblock entry should not cache the file's sha1
1478
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
 
1148
        # The dirblock entry should be updated with the new info
 
1149
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1479
1150
                         entry[1])
1480
1151
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1481
1152
                         state._dirblock_state)
1500
1171
                         link_or_sha1)
1501
1172
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1502
1173
                         state._dirblock_state)
1503
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1504
 
                         entry[1])
1505
1174
        state.save()
1506
1175
 
1507
1176
        # However, if we move the clock forward so the file is considered
1508
 
        # "stable", it should just cache the value.
1509
 
        state.adjust_time(+20)
 
1177
        # "stable", it should just returned the cached value.
 
1178
        state.adjust_time(20)
1510
1179
        link_or_sha1 = state.update_entry(entry, abspath='a',
1511
1180
                                          stat_value=stat_value)
1512
1181
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1513
1182
                         link_or_sha1)
1514
1183
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1515
1184
                          ('sha1', 'a'), ('is_exec', mode, False),
1516
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1517
1185
                         ], state._log)
1518
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1519
 
                         entry[1])
1520
1186
 
1521
 
        # Subsequent calls will just return the cached value
1522
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1523
 
                                          stat_value=stat_value)
 
1187
    def test_update_entry_no_stat_value(self):
 
1188
        """Passing the stat_value is optional."""
 
1189
        state, entry = self.get_state_with_a()
 
1190
        state.adjust_time(-10) # Make sure the file looks new
 
1191
        self.build_tree(['a'])
 
1192
        # Add one where we don't provide the stat or sha already
 
1193
        link_or_sha1 = state.update_entry(entry, abspath='a')
1524
1194
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1525
1195
                         link_or_sha1)
1526
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1527
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1528
 
                          ('sha1', 'a'), ('is_exec', mode, False),
 
1196
        stat_value = os.lstat('a')
 
1197
        self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
 
1198
                          ('is_exec', stat_value.st_mode, False),
1529
1199
                         ], state._log)
1530
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1531
 
                         entry[1])
1532
1200
 
1533
1201
    def test_update_entry_symlink(self):
1534
1202
        """Update entry should read symlinks."""
1535
1203
        if not osutils.has_symlinks():
1536
 
            # PlatformDeficiency / TestSkipped
1537
 
            raise TestSkipped("No symlink support")
 
1204
            return # PlatformDeficiency / TestSkipped
1538
1205
        state, entry = self.get_state_with_a()
1539
1206
        state.save()
1540
1207
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1548
1215
                                          stat_value=stat_value)
1549
1216
        self.assertEqual('target', link_or_sha1)
1550
1217
        self.assertEqual([('read_link', 'a', '')], state._log)
1551
 
        # Dirblock is not updated (the link is too new)
1552
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
 
1218
        # Dirblock is updated
 
1219
        self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1553
1220
                         entry[1])
1554
1221
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1555
1222
                         state._dirblock_state)
1559
1226
                                          stat_value=stat_value)
1560
1227
        self.assertEqual('target', link_or_sha1)
1561
1228
        self.assertEqual([('read_link', 'a', ''),
1562
 
                          ('read_link', 'a', ''),
 
1229
                          ('read_link', 'a', 'target'),
1563
1230
                         ], state._log)
1564
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1565
 
                         entry[1])
1566
1231
        state.adjust_time(+20) # Skip into the future, all files look old
1567
1232
        link_or_sha1 = state.update_entry(entry, abspath='a',
1568
1233
                                          stat_value=stat_value)
1569
1234
        self.assertEqual('target', link_or_sha1)
1570
 
        # We need to re-read the link because only now can we cache it
1571
 
        self.assertEqual([('read_link', 'a', ''),
1572
 
                          ('read_link', 'a', ''),
1573
 
                          ('read_link', 'a', ''),
1574
 
                         ], state._log)
1575
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1576
 
                         entry[1])
1577
 
 
1578
 
        # Another call won't re-read the link
1579
 
        self.assertEqual([('read_link', 'a', ''),
1580
 
                          ('read_link', 'a', ''),
1581
 
                          ('read_link', 'a', ''),
1582
 
                         ], state._log)
1583
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1584
 
                                          stat_value=stat_value)
1585
 
        self.assertEqual('target', link_or_sha1)
1586
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1587
 
                         entry[1])
1588
 
 
1589
 
    def do_update_entry(self, state, entry, abspath):
1590
 
        stat_value = os.lstat(abspath)
1591
 
        return state.update_entry(entry, abspath, stat_value)
 
1235
        # There should not be a new read_link call.
 
1236
        # (this is a weak assertion, because read_link is fairly inexpensive,
 
1237
        # versus the number of symlinks that we would have)
 
1238
        self.assertEqual([('read_link', 'a', ''),
 
1239
                          ('read_link', 'a', 'target'),
 
1240
                         ], state._log)
1592
1241
 
1593
1242
    def test_update_entry_dir(self):
1594
1243
        state, entry = self.get_state_with_a()
1595
1244
        self.build_tree(['a/'])
1596
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1597
 
 
1598
 
    def test_update_entry_dir_unchanged(self):
1599
 
        state, entry = self.get_state_with_a()
1600
 
        self.build_tree(['a/'])
1601
 
        state.adjust_time(+20)
1602
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1603
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1604
 
                         state._dirblock_state)
1605
 
        state.save()
1606
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1607
 
                         state._dirblock_state)
1608
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1609
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1610
 
                         state._dirblock_state)
1611
 
 
1612
 
    def test_update_entry_file_unchanged(self):
1613
 
        state, entry = self.get_state_with_a()
1614
 
        self.build_tree(['a'])
1615
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1616
 
        state.adjust_time(+20)
1617
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1618
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1619
 
                         state._dirblock_state)
1620
 
        state.save()
1621
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1622
 
                         state._dirblock_state)
1623
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1624
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1625
 
                         state._dirblock_state)
 
1245
        self.assertIs(None, state.update_entry(entry, 'a'))
1626
1246
 
1627
1247
    def create_and_test_file(self, state, entry):
1628
1248
        """Create a file at 'a' and verify the state finds it.
1634
1254
        stat_value = os.lstat('a')
1635
1255
        packed_stat = dirstate.pack_stat(stat_value)
1636
1256
 
1637
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1257
        link_or_sha1 = state.update_entry(entry, abspath='a')
1638
1258
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1639
1259
                         link_or_sha1)
1640
1260
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1651
1271
        stat_value = os.lstat('a')
1652
1272
        packed_stat = dirstate.pack_stat(stat_value)
1653
1273
 
1654
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1274
        link_or_sha1 = state.update_entry(entry, abspath='a')
1655
1275
        self.assertIs(None, link_or_sha1)
1656
1276
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1657
1277
 
1666
1286
        This should not be called if this platform does not have symlink
1667
1287
        support.
1668
1288
        """
1669
 
        # caller should care about skipping test on platforms without symlinks
1670
1289
        os.symlink('path/to/foo', 'a')
1671
1290
 
1672
1291
        stat_value = os.lstat('a')
1673
1292
        packed_stat = dirstate.pack_stat(stat_value)
1674
1293
 
1675
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1294
        link_or_sha1 = state.update_entry(entry, abspath='a')
1676
1295
        self.assertEqual('path/to/foo', link_or_sha1)
1677
1296
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1678
1297
                         entry[1])
1679
1298
        return packed_stat
1680
1299
 
 
1300
    def test_update_missing_file(self):
 
1301
        state, entry = self.get_state_with_a()
 
1302
        packed_stat = self.create_and_test_file(state, entry)
 
1303
        # Now if we delete the file, update_entry should recover and
 
1304
        # return None.
 
1305
        os.remove('a')
 
1306
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1307
        # And the record shouldn't be changed.
 
1308
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1309
        self.assertEqual([('f', digest, 14, False, packed_stat)],
 
1310
                         entry[1])
 
1311
 
 
1312
    def test_update_missing_dir(self):
 
1313
        state, entry = self.get_state_with_a()
 
1314
        packed_stat = self.create_and_test_dir(state, entry)
 
1315
        # Now if we delete the directory, update_entry should recover and
 
1316
        # return None.
 
1317
        os.rmdir('a')
 
1318
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1319
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1320
 
 
1321
    def test_update_missing_symlink(self):
 
1322
        if not osutils.has_symlinks():
 
1323
            return # PlatformDeficiency / TestSkipped
 
1324
        state, entry = self.get_state_with_a()
 
1325
        packed_stat = self.create_and_test_symlink(state, entry)
 
1326
        os.remove('a')
 
1327
        self.assertIs(None, state.update_entry(entry, abspath='a'))
 
1328
        # And the record shouldn't be changed.
 
1329
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1330
                         entry[1])
 
1331
 
1681
1332
    def test_update_file_to_dir(self):
1682
1333
        """If a file changes to a directory we return None for the sha.
1683
1334
        We also update the inventory record.
1684
1335
        """
1685
1336
        state, entry = self.get_state_with_a()
1686
 
        # The file sha1 won't be cached unless the file is old
1687
 
        state.adjust_time(+10)
1688
1337
        self.create_and_test_file(state, entry)
1689
1338
        os.remove('a')
1690
1339
        self.create_and_test_dir(state, entry)
1692
1341
    def test_update_file_to_symlink(self):
1693
1342
        """File becomes a symlink"""
1694
1343
        if not osutils.has_symlinks():
1695
 
            # PlatformDeficiency / TestSkipped
1696
 
            raise TestSkipped("No symlink support")
 
1344
            return # PlatformDeficiency / TestSkipped
1697
1345
        state, entry = self.get_state_with_a()
1698
 
        # The file sha1 won't be cached unless the file is old
1699
 
        state.adjust_time(+10)
1700
1346
        self.create_and_test_file(state, entry)
1701
1347
        os.remove('a')
1702
1348
        self.create_and_test_symlink(state, entry)
1704
1350
    def test_update_dir_to_file(self):
1705
1351
        """Directory becoming a file updates the entry."""
1706
1352
        state, entry = self.get_state_with_a()
1707
 
        # The file sha1 won't be cached unless the file is old
1708
 
        state.adjust_time(+10)
1709
1353
        self.create_and_test_dir(state, entry)
1710
1354
        os.rmdir('a')
1711
1355
        self.create_and_test_file(state, entry)
1713
1357
    def test_update_dir_to_symlink(self):
1714
1358
        """Directory becomes a symlink"""
1715
1359
        if not osutils.has_symlinks():
1716
 
            # PlatformDeficiency / TestSkipped
1717
 
            raise TestSkipped("No symlink support")
 
1360
            return # PlatformDeficiency / TestSkipped
1718
1361
        state, entry = self.get_state_with_a()
1719
 
        # The symlink target won't be cached if it isn't old
1720
 
        state.adjust_time(+10)
1721
1362
        self.create_and_test_dir(state, entry)
1722
1363
        os.rmdir('a')
1723
1364
        self.create_and_test_symlink(state, entry)
1724
1365
 
1725
1366
    def test_update_symlink_to_file(self):
1726
1367
        """Symlink becomes a file"""
1727
 
        if not has_symlinks():
1728
 
            raise TestSkipped("No symlink support")
1729
1368
        state, entry = self.get_state_with_a()
1730
 
        # The symlink and file info won't be cached unless old
1731
 
        state.adjust_time(+10)
1732
1369
        self.create_and_test_symlink(state, entry)
1733
1370
        os.remove('a')
1734
1371
        self.create_and_test_file(state, entry)
1735
1372
 
1736
1373
    def test_update_symlink_to_dir(self):
1737
1374
        """Symlink becomes a directory"""
1738
 
        if not has_symlinks():
1739
 
            raise TestSkipped("No symlink support")
1740
1375
        state, entry = self.get_state_with_a()
1741
 
        # The symlink target won't be cached if it isn't old
1742
 
        state.adjust_time(+10)
1743
1376
        self.create_and_test_symlink(state, entry)
1744
1377
        os.remove('a')
1745
1378
        self.create_and_test_dir(state, entry)
1759
1392
        packed_stat = dirstate.pack_stat(stat_value)
1760
1393
 
1761
1394
        state.adjust_time(-10) # Make sure everything is new
 
1395
        # Make sure it wants to kkkkkkkk
1762
1396
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1763
1397
 
1764
1398
        # The row is updated, but the executable bit stays set.
1765
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1766
 
                         entry[1])
1767
 
 
1768
 
        # Make the disk object look old enough to cache
1769
 
        state.adjust_time(+20)
1770
1399
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1771
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1772
1400
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1773
1401
 
1774
1402
 
1824
1452
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1825
1453
 
1826
1454
 
1827
 
class TestBisect(TestCaseWithDirState):
 
1455
class TestBisect(TestCaseWithTransport):
1828
1456
    """Test the ability to bisect into the disk format."""
1829
1457
 
 
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
 
1830
1621
    def assertBisect(self, expected_map, map_keys, state, paths):
1831
1622
        """Assert that bisecting for paths returns the right result.
1832
1623
 
1837
1628
                      (dir, name) tuples, and sorted according to how _bisect
1838
1629
                      requires.
1839
1630
        """
1840
 
        result = state._bisect(paths)
 
1631
        dir_names = sorted(osutils.split(p) for p in paths)
 
1632
        result = state._bisect(dir_names)
1841
1633
        # For now, results are just returned in whatever order we read them.
1842
1634
        # We could sort by (dir, name, file_id) or something like that, but in
1843
1635
        # the end it would still be fairly arbitrary, and we don't want the
1844
1636
        # extra overhead if we can avoid it. So sort everything to make sure
1845
1637
        # equality is true
1846
 
        assert len(map_keys) == len(paths)
 
1638
        assert len(map_keys) == len(dir_names)
1847
1639
        expected = {}
1848
 
        for path, keys in zip(paths, map_keys):
 
1640
        for dir_name, keys in zip(dir_names, map_keys):
1849
1641
            if keys is None:
1850
1642
                # This should not be present in the output
1851
1643
                continue
1852
 
            expected[path] = sorted(expected_map[k] for k in keys)
 
1644
            expected[dir_name] = sorted(expected_map[k] for k in keys)
1853
1645
 
1854
 
        # The returned values are just arranged randomly based on when they
1855
 
        # were read, for testing, make sure it is properly sorted.
1856
 
        for path in result:
1857
 
            result[path].sort()
 
1646
        for dir_name in result:
 
1647
            result[dir_name].sort()
1858
1648
 
1859
1649
        self.assertEqual(expected, result)
1860
1650
 
1897
1687
            dir_name_id, trees_info = entry
1898
1688
            expected[dir_name_id] = trees_info
1899
1689
 
1900
 
        result = state._bisect_recursive(paths)
 
1690
        dir_names = sorted(osutils.split(p) for p in paths)
 
1691
        result = state._bisect_recursive(dir_names)
1901
1692
 
1902
1693
        self.assertEqual(expected, result)
1903
1694
 
1912
1703
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
1913
1704
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
1914
1705
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1915
 
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
1916
1706
        self.assertBisect(expected, [['f']], state, ['f'])
1917
1707
 
1918
1708
    def test_bisect_multi(self):
1921
1711
        # Bisect should be capable of finding multiple entries at the same time
1922
1712
        self.assertBisect(expected, [['a'], ['b'], ['f']],
1923
1713
                          state, ['a', 'b', 'f'])
 
1714
        # ('', 'f') sorts before the others
1924
1715
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1925
 
                          state, ['f', 'b/d', 'b/d/e'])
1926
 
        self.assertBisect(expected, [['b'], ['b-c'], ['b/c']],
1927
 
                          state, ['b', 'b-c', 'b/c'])
 
1716
                          state, ['b/d', 'b/d/e', 'f'])
1928
1717
 
1929
1718
    def test_bisect_one_page(self):
1930
1719
        """Test bisect when there is only 1 page to read"""
1936
1725
        self.assertBisect(expected,[['b/c']], state, ['b/c'])
1937
1726
        self.assertBisect(expected,[['b/d']], state, ['b/d'])
1938
1727
        self.assertBisect(expected,[['b/d/e']], state, ['b/d/e'])
1939
 
        self.assertBisect(expected,[['b-c']], state, ['b-c'])
1940
1728
        self.assertBisect(expected,[['f']], state, ['f'])
1941
1729
        self.assertBisect(expected,[['a'], ['b'], ['f']],
1942
1730
                          state, ['a', 'b', 'f'])
1943
 
        self.assertBisect(expected, [['b/d'], ['b/d/e'], ['f']],
 
1731
        # ('', 'f') sorts before the others
 
1732
        self.assertBisect(expected, [['f'], ['b/d'], ['b/d/e']],
1944
1733
                          state, ['b/d', 'b/d/e', 'f'])
1945
 
        self.assertBisect(expected, [['b'], ['b/c'], ['b-c']],
1946
 
                          state, ['b', 'b/c', 'b-c'])
1947
1734
 
1948
1735
    def test_bisect_duplicate_paths(self):
1949
1736
        """When bisecting for a path, handle multiple entries."""
1957
1744
        self.assertBisect(expected, [['b/d', 'b/d2']], state, ['b/d'])
1958
1745
        self.assertBisect(expected, [['b/d/e', 'b/d/e2']],
1959
1746
                          state, ['b/d/e'])
1960
 
        self.assertBisect(expected, [['b-c', 'b-c2']], state, ['b-c'])
1961
1747
        self.assertBisect(expected, [['f', 'f2']], state, ['f'])
1962
1748
 
1963
1749
    def test_bisect_page_size_too_small(self):
1970
1756
        self.assertBisect(expected, [['b/c']], state, ['b/c'])
1971
1757
        self.assertBisect(expected, [['b/d']], state, ['b/d'])
1972
1758
        self.assertBisect(expected, [['b/d/e']], state, ['b/d/e'])
1973
 
        self.assertBisect(expected, [['b-c']], state, ['b-c'])
1974
1759
        self.assertBisect(expected, [['f']], state, ['f'])
1975
1760
 
1976
1761
    def test_bisect_missing(self):
1979
1764
        self.assertBisect(expected, [None], state, ['foo'])
1980
1765
        self.assertBisect(expected, [None], state, ['b/foo'])
1981
1766
        self.assertBisect(expected, [None], state, ['bar/foo'])
1982
 
        self.assertBisect(expected, [None], state, ['b-c/foo'])
1983
1767
 
1984
1768
        self.assertBisect(expected, [['a'], None, ['b/d']],
1985
1769
                          state, ['a', 'foo', 'b/d'])
2001
1785
    def test_bisect_dirblocks(self):
2002
1786
        tree, state, expected = self.create_duplicated_dirstate()
2003
1787
        self.assertBisectDirBlocks(expected,
2004
 
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2']],
2005
 
            state, [''])
 
1788
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2']], state, [''])
2006
1789
        self.assertBisectDirBlocks(expected,
2007
1790
            [['b/c', 'b/c2', 'b/d', 'b/d2']], state, ['b'])
2008
1791
        self.assertBisectDirBlocks(expected,
2009
1792
            [['b/d/e', 'b/d/e2']], state, ['b/d'])
2010
1793
        self.assertBisectDirBlocks(expected,
2011
 
            [['', 'a', 'a2', 'b', 'b2', 'b-c', 'b-c2', 'f', 'f2'],
 
1794
            [['', 'a', 'a2', 'b', 'b2', 'f', 'f2'],
2012
1795
             ['b/c', 'b/c2', 'b/d', 'b/d2'],
2013
1796
             ['b/d/e', 'b/d/e2'],
2014
1797
            ], state, ['', 'b', 'b/d'])
2029
1812
        self.assertBisectRecursive(expected, ['a'], state, ['a'])
2030
1813
        self.assertBisectRecursive(expected, ['b/c'], state, ['b/c'])
2031
1814
        self.assertBisectRecursive(expected, ['b/d/e'], state, ['b/d/e'])
2032
 
        self.assertBisectRecursive(expected, ['b-c'], state, ['b-c'])
2033
1815
        self.assertBisectRecursive(expected, ['b/d', 'b/d/e'],
2034
1816
                                   state, ['b/d'])
2035
1817
        self.assertBisectRecursive(expected, ['b', 'b/c', 'b/d', 'b/d/e'],
2036
1818
                                   state, ['b'])
2037
 
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'b-c', 'f', 'b/c',
 
1819
        self.assertBisectRecursive(expected, ['', 'a', 'b', 'f', 'b/c',
2038
1820
                                              'b/d', 'b/d/e'],
2039
1821
                                   state, [''])
2040
1822
 
2064
1846
                                   state, ['b'])
2065
1847
 
2066
1848
 
2067
 
class TestDirstateValidation(TestCaseWithDirState):
2068
 
 
2069
 
    def test_validate_correct_dirstate(self):
2070
 
        state = self.create_complex_dirstate()
2071
 
        state._validate()
2072
 
        state.unlock()
2073
 
        # and make sure we can also validate with a read lock
2074
 
        state.lock_read()
2075
 
        try:
2076
 
            state._validate()
2077
 
        finally:
2078
 
            state.unlock()
2079
 
 
2080
 
    def test_dirblock_not_sorted(self):
2081
 
        tree, state, expected = self.create_renamed_dirstate()
2082
 
        state._read_dirblocks_if_needed()
2083
 
        last_dirblock = state._dirblocks[-1]
2084
 
        # we're appending to the dirblock, but this name comes before some of
2085
 
        # the existing names; that's wrong
2086
 
        last_dirblock[1].append(
2087
 
            (('h', 'aaaa', 'a-id'),
2088
 
             [('a', '', 0, False, ''),
2089
 
              ('a', '', 0, False, '')]))
2090
 
        e = self.assertRaises(AssertionError,
2091
 
            state._validate)
2092
 
        self.assertContainsRe(str(e), 'not sorted')
2093
 
 
2094
 
    def test_dirblock_name_mismatch(self):
2095
 
        tree, state, expected = self.create_renamed_dirstate()
2096
 
        state._read_dirblocks_if_needed()
2097
 
        last_dirblock = state._dirblocks[-1]
2098
 
        # add an entry with the wrong directory name
2099
 
        last_dirblock[1].append(
2100
 
            (('', 'z', 'a-id'),
2101
 
             [('a', '', 0, False, ''),
2102
 
              ('a', '', 0, False, '')]))
2103
 
        e = self.assertRaises(AssertionError,
2104
 
            state._validate)
2105
 
        self.assertContainsRe(str(e),
2106
 
            "doesn't match directory name")
2107
 
 
2108
 
    def test_dirblock_missing_rename(self):
2109
 
        tree, state, expected = self.create_renamed_dirstate()
2110
 
        state._read_dirblocks_if_needed()
2111
 
        last_dirblock = state._dirblocks[-1]
2112
 
        # make another entry for a-id, without a correct 'r' pointer to
2113
 
        # the real occurrence in the working tree
2114
 
        last_dirblock[1].append(
2115
 
            (('h', 'z', 'a-id'),
2116
 
             [('a', '', 0, False, ''),
2117
 
              ('a', '', 0, False, '')]))
2118
 
        e = self.assertRaises(AssertionError,
2119
 
            state._validate)
2120
 
        self.assertContainsRe(str(e),
2121
 
            'file a-id is absent in row')
 
1849
class TestBisectDirblock(TestCase):
 
1850
    """Test that bisect_dirblock() returns the expected values.
 
1851
 
 
1852
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
1853
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
1854
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
1855
    """
 
1856
 
 
1857
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
1858
        """Assert that bisect_split works like bisect_left on the split paths.
 
1859
 
 
1860
        :param dirblocks: A list of (path, [info]) pairs.
 
1861
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
1862
        :param path: The path we are indexing.
 
1863
 
 
1864
        All other arguments will be passed along.
 
1865
        """
 
1866
        bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
 
1867
                                                 *args, **kwargs)
 
1868
        split_dirblock = (path.split('/'), [])
 
1869
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
1870
                                             *args)
 
1871
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
1872
                         'bisect_split disagreed. %s != %s'
 
1873
                         ' for key %s'
 
1874
                         % (bisect_left_idx, bisect_split_idx, path)
 
1875
                         )
 
1876
 
 
1877
    def paths_to_dirblocks(self, paths):
 
1878
        """Convert a list of paths into dirblock form.
 
1879
 
 
1880
        Also, ensure that the paths are in proper sorted order.
 
1881
        """
 
1882
        dirblocks = [(path, []) for path in paths]
 
1883
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
1884
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
1885
        return dirblocks, split_dirblocks
 
1886
 
 
1887
    def test_simple(self):
 
1888
        """In the simple case it works just like bisect_left"""
 
1889
        paths = ['', 'a', 'b', 'c', 'd']
 
1890
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1891
        for path in paths:
 
1892
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1893
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
1894
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
1895
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
1896
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
1897
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
1898
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
1899
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
1900
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
1901
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
1902
 
 
1903
    def test_involved(self):
 
1904
        """This is where bisect_left diverges slightly."""
 
1905
        paths = ['', 'a',
 
1906
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1907
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1908
                 'a-a', 'a-z',
 
1909
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1910
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1911
                 'z-a', 'z-z',
 
1912
                ]
 
1913
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1914
        for path in paths:
 
1915
            self.assertBisect(dirblocks, split_dirblocks, path)
 
1916
 
 
1917
    def test_involved_cached(self):
 
1918
        """This is where bisect_left diverges slightly."""
 
1919
        paths = ['', 'a',
 
1920
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
1921
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
1922
                 'a-a', 'a-z',
 
1923
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
1924
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
1925
                 'z-a', 'z-z',
 
1926
                ]
 
1927
        cache = {}
 
1928
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
1929
        for path in paths:
 
1930
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2122
1931