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 |
|
42 | "rustext or the C Extension parsers module " | |
16 |
" |
|
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 |
|
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