Show More
@@ -7,7 +7,68 b'' | |||
|
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> { |
@@ -19,6 +80,7 b' pub fn init_module(py: Python, package: ' | |||
|
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)?; |
@@ -1,19 +1,46 b'' | |||
|
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 |
|
|
16 |
" |
|
|
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 | |
@@ -27,11 +54,52 b' class rustancestorstest(unittest.TestCas' | |||
|
27 | 54 | Algorithmic correctness is asserted by the Rust unit tests. |
|
28 | 55 | """ |
|
29 | 56 | |
|
30 |
def |
|
|
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__': |
General Comments 0
You need to be logged in to leave comments.
Login now