diff tests/test-rust-ancestor.py @ 52532:3ffcdbf0b432

rust: test AncestorsIterator without duplication Up to now, it was acceptable to duplicate the test in `test-rust-ancestor.py` that was relevant to `dagop`. Now that we have more PyO3 implementations, it is better, and actually more convincing, to express the tests in a common base class (has technically to be a mixin to avoid running the tests from the base class itself).
author Georges Racinet <georges.racinet@cloudcrane.io>
date Sat, 07 Dec 2024 18:42:06 +0100
parents 20fe0bf9a9a5
children 6b694bdf752a
line wrap: on
line diff
--- a/tests/test-rust-ancestor.py	Sat Dec 07 18:38:37 2024 +0100
+++ b/tests/test-rust-ancestor.py	Sat Dec 07 18:42:06 2024 +0100
@@ -11,14 +11,6 @@
     pyo3_rustext.__name__
 except ImportError:
     rustext = pyo3_rustext = None
-else:
-    # this would fail already without appropriate ancestor.__package__
-    from mercurial.rustext.ancestor import (
-        AncestorsIterator,
-        LazyAncestors,
-        MissingAncestors,
-    )
-    from mercurial.rustext import dagop
 
 try:
     from mercurial.cext import parsers as cparsers
@@ -26,7 +18,7 @@
     cparsers = None
 
 
-class rustancestorstest(revlogtesting.RustRevlogBasedTestBase):
+class RustAncestorsTestMixin:
     """Test the correctness of binding to Rust code.
 
     This test is merely for the binding to Rust itself: extraction of
@@ -37,9 +29,33 @@
     good enough.
 
     Algorithmic correctness is asserted by the Rust unit tests.
+
+    At this point, we have two sets of bindings, in `hg-cpython` and
+    `hg-pyo3`. This class used to be for the first and now contains
+    the tests that are identical in both bindings. As of this writing,
+    there are more implementations in `hg-cpython` than `hg-pyo3`, hence
+    some more tests in the subclass for `hg-cpython`. When the work on PyO3
+    is complete, the subclasses for `hg-cpython` should have no specific
+    test left. Later on, when we remove the dead code in `hg-cpython`, the tests
+    should migrate from the mixin to the class for `hg-pyo3`, until we can
+    simply remove the mixin.
     """
 
+    @classmethod
+    def ancestors_mod(cls):
+        return cls.rustext_pkg.ancestor
+
+    @classmethod
+    def dagop_mod(cls):
+        return cls.rustext_pkg.dagop
+
+    @classmethod
+    def graph_error(cls):
+        return cls.rustext_pkg.GraphError
+
     def testiteratorrevlist(self):
+        AncestorsIterator = self.ancestors_mod().AncestorsIterator
+
         idx = self.parserustindex()
         # checking test assumption about the index binary data:
         self.assertEqual(
@@ -52,7 +68,76 @@
         ait = AncestorsIterator(idx, [3], 0, False)
         self.assertEqual([r for r in ait], [2, 1, 0])
 
+    def testrefcount(self):
+        AncestorsIterator = self.ancestors_mod().AncestorsIterator
+
+        idx = self.parserustindex()
+        start_count = sys.getrefcount(idx.inner)
+
+        # refcount increases upon iterator init...
+        ait = AncestorsIterator(idx, [3], 0, True)
+        self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
+        self.assertEqual(next(ait), 3)
+
+        # and decreases once the iterator is removed
+        del ait
+        self.assertEqual(sys.getrefcount(idx.inner), start_count)
+
+        # and removing ref to the index after iterator init is no issue
+        ait = AncestorsIterator(idx, [3], 0, True)
+        del idx
+        self.assertEqual(list(ait), [3, 2, 1, 0])
+
+        # the index is not tracked by the GC, hence there is nothing more
+        # we can assert to check that it is properly deleted once its refcount
+        # drops to 0
+
+    def testgrapherror(self):
+        AncestorsIterator = self.ancestors_mod().AncestorsIterator
+        GraphError = self.graph_error()
+
+        data = (
+            revlogtesting.data_non_inlined[: 64 + 27]
+            + b'\xf2'
+            + revlogtesting.data_non_inlined[64 + 28 :]
+        )
+        idx = self.parserustindex(data=data)
+        with self.assertRaises(GraphError) as arc:
+            AncestorsIterator(idx, [1], -1, False)
+        exc = arc.exception
+        self.assertIsInstance(exc, ValueError)
+        # rust-cpython issues appropriate str instances for Python 2 and 3
+        self.assertEqual(exc.args, ('ParentOutOfRange', 1))
+
+    def testwdirunsupported(self):
+        AncestorsIterator = self.ancestors_mod().AncestorsIterator
+        GraphError = self.graph_error()
+
+        # trying to access ancestors of the working directory raises
+        idx = self.parserustindex()
+        with self.assertRaises(GraphError) as arc:
+            list(AncestorsIterator(idx, [wdirrev], -1, False))
+
+        exc = arc.exception
+        self.assertIsInstance(exc, ValueError)
+        # rust-cpython issues appropriate str instances for Python 2 and 3
+        self.assertEqual(exc.args, ('InvalidRevision', wdirrev))
+
+    def testheadrevs(self):
+        dagop = self.dagop_mod()
+
+        idx = self.parserustindex()
+        self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3})
+
+
+class RustCPythonAncestorsTest(
+    revlogtesting.RustRevlogBasedTestBase, RustAncestorsTestMixin
+):
+    rustext_pkg = rustext
+
     def testlazyancestors(self):
+        LazyAncestors = self.ancestors_mod().LazyAncestors
+
         idx = self.parserustindex()
         start_count = sys.getrefcount(idx.inner)  # should be 2 (see Python doc)
         self.assertEqual(
@@ -82,6 +167,8 @@
         self.assertFalse(LazyAncestors(idx, [0], 0, False))
 
     def testmissingancestors(self):
+        MissingAncestors = self.ancestors_mod().MissingAncestors
+
         idx = self.parserustindex()
         missanc = MissingAncestors(idx, [1])
         self.assertTrue(missanc.hasbases())
@@ -92,71 +179,26 @@
         self.assertEqual(missanc.basesheads(), {2})
 
     def testmissingancestorsremove(self):
+        MissingAncestors = self.ancestors_mod().MissingAncestors
+
         idx = self.parserustindex()
         missanc = MissingAncestors(idx, [1])
         revs = {0, 1, 2, 3}
         missanc.removeancestorsfrom(revs)
         self.assertEqual(revs, {2, 3})
 
-    def testrefcount(self):
-        idx = self.parserustindex()
-        start_count = sys.getrefcount(idx.inner)
-
-        # refcount increases upon iterator init...
-        ait = AncestorsIterator(idx, [3], 0, True)
-        self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
-        self.assertEqual(next(ait), 3)
-
-        # and decreases once the iterator is removed
-        del ait
-        self.assertEqual(sys.getrefcount(idx.inner), start_count)
-
-        # and removing ref to the index after iterator init is no issue
-        ait = AncestorsIterator(idx, [3], 0, True)
-        del idx
-        self.assertEqual(list(ait), [3, 2, 1, 0])
-
-        # the index is not tracked by the GC, hence there is nothing more
-        # we can assert to check that it is properly deleted once its refcount
-        # drops to 0
 
-    def testgrapherror(self):
-        data = (
-            revlogtesting.data_non_inlined[: 64 + 27]
-            + b'\xf2'
-            + revlogtesting.data_non_inlined[64 + 28 :]
-        )
-        idx = self.parserustindex(data=data)
-        with self.assertRaises(rustext.GraphError) as arc:
-            AncestorsIterator(idx, [1], -1, False)
-        exc = arc.exception
-        self.assertIsInstance(exc, ValueError)
-        # rust-cpython issues appropriate str instances for Python 2 and 3
-        self.assertEqual(exc.args, ('ParentOutOfRange', 1))
+class PyO3AncestorsTest(
+    revlogtesting.RustRevlogBasedTestBase, RustAncestorsTestMixin
+):
+    rustext_pkg = pyo3_rustext
 
-    def testwdirunsupported(self):
-        # trying to access ancestors of the working directory raises
-        idx = self.parserustindex()
-        with self.assertRaises(rustext.GraphError) as arc:
-            list(AncestorsIterator(idx, [wdirrev], -1, False))
+    def test_rank(self):
+        dagop = self.dagop_mod()
 
-        exc = arc.exception
-        self.assertIsInstance(exc, ValueError)
-        # rust-cpython issues appropriate str instances for Python 2 and 3
-        self.assertEqual(exc.args, ('InvalidRevision', wdirrev))
-
-    def testheadrevs(self):
-        idx = self.parserustindex()
-        self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3})
-
-    def testpyo3_headrevs(self):
-        idx = self.parserustindex()
-        self.assertEqual(pyo3_rustext.dagop.headrevs(idx, [1, 2, 3]), {3})
-
-    def testpyo3_rank(self):
         idx = self.parserustindex()
         try:
-            pyo3_rustext.dagop.rank(idx, 1, 2)
+            dagop.rank(idx, 1, 2)
         except pyo3_rustext.GraphError as exc:
             self.assertEqual(exc.args, ("InconsistentGraphData",))