diff --git a/mercurial/testing/revlog.py b/mercurial/testing/revlog.py index d55f2f7d278562028d250ccdd9ba7ce0bb46a0c4_bWVyY3VyaWFsL3Rlc3RpbmcvcmV2bG9nLnB5..674e1e7be85ae60a575a94fe643c92684c7abb37_bWVyY3VyaWFsL3Rlc3RpbmcvcmV2bG9nLnB5 100644 --- a/mercurial/testing/revlog.py +++ b/mercurial/testing/revlog.py @@ -44,5 +44,15 @@ rust_revlog = None +try: + from ..pyo3_rustext import ( # pytype: disable=import-error + revlog as pyo3_revlog, + ) + + pyo3_revlog.__name__ # force actual import +except ImportError: + pyo3_revlog = None + + @unittest.skipIf( cparsers is None, @@ -47,6 +57,9 @@ @unittest.skipIf( cparsers is None, - 'The C version of the "parsers" module is not available. It is needed for this test.', + ( + 'The C version of the "parsers" module is not available. ' + 'It is needed for this test.' + ), ) class RevlogBasedTestBase(unittest.TestCase): def parseindex(self, data=None): @@ -65,9 +78,17 @@ revlog_delta_config = revlog.DeltaConfig() revlog_feature_config = revlog.FeatureConfig() + @classmethod + def irl_class(cls): + return rust_revlog.InnerRevlog + + @classmethod + def nodetree(cls, idx): + return rust_revlog.NodeTree(idx) + def make_inner_revlog( self, data=None, vfs_is_readonly=True, kind=KIND_CHANGELOG ): if data is None: data = data_non_inlined @@ -68,10 +89,10 @@ def make_inner_revlog( self, data=None, vfs_is_readonly=True, kind=KIND_CHANGELOG ): if data is None: data = data_non_inlined - return rust_revlog.InnerRevlog( + return self.irl_class()( vfs_base=b"Just a path", fncache=None, # might be enough for now vfs_is_readonly=vfs_is_readonly, @@ -91,3 +112,17 @@ def parserustindex(self, data=None): return revlog.RustIndexProxy(self.make_inner_revlog(data=data)) + + +@unittest.skipIf( + pyo3_revlog is None, + 'The Rust PyO3 revlog module is not available. It is needed for this test.', +) +class PyO3RevlogBasedTestBase(RustRevlogBasedTestBase): + @classmethod + def irl_class(cls): + return pyo3_revlog.InnerRevlog + + @classmethod + def nodetree(cls, idx): + return pyo3_revlog.NodeTree(idx) diff --git a/tests/test-rust-revlog.py b/tests/test-rust-revlog.py new file mode 100644 index 0000000000000000000000000000000000000000..674e1e7be85ae60a575a94fe643c92684c7abb37_dGVzdHMvdGVzdC1ydXN0LXJldmxvZy5weQ== --- /dev/null +++ b/tests/test-rust-revlog.py @@ -0,0 +1,97 @@ +import struct + +from mercurial.node import ( + bin as node_bin, + hex, +) + +try: + from mercurial import rustext + + rustext.__name__ # trigger immediate actual import +except ImportError: + rustext = None +else: + # this would fail already without appropriate ancestor.__package__ + from mercurial.rustext.ancestor import LazyAncestors + +from mercurial.testing import revlog as revlogtesting + +header = struct.unpack(">I", revlogtesting.data_non_inlined[:4])[0] + + +class RustInnerRevlogTestMixin: + """Common tests for both Rust Python bindings.""" + + node_hex0 = b'd1f4bbb0befc13bd8cd39d0fcdd93b8c078c4a2f' + node0 = node_bin(node_hex0) + + +# Conditional skipping done by the base class +class RustInnerRevlogTest( + revlogtesting.RustRevlogBasedTestBase, RustInnerRevlogTestMixin +): + """For reference""" + + def test_heads(self): + idx = self.parserustindex() + self.assertEqual(idx.headrevs(), [3]) + + def test_len(self): + idx = self.parserustindex() + self.assertEqual(len(idx), 4) + + def test_ancestors(self): + rustidx = self.parserustindex() + lazy = LazyAncestors(rustidx, [3], 0, True) + # we have two more references to the index: + # - in its inner iterator for __contains__ and __bool__ + # - in the LazyAncestors instance itself (to spawn new iterators) + self.assertTrue(2 in lazy) + self.assertTrue(bool(lazy)) + self.assertEqual(list(lazy), [3, 2, 1, 0]) + # a second time to validate that we spawn new iterators + self.assertEqual(list(lazy), [3, 2, 1, 0]) + + # let's check bool for an empty one + self.assertFalse(LazyAncestors(rustidx, [0], 0, False)) + + def test_standalone_nodetree(self): + idx = self.parserustindex() + nt = self.nodetree(idx) + for i in range(4): + nt.insert(i) + + bin_nodes = [entry[7] for entry in idx] + hex_nodes = [hex(n) for n in bin_nodes] + + for i, node in enumerate(hex_nodes): + self.assertEqual(nt.prefix_rev_lookup(node), i) + self.assertEqual(nt.prefix_rev_lookup(node[:5]), i) + + # all 4 revisions in idx (standard data set) have different + # first nybbles in their Node IDs, + # hence `nt.shortest()` should return 1 for them, except when + # the leading nybble is 0 (ambiguity with NULL_NODE) + for i, (bin_node, hex_node) in enumerate(zip(bin_nodes, hex_nodes)): + shortest = nt.shortest(bin_node) + expected = 2 if hex_node[0] == ord('0') else 1 + self.assertEqual(shortest, expected) + self.assertEqual(nt.prefix_rev_lookup(hex_node[:shortest]), i) + + # test invalidation (generation poisoning) detection + del idx[3] + self.assertTrue(nt.is_invalidated()) + + +# Conditional skipping done by the base class +class PyO3InnerRevlogTest( + revlogtesting.PyO3RevlogBasedTestBase, RustInnerRevlogTestMixin +): + """Testing new PyO3 bindings, by comparison with rust-cpython bindings.""" + + +if __name__ == '__main__': + import silenttestrunner + + silenttestrunner.main(__name__)