##// 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 1 // ancestors.rs
2 2 //
3 3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
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::ancestors module provided by the
9 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 73 /// Create the module, with __package__ given from parent
13 74 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
14 75 let dotted_name = &format!("{}.ancestor", package);
15 76 let m = PyModule::new(py, dotted_name)?;
16 77 m.add(py, "__package__", package)?;
17 78 m.add(
18 79 py,
19 80 "__doc__",
20 81 "Generic DAG ancestor algorithms - Rust implementation",
21 82 )?;
83 m.add_class::<AncestorsIterator>(py)?;
22 84
23 85 let sys = PyModule::import(py, "sys")?;
24 86 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
25 87 sys_modules.set_item(py, dotted_name, &m)?;
26 88 // Example C code (see pyexpat.c and import.c) will "give away the
27 89 // reference", but we won't because it will be consumed once the
28 90 // Rust PyObject is dropped.
29 91 Ok(m)
30 92 }
@@ -1,39 +1,107
1 1 from __future__ import absolute_import
2 import sys
2 3 import unittest
3 4
4 5 try:
5 6 from mercurial import rustext
7 rustext.__name__ # trigger immediate actual import
6 8 except ImportError:
7 9 rustext = None
10 else:
11 # this would fail already without appropriate ancestor.__package__
12 from mercurial.rustext.ancestor import AncestorsIterator
8 13
9 14 try:
10 15 from mercurial.cext import parsers as cparsers
11 16 except ImportError:
12 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 41 @unittest.skipIf(rustext is None or cparsers is None,
15 "rustext.ancestor or the C Extension parsers module "
16 "it relies on is not available")
42 "rustext or the C Extension parsers module "
43 "ancestor relies on is not available")
17 44 class rustancestorstest(unittest.TestCase):
18 45 """Test the correctness of binding to Rust code.
19 46
20 47 This test is merely for the binding to Rust itself: extraction of
21 48 Python variable, giving back the results etc.
22 49
23 50 It is not meant to test the algorithmic correctness of the operations
24 51 on ancestors it provides. Hence the very simple embedded index data is
25 52 good enough.
26 53
27 54 Algorithmic correctness is asserted by the Rust unit tests.
28 55 """
29 56
30 def testmodule(self):
31 self.assertTrue('DAG' in rustext.ancestor.__doc__)
57 def parseindex(self):
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 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 105 if __name__ == '__main__':
38 106 import silenttestrunner
39 107 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now