~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-04-08 06:17:41 UTC
  • mfrom: (4797.33.16 apport)
  • Revision ID: pqm@pqm.ubuntu.com-20100408061741-m7vl6z97vu33riv7
(robertc) Make sure ExecutablePath and InterpreterPath are set in
        Apport. (Martin Pool, James Westby, lp:528114)

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
        self.overrideAttr(osutils,
 
68
                          '_selected_dir_reader', self._dir_reader_class())
 
69
 
53
70
    def create_empty_dirstate(self):
54
71
        """Return a locked but empty dirstate"""
55
72
        state = dirstate.DirState.initialize('dirstate')
396
413
            (('', '', tree.get_root_id()), # common details
397
414
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
398
415
              ('d', '', 0, False, rev_id), # first parent details
399
 
              ('d', '', 0, False, rev_id2), # second parent details
 
416
              ('d', '', 0, False, rev_id), # second parent details
400
417
             ])])
401
418
        state = dirstate.DirState.from_tree(tree, 'dirstate')
402
419
        self.check_state_with_reopen(expected_result, state)
477
494
            (('', '', tree.get_root_id()), # common details
478
495
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
479
496
              ('d', '', 0, False, rev_id), # first parent details
480
 
              ('d', '', 0, False, rev_id2), # second parent details
 
497
              ('d', '', 0, False, rev_id), # second parent details
481
498
             ]),
482
499
            (('', 'a file', 'a-file-id'), # common
483
500
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
563
580
        state.lock_read()
564
581
        try:
565
582
            entry = state._get_entry(0, path_utf8='a-file')
566
 
            # The current sha1 sum should be empty
567
 
            self.assertEqual('', entry[1][0][1])
 
583
            # The current size should be 0 (default)
 
584
            self.assertEqual(0, entry[1][0][2])
568
585
            # We should have a real entry.
569
586
            self.assertNotEqual((None, None), entry)
570
587
            # Make sure everything is old enough
571
588
            state._sha_cutoff_time()
572
589
            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)
 
590
            # Change the file length
 
591
            self.build_tree_contents([('a-file', 'shorter')])
 
592
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
593
                os.lstat('a-file'))
 
594
            # new file, no cached sha:
 
595
            self.assertEqual(None, sha1sum)
577
596
 
578
597
            # The dirblock has been updated
579
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
598
            self.assertEqual(7, entry[1][0][2])
580
599
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
581
600
                             state._dirblock_state)
582
601
 
592
611
        state.lock_read()
593
612
        try:
594
613
            entry = state._get_entry(0, path_utf8='a-file')
595
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
614
            self.assertEqual(7, entry[1][0][2])
596
615
        finally:
597
616
            state.unlock()
598
617
 
611
630
        state.lock_read()
612
631
        try:
613
632
            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)
 
633
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
634
                os.lstat('a-file'))
 
635
            # No sha - too new
 
636
            self.assertEqual(None, sha1sum)
618
637
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
619
638
                             state._dirblock_state)
620
639
 
637
656
                state2.unlock()
638
657
        finally:
639
658
            state.unlock()
640
 
        
 
659
 
641
660
        # The file on disk should not be modified.
642
661
        state = dirstate.DirState.on_file('dirstate')
643
662
        state.lock_read()
743
762
        # https://bugs.launchpad.net/bzr/+bug/146176
744
763
        # set_state_from_inventory should preserve the stat and hash value for
745
764
        # workingtree files that are not changed by the inventory.
746
 
       
 
765
 
747
766
        tree = self.make_branch_and_tree('.')
748
767
        # depends on the default format using dirstate...
749
768
        tree.lock_write()
750
769
        try:
751
 
            # make a dirstate with some valid hashcache data 
 
770
            # make a dirstate with some valid hashcache data
752
771
            # file on disk, but that's not needed for this test
753
772
            foo_contents = 'contents of foo'
754
773
            self.build_tree_contents([('foo', foo_contents)])
774
793
                (('', 'foo', 'foo-id',),
775
794
                 [('f', foo_sha, foo_size, False, foo_packed)]),
776
795
                tree._dirstate._get_entry(0, 'foo-id'))
777
 
           
 
796
 
778
797
            # extract the inventory, and add something to it
779
798
            inv = tree._get_inventory()
780
799
            # should see the file we poked in...
802
821
        finally:
803
822
            tree.unlock()
804
823
 
805
 
 
806
824
    def test_set_state_from_inventory_mixed_paths(self):
807
825
        tree1 = self.make_branch_and_tree('tree1')
808
826
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
849
867
        state = dirstate.DirState.initialize('dirstate')
850
868
        try:
851
869
            # 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')])]
 
870
            root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
 
871
            self.assertEqual([root_entry], list(state._iter_entries()))
 
872
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
873
            self.assertEqual(root_entry,
 
874
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
875
            self.assertEqual((None, None),
 
876
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
877
            state.set_path_id('', 'second-root-id')
 
878
            new_root_entry = (('', '', 'second-root-id'),
 
879
                              [('d', '', 0, False, 'x'*32)])
 
880
            expected_rows = [new_root_entry]
860
881
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
882
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
883
            self.assertEqual(new_root_entry, 
 
884
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
885
            self.assertEqual((None, None),
 
886
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
861
887
            # should work across save too
862
888
            state.save()
863
889
        finally:
881
907
        state._validate()
882
908
        try:
883
909
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
884
 
            state.set_path_id('', 'foobarbaz')
 
910
            root_entry = (('', '', 'TREE_ROOT'),
 
911
                          [('d', '', 0, False, 'x'*32),
 
912
                           ('d', '', 0, False, 'parent-revid')])
 
913
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
914
            self.assertEqual(root_entry,
 
915
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
916
            self.assertEqual((None, None),
 
917
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
918
            state.set_path_id('', 'Asecond-root-id')
885
919
            state._validate()
886
920
            # 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
 
                ]
 
921
            old_root_entry = (('', '', 'TREE_ROOT'),
 
922
                              [('a', '', 0, False, ''),
 
923
                               ('d', '', 0, False, 'parent-revid')])
 
924
            new_root_entry = (('', '', 'Asecond-root-id'),
 
925
                              [('d', '', 0, False, ''),
 
926
                               ('a', '', 0, False, '')])
 
927
            expected_rows = [new_root_entry, old_root_entry]
897
928
            state._validate()
898
929
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
930
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
931
            self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
 
932
            self.assertEqual((None, None),
 
933
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
934
            self.assertEqual(old_root_entry,
 
935
                             state._get_entry(1, fileid_utf8='TREE_ROOT'))
 
936
            self.assertEqual(new_root_entry,
 
937
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
938
            self.assertEqual((None, None),
 
939
                             state._get_entry(1, fileid_utf8='Asecond-root-id'))
899
940
            # should work across save too
900
941
            state.save()
901
942
        finally:
917
958
        finally:
918
959
            state.unlock()
919
960
 
920
 
 
921
961
    def test_set_parent_trees_no_content(self):
922
962
        # set_parent_trees is a slow but important api to support.
923
963
        tree1 = self.make_branch_and_memory_tree('tree1')
928
968
        finally:
929
969
            tree1.unlock()
930
970
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
931
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
971
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
932
972
        tree2.lock_write()
933
973
        try:
934
974
            revid2 = tree2.commit('foo')
966
1006
            state.set_parent_trees(
967
1007
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
968
1008
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
969
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
1009
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1010
                                   _mod_revision.NULL_REVISION))),
970
1011
                ['ghost-rev'])
971
1012
            self.assertEqual([revid1, revid2, 'ghost-rev'],
972
1013
                             state.get_parent_ids())
976
1017
                [(('', '', root_id), [
977
1018
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
978
1019
                  ('d', '', 0, False, revid1),
979
 
                  ('d', '', 0, False, revid2)
 
1020
                  ('d', '', 0, False, revid1)
980
1021
                  ])],
981
1022
                list(state._iter_entries()))
982
1023
        finally:
997
1038
        finally:
998
1039
            tree1.unlock()
999
1040
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
1000
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1041
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1001
1042
        tree2.lock_write()
1002
1043
        try:
1003
1044
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1010
1051
            (('', '', root_id), [
1011
1052
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
1012
1053
             ('d', '', 0, False, revid1.encode('utf8')),
1013
 
             ('d', '', 0, False, revid2.encode('utf8'))
 
1054
             ('d', '', 0, False, revid1.encode('utf8'))
1014
1055
             ]),
1015
1056
            (('', 'a file', 'file-id'), [
1016
1057
             ('a', '', 0, False, ''),
1062
1103
            state.unlock()
1063
1104
        state = dirstate.DirState.on_file('dirstate')
1064
1105
        state.lock_read()
1065
 
        try:
1066
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1067
 
        finally:
1068
 
            state.unlock()
 
1106
        self.addCleanup(state.unlock)
 
1107
        self.assertEqual(expected_entries, list(state._iter_entries()))
1069
1108
 
1070
1109
    def test_add_path_to_unversioned_directory(self):
1071
1110
        """Adding a path to an unversioned directory should error.
1076
1115
        """
1077
1116
        self.build_tree(['unversioned/', 'unversioned/a file'])
1078
1117
        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()
 
1118
        self.addCleanup(state.unlock)
 
1119
        self.assertRaises(errors.NotVersionedError, state.add,
 
1120
                          'unversioned/a file', 'a-file-id', 'file', None, None)
1084
1121
 
1085
1122
    def test_add_directory_to_root_no_parents_all_data(self):
1086
1123
        # The most trivial addition of a dir is when there are no parents and
1106
1143
            state.unlock()
1107
1144
        state = dirstate.DirState.on_file('dirstate')
1108
1145
        state.lock_read()
 
1146
        self.addCleanup(state.unlock)
1109
1147
        state._validate()
1110
 
        try:
1111
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1112
 
        finally:
1113
 
            state.unlock()
 
1148
        self.assertEqual(expected_entries, list(state._iter_entries()))
1114
1149
 
1115
 
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1150
    def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1116
1151
        # The most trivial addition of a symlink when there are no parents and
1117
1152
        # its in the root and all data about the file is supplied
1118
1153
        # 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')
 
1154
        self.requireFeature(tests.SymlinkFeature)
 
1155
        os.symlink(target, link_name)
 
1156
        stat = os.lstat(link_name)
1122
1157
        expected_entries = [
1123
1158
            (('', '', 'TREE_ROOT'), [
1124
1159
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1125
1160
             ]),
1126
 
            (('', 'a link', 'a link id'), [
1127
 
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
1161
            (('', link_name.encode('UTF-8'), 'a link id'), [
 
1162
             ('l', target.encode('UTF-8'), stat[6],
 
1163
              False, dirstate.pack_stat(stat)), # current tree
1128
1164
             ]),
1129
1165
            ]
1130
1166
        state = dirstate.DirState.initialize('dirstate')
1131
1167
        try:
1132
 
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
1168
            state.add(link_name, 'a link id', 'symlink', stat,
 
1169
                      target.encode('UTF-8'))
1133
1170
            # having added it, it should be in the output of iter_entries.
1134
1171
            self.assertEqual(expected_entries, list(state._iter_entries()))
1135
1172
            # saving and reloading should not affect this.
1138
1175
            state.unlock()
1139
1176
        state = dirstate.DirState.on_file('dirstate')
1140
1177
        state.lock_read()
1141
 
        try:
1142
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1143
 
        finally:
1144
 
            state.unlock()
 
1178
        self.addCleanup(state.unlock)
 
1179
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1180
 
 
1181
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1182
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
 
1183
 
 
1184
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
 
1185
        self.requireFeature(tests.UnicodeFilenameFeature)
 
1186
        self._test_add_symlink_to_root_no_parents_all_data(
 
1187
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1145
1188
 
1146
1189
    def test_add_directory_and_child_no_parents_all_data(self):
1147
1190
        # after adding a directory, we should be able to add children to it.
1172
1215
            state.unlock()
1173
1216
        state = dirstate.DirState.on_file('dirstate')
1174
1217
        state.lock_read()
1175
 
        try:
1176
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1177
 
        finally:
1178
 
            state.unlock()
 
1218
        self.addCleanup(state.unlock)
 
1219
        self.assertEqual(expected_entries, list(state._iter_entries()))
1179
1220
 
1180
1221
    def test_add_tree_reference(self):
1181
1222
        # make a dirstate and add a tree reference
1195
1236
            state.unlock()
1196
1237
        # now check we can read it back
1197
1238
        state.lock_read()
 
1239
        self.addCleanup(state.unlock)
1198
1240
        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()
 
1241
        entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1242
        self.assertEqual(entry, entry2)
 
1243
        self.assertEqual(entry, expected_entry)
 
1244
        # and lookup by id should work too
 
1245
        entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1246
        self.assertEqual(entry, expected_entry)
1208
1247
 
1209
1248
    def test_add_forbidden_names(self):
1210
1249
        state = dirstate.DirState.initialize('dirstate')
1214
1253
        self.assertRaises(errors.BzrError,
1215
1254
            state.add, '..', 'ass-id', 'directory', None, None)
1216
1255
 
 
1256
    def test_set_state_with_rename_b_a_bug_395556(self):
 
1257
        # bug 395556 uncovered a bug where the dirstate ends up with a false
 
1258
        # relocation record - in a tree with no parents there should be no
 
1259
        # absent or relocated records. This then leads to further corruption
 
1260
        # when a commit occurs, as the incorrect relocation gathers an
 
1261
        # incorrect absent in tree 1, and future changes go to pot.
 
1262
        tree1 = self.make_branch_and_tree('tree1')
 
1263
        self.build_tree(['tree1/b'])
 
1264
        tree1.lock_write()
 
1265
        try:
 
1266
            tree1.add(['b'], ['b-id'])
 
1267
            root_id = tree1.get_root_id()
 
1268
            inv = tree1.inventory
 
1269
            state = dirstate.DirState.initialize('dirstate')
 
1270
            try:
 
1271
                # Set the initial state with 'b'
 
1272
                state.set_state_from_inventory(inv)
 
1273
                inv.rename('b-id', root_id, 'a')
 
1274
                # Set the new state with 'a', which currently corrupts.
 
1275
                state.set_state_from_inventory(inv)
 
1276
                expected_result1 = [('', '', root_id, 'd'),
 
1277
                                    ('', 'a', 'b-id', 'f'),
 
1278
                                   ]
 
1279
                values = []
 
1280
                for entry in state._iter_entries():
 
1281
                    values.append(entry[0] + entry[1][0][:1])
 
1282
                self.assertEqual(expected_result1, values)
 
1283
            finally:
 
1284
                state.unlock()
 
1285
        finally:
 
1286
            tree1.unlock()
 
1287
 
1217
1288
 
1218
1289
class TestGetLines(TestCaseWithDirState):
1219
1290
 
1452
1523
        There is one parent tree, which has the same shape with the following variations:
1453
1524
        b/g in the parent is gone.
1454
1525
        b/h in the parent has a different id
1455
 
        b/i is new in the parent 
 
1526
        b/i is new in the parent
1456
1527
        c is renamed to b/j in the parent
1457
1528
 
1458
1529
        :return: The dirstate, still write-locked.
1548
1619
            list(state._iter_child_entries(1, '')))
1549
1620
 
1550
1621
 
1551
 
class TestDirstateSortOrder(TestCaseWithTransport):
 
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1552
1623
    """Test that DirState adds entries in the right order."""
1553
1624
 
1554
1625
    def test_add_sorting(self):
1603
1674
 
1604
1675
        # *really* cheesy way to just get an empty tree
1605
1676
        repo = self.make_repository('repo')
1606
 
        empty_tree = repo.revision_tree(None)
 
1677
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1607
1678
        state.set_parent_trees([('null:', empty_tree)], [])
1608
1679
 
1609
1680
        dirblock_names = [d[0] for d in state._dirblocks]
1613
1684
class InstrumentedDirState(dirstate.DirState):
1614
1685
    """An DirState with instrumented sha1 functionality."""
1615
1686
 
1616
 
    def __init__(self, path):
1617
 
        super(InstrumentedDirState, self).__init__(path)
 
1687
    def __init__(self, path, sha1_provider):
 
1688
        super(InstrumentedDirState, self).__init__(path, sha1_provider)
1618
1689
        self._time_offset = 0
1619
1690
        self._log = []
1620
1691
        # member is dynamically set in DirState.__init__ to turn on trace
 
1692
        self._sha1_provider = sha1_provider
1621
1693
        self._sha1_file = self._sha1_file_and_log
1622
1694
 
1623
1695
    def _sha_cutoff_time(self):
1626
1698
 
1627
1699
    def _sha1_file_and_log(self, abspath):
1628
1700
        self._log.append(('sha1', abspath))
1629
 
        return osutils.sha_file_by_name(abspath)
 
1701
        return self._sha1_provider.sha1(abspath)
1630
1702
 
1631
1703
    def _read_link(self, abspath, old_link):
1632
1704
        self._log.append(('read_link', abspath, old_link))
1663
1735
        self.st_ino = ino
1664
1736
        self.st_mode = mode
1665
1737
 
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):
 
1738
    @staticmethod
 
1739
    def from_stat(st):
 
1740
        return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
 
1741
            st.st_ino, st.st_mode)
 
1742
 
 
1743
 
 
1744
class TestPackStat(tests.TestCaseWithTransport):
1988
1745
 
1989
1746
    def assertPackStat(self, expected, stat_value):
1990
1747
        """Check the packed and serialized form of a stat value."""
2517
2274
        self.assertEqual(exp_dirblocks, state._dirblocks)
2518
2275
 
2519
2276
 
2520
 
class Test_InvEntryToDetails(TestCaseWithDirState):
 
2277
class Test_InvEntryToDetails(tests.TestCase):
2521
2278
 
2522
2279
    def assertDetails(self, expected, inv_entry):
2523
2280
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
2530
2287
        self.assertIsInstance(tree_data, str)
2531
2288
 
2532
2289
    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',
 
2290
        inv_entry = inventory.InventoryLink('link-file-id',
 
2291
                                            u'nam\N{Euro Sign}e',
2536
2292
                                            'link-parent-id')
2537
2293
        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)
 
2294
        target = u'link-targ\N{Euro Sign}t'
 
2295
        inv_entry.symlink_target = target
 
2296
        self.assertDetails(('l', target.encode('UTF-8'), 0, False,
 
2297
                            'link-revision-id'), inv_entry)
 
2298
 
 
2299
 
 
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
 
2301
 
 
2302
    def test_sha1provider_is_an_interface(self):
 
2303
        p = dirstate.SHA1Provider()
 
2304
        self.assertRaises(NotImplementedError, p.sha1, "foo")
 
2305
        self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
 
2306
 
 
2307
    def test_defaultsha1provider_sha1(self):
 
2308
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2309
        self.build_tree_contents([('foo', text)])
 
2310
        expected_sha = osutils.sha_string(text)
 
2311
        p = dirstate.DefaultSHA1Provider()
 
2312
        self.assertEqual(expected_sha, p.sha1('foo'))
 
2313
 
 
2314
    def test_defaultsha1provider_stat_and_sha1(self):
 
2315
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2316
        self.build_tree_contents([('foo', text)])
 
2317
        expected_sha = osutils.sha_string(text)
 
2318
        p = dirstate.DefaultSHA1Provider()
 
2319
        statvalue, sha1 = p.stat_and_sha1('foo')
 
2320
        self.assertTrue(len(statvalue) >= 10)
 
2321
        self.assertEqual(len(text), statvalue.st_size)
 
2322
        self.assertEqual(expected_sha, sha1)