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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
44
47
# set_path_id setting id when state is in memory modified
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)
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
50
class TestCaseWithDirState(TestCaseWithTransport):
58
51
"""Helper functions for creating DirState objects with various content."""
61
_dir_reader_class = None
62
_native_to_unicode = None # Not used yet
65
tests.TestCaseWithTransport.setUp(self)
67
self.overrideAttr(osutils,
68
'_selected_dir_reader', self._dir_reader_class())
70
53
def create_empty_dirstate(self):
71
54
"""Return a locked but empty dirstate"""
72
55
state = dirstate.DirState.initialize('dirstate')
413
396
(('', '', tree.get_root_id()), # common details
414
397
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
415
398
('d', '', 0, False, rev_id), # first parent details
416
('d', '', 0, False, rev_id), # second parent details
399
('d', '', 0, False, rev_id2), # second parent details
418
401
state = dirstate.DirState.from_tree(tree, 'dirstate')
419
402
self.check_state_with_reopen(expected_result, state)
494
477
(('', '', tree.get_root_id()), # common details
495
478
[('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
496
479
('d', '', 0, False, rev_id), # first parent details
497
('d', '', 0, False, rev_id), # second parent details
480
('d', '', 0, False, rev_id2), # second parent details
499
482
(('', 'a file', 'a-file-id'), # common
500
483
[('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
580
563
state.lock_read()
582
565
entry = state._get_entry(0, path_utf8='a-file')
583
# The current size should be 0 (default)
584
self.assertEqual(0, entry[1][0][2])
566
# The current sha1 sum should be empty
567
self.assertEqual('', entry[1][0][1])
585
568
# We should have a real entry.
586
569
self.assertNotEqual((None, None), entry)
587
570
# Make sure everything is old enough
588
571
state._sha_cutoff_time()
589
572
state._cutoff_time += 10
590
# Change the file length
591
self.build_tree_contents([('a-file', 'shorter')])
592
sha1sum = dirstate.update_entry(state, entry, 'a-file',
594
# new file, no cached sha:
595
self.assertEqual(None, sha1sum)
573
sha1sum = dirstate.update_entry(state, entry, 'a-file', os.lstat('a-file'))
574
# We should have gotten a real sha1
575
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
597
578
# The dirblock has been updated
598
self.assertEqual(7, entry[1][0][2])
579
self.assertEqual(sha1sum, entry[1][0][1])
599
580
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
600
581
state._dirblock_state)
630
611
state.lock_read()
632
613
entry = state._get_entry(0, path_utf8='a-file')
633
sha1sum = dirstate.update_entry(state, entry, 'a-file',
636
self.assertEqual(None, sha1sum)
614
sha1sum = dirstate.update_entry(state, entry, 'a-file', os.lstat('a-file'))
615
# We should have gotten a real sha1
616
self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
637
618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
638
619
state._dirblock_state)
762
743
# https://bugs.launchpad.net/bzr/+bug/146176
763
744
# set_state_from_inventory should preserve the stat and hash value for
764
745
# workingtree files that are not changed by the inventory.
766
747
tree = self.make_branch_and_tree('.')
767
748
# depends on the default format using dirstate...
768
749
tree.lock_write()
770
# make a dirstate with some valid hashcache data
751
# make a dirstate with some valid hashcache data
771
752
# file on disk, but that's not needed for this test
772
753
foo_contents = 'contents of foo'
773
754
self.build_tree_contents([('foo', foo_contents)])
867
849
state = dirstate.DirState.initialize('dirstate')
869
851
# check precondition to be sure the state does change appropriately.
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]
853
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
854
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
855
list(state._iter_entries()))
856
state.set_path_id('', 'foobarbaz')
858
(('', '', 'foobarbaz'), [('d', '', 0, False,
859
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
881
860
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'))
887
861
# should work across save too
907
881
state._validate()
909
883
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
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')
884
state.set_path_id('', 'foobarbaz')
919
885
state._validate()
920
886
# now see that it is what we expected
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]
888
(('', '', 'TREE_ROOT'),
889
[('a', '', 0, False, ''),
890
('d', '', 0, False, 'parent-revid'),
892
(('', '', 'foobarbaz'),
893
[('d', '', 0, False, ''),
894
('a', '', 0, False, ''),
928
897
state._validate()
929
898
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'))
940
899
# should work across save too
1017
977
[(('', '', root_id), [
1018
978
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1019
979
('d', '', 0, False, revid1),
1020
('d', '', 0, False, revid1)
980
('d', '', 0, False, revid2)
1022
982
list(state._iter_entries()))
1051
1011
(('', '', root_id), [
1052
1012
('d', '', 0, False, dirstate.DirState.NULLSTAT),
1053
1013
('d', '', 0, False, revid1.encode('utf8')),
1054
('d', '', 0, False, revid1.encode('utf8'))
1014
('d', '', 0, False, revid2.encode('utf8'))
1056
1016
(('', 'a file', 'file-id'), [
1057
1017
('a', '', 0, False, ''),
1116
1078
self.build_tree(['unversioned/', 'unversioned/a file'])
1117
1079
state = dirstate.DirState.initialize('dirstate')
1118
self.addCleanup(state.unlock)
1119
self.assertRaises(errors.NotVersionedError, state.add,
1120
'unversioned/a file', 'a-file-id', 'file', None, None)
1081
self.assertRaises(errors.NotVersionedError, state.add,
1082
'unversioned/a file', 'a-file-id', 'file', None, None)
1122
1086
def test_add_directory_to_root_no_parents_all_data(self):
1123
1087
# The most trivial addition of a dir is when there are no parents and
1144
1108
state = dirstate.DirState.on_file('dirstate')
1145
1109
state.lock_read()
1146
self.addCleanup(state.unlock)
1147
1110
state._validate()
1148
self.assertEqual(expected_entries, list(state._iter_entries()))
1112
self.assertEqual(expected_entries, list(state._iter_entries()))
1150
def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1116
def test_add_symlink_to_root_no_parents_all_data(self):
1151
1117
# The most trivial addition of a symlink when there are no parents and
1152
1118
# its in the root and all data about the file is supplied
1153
1119
# bzr doesn't support fake symlinks on windows, yet.
1154
self.requireFeature(tests.SymlinkFeature)
1155
os.symlink(target, link_name)
1156
stat = os.lstat(link_name)
1120
self.requireFeature(SymlinkFeature)
1121
os.symlink('target', 'a link')
1122
stat = os.lstat('a link')
1157
1123
expected_entries = [
1158
1124
(('', '', 'TREE_ROOT'), [
1159
1125
('d', '', 0, False, dirstate.DirState.NULLSTAT), # 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
1127
(('', 'a link', 'a link id'), [
1128
('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
1166
1131
state = dirstate.DirState.initialize('dirstate')
1168
state.add(link_name, 'a link id', 'symlink', stat,
1169
target.encode('UTF-8'))
1133
state.add('a link', 'a link id', 'symlink', stat, 'target')
1170
1134
# having added it, it should be in the output of iter_entries.
1171
1135
self.assertEqual(expected_entries, list(state._iter_entries()))
1172
1136
# saving and reloading should not affect this.
1176
1140
state = dirstate.DirState.on_file('dirstate')
1177
1141
state.lock_read()
1178
self.addCleanup(state.unlock)
1179
self.assertEqual(expected_entries, list(state._iter_entries()))
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')
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')
1143
self.assertEqual(expected_entries, list(state._iter_entries()))
1189
1147
def test_add_directory_and_child_no_parents_all_data(self):
1190
1148
# after adding a directory, we should be able to add children to it.
1237
1197
# now check we can read it back
1238
1198
state.lock_read()
1239
self.addCleanup(state.unlock)
1240
1199
state._validate()
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)
1201
entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1202
self.assertEqual(entry, entry2)
1203
self.assertEqual(entry, expected_entry)
1204
# and lookup by id should work too
1205
entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1206
self.assertEqual(entry, expected_entry)
1248
1210
def test_add_forbidden_names(self):
1249
1211
state = dirstate.DirState.initialize('dirstate')
1253
1215
self.assertRaises(errors.BzrError,
1254
1216
state.add, '..', 'ass-id', 'directory', None, None)
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'])
1266
tree1.add(['b'], ['b-id'])
1267
root_id = tree1.get_root_id()
1268
inv = tree1.inventory
1269
state = dirstate.DirState.initialize('dirstate')
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'),
1280
for entry in state._iter_entries():
1281
values.append(entry[0] + entry[1][0][:1])
1282
self.assertEqual(expected_result1, values)
1289
1219
class TestGetLines(TestCaseWithDirState):
1684
1614
class InstrumentedDirState(dirstate.DirState):
1685
1615
"""An DirState with instrumented sha1 functionality."""
1687
def __init__(self, path, sha1_provider):
1688
super(InstrumentedDirState, self).__init__(path, sha1_provider)
1617
def __init__(self, path):
1618
super(InstrumentedDirState, self).__init__(path)
1689
1619
self._time_offset = 0
1691
1621
# member is dynamically set in DirState.__init__ to turn on trace
1692
self._sha1_provider = sha1_provider
1693
1622
self._sha1_file = self._sha1_file_and_log
1695
1624
def _sha_cutoff_time(self):
2287
2211
self.assertIsInstance(tree_data, str)
2289
2213
def test_unicode_symlink(self):
2290
inv_entry = inventory.InventoryLink('link-file-id',
2291
u'nam\N{Euro Sign}e',
2214
# In general, the code base doesn't support a target that contains
2215
# non-ascii characters. So we just assert tha
2216
inv_entry = inventory.InventoryLink('link-file-id', 'name',
2292
2217
'link-parent-id')
2293
2218
inv_entry.revision = 'link-revision-id'
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)
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
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")
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'))
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)
2219
inv_entry.symlink_target = u'link-target'
2220
details = self.assertDetails(('l', 'link-target', 0, False,
2221
'link-revision-id'), inv_entry)