##// END OF EJS Templates
rust-cpython: binding for LazyAncestors...
Georges Racinet -
r41149:b31a41f2 default
parent child Browse files
Show More
@@ -1,92 +1,124 b''
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 cindex::Index;
10 use cindex::Index;
11 use cpython::{
11 use cpython::{
12 ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, Python,
12 ObjectProtocol, PyClone, PyDict, PyModule, PyObject, PyResult, Python,
13 };
13 };
14 use exceptions::GraphError;
14 use exceptions::GraphError;
15 use hg;
15 use hg;
16 use hg::AncestorsIterator as CoreIterator;
17 use hg::Revision;
16 use hg::Revision;
17 use hg::{AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy};
18 use std::cell::RefCell;
18 use std::cell::RefCell;
19
19
20 /// Utility function to convert a Python iterable into a Vec<Revision>
20 /// Utility function to convert a Python iterable into a Vec<Revision>
21 ///
21 ///
22 /// We need this to feed to AncestorIterators constructors because
22 /// We need this to feed to AncestorIterators constructors because
23 /// a PyErr can arise at each step of iteration, whereas our inner objects
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>
24 /// expect iterables over Revision, not over some Result<Revision, PyErr>
25 fn reviter_to_revvec(py: Python, revs: PyObject) -> PyResult<Vec<Revision>> {
25 fn reviter_to_revvec(py: Python, revs: PyObject) -> PyResult<Vec<Revision>> {
26 revs.iter(py)?
26 revs.iter(py)?
27 .map(|r| r.and_then(|o| o.extract::<Revision>(py)))
27 .map(|r| r.and_then(|o| o.extract::<Revision>(py)))
28 .collect()
28 .collect()
29 }
29 }
30
30
31 py_class!(class AncestorsIterator |py| {
31 py_class!(class AncestorsIterator |py| {
32 // TODO RW lock ?
32 // TODO RW lock ?
33 data inner: RefCell<Box<CoreIterator<Index>>>;
33 data inner: RefCell<Box<CoreIterator<Index>>>;
34
34
35 def __next__(&self) -> PyResult<Option<Revision>> {
35 def __next__(&self) -> PyResult<Option<Revision>> {
36 match self.inner(py).borrow_mut().next() {
36 match self.inner(py).borrow_mut().next() {
37 Some(Err(e)) => Err(GraphError::pynew(py, e)),
37 Some(Err(e)) => Err(GraphError::pynew(py, e)),
38 None => Ok(None),
38 None => Ok(None),
39 Some(Ok(r)) => Ok(Some(r)),
39 Some(Ok(r)) => Ok(Some(r)),
40 }
40 }
41 }
41 }
42
42
43 def __contains__(&self, rev: Revision) -> PyResult<bool> {
43 def __contains__(&self, rev: Revision) -> PyResult<bool> {
44 self.inner(py).borrow_mut().contains(rev).map_err(|e| GraphError::pynew(py, e))
44 self.inner(py).borrow_mut().contains(rev).map_err(|e| GraphError::pynew(py, e))
45 }
45 }
46
46
47 def __iter__(&self) -> PyResult<Self> {
47 def __iter__(&self) -> PyResult<Self> {
48 Ok(self.clone_ref(py))
48 Ok(self.clone_ref(py))
49 }
49 }
50
50
51 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
51 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
52 inclusive: bool) -> PyResult<AncestorsIterator> {
52 inclusive: bool) -> PyResult<AncestorsIterator> {
53 let initvec = reviter_to_revvec(py, initrevs)?;
53 let initvec = reviter_to_revvec(py, initrevs)?;
54 let ait = match hg::AncestorsIterator::new(Index::new(py, index)?,
54 let ait = match hg::AncestorsIterator::new(Index::new(py, index)?,
55 initvec, stoprev,
55 initvec, stoprev,
56 inclusive) {
56 inclusive) {
57 Ok(ait) => ait,
57 Ok(ait) => ait,
58 Err(e) => {
58 Err(e) => {
59 return Err(GraphError::pynew(py, e));
59 return Err(GraphError::pynew(py, e));
60 }
60 }
61 };
61 };
62 AncestorsIterator::from_inner(py, ait)
62 AncestorsIterator::from_inner(py, ait)
63 }
63 }
64
64
65 });
65 });
66
66
67 impl AncestorsIterator {
67 impl AncestorsIterator {
68 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
68 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
69 Self::create_instance(py, RefCell::new(Box::new(ait)))
69 Self::create_instance(py, RefCell::new(Box::new(ait)))
70 }
70 }
71 }
71 }
72
72
73 py_class!(class LazyAncestors |py| {
74 data inner: RefCell<Box<CoreLazy<Index>>>;
75
76 def __contains__(&self, rev: Revision) -> PyResult<bool> {
77 self.inner(py)
78 .borrow_mut()
79 .contains(rev)
80 .map_err(|e| GraphError::pynew(py, e))
81 }
82
83 def __iter__(&self) -> PyResult<AncestorsIterator> {
84 AncestorsIterator::from_inner(py, self.inner(py).borrow().iter())
85 }
86
87 def __bool__(&self) -> PyResult<bool> {
88 Ok(!self.inner(py).borrow().is_empty())
89 }
90
91 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
92 inclusive: bool) -> PyResult<Self> {
93 let initvec = reviter_to_revvec(py, initrevs)?;
94
95 let lazy =
96 CoreLazy::new(Index::new(py, index)?, initvec, stoprev, inclusive)
97 .map_err(|e| GraphError::pynew(py, e))?;
98
99 Self::create_instance(py, RefCell::new(Box::new(lazy)))
100 }
101
102 });
103
73 /// Create the module, with __package__ given from parent
104 /// Create the module, with __package__ given from parent
74 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
105 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
75 let dotted_name = &format!("{}.ancestor", package);
106 let dotted_name = &format!("{}.ancestor", package);
76 let m = PyModule::new(py, dotted_name)?;
107 let m = PyModule::new(py, dotted_name)?;
77 m.add(py, "__package__", package)?;
108 m.add(py, "__package__", package)?;
78 m.add(
109 m.add(
79 py,
110 py,
80 "__doc__",
111 "__doc__",
81 "Generic DAG ancestor algorithms - Rust implementation",
112 "Generic DAG ancestor algorithms - Rust implementation",
82 )?;
113 )?;
83 m.add_class::<AncestorsIterator>(py)?;
114 m.add_class::<AncestorsIterator>(py)?;
115 m.add_class::<LazyAncestors>(py)?;
84
116
85 let sys = PyModule::import(py, "sys")?;
117 let sys = PyModule::import(py, "sys")?;
86 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
118 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
87 sys_modules.set_item(py, dotted_name, &m)?;
119 sys_modules.set_item(py, dotted_name, &m)?;
88 // Example C code (see pyexpat.c and import.c) will "give away the
120 // Example C code (see pyexpat.c and import.c) will "give away the
89 // reference", but we won't because it will be consumed once the
121 // reference", but we won't because it will be consumed once the
90 // Rust PyObject is dropped.
122 // Rust PyObject is dropped.
91 Ok(m)
123 Ok(m)
92 }
124 }
@@ -1,107 +1,141 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2 import sys
2 import sys
3 import unittest
3 import unittest
4
4
5 try:
5 try:
6 from mercurial import rustext
6 from mercurial import rustext
7 rustext.__name__ # trigger immediate actual import
7 rustext.__name__ # trigger immediate actual import
8 except ImportError:
8 except ImportError:
9 rustext = None
9 rustext = None
10 else:
10 else:
11 # this would fail already without appropriate ancestor.__package__
11 # this would fail already without appropriate ancestor.__package__
12 from mercurial.rustext.ancestor import AncestorsIterator
12 from mercurial.rustext.ancestor import (
13 AncestorsIterator,
14 LazyAncestors
15 )
13
16
14 try:
17 try:
15 from mercurial.cext import parsers as cparsers
18 from mercurial.cext import parsers as cparsers
16 except ImportError:
19 except ImportError:
17 cparsers = None
20 cparsers = None
18
21
19 # picked from test-parse-index2, copied rather than imported
22 # picked from test-parse-index2, copied rather than imported
20 # so that it stays stable even if test-parse-index2 changes or disappears.
23 # so that it stays stable even if test-parse-index2 changes or disappears.
21 data_non_inlined = (
24 data_non_inlined = (
22 b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19'
25 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'
26 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'
27 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'
28 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'
29 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'
30 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'
31 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'
32 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'
33 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'
34 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'
35 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'
36 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'
37 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'
38 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'
39 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'
40 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00'
38 )
41 )
39
42
40
43
41 @unittest.skipIf(rustext is None or cparsers is None,
44 @unittest.skipIf(rustext is None or cparsers is None,
42 "rustext or the C Extension parsers module "
45 "rustext or the C Extension parsers module "
43 "ancestor relies on is not available")
46 "ancestor relies on is not available")
44 class rustancestorstest(unittest.TestCase):
47 class rustancestorstest(unittest.TestCase):
45 """Test the correctness of binding to Rust code.
48 """Test the correctness of binding to Rust code.
46
49
47 This test is merely for the binding to Rust itself: extraction of
50 This test is merely for the binding to Rust itself: extraction of
48 Python variable, giving back the results etc.
51 Python variable, giving back the results etc.
49
52
50 It is not meant to test the algorithmic correctness of the operations
53 It is not meant to test the algorithmic correctness of the operations
51 on ancestors it provides. Hence the very simple embedded index data is
54 on ancestors it provides. Hence the very simple embedded index data is
52 good enough.
55 good enough.
53
56
54 Algorithmic correctness is asserted by the Rust unit tests.
57 Algorithmic correctness is asserted by the Rust unit tests.
55 """
58 """
56
59
57 def parseindex(self):
60 def parseindex(self):
58 return cparsers.parse_index2(data_non_inlined, False)[0]
61 return cparsers.parse_index2(data_non_inlined, False)[0]
59
62
60 def testiteratorrevlist(self):
63 def testiteratorrevlist(self):
61 idx = self.parseindex()
64 idx = self.parseindex()
62 # checking test assumption about the index binary data:
65 # checking test assumption about the index binary data:
63 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
66 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
64 {0: (-1, -1),
67 {0: (-1, -1),
65 1: (0, -1),
68 1: (0, -1),
66 2: (1, -1),
69 2: (1, -1),
67 3: (2, -1)})
70 3: (2, -1)})
68 ait = AncestorsIterator(idx, [3], 0, True)
71 ait = AncestorsIterator(idx, [3], 0, True)
69 self.assertEqual([r for r in ait], [3, 2, 1, 0])
72 self.assertEqual([r for r in ait], [3, 2, 1, 0])
70
73
71 ait = AncestorsIterator(idx, [3], 0, False)
74 ait = AncestorsIterator(idx, [3], 0, False)
72 self.assertEqual([r for r in ait], [2, 1, 0])
75 self.assertEqual([r for r in ait], [2, 1, 0])
73
76
77 def testlazyancestors(self):
78 idx = self.parseindex()
79 start_count = sys.getrefcount(idx) # should be 2 (see Python doc)
80 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
81 {0: (-1, -1),
82 1: (0, -1),
83 2: (1, -1),
84 3: (2, -1)})
85 lazy = LazyAncestors(idx, [3], 0, True)
86 # we have two more references to the index:
87 # - in its inner iterator for __contains__ and __bool__
88 # - in the LazyAncestors instance itself (to spawn new iterators)
89 self.assertEqual(sys.getrefcount(idx), start_count + 2)
90
91 self.assertTrue(2 in lazy)
92 self.assertTrue(bool(lazy))
93 self.assertEqual(list(lazy), [3, 2, 1, 0])
94 # a second time to validate that we spawn new iterators
95 self.assertEqual(list(lazy), [3, 2, 1, 0])
96
97 # now let's watch the refcounts closer
98 ait = iter(lazy)
99 self.assertEqual(sys.getrefcount(idx), start_count + 3)
100 del ait
101 self.assertEqual(sys.getrefcount(idx), start_count + 2)
102 del lazy
103 self.assertEqual(sys.getrefcount(idx), start_count)
104
105 # let's check bool for an empty one
106 self.assertFalse(LazyAncestors(idx, [0], 0, False))
107
74 def testrefcount(self):
108 def testrefcount(self):
75 idx = self.parseindex()
109 idx = self.parseindex()
76 start_count = sys.getrefcount(idx)
110 start_count = sys.getrefcount(idx)
77
111
78 # refcount increases upon iterator init...
112 # refcount increases upon iterator init...
79 ait = AncestorsIterator(idx, [3], 0, True)
113 ait = AncestorsIterator(idx, [3], 0, True)
80 self.assertEqual(sys.getrefcount(idx), start_count + 1)
114 self.assertEqual(sys.getrefcount(idx), start_count + 1)
81 self.assertEqual(next(ait), 3)
115 self.assertEqual(next(ait), 3)
82
116
83 # and decreases once the iterator is removed
117 # and decreases once the iterator is removed
84 del ait
118 del ait
85 self.assertEqual(sys.getrefcount(idx), start_count)
119 self.assertEqual(sys.getrefcount(idx), start_count)
86
120
87 # and removing ref to the index after iterator init is no issue
121 # and removing ref to the index after iterator init is no issue
88 ait = AncestorsIterator(idx, [3], 0, True)
122 ait = AncestorsIterator(idx, [3], 0, True)
89 del idx
123 del idx
90 self.assertEqual([r for r in ait], [3, 2, 1, 0])
124 self.assertEqual(list(ait), [3, 2, 1, 0])
91
125
92 def testgrapherror(self):
126 def testgrapherror(self):
93 data = (data_non_inlined[:64 + 27] +
127 data = (data_non_inlined[:64 + 27] +
94 b'\xf2' +
128 b'\xf2' +
95 data_non_inlined[64 + 28:])
129 data_non_inlined[64 + 28:])
96 idx = cparsers.parse_index2(data, False)[0]
130 idx = cparsers.parse_index2(data, False)[0]
97 with self.assertRaises(rustext.GraphError) as arc:
131 with self.assertRaises(rustext.GraphError) as arc:
98 AncestorsIterator(idx, [1], -1, False)
132 AncestorsIterator(idx, [1], -1, False)
99 exc = arc.exception
133 exc = arc.exception
100 self.assertIsInstance(exc, ValueError)
134 self.assertIsInstance(exc, ValueError)
101 # rust-cpython issues appropriate str instances for Python 2 and 3
135 # rust-cpython issues appropriate str instances for Python 2 and 3
102 self.assertEqual(exc.args, ('ParentOutOfRange', 1))
136 self.assertEqual(exc.args, ('ParentOutOfRange', 1))
103
137
104
138
105 if __name__ == '__main__':
139 if __name__ == '__main__':
106 import silenttestrunner
140 import silenttestrunner
107 silenttestrunner.main(__name__)
141 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now