Show More
@@ -861,6 +861,110 b' class ifiledatatests(basetestcase):' | |||||
861 | self.assertFalse(f.cmp(node1, fulltext1)) |
|
861 | self.assertFalse(f.cmp(node1, fulltext1)) | |
862 | self.assertTrue(f.cmp(node1, stored0)) |
|
862 | self.assertTrue(f.cmp(node1, stored0)) | |
863 |
|
863 | |||
|
864 | def testbadnoderead(self): | |||
|
865 | f = self._makefilefn() | |||
|
866 | ||||
|
867 | fulltext0 = b'foo\n' * 30 | |||
|
868 | fulltext1 = fulltext0 + b'bar\n' | |||
|
869 | ||||
|
870 | with self._maketransactionfn() as tr: | |||
|
871 | node0 = f.add(fulltext0, None, tr, 0, nullid, nullid) | |||
|
872 | node1 = b'\xaa' * 20 | |||
|
873 | ||||
|
874 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, | |||
|
875 | rawtext=fulltext1) | |||
|
876 | ||||
|
877 | self.assertEqual(len(f), 2) | |||
|
878 | self.assertEqual(f.parents(node1), (node0, nullid)) | |||
|
879 | ||||
|
880 | # revision() raises since it performs hash verification. | |||
|
881 | with self.assertRaises(error.StorageError): | |||
|
882 | f.revision(node1) | |||
|
883 | ||||
|
884 | # revision(raw=True) still verifies hashes. | |||
|
885 | # TODO this is buggy because of cache interaction. | |||
|
886 | self.assertEqual(f.revision(node1, raw=True), fulltext1) | |||
|
887 | ||||
|
888 | # read() behaves like revision(). | |||
|
889 | # TODO this is buggy because of cache interaction. | |||
|
890 | f.read(node1) | |||
|
891 | ||||
|
892 | # We can't test renamed() here because some backends may not require | |||
|
893 | # reading/validating the fulltext to return rename metadata. | |||
|
894 | ||||
|
895 | def testbadnoderevisionraw(self): | |||
|
896 | # Like above except we test revision(raw=True) first to isolate | |||
|
897 | # revision caching behavior. | |||
|
898 | f = self._makefilefn() | |||
|
899 | ||||
|
900 | fulltext0 = b'foo\n' * 30 | |||
|
901 | fulltext1 = fulltext0 + b'bar\n' | |||
|
902 | ||||
|
903 | with self._maketransactionfn() as tr: | |||
|
904 | node0 = f.add(fulltext0, None, tr, 0, nullid, nullid) | |||
|
905 | node1 = b'\xaa' * 20 | |||
|
906 | ||||
|
907 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, | |||
|
908 | rawtext=fulltext1) | |||
|
909 | ||||
|
910 | with self.assertRaises(error.StorageError): | |||
|
911 | f.revision(node1, raw=True) | |||
|
912 | ||||
|
913 | with self.assertRaises(error.StorageError): | |||
|
914 | f.revision(node1, raw=True) | |||
|
915 | ||||
|
916 | def testbadnoderevisionraw(self): | |||
|
917 | # Like above except we test read() first to isolate revision caching | |||
|
918 | # behavior. | |||
|
919 | f = self._makefilefn() | |||
|
920 | ||||
|
921 | fulltext0 = b'foo\n' * 30 | |||
|
922 | fulltext1 = fulltext0 + b'bar\n' | |||
|
923 | ||||
|
924 | with self._maketransactionfn() as tr: | |||
|
925 | node0 = f.add(fulltext0, None, tr, 0, nullid, nullid) | |||
|
926 | node1 = b'\xaa' * 20 | |||
|
927 | ||||
|
928 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, | |||
|
929 | rawtext=fulltext1) | |||
|
930 | ||||
|
931 | with self.assertRaises(error.StorageError): | |||
|
932 | f.read(node1) | |||
|
933 | ||||
|
934 | # TODO this should raise error.StorageError. | |||
|
935 | f.read(node1) | |||
|
936 | ||||
|
937 | def testbadnodedelta(self): | |||
|
938 | f = self._makefilefn() | |||
|
939 | ||||
|
940 | fulltext0 = b'foo\n' * 31 | |||
|
941 | fulltext1 = fulltext0 + b'bar\n' | |||
|
942 | fulltext2 = fulltext1 + b'baz\n' | |||
|
943 | ||||
|
944 | with self._maketransactionfn() as tr: | |||
|
945 | node0 = f.add(fulltext0, None, tr, 0, nullid, nullid) | |||
|
946 | node1 = b'\xaa' * 20 | |||
|
947 | ||||
|
948 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, | |||
|
949 | rawtext=fulltext1) | |||
|
950 | ||||
|
951 | with self.assertRaises(error.StorageError): | |||
|
952 | f.read(node1) | |||
|
953 | ||||
|
954 | diff = mdiff.textdiff(fulltext1, fulltext2) | |||
|
955 | node2 = storageutil.hashrevisionsha1(fulltext2, node1, nullid) | |||
|
956 | deltas = [(node2, node1, nullid, b'\x01' * 20, node1, diff, 0)] | |||
|
957 | ||||
|
958 | # This /might/ fail on some backends. | |||
|
959 | with self._maketransactionfn() as tr: | |||
|
960 | f.addgroup(deltas, lambda x: 0, tr) | |||
|
961 | ||||
|
962 | self.assertEqual(len(f), 3) | |||
|
963 | ||||
|
964 | # Assuming a delta is stored, we shouldn't need to validate node1 in | |||
|
965 | # order to retrieve node2. | |||
|
966 | self.assertEqual(f.read(node2), fulltext2) | |||
|
967 | ||||
864 | def testcensored(self): |
|
968 | def testcensored(self): | |
865 | f = self._makefilefn() |
|
969 | f = self._makefilefn() | |
866 |
|
970 | |||
@@ -868,20 +972,46 b' class ifiledatatests(basetestcase):' | |||||
868 | b'censored': b'tombstone', |
|
972 | b'censored': b'tombstone', | |
869 | }, b'') |
|
973 | }, b'') | |
870 |
|
974 | |||
871 | # TODO tests are incomplete because we need the node to be |
|
|||
872 | # different due to presence of censor metadata. But we can't |
|
|||
873 | # do this with addrevision(). |
|
|||
874 | with self._maketransactionfn() as tr: |
|
975 | with self._maketransactionfn() as tr: | |
875 | node0 = f.add(b'foo', None, tr, 0, nullid, nullid) |
|
976 | node0 = f.add(b'foo', None, tr, 0, nullid, nullid) | |
876 | f.addrevision(stored1, tr, 1, node0, nullid, |
|
977 | ||
877 | flags=repository.REVISION_FLAG_CENSORED) |
|
978 | # The node value doesn't matter since we can't verify it. | |
|
979 | node1 = b'\xbb' * 20 | |||
|
980 | ||||
|
981 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, stored1, | |||
|
982 | censored=True) | |||
878 |
|
983 | |||
879 | self.assertTrue(f.iscensored(1)) |
|
984 | self.assertTrue(f.iscensored(1)) | |
880 |
|
985 | |||
881 | self.assertEqual(f.revision(1), stored1) |
|
986 | with self.assertRaises(error.CensoredNodeError): | |
|
987 | f.revision(1) | |||
|
988 | ||||
882 | self.assertEqual(f.revision(1, raw=True), stored1) |
|
989 | self.assertEqual(f.revision(1, raw=True), stored1) | |
883 |
|
990 | |||
884 | self.assertEqual(f.read(1), b'') |
|
991 | with self.assertRaises(error.CensoredNodeError): | |
|
992 | f.read(1) | |||
|
993 | ||||
|
994 | def testcensoredrawrevision(self): | |||
|
995 | # Like above, except we do the revision(raw=True) request first to | |||
|
996 | # isolate revision caching behavior. | |||
|
997 | ||||
|
998 | f = self._makefilefn() | |||
|
999 | ||||
|
1000 | stored1 = storageutil.packmeta({ | |||
|
1001 | b'censored': b'tombstone', | |||
|
1002 | }, b'') | |||
|
1003 | ||||
|
1004 | with self._maketransactionfn() as tr: | |||
|
1005 | node0 = f.add(b'foo', None, tr, 0, nullid, nullid) | |||
|
1006 | ||||
|
1007 | # The node value doesn't matter since we can't verify it. | |||
|
1008 | node1 = b'\xbb' * 20 | |||
|
1009 | ||||
|
1010 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, stored1, | |||
|
1011 | censored=True) | |||
|
1012 | ||||
|
1013 | with self.assertRaises(error.CensoredNodeError): | |||
|
1014 | f.revision(1, raw=True) | |||
885 |
|
1015 | |||
886 | class ifilemutationtests(basetestcase): |
|
1016 | class ifilemutationtests(basetestcase): | |
887 | """Generic tests for the ifilemutation interface. |
|
1017 | """Generic tests for the ifilemutation interface. | |
@@ -1004,6 +1134,55 b' class ifilemutationtests(basetestcase):' | |||||
1004 | self.assertEqual(f.node(1), nodes[1]) |
|
1134 | self.assertEqual(f.node(1), nodes[1]) | |
1005 | self.assertEqual(f.node(2), nodes[2]) |
|
1135 | self.assertEqual(f.node(2), nodes[2]) | |
1006 |
|
1136 | |||
|
1137 | def testdeltaagainstcensored(self): | |||
|
1138 | # Attempt to apply a delta made against a censored revision. | |||
|
1139 | f = self._makefilefn() | |||
|
1140 | ||||
|
1141 | stored1 = storageutil.packmeta({ | |||
|
1142 | b'censored': b'tombstone', | |||
|
1143 | }, b'') | |||
|
1144 | ||||
|
1145 | with self._maketransactionfn() as tr: | |||
|
1146 | node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid) | |||
|
1147 | ||||
|
1148 | # The node value doesn't matter since we can't verify it. | |||
|
1149 | node1 = b'\xbb' * 20 | |||
|
1150 | ||||
|
1151 | self._addrawrevisionfn(f, tr, node1, node0, nullid, 1, stored1, | |||
|
1152 | censored=True) | |||
|
1153 | ||||
|
1154 | delta = mdiff.textdiff(b'bar\n' * 30, (b'bar\n' * 30) + b'baz\n') | |||
|
1155 | deltas = [(b'\xcc' * 20, node1, nullid, b'\x01' * 20, node1, delta, 0)] | |||
|
1156 | ||||
|
1157 | with self._maketransactionfn() as tr: | |||
|
1158 | with self.assertRaises(error.CensoredBaseError): | |||
|
1159 | f.addgroup(deltas, lambda x: 0, tr) | |||
|
1160 | ||||
|
1161 | def testcensorrevisionbasic(self): | |||
|
1162 | f = self._makefilefn() | |||
|
1163 | ||||
|
1164 | with self._maketransactionfn() as tr: | |||
|
1165 | node0 = f.add(b'foo\n' * 30, None, tr, 0, nullid, nullid) | |||
|
1166 | node1 = f.add(b'foo\n' * 31, None, tr, 1, node0, nullid) | |||
|
1167 | node2 = f.add(b'foo\n' * 32, None, tr, 2, node1, nullid) | |||
|
1168 | ||||
|
1169 | with self._maketransactionfn() as tr: | |||
|
1170 | f.censorrevision(tr, node1) | |||
|
1171 | ||||
|
1172 | self.assertEqual(len(f), 3) | |||
|
1173 | self.assertEqual(list(f.revs()), [0, 1, 2]) | |||
|
1174 | ||||
|
1175 | self.assertEqual(f.read(node0), b'foo\n' * 30) | |||
|
1176 | ||||
|
1177 | # TODO revlog can't resolve revision after censor. Probably due to a | |||
|
1178 | # cache on the revlog instance. | |||
|
1179 | with self.assertRaises(error.StorageError): | |||
|
1180 | self.assertEqual(f.read(node2), b'foo\n' * 32) | |||
|
1181 | ||||
|
1182 | # TODO should raise CensoredNodeError, but fallout from above prevents. | |||
|
1183 | with self.assertRaises(error.StorageError): | |||
|
1184 | f.read(node1) | |||
|
1185 | ||||
1007 | def testgetstrippointnoparents(self): |
|
1186 | def testgetstrippointnoparents(self): | |
1008 | # N revisions where none have parents. |
|
1187 | # N revisions where none have parents. | |
1009 | f = self._makefilefn() |
|
1188 | f = self._makefilefn() | |
@@ -1121,7 +1300,7 b' class ifilemutationtests(basetestcase):' | |||||
1121 | with self.assertRaises(error.LookupError): |
|
1300 | with self.assertRaises(error.LookupError): | |
1122 | f.rev(node1) |
|
1301 | f.rev(node1) | |
1123 |
|
1302 | |||
1124 | def makeifileindextests(makefilefn, maketransactionfn): |
|
1303 | def makeifileindextests(makefilefn, maketransactionfn, addrawrevisionfn): | |
1125 | """Create a unittest.TestCase class suitable for testing file storage. |
|
1304 | """Create a unittest.TestCase class suitable for testing file storage. | |
1126 |
|
1305 | |||
1127 | ``makefilefn`` is a callable which receives the test case as an |
|
1306 | ``makefilefn`` is a callable which receives the test case as an | |
@@ -1130,6 +1309,10 b' def makeifileindextests(makefilefn, make' | |||||
1130 | ``maketransactionfn`` is a callable which receives the test case as an |
|
1309 | ``maketransactionfn`` is a callable which receives the test case as an | |
1131 | argument and returns a transaction object. |
|
1310 | argument and returns a transaction object. | |
1132 |
|
1311 | |||
|
1312 | ``addrawrevisionfn`` is a callable which receives arguments describing a | |||
|
1313 | low-level revision to add. This callable allows the insertion of | |||
|
1314 | potentially bad data into the store in order to facilitate testing. | |||
|
1315 | ||||
1133 | Returns a type that is a ``unittest.TestCase`` that can be used for |
|
1316 | Returns a type that is a ``unittest.TestCase`` that can be used for | |
1134 | testing the object implementing the file storage interface. Simply |
|
1317 | testing the object implementing the file storage interface. Simply | |
1135 | assign the returned value to a module-level attribute and a test loader |
|
1318 | assign the returned value to a module-level attribute and a test loader | |
@@ -1138,19 +1321,22 b' def makeifileindextests(makefilefn, make' | |||||
1138 | d = { |
|
1321 | d = { | |
1139 | r'_makefilefn': makefilefn, |
|
1322 | r'_makefilefn': makefilefn, | |
1140 | r'_maketransactionfn': maketransactionfn, |
|
1323 | r'_maketransactionfn': maketransactionfn, | |
|
1324 | r'_addrawrevisionfn': addrawrevisionfn, | |||
1141 | } |
|
1325 | } | |
1142 | return type(r'ifileindextests', (ifileindextests,), d) |
|
1326 | return type(r'ifileindextests', (ifileindextests,), d) | |
1143 |
|
1327 | |||
1144 | def makeifiledatatests(makefilefn, maketransactionfn): |
|
1328 | def makeifiledatatests(makefilefn, maketransactionfn, addrawrevisionfn): | |
1145 | d = { |
|
1329 | d = { | |
1146 | r'_makefilefn': makefilefn, |
|
1330 | r'_makefilefn': makefilefn, | |
1147 | r'_maketransactionfn': maketransactionfn, |
|
1331 | r'_maketransactionfn': maketransactionfn, | |
|
1332 | r'_addrawrevisionfn': addrawrevisionfn, | |||
1148 | } |
|
1333 | } | |
1149 | return type(r'ifiledatatests', (ifiledatatests,), d) |
|
1334 | return type(r'ifiledatatests', (ifiledatatests,), d) | |
1150 |
|
1335 | |||
1151 | def makeifilemutationtests(makefilefn, maketransactionfn): |
|
1336 | def makeifilemutationtests(makefilefn, maketransactionfn, addrawrevisionfn): | |
1152 | d = { |
|
1337 | d = { | |
1153 | r'_makefilefn': makefilefn, |
|
1338 | r'_makefilefn': makefilefn, | |
1154 | r'_maketransactionfn': maketransactionfn, |
|
1339 | r'_maketransactionfn': maketransactionfn, | |
|
1340 | r'_addrawrevisionfn': addrawrevisionfn, | |||
1155 | } |
|
1341 | } | |
1156 | return type(r'ifilemutationtests', (ifilemutationtests,), d) |
|
1342 | return type(r'ifilemutationtests', (ifilemutationtests,), d) |
@@ -5,7 +5,9 b' from __future__ import absolute_import' | |||||
5 | import silenttestrunner |
|
5 | import silenttestrunner | |
6 |
|
6 | |||
7 | from mercurial import ( |
|
7 | from mercurial import ( | |
|
8 | error, | |||
8 | filelog, |
|
9 | filelog, | |
|
10 | revlog, | |||
9 | transaction, |
|
11 | transaction, | |
10 | ui as uimod, |
|
12 | ui as uimod, | |
11 | vfs as vfsmod, |
|
13 | vfs as vfsmod, | |
@@ -33,14 +35,39 b' def maketransaction(self):' | |||||
33 | return transaction.transaction(STATE['ui'].warn, STATE['vfs'], vfsmap, |
|
35 | return transaction.transaction(STATE['ui'].warn, STATE['vfs'], vfsmap, | |
34 | b'journal', b'undo') |
|
36 | b'journal', b'undo') | |
35 |
|
37 | |||
|
38 | def addrawrevision(self, fl, tr, node, p1, p2, linkrev, rawtext=None, | |||
|
39 | delta=None, censored=False, ellipsis=False, extstored=False): | |||
|
40 | flags = 0 | |||
|
41 | ||||
|
42 | if censored: | |||
|
43 | flags |= revlog.REVIDX_ISCENSORED | |||
|
44 | if ellipsis: | |||
|
45 | flags |= revlog.REVIDX_ELLIPSIS | |||
|
46 | if extstored: | |||
|
47 | flags |= revlog.REVIDX_EXTSTORED | |||
|
48 | ||||
|
49 | if rawtext is not None: | |||
|
50 | fl._revlog.addrawrevision(rawtext, tr, linkrev, p1, p2, node, flags) | |||
|
51 | elif delta is not None: | |||
|
52 | raise error.Abort('support for storing raw deltas not yet supported') | |||
|
53 | else: | |||
|
54 | raise error.Abort('must supply rawtext or delta arguments') | |||
|
55 | ||||
|
56 | # We may insert bad data. Clear caches to prevent e.g. cache hits to | |||
|
57 | # bypass hash verification. | |||
|
58 | fl._revlog.clearcaches() | |||
|
59 | ||||
36 | # Assigning module-level attributes that inherit from unittest.TestCase |
|
60 | # Assigning module-level attributes that inherit from unittest.TestCase | |
37 | # is all that is needed to register tests. |
|
61 | # is all that is needed to register tests. | |
38 | filelogindextests = storagetesting.makeifileindextests(makefilefn, |
|
62 | filelogindextests = storagetesting.makeifileindextests(makefilefn, | |
39 |
maketransaction |
|
63 | maketransaction, | |
|
64 | addrawrevision) | |||
40 | filelogdatatests = storagetesting.makeifiledatatests(makefilefn, |
|
65 | filelogdatatests = storagetesting.makeifiledatatests(makefilefn, | |
41 |
maketransaction |
|
66 | maketransaction, | |
|
67 | addrawrevision) | |||
42 | filelogmutationtests = storagetesting.makeifilemutationtests(makefilefn, |
|
68 | filelogmutationtests = storagetesting.makeifilemutationtests(makefilefn, | |
43 |
maketransaction |
|
69 | maketransaction, | |
|
70 | addrawrevision) | |||
44 |
|
71 | |||
45 | if __name__ == '__main__': |
|
72 | if __name__ == '__main__': | |
46 | silenttestrunner.main(__name__) |
|
73 | silenttestrunner.main(__name__) |
General Comments 0
You need to be logged in to leave comments.
Login now