~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

NEWS section template into a separate file

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
18
18
 
19
19
import bisect
20
20
import os
21
 
import time
22
21
 
23
22
from bzrlib import (
24
23
    dirstate,
25
24
    errors,
26
25
    inventory,
 
26
    memorytree,
27
27
    osutils,
 
28
    revision as _mod_revision,
 
29
    tests,
28
30
    )
29
 
from bzrlib.memorytree import MemoryTree
30
 
from bzrlib.tests import (
31
 
        SymlinkFeature,
32
 
        TestCase,
33
 
        TestCaseWithTransport,
34
 
        )
 
31
from bzrlib.tests import test_osutils
35
32
 
36
33
 
37
34
# TODO:
47
44
# set_path_id  setting id when state is in memory modified
48
45
 
49
46
 
50
 
class TestCaseWithDirState(TestCaseWithTransport):
 
47
def load_tests(basic_tests, module, loader):
 
48
    suite = loader.suiteClass()
 
49
    dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
 
50
        basic_tests, tests.condition_isinstance(TestCaseWithDirState))
 
51
    tests.multiply_tests(dir_reader_tests,
 
52
                         test_osutils.dir_reader_scenarios(), suite)
 
53
    suite.addTest(remaining_tests)
 
54
    return suite
 
55
 
 
56
 
 
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
51
58
    """Helper functions for creating DirState objects with various content."""
52
59
 
 
60
    # Set by load_tests
 
61
    _dir_reader_class = None
 
62
    _native_to_unicode = None # Not used yet
 
63
 
 
64
    def setUp(self):
 
65
        tests.TestCaseWithTransport.setUp(self)
 
66
 
 
67
        # Save platform specific info and reset it
 
68
        cur_dir_reader = osutils._selected_dir_reader
 
69
 
 
70
        def restore():
 
71
            osutils._selected_dir_reader = cur_dir_reader
 
72
        self.addCleanup(restore)
 
73
 
 
74
        osutils._selected_dir_reader = self._dir_reader_class()
 
75
 
53
76
    def create_empty_dirstate(self):
54
77
        """Return a locked but empty dirstate"""
55
78
        state = dirstate.DirState.initialize('dirstate')
396
419
            (('', '', tree.get_root_id()), # common details
397
420
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
421
              ('d', '', 0, False, rev_id), # first parent details
399
 
              ('d', '', 0, False, rev_id2), # second parent details
 
422
              ('d', '', 0, False, rev_id), # second parent details
400
423
             ])])
401
424
        state = dirstate.DirState.from_tree(tree, 'dirstate')
402
425
        self.check_state_with_reopen(expected_result, state)
477
500
            (('', '', tree.get_root_id()), # common details
478
501
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
502
              ('d', '', 0, False, rev_id), # first parent details
480
 
              ('d', '', 0, False, rev_id2), # second parent details
 
503
              ('d', '', 0, False, rev_id), # second parent details
481
504
             ]),
482
505
            (('', 'a file', 'a-file-id'), # common
483
506
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
563
586
        state.lock_read()
564
587
        try:
565
588
            entry = state._get_entry(0, path_utf8='a-file')
566
 
            # The current sha1 sum should be empty
567
 
            self.assertEqual('', entry[1][0][1])
 
589
            # The current size should be 0 (default)
 
590
            self.assertEqual(0, entry[1][0][2])
568
591
            # We should have a real entry.
569
592
            self.assertNotEqual((None, None), entry)
570
593
            # Make sure everything is old enough
571
594
            state._sha_cutoff_time()
572
595
            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)
 
596
            # Change the file length
 
597
            self.build_tree_contents([('a-file', 'shorter')])
 
598
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
599
                os.lstat('a-file'))
 
600
            # new file, no cached sha:
 
601
            self.assertEqual(None, sha1sum)
577
602
 
578
603
            # The dirblock has been updated
579
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
604
            self.assertEqual(7, entry[1][0][2])
580
605
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
606
                             state._dirblock_state)
582
607
 
592
617
        state.lock_read()
593
618
        try:
594
619
            entry = state._get_entry(0, path_utf8='a-file')
595
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
620
            self.assertEqual(7, entry[1][0][2])
596
621
        finally:
597
622
            state.unlock()
598
623
 
611
636
        state.lock_read()
612
637
        try:
613
638
            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)
 
639
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
640
                os.lstat('a-file'))
 
641
            # No sha - too new
 
642
            self.assertEqual(None, sha1sum)
618
643
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
644
                             state._dirblock_state)
620
645
 
637
662
                state2.unlock()
638
663
        finally:
639
664
            state.unlock()
640
 
        
 
665
 
641
666
        # The file on disk should not be modified.
642
667
        state = dirstate.DirState.on_file('dirstate')
643
668
        state.lock_read()
743
768
        # https://bugs.launchpad.net/bzr/+bug/146176
744
769
        # set_state_from_inventory should preserve the stat and hash value for
745
770
        # workingtree files that are not changed by the inventory.
746
 
       
 
771
 
747
772
        tree = self.make_branch_and_tree('.')
748
773
        # depends on the default format using dirstate...
749
774
        tree.lock_write()
750
775
        try:
751
 
            # make a dirstate with some valid hashcache data 
 
776
            # make a dirstate with some valid hashcache data
752
777
            # file on disk, but that's not needed for this test
753
778
            foo_contents = 'contents of foo'
754
779
            self.build_tree_contents([('foo', foo_contents)])
774
799
                (('', 'foo', 'foo-id',),
775
800
                 [('f', foo_sha, foo_size, False, foo_packed)]),
776
801
                tree._dirstate._get_entry(0, 'foo-id'))
777
 
           
 
802
 
778
803
            # extract the inventory, and add something to it
779
804
            inv = tree._get_inventory()
780
805
            # should see the file we poked in...
802
827
        finally:
803
828
            tree.unlock()
804
829
 
805
 
 
806
830
    def test_set_state_from_inventory_mixed_paths(self):
807
831
        tree1 = self.make_branch_and_tree('tree1')
808
832
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
849
873
        state = dirstate.DirState.initialize('dirstate')
850
874
        try:
851
875
            # check precondition to be sure the state does change appropriately.
852
 
            self.assertEqual(
853
 
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
854
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
855
 
                list(state._iter_entries()))
856
 
            state.set_path_id('', 'foobarbaz')
857
 
            expected_rows = [
858
 
                (('', '', 'foobarbaz'), [('d', '', 0, False,
859
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
876
            root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
 
877
            self.assertEqual([root_entry], list(state._iter_entries()))
 
878
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
879
            self.assertEqual(root_entry,
 
880
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
881
            self.assertEqual((None, None),
 
882
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
883
            state.set_path_id('', 'second-root-id')
 
884
            new_root_entry = (('', '', 'second-root-id'),
 
885
                              [('d', '', 0, False, 'x'*32)])
 
886
            expected_rows = [new_root_entry]
860
887
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
888
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
889
            self.assertEqual(new_root_entry, 
 
890
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
891
            self.assertEqual((None, None),
 
892
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
861
893
            # should work across save too
862
894
            state.save()
863
895
        finally:
881
913
        state._validate()
882
914
        try:
883
915
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
884
 
            state.set_path_id('', 'foobarbaz')
 
916
            root_entry = (('', '', 'TREE_ROOT'),
 
917
                          [('d', '', 0, False, 'x'*32),
 
918
                           ('d', '', 0, False, 'parent-revid')])
 
919
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
920
            self.assertEqual(root_entry,
 
921
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
922
            self.assertEqual((None, None),
 
923
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
924
            state.set_path_id('', 'Asecond-root-id')
885
925
            state._validate()
886
926
            # now see that it is what we expected
887
 
            expected_rows = [
888
 
                (('', '', 'TREE_ROOT'),
889
 
                    [('a', '', 0, False, ''),
890
 
                     ('d', '', 0, False, 'parent-revid'),
891
 
                     ]),
892
 
                (('', '', 'foobarbaz'),
893
 
                    [('d', '', 0, False, ''),
894
 
                     ('a', '', 0, False, ''),
895
 
                     ]),
896
 
                ]
 
927
            old_root_entry = (('', '', 'TREE_ROOT'),
 
928
                              [('a', '', 0, False, ''),
 
929
                               ('d', '', 0, False, 'parent-revid')])
 
930
            new_root_entry = (('', '', 'Asecond-root-id'),
 
931
                              [('d', '', 0, False, ''),
 
932
                               ('a', '', 0, False, '')])
 
933
            expected_rows = [new_root_entry, old_root_entry]
897
934
            state._validate()
898
935
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
936
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
937
            self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
 
938
            self.assertEqual((None, None),
 
939
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
940
            self.assertEqual(old_root_entry,
 
941
                             state._get_entry(1, fileid_utf8='TREE_ROOT'))
 
942
            self.assertEqual(new_root_entry,
 
943
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
944
            self.assertEqual((None, None),
 
945
                             state._get_entry(1, fileid_utf8='Asecond-root-id'))
899
946
            # should work across save too
900
947
            state.save()
901
948
        finally:
917
964
        finally:
918
965
            state.unlock()
919
966
 
920
 
 
921
967
    def test_set_parent_trees_no_content(self):
922
968
        # set_parent_trees is a slow but important api to support.
923
969
        tree1 = self.make_branch_and_memory_tree('tree1')
928
974
        finally:
929
975
            tree1.unlock()
930
976
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
931
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
977
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
932
978
        tree2.lock_write()
933
979
        try:
934
980
            revid2 = tree2.commit('foo')
966
1012
            state.set_parent_trees(
967
1013
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
968
1014
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
969
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
1015
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1016
                                   _mod_revision.NULL_REVISION))),
970
1017
                ['ghost-rev'])
971
1018
            self.assertEqual([revid1, revid2, 'ghost-rev'],
972
1019
                             state.get_parent_ids())
976
1023
                [(('', '', root_id), [
977
1024
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
978
1025
                  ('d', '', 0, False, revid1),
979
 
                  ('d', '', 0, False, revid2)
 
1026
                  ('d', '', 0, False, revid1)
980
1027
                  ])],
981
1028
                list(state._iter_entries()))
982
1029
        finally:
997
1044
        finally:
998
1045
            tree1.unlock()
999
1046
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1000
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1047
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1001
1048
        tree2.lock_write()
1002
1049
        try:
1003
1050
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1010
1057
            (('', '', root_id), [
1011
1058
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
1012
1059
             ('d', '', 0, False, revid1.encode('utf8')),
1013
 
             ('d', '', 0, False, revid2.encode('utf8'))
 
1060
             ('d', '', 0, False, revid1.encode('utf8'))
1014
1061
             ]),
1015
1062
            (('', 'a file', 'file-id'), [
1016
1063
             ('a', '', 0, False, ''),
1062
1109
            state.unlock()
1063
1110
        state = dirstate.DirState.on_file('dirstate')
1064
1111
        state.lock_read()
1065
 
        try:
1066
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1067
 
        finally:
1068
 
            state.unlock()
 
1112
        self.addCleanup(state.unlock)
 
1113
        self.assertEqual(expected_entries, list(state._iter_entries()))
1069
1114
 
1070
1115
    def test_add_path_to_unversioned_directory(self):
1071
1116
        """Adding a path to an unversioned directory should error.
1076
1121
        """
1077
1122
        self.build_tree(['unversioned/', 'unversioned/a file'])
1078
1123
        state = dirstate.DirState.initialize('dirstate')
1079
 
        try:
1080
 
            self.assertRaises(errors.NotVersionedError, state.add,
1081
 
                'unversioned/a file', 'a-file-id', 'file', None, None)
1082
 
        finally:
1083
 
            state.unlock()
 
1124
        self.addCleanup(state.unlock)
 
1125
        self.assertRaises(errors.NotVersionedError, state.add,
 
1126
                          'unversioned/a file', 'a-file-id', 'file', None, None)
1084
1127
 
1085
1128
    def test_add_directory_to_root_no_parents_all_data(self):
1086
1129
        # The most trivial addition of a dir is when there are no parents and
1106
1149
            state.unlock()
1107
1150
        state = dirstate.DirState.on_file('dirstate')
1108
1151
        state.lock_read()
 
1152
        self.addCleanup(state.unlock)
1109
1153
        state._validate()
1110
 
        try:
1111
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1112
 
        finally:
1113
 
            state.unlock()
 
1154
        self.assertEqual(expected_entries, list(state._iter_entries()))
1114
1155
 
1115
 
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1156
    def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1116
1157
        # The most trivial addition of a symlink when there are no parents and
1117
1158
        # its in the root and all data about the file is supplied
1118
1159
        # bzr doesn't support fake symlinks on windows, yet.
1119
 
        self.requireFeature(SymlinkFeature)
1120
 
        os.symlink('target', 'a link')
1121
 
        stat = os.lstat('a link')
 
1160
        self.requireFeature(tests.SymlinkFeature)
 
1161
        os.symlink(target, link_name)
 
1162
        stat = os.lstat(link_name)
1122
1163
        expected_entries = [
1123
1164
            (('', '', 'TREE_ROOT'), [
1124
1165
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1125
1166
             ]),
1126
 
            (('', 'a link', 'a link id'), [
1127
 
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
1167
            (('', link_name.encode('UTF-8'), 'a link id'), [
 
1168
             ('l', target.encode('UTF-8'), stat[6],
 
1169
              False, dirstate.pack_stat(stat)), # current tree
1128
1170
             ]),
1129
1171
            ]
1130
1172
        state = dirstate.DirState.initialize('dirstate')
1131
1173
        try:
1132
 
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
1174
            state.add(link_name, 'a link id', 'symlink', stat,
 
1175
                      target.encode('UTF-8'))
1133
1176
            # having added it, it should be in the output of iter_entries.
1134
1177
            self.assertEqual(expected_entries, list(state._iter_entries()))
1135
1178
            # saving and reloading should not affect this.
1138
1181
            state.unlock()
1139
1182
        state = dirstate.DirState.on_file('dirstate')
1140
1183
        state.lock_read()
1141
 
        try:
1142
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1143
 
        finally:
1144
 
            state.unlock()
 
1184
        self.addCleanup(state.unlock)
 
1185
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1186
 
 
1187
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1188
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
 
1189
 
 
1190
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
 
1191
        self.requireFeature(tests.UnicodeFilenameFeature)
 
1192
        self._test_add_symlink_to_root_no_parents_all_data(
 
1193
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1145
1194
 
1146
1195
    def test_add_directory_and_child_no_parents_all_data(self):
1147
1196
        # after adding a directory, we should be able to add children to it.
1172
1221
            state.unlock()
1173
1222
        state = dirstate.DirState.on_file('dirstate')
1174
1223
        state.lock_read()
1175
 
        try:
1176
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1177
 
        finally:
1178
 
            state.unlock()
 
1224
        self.addCleanup(state.unlock)
 
1225
        self.assertEqual(expected_entries, list(state._iter_entries()))
1179
1226
 
1180
1227
    def test_add_tree_reference(self):
1181
1228
        # make a dirstate and add a tree reference
1195
1242
            state.unlock()
1196
1243
        # now check we can read it back
1197
1244
        state.lock_read()
 
1245
        self.addCleanup(state.unlock)
1198
1246
        state._validate()
1199
 
        try:
1200
 
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1201
 
            self.assertEqual(entry, entry2)
1202
 
            self.assertEqual(entry, expected_entry)
1203
 
            # and lookup by id should work too
1204
 
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1205
 
            self.assertEqual(entry, expected_entry)
1206
 
        finally:
1207
 
            state.unlock()
 
1247
        entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1248
        self.assertEqual(entry, entry2)
 
1249
        self.assertEqual(entry, expected_entry)
 
1250
        # and lookup by id should work too
 
1251
        entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1252
        self.assertEqual(entry, expected_entry)
1208
1253
 
1209
1254
    def test_add_forbidden_names(self):
1210
1255
        state = dirstate.DirState.initialize('dirstate')
1214
1259
        self.assertRaises(errors.BzrError,
1215
1260
            state.add, '..', 'ass-id', 'directory', None, None)
1216
1261
 
 
1262
    def test_set_state_with_rename_b_a_bug_395556(self):
 
1263
        # bug 395556 uncovered a bug where the dirstate ends up with a false
 
1264
        # relocation record - in a tree with no parents there should be no
 
1265
        # absent or relocated records. This then leads to further corruption
 
1266
        # when a commit occurs, as the incorrect relocation gathers an
 
1267
        # incorrect absent in tree 1, and future changes go to pot.
 
1268
        tree1 = self.make_branch_and_tree('tree1')
 
1269
        self.build_tree(['tree1/b'])
 
1270
        tree1.lock_write()
 
1271
        try:
 
1272
            tree1.add(['b'], ['b-id'])
 
1273
            root_id = tree1.get_root_id()
 
1274
            inv = tree1.inventory
 
1275
            state = dirstate.DirState.initialize('dirstate')
 
1276
            try:
 
1277
                # Set the initial state with 'b'
 
1278
                state.set_state_from_inventory(inv)
 
1279
                inv.rename('b-id', root_id, 'a')
 
1280
                # Set the new state with 'a', which currently corrupts.
 
1281
                state.set_state_from_inventory(inv)
 
1282
                expected_result1 = [('', '', root_id, 'd'),
 
1283
                                    ('', 'a', 'b-id', 'f'),
 
1284
                                   ]
 
1285
                values = []
 
1286
                for entry in state._iter_entries():
 
1287
                    values.append(entry[0] + entry[1][0][:1])
 
1288
                self.assertEqual(expected_result1, values)
 
1289
            finally:
 
1290
                state.unlock()
 
1291
        finally:
 
1292
            tree1.unlock()
 
1293
 
1217
1294
 
1218
1295
class TestGetLines(TestCaseWithDirState):
1219
1296
 
1452
1529
        There is one parent tree, which has the same shape with the following variations:
1453
1530
        b/g in the parent is gone.
1454
1531
        b/h in the parent has a different id
1455
 
        b/i is new in the parent 
 
1532
        b/i is new in the parent
1456
1533
        c is renamed to b/j in the parent
1457
1534
 
1458
1535
        :return: The dirstate, still write-locked.
1548
1625
            list(state._iter_child_entries(1, '')))
1549
1626
 
1550
1627
 
1551
 
class TestDirstateSortOrder(TestCaseWithTransport):
 
1628
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1552
1629
    """Test that DirState adds entries in the right order."""
1553
1630
 
1554
1631
    def test_add_sorting(self):
1603
1680
 
1604
1681
        # *really* cheesy way to just get an empty tree
1605
1682
        repo = self.make_repository('repo')
1606
 
        empty_tree = repo.revision_tree(None)
 
1683
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1607
1684
        state.set_parent_trees([('null:', empty_tree)], [])
1608
1685
 
1609
1686
        dirblock_names = [d[0] for d in state._dirblocks]
1613
1690
class InstrumentedDirState(dirstate.DirState):
1614
1691
    """An DirState with instrumented sha1 functionality."""
1615
1692
 
1616
 
    def __init__(self, path):
1617
 
        super(InstrumentedDirState, self).__init__(path)
 
1693
    def __init__(self, path, sha1_provider):
 
1694
        super(InstrumentedDirState, self).__init__(path, sha1_provider)
1618
1695
        self._time_offset = 0
1619
1696
        self._log = []
1620
1697
        # member is dynamically set in DirState.__init__ to turn on trace
 
1698
        self._sha1_provider = sha1_provider
1621
1699
        self._sha1_file = self._sha1_file_and_log
1622
1700
 
1623
1701
    def _sha_cutoff_time(self):
1626
1704
 
1627
1705
    def _sha1_file_and_log(self, abspath):
1628
1706
        self._log.append(('sha1', abspath))
1629
 
        return osutils.sha_file_by_name(abspath)
 
1707
        return self._sha1_provider.sha1(abspath)
1630
1708
 
1631
1709
    def _read_link(self, abspath, old_link):
1632
1710
        self._log.append(('read_link', abspath, old_link))
1663
1741
        self.st_ino = ino
1664
1742
        self.st_mode = mode
1665
1743
 
1666
 
 
1667
 
class TestUpdateEntry(TestCaseWithDirState):
1668
 
    """Test the DirState.update_entry functions"""
1669
 
 
1670
 
    def get_state_with_a(self):
1671
 
        """Create a DirState tracking a single object named 'a'"""
1672
 
        state = InstrumentedDirState.initialize('dirstate')
1673
 
        self.addCleanup(state.unlock)
1674
 
        state.add('a', 'a-id', 'file', None, '')
1675
 
        entry = state._get_entry(0, path_utf8='a')
1676
 
        return state, entry
1677
 
 
1678
 
    def test_update_entry(self):
1679
 
        state, entry = self.get_state_with_a()
1680
 
        self.build_tree(['a'])
1681
 
        # Add one where we don't provide the stat or sha already
1682
 
        self.assertEqual(('', 'a', 'a-id'), entry[0])
1683
 
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1684
 
                         entry[1])
1685
 
        # Flush the buffers to disk
1686
 
        state.save()
1687
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1688
 
                         state._dirblock_state)
1689
 
 
1690
 
        stat_value = os.lstat('a')
1691
 
        packed_stat = dirstate.pack_stat(stat_value)
1692
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1693
 
                                          stat_value=stat_value)
1694
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1695
 
                         link_or_sha1)
1696
 
 
1697
 
        # The dirblock entry should not cache the file's sha1
1698
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1699
 
                         entry[1])
1700
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1701
 
                         state._dirblock_state)
1702
 
        mode = stat_value.st_mode
1703
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1704
 
 
1705
 
        state.save()
1706
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1707
 
                         state._dirblock_state)
1708
 
 
1709
 
        # If we do it again right away, we don't know if the file has changed
1710
 
        # so we will re-read the file. Roll the clock back so the file is
1711
 
        # guaranteed to look too new.
1712
 
        state.adjust_time(-10)
1713
 
 
1714
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1715
 
                                          stat_value=stat_value)
1716
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1717
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1718
 
                         ], state._log)
1719
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1720
 
                         link_or_sha1)
1721
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1722
 
                         state._dirblock_state)
1723
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1724
 
                         entry[1])
1725
 
        state.save()
1726
 
 
1727
 
        # However, if we move the clock forward so the file is considered
1728
 
        # "stable", it should just cache the value.
1729
 
        state.adjust_time(+20)
1730
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1731
 
                                          stat_value=stat_value)
1732
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1733
 
                         link_or_sha1)
1734
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1735
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1736
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1737
 
                         ], state._log)
1738
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1739
 
                         entry[1])
1740
 
 
1741
 
        # Subsequent calls will just return the cached value
1742
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1743
 
                                          stat_value=stat_value)
1744
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1745
 
                         link_or_sha1)
1746
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1747
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1748
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1749
 
                         ], state._log)
1750
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1751
 
                         entry[1])
1752
 
 
1753
 
    def test_update_entry_symlink(self):
1754
 
        """Update entry should read symlinks."""
1755
 
        self.requireFeature(SymlinkFeature)
1756
 
        state, entry = self.get_state_with_a()
1757
 
        state.save()
1758
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1759
 
                         state._dirblock_state)
1760
 
        os.symlink('target', 'a')
1761
 
 
1762
 
        state.adjust_time(-10) # Make the symlink look new
1763
 
        stat_value = os.lstat('a')
1764
 
        packed_stat = dirstate.pack_stat(stat_value)
1765
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1766
 
                                          stat_value=stat_value)
1767
 
        self.assertEqual('target', link_or_sha1)
1768
 
        self.assertEqual([('read_link', 'a', '')], state._log)
1769
 
        # Dirblock is not updated (the link is too new)
1770
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1771
 
                         entry[1])
1772
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1773
 
                         state._dirblock_state)
1774
 
 
1775
 
        # Because the stat_value looks new, we should re-read the target
1776
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1777
 
                                          stat_value=stat_value)
1778
 
        self.assertEqual('target', link_or_sha1)
1779
 
        self.assertEqual([('read_link', 'a', ''),
1780
 
                          ('read_link', 'a', ''),
1781
 
                         ], state._log)
1782
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1783
 
                         entry[1])
1784
 
        state.adjust_time(+20) # Skip into the future, all files look old
1785
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1786
 
                                          stat_value=stat_value)
1787
 
        self.assertEqual('target', link_or_sha1)
1788
 
        # We need to re-read the link because only now can we cache it
1789
 
        self.assertEqual([('read_link', 'a', ''),
1790
 
                          ('read_link', 'a', ''),
1791
 
                          ('read_link', 'a', ''),
1792
 
                         ], state._log)
1793
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1794
 
                         entry[1])
1795
 
 
1796
 
        # Another call won't re-read the link
1797
 
        self.assertEqual([('read_link', 'a', ''),
1798
 
                          ('read_link', 'a', ''),
1799
 
                          ('read_link', 'a', ''),
1800
 
                         ], state._log)
1801
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1802
 
                                          stat_value=stat_value)
1803
 
        self.assertEqual('target', link_or_sha1)
1804
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1805
 
                         entry[1])
1806
 
 
1807
 
    def do_update_entry(self, state, entry, abspath):
1808
 
        stat_value = os.lstat(abspath)
1809
 
        return state.update_entry(entry, abspath, stat_value)
1810
 
 
1811
 
    def test_update_entry_dir(self):
1812
 
        state, entry = self.get_state_with_a()
1813
 
        self.build_tree(['a/'])
1814
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1815
 
 
1816
 
    def test_update_entry_dir_unchanged(self):
1817
 
        state, entry = self.get_state_with_a()
1818
 
        self.build_tree(['a/'])
1819
 
        state.adjust_time(+20)
1820
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1821
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1822
 
                         state._dirblock_state)
1823
 
        state.save()
1824
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1825
 
                         state._dirblock_state)
1826
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1827
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1828
 
                         state._dirblock_state)
1829
 
 
1830
 
    def test_update_entry_file_unchanged(self):
1831
 
        state, entry = self.get_state_with_a()
1832
 
        self.build_tree(['a'])
1833
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1834
 
        state.adjust_time(+20)
1835
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1836
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1837
 
                         state._dirblock_state)
1838
 
        state.save()
1839
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1840
 
                         state._dirblock_state)
1841
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1842
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1843
 
                         state._dirblock_state)
1844
 
 
1845
 
    def create_and_test_file(self, state, entry):
1846
 
        """Create a file at 'a' and verify the state finds it.
1847
 
 
1848
 
        The state should already be versioning *something* at 'a'. This makes
1849
 
        sure that state.update_entry recognizes it as a file.
1850
 
        """
1851
 
        self.build_tree(['a'])
1852
 
        stat_value = os.lstat('a')
1853
 
        packed_stat = dirstate.pack_stat(stat_value)
1854
 
 
1855
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1856
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1857
 
                         link_or_sha1)
1858
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1859
 
                         entry[1])
1860
 
        return packed_stat
1861
 
 
1862
 
    def create_and_test_dir(self, state, entry):
1863
 
        """Create a directory at 'a' and verify the state finds it.
1864
 
 
1865
 
        The state should already be versioning *something* at 'a'. This makes
1866
 
        sure that state.update_entry recognizes it as a directory.
1867
 
        """
1868
 
        self.build_tree(['a/'])
1869
 
        stat_value = os.lstat('a')
1870
 
        packed_stat = dirstate.pack_stat(stat_value)
1871
 
 
1872
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1873
 
        self.assertIs(None, link_or_sha1)
1874
 
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1875
 
 
1876
 
        return packed_stat
1877
 
 
1878
 
    def create_and_test_symlink(self, state, entry):
1879
 
        """Create a symlink at 'a' and verify the state finds it.
1880
 
 
1881
 
        The state should already be versioning *something* at 'a'. This makes
1882
 
        sure that state.update_entry recognizes it as a symlink.
1883
 
 
1884
 
        This should not be called if this platform does not have symlink
1885
 
        support.
1886
 
        """
1887
 
        # caller should care about skipping test on platforms without symlinks
1888
 
        os.symlink('path/to/foo', 'a')
1889
 
 
1890
 
        stat_value = os.lstat('a')
1891
 
        packed_stat = dirstate.pack_stat(stat_value)
1892
 
 
1893
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1894
 
        self.assertEqual('path/to/foo', link_or_sha1)
1895
 
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1896
 
                         entry[1])
1897
 
        return packed_stat
1898
 
 
1899
 
    def test_update_file_to_dir(self):
1900
 
        """If a file changes to a directory we return None for the sha.
1901
 
        We also update the inventory record.
1902
 
        """
1903
 
        state, entry = self.get_state_with_a()
1904
 
        # The file sha1 won't be cached unless the file is old
1905
 
        state.adjust_time(+10)
1906
 
        self.create_and_test_file(state, entry)
1907
 
        os.remove('a')
1908
 
        self.create_and_test_dir(state, entry)
1909
 
 
1910
 
    def test_update_file_to_symlink(self):
1911
 
        """File becomes a symlink"""
1912
 
        self.requireFeature(SymlinkFeature)
1913
 
        state, entry = self.get_state_with_a()
1914
 
        # The file sha1 won't be cached unless the file is old
1915
 
        state.adjust_time(+10)
1916
 
        self.create_and_test_file(state, entry)
1917
 
        os.remove('a')
1918
 
        self.create_and_test_symlink(state, entry)
1919
 
 
1920
 
    def test_update_dir_to_file(self):
1921
 
        """Directory becoming a file updates the entry."""
1922
 
        state, entry = self.get_state_with_a()
1923
 
        # The file sha1 won't be cached unless the file is old
1924
 
        state.adjust_time(+10)
1925
 
        self.create_and_test_dir(state, entry)
1926
 
        os.rmdir('a')
1927
 
        self.create_and_test_file(state, entry)
1928
 
 
1929
 
    def test_update_dir_to_symlink(self):
1930
 
        """Directory becomes a symlink"""
1931
 
        self.requireFeature(SymlinkFeature)
1932
 
        state, entry = self.get_state_with_a()
1933
 
        # The symlink target won't be cached if it isn't old
1934
 
        state.adjust_time(+10)
1935
 
        self.create_and_test_dir(state, entry)
1936
 
        os.rmdir('a')
1937
 
        self.create_and_test_symlink(state, entry)
1938
 
 
1939
 
    def test_update_symlink_to_file(self):
1940
 
        """Symlink becomes a file"""
1941
 
        self.requireFeature(SymlinkFeature)
1942
 
        state, entry = self.get_state_with_a()
1943
 
        # The symlink and file info won't be cached unless old
1944
 
        state.adjust_time(+10)
1945
 
        self.create_and_test_symlink(state, entry)
1946
 
        os.remove('a')
1947
 
        self.create_and_test_file(state, entry)
1948
 
 
1949
 
    def test_update_symlink_to_dir(self):
1950
 
        """Symlink becomes a directory"""
1951
 
        self.requireFeature(SymlinkFeature)
1952
 
        state, entry = self.get_state_with_a()
1953
 
        # The symlink target won't be cached if it isn't old
1954
 
        state.adjust_time(+10)
1955
 
        self.create_and_test_symlink(state, entry)
1956
 
        os.remove('a')
1957
 
        self.create_and_test_dir(state, entry)
1958
 
 
1959
 
    def test__is_executable_win32(self):
1960
 
        state, entry = self.get_state_with_a()
1961
 
        self.build_tree(['a'])
1962
 
 
1963
 
        # Make sure we are using the win32 implementation of _is_executable
1964
 
        state._is_executable = state._is_executable_win32
1965
 
 
1966
 
        # The file on disk is not executable, but we are marking it as though
1967
 
        # it is. With _is_executable_win32 we ignore what is on disk.
1968
 
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1969
 
 
1970
 
        stat_value = os.lstat('a')
1971
 
        packed_stat = dirstate.pack_stat(stat_value)
1972
 
 
1973
 
        state.adjust_time(-10) # Make sure everything is new
1974
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1975
 
 
1976
 
        # The row is updated, but the executable bit stays set.
1977
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1978
 
                         entry[1])
1979
 
 
1980
 
        # Make the disk object look old enough to cache
1981
 
        state.adjust_time(+20)
1982
 
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1983
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1984
 
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1985
 
 
1986
 
 
1987
 
class TestPackStat(TestCaseWithTransport):
 
1744
    @staticmethod
 
1745
    def from_stat(st):
 
1746
        return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
 
1747
            st.st_ino, st.st_mode)
 
1748
 
 
1749
 
 
1750
class TestPackStat(tests.TestCaseWithTransport):
1988
1751
 
1989
1752
    def assertPackStat(self, expected, stat_value):
1990
1753
        """Check the packed and serialized form of a stat value."""
2517
2280
        self.assertEqual(exp_dirblocks, state._dirblocks)
2518
2281
 
2519
2282
 
2520
 
class Test_InvEntryToDetails(TestCaseWithDirState):
 
2283
class Test_InvEntryToDetails(tests.TestCase):
2521
2284
 
2522
2285
    def assertDetails(self, expected, inv_entry):
2523
2286
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
2530
2293
        self.assertIsInstance(tree_data, str)
2531
2294
 
2532
2295
    def test_unicode_symlink(self):
2533
 
        # In general, the code base doesn't support a target that contains
2534
 
        # non-ascii characters. So we just assert tha 
2535
 
        inv_entry = inventory.InventoryLink('link-file-id', 'name',
 
2296
        inv_entry = inventory.InventoryLink('link-file-id',
 
2297
                                            u'nam\N{Euro Sign}e',
2536
2298
                                            'link-parent-id')
2537
2299
        inv_entry.revision = 'link-revision-id'
2538
 
        inv_entry.symlink_target = u'link-target'
2539
 
        details = self.assertDetails(('l', 'link-target', 0, False,
2540
 
                                      'link-revision-id'), inv_entry)
 
2300
        target = u'link-targ\N{Euro Sign}t'
 
2301
        inv_entry.symlink_target = target
 
2302
        self.assertDetails(('l', target.encode('UTF-8'), 0, False,
 
2303
                            'link-revision-id'), inv_entry)
 
2304
 
 
2305
 
 
2306
class TestSHA1Provider(tests.TestCaseInTempDir):
 
2307
 
 
2308
    def test_sha1provider_is_an_interface(self):
 
2309
        p = dirstate.SHA1Provider()
 
2310
        self.assertRaises(NotImplementedError, p.sha1, "foo")
 
2311
        self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
 
2312
 
 
2313
    def test_defaultsha1provider_sha1(self):
 
2314
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2315
        self.build_tree_contents([('foo', text)])
 
2316
        expected_sha = osutils.sha_string(text)
 
2317
        p = dirstate.DefaultSHA1Provider()
 
2318
        self.assertEqual(expected_sha, p.sha1('foo'))
 
2319
 
 
2320
    def test_defaultsha1provider_stat_and_sha1(self):
 
2321
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2322
        self.build_tree_contents([('foo', text)])
 
2323
        expected_sha = osutils.sha_string(text)
 
2324
        p = dirstate.DefaultSHA1Provider()
 
2325
        statvalue, sha1 = p.stat_and_sha1('foo')
 
2326
        self.assertTrue(len(statvalue) >= 10)
 
2327
        self.assertEqual(len(text), statvalue.st_size)
 
2328
        self.assertEqual(expected_sha, sha1)