Show More
@@ -1,64 +1,93 | |||||
1 | from __future__ import annotations |
|
1 | from __future__ import annotations | |
2 |
|
2 | |||
3 | import unittest |
|
3 | import unittest | |
4 |
|
4 | |||
5 | # picked from test-parse-index2, copied rather than imported |
|
5 | # picked from test-parse-index2, copied rather than imported | |
6 | # so that it stays stable even if test-parse-index2 changes or disappears. |
|
6 | # so that it stays stable even if test-parse-index2 changes or disappears. | |
7 | data_non_inlined = ( |
|
7 | data_non_inlined = ( | |
8 | b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' |
|
8 | b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' | |
9 | b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' |
|
9 | b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' | |
10 | b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' |
|
10 | b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' | |
11 | b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
11 | b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
12 | b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' |
|
12 | b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' | |
13 | b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' |
|
13 | b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' | |
14 | b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' |
|
14 | b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' | |
15 | b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
15 | b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
16 | b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' |
|
16 | b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' | |
17 | b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' |
|
17 | b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' | |
18 | b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' |
|
18 | b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' | |
19 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' |
|
19 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' | |
20 | b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' |
|
20 | b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' | |
21 | b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' |
|
21 | b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' | |
22 | b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' |
|
22 | b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' | |
23 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
|
23 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |
24 | ) |
|
24 | ) | |
25 |
|
25 | |||
26 |
from ..revlogutils.constants import |
|
26 | from ..revlogutils.constants import ( | |
|
27 | KIND_CHANGELOG, | |||
|
28 | ) | |||
|
29 | from .. import revlog | |||
27 |
|
30 | |||
28 |
|
31 | |||
29 | try: |
|
32 | try: | |
30 | from ..cext import parsers as cparsers # pytype: disable=import-error |
|
33 | from ..cext import parsers as cparsers # pytype: disable=import-error | |
31 | except ImportError: |
|
34 | except ImportError: | |
32 | cparsers = None |
|
35 | cparsers = None | |
33 |
|
36 | |||
34 | try: |
|
37 | try: | |
35 |
from ..rustext |
|
38 | from ..rustext import ( # pytype: disable=import-error | |
36 |
|
|
39 | revlog as rust_revlog, | |
37 | ) |
|
40 | ) | |
|
41 | ||||
|
42 | rust_revlog.__name__ # force actual import | |||
38 | except ImportError: |
|
43 | except ImportError: | |
39 |
|
|
44 | rust_revlog = None | |
40 |
|
45 | |||
41 |
|
46 | |||
42 | @unittest.skipIf( |
|
47 | @unittest.skipIf( | |
43 | cparsers is None, |
|
48 | cparsers is None, | |
44 | 'The C version of the "parsers" module is not available. It is needed for this test.', |
|
49 | 'The C version of the "parsers" module is not available. It is needed for this test.', | |
45 | ) |
|
50 | ) | |
46 | class RevlogBasedTestBase(unittest.TestCase): |
|
51 | class RevlogBasedTestBase(unittest.TestCase): | |
47 | def parseindex(self, data=None): |
|
52 | def parseindex(self, data=None): | |
48 | if data is None: |
|
53 | if data is None: | |
49 | data = data_non_inlined |
|
54 | data = data_non_inlined | |
50 | return cparsers.parse_index2(data, False)[0] |
|
55 | return cparsers.parse_index2(data, False)[0] | |
51 |
|
56 | |||
52 |
|
57 | |||
53 | @unittest.skipIf( |
|
58 | @unittest.skipIf( | |
54 |
|
|
59 | rust_revlog is None, | |
55 |
'The Rust |
|
60 | 'The Rust revlog module is not available. It is needed for this test.', | |
56 | ) |
|
61 | ) | |
57 | class RustRevlogBasedTestBase(unittest.TestCase): |
|
62 | class RustRevlogBasedTestBase(unittest.TestCase): | |
58 | def parserustindex(self, data=None): |
|
63 | # defaults | |
|
64 | revlog_data_config = revlog.DataConfig() | |||
|
65 | revlog_delta_config = revlog.DeltaConfig() | |||
|
66 | revlog_feature_config = revlog.FeatureConfig() | |||
|
67 | ||||
|
68 | def make_inner_revlog( | |||
|
69 | self, data=None, vfs_is_readonly=True, kind=KIND_CHANGELOG | |||
|
70 | ): | |||
59 | if data is None: |
|
71 | if data is None: | |
60 | data = data_non_inlined |
|
72 | data = data_non_inlined | |
61 | # not inheriting RevlogBasedTestCase to avoid having a |
|
73 | ||
62 | # `parseindex` method that would be shadowed by future subclasses |
|
74 | return rust_revlog.InnerRevlog( | |
63 | # this duplication will soon be removed |
|
75 | vfs_base=b"Just a path", | |
64 | return RustIndex(data, REVLOGV1) |
|
76 | fncache=None, # might be enough for now | |
|
77 | vfs_is_readonly=vfs_is_readonly, | |||
|
78 | index_data=data, | |||
|
79 | index_file=b'test.i', | |||
|
80 | data_file=b'test.d', | |||
|
81 | sidedata_file=None, | |||
|
82 | inline=False, | |||
|
83 | data_config=self.revlog_data_config, | |||
|
84 | delta_config=self.revlog_delta_config, | |||
|
85 | feature_config=self.revlog_feature_config, | |||
|
86 | chunk_cache=None, | |||
|
87 | default_compression_header=None, | |||
|
88 | revlog_type=kind, | |||
|
89 | use_persistent_nodemap=False, # until we cook one. | |||
|
90 | ) | |||
|
91 | ||||
|
92 | def parserustindex(self, data=None): | |||
|
93 | return revlog.RustIndexProxy(self.make_inner_revlog(data=data)) |
@@ -1,167 +1,155 | |||||
1 | import sys |
|
1 | import sys | |
2 | import unittest |
|
|||
3 |
|
2 | |||
4 | from mercurial.node import wdirrev |
|
3 | from mercurial.node import wdirrev | |
5 |
|
4 | |||
6 | from mercurial.testing import revlog as revlogtesting |
|
5 | from mercurial.testing import revlog as revlogtesting | |
7 |
|
6 | |||
8 | try: |
|
7 | try: | |
9 | from mercurial import rustext |
|
8 | from mercurial import rustext | |
10 |
|
9 | |||
11 | rustext.__name__ # trigger immediate actual import |
|
10 | rustext.__name__ # trigger immediate actual import | |
12 | except ImportError: |
|
11 | except ImportError: | |
13 | rustext = None |
|
12 | rustext = None | |
14 | else: |
|
13 | else: | |
15 | # this would fail already without appropriate ancestor.__package__ |
|
14 | # this would fail already without appropriate ancestor.__package__ | |
16 | from mercurial.rustext.ancestor import ( |
|
15 | from mercurial.rustext.ancestor import ( | |
17 | AncestorsIterator, |
|
16 | AncestorsIterator, | |
18 | LazyAncestors, |
|
17 | LazyAncestors, | |
19 | MissingAncestors, |
|
18 | MissingAncestors, | |
20 | ) |
|
19 | ) | |
21 | from mercurial.rustext import dagop |
|
20 | from mercurial.rustext import dagop | |
22 |
|
21 | |||
23 | try: |
|
22 | try: | |
24 | from mercurial.cext import parsers as cparsers |
|
23 | from mercurial.cext import parsers as cparsers | |
25 | except ImportError: |
|
24 | except ImportError: | |
26 | cparsers = None |
|
25 | cparsers = None | |
27 |
|
26 | |||
28 |
|
27 | |||
29 | @unittest.skipIf( |
|
|||
30 | rustext is None, |
|
|||
31 | 'The Rust version of the "ancestor" module is not available. It is needed' |
|
|||
32 | ' for this test.', |
|
|||
33 | ) |
|
|||
34 | @unittest.skipIf( |
|
|||
35 | rustext is None, |
|
|||
36 | 'The Rust or C version of the "parsers" module, which the "ancestor" module' |
|
|||
37 | ' relies on, is not available.', |
|
|||
38 | ) |
|
|||
39 | class rustancestorstest(revlogtesting.RustRevlogBasedTestBase): |
|
28 | class rustancestorstest(revlogtesting.RustRevlogBasedTestBase): | |
40 | """Test the correctness of binding to Rust code. |
|
29 | """Test the correctness of binding to Rust code. | |
41 |
|
30 | |||
42 | This test is merely for the binding to Rust itself: extraction of |
|
31 | This test is merely for the binding to Rust itself: extraction of | |
43 | Python variable, giving back the results etc. |
|
32 | Python variable, giving back the results etc. | |
44 |
|
33 | |||
45 | It is not meant to test the algorithmic correctness of the operations |
|
34 | It is not meant to test the algorithmic correctness of the operations | |
46 | on ancestors it provides. Hence the very simple embedded index data is |
|
35 | on ancestors it provides. Hence the very simple embedded index data is | |
47 | good enough. |
|
36 | good enough. | |
48 |
|
37 | |||
49 | Algorithmic correctness is asserted by the Rust unit tests. |
|
38 | Algorithmic correctness is asserted by the Rust unit tests. | |
50 | """ |
|
39 | """ | |
51 |
|
40 | |||
52 | def testiteratorrevlist(self): |
|
41 | def testiteratorrevlist(self): | |
53 | idx = self.parserustindex() |
|
42 | idx = self.parserustindex() | |
54 | # checking test assumption about the index binary data: |
|
43 | # checking test assumption about the index binary data: | |
55 | self.assertEqual( |
|
44 | self.assertEqual( | |
56 | {i: (r[5], r[6]) for i, r in enumerate(idx)}, |
|
45 | {i: (r[5], r[6]) for i, r in enumerate(idx)}, | |
57 | {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}, |
|
46 | {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}, | |
58 | ) |
|
47 | ) | |
59 | ait = AncestorsIterator(idx, [3], 0, True) |
|
48 | ait = AncestorsIterator(idx, [3], 0, True) | |
60 | self.assertEqual([r for r in ait], [3, 2, 1, 0]) |
|
49 | self.assertEqual([r for r in ait], [3, 2, 1, 0]) | |
61 |
|
50 | |||
62 | ait = AncestorsIterator(idx, [3], 0, False) |
|
51 | ait = AncestorsIterator(idx, [3], 0, False) | |
63 | self.assertEqual([r for r in ait], [2, 1, 0]) |
|
52 | self.assertEqual([r for r in ait], [2, 1, 0]) | |
64 |
|
53 | |||
65 | def testlazyancestors(self): |
|
54 | def testlazyancestors(self): | |
66 | idx = self.parserustindex() |
|
55 | idx = self.parserustindex() | |
67 | start_count = sys.getrefcount(idx) # should be 2 (see Python doc) |
|
56 | start_count = sys.getrefcount(idx.inner) # should be 2 (see Python doc) | |
68 | self.assertEqual( |
|
57 | self.assertEqual( | |
69 | {i: (r[5], r[6]) for i, r in enumerate(idx)}, |
|
58 | {i: (r[5], r[6]) for i, r in enumerate(idx)}, | |
70 | {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}, |
|
59 | {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}, | |
71 | ) |
|
60 | ) | |
72 | lazy = LazyAncestors(idx, [3], 0, True) |
|
61 | lazy = LazyAncestors(idx, [3], 0, True) | |
73 | # we have two more references to the index: |
|
62 | # the LazyAncestors instance holds just one reference to the | |
74 | # - in its inner iterator for __contains__ and __bool__ |
|
63 | # inner revlog. | |
75 | # - in the LazyAncestors instance itself (to spawn new iterators) |
|
64 | self.assertEqual(sys.getrefcount(idx.inner), start_count + 1) | |
76 | self.assertEqual(sys.getrefcount(idx), start_count + 2) |
|
|||
77 |
|
65 | |||
78 | self.assertTrue(2 in lazy) |
|
66 | self.assertTrue(2 in lazy) | |
79 | self.assertTrue(bool(lazy)) |
|
67 | self.assertTrue(bool(lazy)) | |
80 | self.assertEqual(list(lazy), [3, 2, 1, 0]) |
|
68 | self.assertEqual(list(lazy), [3, 2, 1, 0]) | |
81 | # a second time to validate that we spawn new iterators |
|
69 | # a second time to validate that we spawn new iterators | |
82 | self.assertEqual(list(lazy), [3, 2, 1, 0]) |
|
70 | self.assertEqual(list(lazy), [3, 2, 1, 0]) | |
83 |
|
71 | |||
84 | # now let's watch the refcounts closer |
|
72 | # now let's watch the refcounts closer | |
85 | ait = iter(lazy) |
|
73 | ait = iter(lazy) | |
86 |
self.assertEqual(sys.getrefcount(idx), start_count + |
|
74 | self.assertEqual(sys.getrefcount(idx.inner), start_count + 2) | |
87 | del ait |
|
75 | del ait | |
88 |
self.assertEqual(sys.getrefcount(idx), start_count + |
|
76 | self.assertEqual(sys.getrefcount(idx.inner), start_count + 1) | |
89 | del lazy |
|
77 | del lazy | |
90 | self.assertEqual(sys.getrefcount(idx), start_count) |
|
78 | self.assertEqual(sys.getrefcount(idx.inner), start_count) | |
91 |
|
79 | |||
92 | # let's check bool for an empty one |
|
80 | # let's check bool for an empty one | |
93 | self.assertFalse(LazyAncestors(idx, [0], 0, False)) |
|
81 | self.assertFalse(LazyAncestors(idx, [0], 0, False)) | |
94 |
|
82 | |||
95 | def testmissingancestors(self): |
|
83 | def testmissingancestors(self): | |
96 | idx = self.parserustindex() |
|
84 | idx = self.parserustindex() | |
97 | missanc = MissingAncestors(idx, [1]) |
|
85 | missanc = MissingAncestors(idx, [1]) | |
98 | self.assertTrue(missanc.hasbases()) |
|
86 | self.assertTrue(missanc.hasbases()) | |
99 | self.assertEqual(missanc.missingancestors([3]), [2, 3]) |
|
87 | self.assertEqual(missanc.missingancestors([3]), [2, 3]) | |
100 | missanc.addbases({2}) |
|
88 | missanc.addbases({2}) | |
101 | self.assertEqual(missanc.bases(), {1, 2}) |
|
89 | self.assertEqual(missanc.bases(), {1, 2}) | |
102 | self.assertEqual(missanc.missingancestors([3]), [3]) |
|
90 | self.assertEqual(missanc.missingancestors([3]), [3]) | |
103 | self.assertEqual(missanc.basesheads(), {2}) |
|
91 | self.assertEqual(missanc.basesheads(), {2}) | |
104 |
|
92 | |||
105 | def testmissingancestorsremove(self): |
|
93 | def testmissingancestorsremove(self): | |
106 | idx = self.parserustindex() |
|
94 | idx = self.parserustindex() | |
107 | missanc = MissingAncestors(idx, [1]) |
|
95 | missanc = MissingAncestors(idx, [1]) | |
108 | revs = {0, 1, 2, 3} |
|
96 | revs = {0, 1, 2, 3} | |
109 | missanc.removeancestorsfrom(revs) |
|
97 | missanc.removeancestorsfrom(revs) | |
110 | self.assertEqual(revs, {2, 3}) |
|
98 | self.assertEqual(revs, {2, 3}) | |
111 |
|
99 | |||
112 | def testrefcount(self): |
|
100 | def testrefcount(self): | |
113 | idx = self.parserustindex() |
|
101 | idx = self.parserustindex() | |
114 | start_count = sys.getrefcount(idx) |
|
102 | start_count = sys.getrefcount(idx.inner) | |
115 |
|
103 | |||
116 | # refcount increases upon iterator init... |
|
104 | # refcount increases upon iterator init... | |
117 | ait = AncestorsIterator(idx, [3], 0, True) |
|
105 | ait = AncestorsIterator(idx, [3], 0, True) | |
118 | self.assertEqual(sys.getrefcount(idx), start_count + 1) |
|
106 | self.assertEqual(sys.getrefcount(idx.inner), start_count + 1) | |
119 | self.assertEqual(next(ait), 3) |
|
107 | self.assertEqual(next(ait), 3) | |
120 |
|
108 | |||
121 | # and decreases once the iterator is removed |
|
109 | # and decreases once the iterator is removed | |
122 | del ait |
|
110 | del ait | |
123 | self.assertEqual(sys.getrefcount(idx), start_count) |
|
111 | self.assertEqual(sys.getrefcount(idx.inner), start_count) | |
124 |
|
112 | |||
125 | # and removing ref to the index after iterator init is no issue |
|
113 | # and removing ref to the index after iterator init is no issue | |
126 | ait = AncestorsIterator(idx, [3], 0, True) |
|
114 | ait = AncestorsIterator(idx, [3], 0, True) | |
127 | del idx |
|
115 | del idx | |
128 | self.assertEqual(list(ait), [3, 2, 1, 0]) |
|
116 | self.assertEqual(list(ait), [3, 2, 1, 0]) | |
129 |
|
117 | |||
130 | # the index is not tracked by the GC, hence there is nothing more |
|
118 | # the index is not tracked by the GC, hence there is nothing more | |
131 | # we can assert to check that it is properly deleted once its refcount |
|
119 | # we can assert to check that it is properly deleted once its refcount | |
132 | # drops to 0 |
|
120 | # drops to 0 | |
133 |
|
121 | |||
134 | def testgrapherror(self): |
|
122 | def testgrapherror(self): | |
135 | data = ( |
|
123 | data = ( | |
136 | revlogtesting.data_non_inlined[: 64 + 27] |
|
124 | revlogtesting.data_non_inlined[: 64 + 27] | |
137 | + b'\xf2' |
|
125 | + b'\xf2' | |
138 | + revlogtesting.data_non_inlined[64 + 28 :] |
|
126 | + revlogtesting.data_non_inlined[64 + 28 :] | |
139 | ) |
|
127 | ) | |
140 | idx = self.parserustindex(data=data) |
|
128 | idx = self.parserustindex(data=data) | |
141 | with self.assertRaises(rustext.GraphError) as arc: |
|
129 | with self.assertRaises(rustext.GraphError) as arc: | |
142 | AncestorsIterator(idx, [1], -1, False) |
|
130 | AncestorsIterator(idx, [1], -1, False) | |
143 | exc = arc.exception |
|
131 | exc = arc.exception | |
144 | self.assertIsInstance(exc, ValueError) |
|
132 | self.assertIsInstance(exc, ValueError) | |
145 | # rust-cpython issues appropriate str instances for Python 2 and 3 |
|
133 | # rust-cpython issues appropriate str instances for Python 2 and 3 | |
146 | self.assertEqual(exc.args, ('ParentOutOfRange', 1)) |
|
134 | self.assertEqual(exc.args, ('ParentOutOfRange', 1)) | |
147 |
|
135 | |||
148 | def testwdirunsupported(self): |
|
136 | def testwdirunsupported(self): | |
149 | # trying to access ancestors of the working directory raises |
|
137 | # trying to access ancestors of the working directory raises | |
150 | idx = self.parserustindex() |
|
138 | idx = self.parserustindex() | |
151 | with self.assertRaises(rustext.GraphError) as arc: |
|
139 | with self.assertRaises(rustext.GraphError) as arc: | |
152 | list(AncestorsIterator(idx, [wdirrev], -1, False)) |
|
140 | list(AncestorsIterator(idx, [wdirrev], -1, False)) | |
153 |
|
141 | |||
154 | exc = arc.exception |
|
142 | exc = arc.exception | |
155 | self.assertIsInstance(exc, ValueError) |
|
143 | self.assertIsInstance(exc, ValueError) | |
156 | # rust-cpython issues appropriate str instances for Python 2 and 3 |
|
144 | # rust-cpython issues appropriate str instances for Python 2 and 3 | |
157 | self.assertEqual(exc.args, ('InvalidRevision', wdirrev)) |
|
145 | self.assertEqual(exc.args, ('InvalidRevision', wdirrev)) | |
158 |
|
146 | |||
159 | def testheadrevs(self): |
|
147 | def testheadrevs(self): | |
160 | idx = self.parserustindex() |
|
148 | idx = self.parserustindex() | |
161 | self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3}) |
|
149 | self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3}) | |
162 |
|
150 | |||
163 |
|
151 | |||
164 | if __name__ == '__main__': |
|
152 | if __name__ == '__main__': | |
165 | import silenttestrunner |
|
153 | import silenttestrunner | |
166 |
|
154 | |||
167 | silenttestrunner.main(__name__) |
|
155 | silenttestrunner.main(__name__) |
General Comments 0
You need to be logged in to leave comments.
Login now