##// END OF EJS Templates
rust-cpython: binding for AncestorsIterator...
Georges Racinet -
r41083:d9f439fc default
parent child Browse files
Show More
@@ -1,30 +1,92
1 // ancestors.rs
1 // ancestors.rs
2 //
2 //
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
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> {
14 let dotted_name = &format!("{}.ancestor", package);
75 let dotted_name = &format!("{}.ancestor", package);
15 let m = PyModule::new(py, dotted_name)?;
76 let m = PyModule::new(py, dotted_name)?;
16 m.add(py, "__package__", package)?;
77 m.add(py, "__package__", package)?;
17 m.add(
78 m.add(
18 py,
79 py,
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)?;
25 sys_modules.set_item(py, dotted_name, &m)?;
87 sys_modules.set_item(py, dotted_name, &m)?;
26 // Example C code (see pyexpat.c and import.c) will "give away the
88 // Example C code (see pyexpat.c and import.c) will "give away the
27 // reference", but we won't because it will be consumed once the
89 // reference", but we won't because it will be consumed once the
28 // Rust PyObject is dropped.
90 // Rust PyObject is dropped.
29 Ok(m)
91 Ok(m)
30 }
92 }
@@ -1,39 +1,107
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
20 This test is merely for the binding to Rust itself: extraction of
47 This test is merely for the binding to Rust itself: extraction of
21 Python variable, giving back the results etc.
48 Python variable, giving back the results etc.
22
49
23 It is not meant to test the algorithmic correctness of the operations
50 It is not meant to test the algorithmic correctness of the operations
24 on ancestors it provides. Hence the very simple embedded index data is
51 on ancestors it provides. Hence the very simple embedded index data is
25 good enough.
52 good enough.
26
53
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__':
38 import silenttestrunner
106 import silenttestrunner
39 silenttestrunner.main(__name__)
107 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now