##// END OF EJS Templates
rust-pyo3: dagop submodule implementation...
Georges Racinet -
r53310:20fe0bf9 default
parent child Browse files
Show More
@@ -1,22 +1,73
1 1 // dagops.rs
2 2 //
3 3 // Copyright 2024 Georges Racinet <georges.racinet@cloudcrane.io>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dagops` module provided by the
9 9 //! `hg-core` package.
10 10 //!
11 11 //! From Python, this will be seen as `mercurial.pyo3-rustext.dagop`
12 12 use pyo3::prelude::*;
13 13
14 use std::collections::HashSet;
15
16 use hg::{dagops, Revision};
17
18 use crate::convert_cpython::{from_cpython_pyerr, proxy_index_extract};
19 use crate::exceptions::GraphError;
20 use crate::revision::{rev_pyiter_collect, PyRevision};
14 21 use crate::util::new_submodule;
15 22
23 /// Using the the `index_proxy`, return heads out of any Python iterable of
24 /// Revisions
25 ///
26 /// This is the Rust counterpart for `mercurial.dagop.headrevs`
27 #[pyfunction]
28 pub fn headrevs(
29 index_proxy: &Bound<'_, PyAny>,
30 revs: &Bound<'_, PyAny>,
31 ) -> PyResult<HashSet<PyRevision>> {
32 let (py, py_leaked) = proxy_index_extract(index_proxy)?;
33 // Safety: we don't leak the "faked" reference out of `UnsafePyLeaked`
34 let index = &*unsafe {
35 py_leaked
36 .try_borrow(py)
37 .map_err(|e| from_cpython_pyerr(py, e))?
38 };
39
40 let mut as_set: HashSet<Revision> = rev_pyiter_collect(revs, index)?;
41 dagops::retain_heads(index, &mut as_set).map_err(GraphError::from_hg)?;
42 Ok(as_set.into_iter().map(Into::into).collect())
43 }
44
45 /// Computes the rank, i.e. the number of ancestors including itself,
46 /// of a node represented by its parents.
47 ///
48 /// Currently, the pure Rust index supports only the REVLOGV1 format, hence
49 /// the only possible return value is that the rank is unknown.
50 ///
51 /// References:
52 /// - C implementation, function `index_fast_rank()`.
53 /// - `impl vcsgraph::graph::RankedGraph for Index` in `crate::cindex`.
54 #[pyfunction]
55 pub fn rank(
56 _index: &Bound<'_, PyAny>,
57 _p1r: PyRevision,
58 _p2r: PyRevision,
59 ) -> PyResult<()> {
60 Err(GraphError::from_vcsgraph(
61 vcsgraph::graph::GraphReadError::InconsistentGraphData,
62 ))
63 }
64
16 65 pub fn init_module<'py>(
17 66 py: Python<'py>,
18 67 package: &str,
19 68 ) -> PyResult<Bound<'py, PyModule>> {
20 69 let m = new_submodule(py, package, "dagop")?;
70 m.add_function(wrap_pyfunction!(headrevs, &m)?)?;
71 m.add_function(wrap_pyfunction!(rank, &m)?)?;
21 72 Ok(m)
22 73 }
@@ -1,155 +1,167
1 1 import sys
2 2
3 3 from mercurial.node import wdirrev
4 4
5 5 from mercurial.testing import revlog as revlogtesting
6 6
7 7 try:
8 from mercurial import rustext
8 from mercurial import pyo3_rustext, rustext
9 9
10 10 rustext.__name__ # trigger immediate actual import
11 pyo3_rustext.__name__
11 12 except ImportError:
12 rustext = None
13 rustext = pyo3_rustext = None
13 14 else:
14 15 # this would fail already without appropriate ancestor.__package__
15 16 from mercurial.rustext.ancestor import (
16 17 AncestorsIterator,
17 18 LazyAncestors,
18 19 MissingAncestors,
19 20 )
20 21 from mercurial.rustext import dagop
21 22
22 23 try:
23 24 from mercurial.cext import parsers as cparsers
24 25 except ImportError:
25 26 cparsers = None
26 27
27 28
28 29 class rustancestorstest(revlogtesting.RustRevlogBasedTestBase):
29 30 """Test the correctness of binding to Rust code.
30 31
31 32 This test is merely for the binding to Rust itself: extraction of
32 33 Python variable, giving back the results etc.
33 34
34 35 It is not meant to test the algorithmic correctness of the operations
35 36 on ancestors it provides. Hence the very simple embedded index data is
36 37 good enough.
37 38
38 39 Algorithmic correctness is asserted by the Rust unit tests.
39 40 """
40 41
41 42 def testiteratorrevlist(self):
42 43 idx = self.parserustindex()
43 44 # checking test assumption about the index binary data:
44 45 self.assertEqual(
45 46 {i: (r[5], r[6]) for i, r in enumerate(idx)},
46 47 {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)},
47 48 )
48 49 ait = AncestorsIterator(idx, [3], 0, True)
49 50 self.assertEqual([r for r in ait], [3, 2, 1, 0])
50 51
51 52 ait = AncestorsIterator(idx, [3], 0, False)
52 53 self.assertEqual([r for r in ait], [2, 1, 0])
53 54
54 55 def testlazyancestors(self):
55 56 idx = self.parserustindex()
56 57 start_count = sys.getrefcount(idx.inner) # should be 2 (see Python doc)
57 58 self.assertEqual(
58 59 {i: (r[5], r[6]) for i, r in enumerate(idx)},
59 60 {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)},
60 61 )
61 62 lazy = LazyAncestors(idx, [3], 0, True)
62 63 # the LazyAncestors instance holds just one reference to the
63 64 # inner revlog.
64 65 self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
65 66
66 67 self.assertTrue(2 in lazy)
67 68 self.assertTrue(bool(lazy))
68 69 self.assertEqual(list(lazy), [3, 2, 1, 0])
69 70 # a second time to validate that we spawn new iterators
70 71 self.assertEqual(list(lazy), [3, 2, 1, 0])
71 72
72 73 # now let's watch the refcounts closer
73 74 ait = iter(lazy)
74 75 self.assertEqual(sys.getrefcount(idx.inner), start_count + 2)
75 76 del ait
76 77 self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
77 78 del lazy
78 79 self.assertEqual(sys.getrefcount(idx.inner), start_count)
79 80
80 81 # let's check bool for an empty one
81 82 self.assertFalse(LazyAncestors(idx, [0], 0, False))
82 83
83 84 def testmissingancestors(self):
84 85 idx = self.parserustindex()
85 86 missanc = MissingAncestors(idx, [1])
86 87 self.assertTrue(missanc.hasbases())
87 88 self.assertEqual(missanc.missingancestors([3]), [2, 3])
88 89 missanc.addbases({2})
89 90 self.assertEqual(missanc.bases(), {1, 2})
90 91 self.assertEqual(missanc.missingancestors([3]), [3])
91 92 self.assertEqual(missanc.basesheads(), {2})
92 93
93 94 def testmissingancestorsremove(self):
94 95 idx = self.parserustindex()
95 96 missanc = MissingAncestors(idx, [1])
96 97 revs = {0, 1, 2, 3}
97 98 missanc.removeancestorsfrom(revs)
98 99 self.assertEqual(revs, {2, 3})
99 100
100 101 def testrefcount(self):
101 102 idx = self.parserustindex()
102 103 start_count = sys.getrefcount(idx.inner)
103 104
104 105 # refcount increases upon iterator init...
105 106 ait = AncestorsIterator(idx, [3], 0, True)
106 107 self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
107 108 self.assertEqual(next(ait), 3)
108 109
109 110 # and decreases once the iterator is removed
110 111 del ait
111 112 self.assertEqual(sys.getrefcount(idx.inner), start_count)
112 113
113 114 # and removing ref to the index after iterator init is no issue
114 115 ait = AncestorsIterator(idx, [3], 0, True)
115 116 del idx
116 117 self.assertEqual(list(ait), [3, 2, 1, 0])
117 118
118 119 # the index is not tracked by the GC, hence there is nothing more
119 120 # we can assert to check that it is properly deleted once its refcount
120 121 # drops to 0
121 122
122 123 def testgrapherror(self):
123 124 data = (
124 125 revlogtesting.data_non_inlined[: 64 + 27]
125 126 + b'\xf2'
126 127 + revlogtesting.data_non_inlined[64 + 28 :]
127 128 )
128 129 idx = self.parserustindex(data=data)
129 130 with self.assertRaises(rustext.GraphError) as arc:
130 131 AncestorsIterator(idx, [1], -1, False)
131 132 exc = arc.exception
132 133 self.assertIsInstance(exc, ValueError)
133 134 # rust-cpython issues appropriate str instances for Python 2 and 3
134 135 self.assertEqual(exc.args, ('ParentOutOfRange', 1))
135 136
136 137 def testwdirunsupported(self):
137 138 # trying to access ancestors of the working directory raises
138 139 idx = self.parserustindex()
139 140 with self.assertRaises(rustext.GraphError) as arc:
140 141 list(AncestorsIterator(idx, [wdirrev], -1, False))
141 142
142 143 exc = arc.exception
143 144 self.assertIsInstance(exc, ValueError)
144 145 # rust-cpython issues appropriate str instances for Python 2 and 3
145 146 self.assertEqual(exc.args, ('InvalidRevision', wdirrev))
146 147
147 148 def testheadrevs(self):
148 149 idx = self.parserustindex()
149 150 self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3})
150 151
152 def testpyo3_headrevs(self):
153 idx = self.parserustindex()
154 self.assertEqual(pyo3_rustext.dagop.headrevs(idx, [1, 2, 3]), {3})
155
156 def testpyo3_rank(self):
157 idx = self.parserustindex()
158 try:
159 pyo3_rustext.dagop.rank(idx, 1, 2)
160 except pyo3_rustext.GraphError as exc:
161 self.assertEqual(exc.args, ("InconsistentGraphData",))
162
151 163
152 164 if __name__ == '__main__':
153 165 import silenttestrunner
154 166
155 167 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now