##// END OF EJS Templates
rust-cpython: binding for AncestorsIterator...
Georges Racinet -
r41083:d9f439fc default
parent child Browse files
Show More
@@ -7,7 +7,68
7
7
8 //! Bindings for the hg::ancestors module provided by the
8 //! Bindings for the hg::ancestors module provided by the
9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
10 use cpython::{PyDict, PyModule, PyResult, Python};
10 use cindex::Index;
11 use cpython::{
12 ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, Python,
13 };
14 use exceptions::GraphError;
15 use hg;
16 use hg::AncestorsIterator as CoreIterator;
17 use hg::Revision;
18 use std::cell::RefCell;
19
20 /// Utility function to convert a Python iterable into a Vec<Revision>
21 ///
22 /// We need this to feed to AncestorIterators constructors because
23 /// a PyErr can arise at each step of iteration, whereas our inner objects
24 /// expect iterables over Revision, not over some Result<Revision, PyErr>
25 fn reviter_to_revvec(py: Python, revs: PyObject) -> PyResult<Vec<Revision>> {
26 revs.iter(py)?
27 .map(|r| r.and_then(|o| o.extract::<Revision>(py)))
28 .collect()
29 }
30
31 py_class!(class AncestorsIterator |py| {
32 // TODO RW lock ?
33 data inner: RefCell<Box<CoreIterator<Index>>>;
34
35 def __next__(&self) -> PyResult<Option<Revision>> {
36 match self.inner(py).borrow_mut().next() {
37 Some(Err(e)) => Err(GraphError::pynew(py, e)),
38 None => Ok(None),
39 Some(Ok(r)) => Ok(Some(r)),
40 }
41 }
42
43 def __contains__(&self, rev: Revision) -> PyResult<bool> {
44 self.inner(py).borrow_mut().contains(rev).map_err(|e| GraphError::pynew(py, e))
45 }
46
47 def __iter__(&self) -> PyResult<Self> {
48 Ok(self.clone_ref(py))
49 }
50
51 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
52 inclusive: bool) -> PyResult<AncestorsIterator> {
53 let initvec = reviter_to_revvec(py, initrevs)?;
54 let ait = match hg::AncestorsIterator::new(Index::new(py, index)?,
55 initvec, stoprev,
56 inclusive) {
57 Ok(ait) => ait,
58 Err(e) => {
59 return Err(GraphError::pynew(py, e));
60 }
61 };
62 AncestorsIterator::from_inner(py, ait)
63 }
64
65 });
66
67 impl AncestorsIterator {
68 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
69 Self::create_instance(py, RefCell::new(Box::new(ait)))
70 }
71 }
11
72
12 /// Create the module, with __package__ given from parent
73 /// Create the module, with __package__ given from parent
13 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
74 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
@@ -19,6 +80,7 pub fn init_module(py: Python, package:
19 "__doc__",
80 "__doc__",
20 "Generic DAG ancestor algorithms - Rust implementation",
81 "Generic DAG ancestor algorithms - Rust implementation",
21 )?;
82 )?;
83 m.add_class::<AncestorsIterator>(py)?;
22
84
23 let sys = PyModule::import(py, "sys")?;
85 let sys = PyModule::import(py, "sys")?;
24 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
86 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
@@ -1,19 +1,46
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2 import sys
2 import unittest
3 import unittest
3
4
4 try:
5 try:
5 from mercurial import rustext
6 from mercurial import rustext
7 rustext.__name__ # trigger immediate actual import
6 except ImportError:
8 except ImportError:
7 rustext = None
9 rustext = None
10 else:
11 # this would fail already without appropriate ancestor.__package__
12 from mercurial.rustext.ancestor import AncestorsIterator
8
13
9 try:
14 try:
10 from mercurial.cext import parsers as cparsers
15 from mercurial.cext import parsers as cparsers
11 except ImportError:
16 except ImportError:
12 cparsers = None
17 cparsers = None
13
18
19 # picked from test-parse-index2, copied rather than imported
20 # so that it stays stable even if test-parse-index2 changes or disappears.
21 data_non_inlined = (
22 b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19'
23 b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff'
24 b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d'
25 b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00'
26 b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00'
27 b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff'
28 b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh'
29 b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
30 b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00'
31 b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n'
32 b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00'
33 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F'
34 b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01'
35 b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1'
36 b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00'
37 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00'
38 )
39
40
14 @unittest.skipIf(rustext is None or cparsers is None,
41 @unittest.skipIf(rustext is None or cparsers is None,
15 "rustext.ancestor or the C Extension parsers module "
42 "rustext or the C Extension parsers module "
16 "it relies on is not available")
43 "ancestor relies on is not available")
17 class rustancestorstest(unittest.TestCase):
44 class rustancestorstest(unittest.TestCase):
18 """Test the correctness of binding to Rust code.
45 """Test the correctness of binding to Rust code.
19
46
@@ -27,11 +54,52 class rustancestorstest(unittest.TestCas
27 Algorithmic correctness is asserted by the Rust unit tests.
54 Algorithmic correctness is asserted by the Rust unit tests.
28 """
55 """
29
56
30 def testmodule(self):
57 def parseindex(self):
31 self.assertTrue('DAG' in rustext.ancestor.__doc__)
58 return cparsers.parse_index2(data_non_inlined, False)[0]
59
60 def testiteratorrevlist(self):
61 idx = self.parseindex()
62 # checking test assumption about the index binary data:
63 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
64 {0: (-1, -1),
65 1: (0, -1),
66 2: (1, -1),
67 3: (2, -1)})
68 ait = AncestorsIterator(idx, [3], 0, True)
69 self.assertEqual([r for r in ait], [3, 2, 1, 0])
70
71 ait = AncestorsIterator(idx, [3], 0, False)
72 self.assertEqual([r for r in ait], [2, 1, 0])
73
74 def testrefcount(self):
75 idx = self.parseindex()
76 start_count = sys.getrefcount(idx)
77
78 # refcount increases upon iterator init...
79 ait = AncestorsIterator(idx, [3], 0, True)
80 self.assertEqual(sys.getrefcount(idx), start_count + 1)
81 self.assertEqual(next(ait), 3)
82
83 # and decreases once the iterator is removed
84 del ait
85 self.assertEqual(sys.getrefcount(idx), start_count)
86
87 # and removing ref to the index after iterator init is no issue
88 ait = AncestorsIterator(idx, [3], 0, True)
89 del idx
90 self.assertEqual([r for r in ait], [3, 2, 1, 0])
32
91
33 def testgrapherror(self):
92 def testgrapherror(self):
34 self.assertTrue('GraphError' in dir(rustext))
93 data = (data_non_inlined[:64 + 27] +
94 b'\xf2' +
95 data_non_inlined[64 + 28:])
96 idx = cparsers.parse_index2(data, False)[0]
97 with self.assertRaises(rustext.GraphError) as arc:
98 AncestorsIterator(idx, [1], -1, False)
99 exc = arc.exception
100 self.assertIsInstance(exc, ValueError)
101 # rust-cpython issues appropriate str instances for Python 2 and 3
102 self.assertEqual(exc.args, ('ParentOutOfRange', 1))
35
103
36
104
37 if __name__ == '__main__':
105 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now