Show More
@@ -0,0 +1,114 | |||||
|
1 | // discovery.rs | |||
|
2 | // | |||
|
3 | // Copyright 2018 Georges Racinet <gracinet@anybox.fr> | |||
|
4 | // | |||
|
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. | |||
|
7 | ||||
|
8 | //! Bindings for the `hg::discovery` module provided by the | |||
|
9 | //! `hg-core` crate. From Python, this will be seen as `rustext.discovery` | |||
|
10 | //! | |||
|
11 | //! # Classes visible from Python: | |||
|
12 | //! - [`PartialDiscover`] is the Rust implementation of | |||
|
13 | //! `mercurial.setdiscovery.partialdiscovery`. | |||
|
14 | ||||
|
15 | use crate::conversion::{py_set, rev_pyiter_collect}; | |||
|
16 | use cindex::Index; | |||
|
17 | use cpython::{ObjectProtocol, PyDict, PyModule, PyObject, PyResult, Python}; | |||
|
18 | use exceptions::GraphError; | |||
|
19 | use hg::discovery::PartialDiscovery as CorePartialDiscovery; | |||
|
20 | use hg::Revision; | |||
|
21 | ||||
|
22 | use std::cell::RefCell; | |||
|
23 | ||||
|
24 | py_class!(pub class PartialDiscovery |py| { | |||
|
25 | data inner: RefCell<Box<CorePartialDiscovery<Index>>>; | |||
|
26 | ||||
|
27 | def __new__( | |||
|
28 | _cls, | |||
|
29 | index: PyObject, | |||
|
30 | targetheads: PyObject | |||
|
31 | ) -> PyResult<PartialDiscovery> { | |||
|
32 | Self::create_instance( | |||
|
33 | py, | |||
|
34 | RefCell::new(Box::new(CorePartialDiscovery::new( | |||
|
35 | Index::new(py, index)?, | |||
|
36 | rev_pyiter_collect(py, &targetheads)?, | |||
|
37 | ))) | |||
|
38 | ) | |||
|
39 | } | |||
|
40 | ||||
|
41 | def addcommons(&self, commons: PyObject) -> PyResult<PyObject> { | |||
|
42 | let mut inner = self.inner(py).borrow_mut(); | |||
|
43 | let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?; | |||
|
44 | inner.add_common_revisions(commons_vec) | |||
|
45 | .map_err(|e| GraphError::pynew(py, e))?; | |||
|
46 | Ok(py.None()) | |||
|
47 | } | |||
|
48 | ||||
|
49 | def addmissings(&self, missings: PyObject) -> PyResult<PyObject> { | |||
|
50 | let mut inner = self.inner(py).borrow_mut(); | |||
|
51 | let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?; | |||
|
52 | inner.add_missing_revisions(missings_vec) | |||
|
53 | .map_err(|e| GraphError::pynew(py, e))?; | |||
|
54 | Ok(py.None()) | |||
|
55 | } | |||
|
56 | ||||
|
57 | def addinfo(&self, sample: PyObject) -> PyResult<PyObject> { | |||
|
58 | let mut missing: Vec<Revision> = Vec::new(); | |||
|
59 | let mut common: Vec<Revision> = Vec::new(); | |||
|
60 | for info in sample.iter(py)? { // info is a pair (Revision, bool) | |||
|
61 | let mut revknown = info?.iter(py)?; | |||
|
62 | let rev: Revision = revknown.next().unwrap()?.extract(py)?; | |||
|
63 | let known: bool = revknown.next().unwrap()?.extract(py)?; | |||
|
64 | if known { | |||
|
65 | common.push(rev); | |||
|
66 | } else { | |||
|
67 | missing.push(rev); | |||
|
68 | } | |||
|
69 | } | |||
|
70 | let mut inner = self.inner(py).borrow_mut(); | |||
|
71 | inner.add_common_revisions(common) | |||
|
72 | .map_err(|e| GraphError::pynew(py, e))?; | |||
|
73 | inner.add_missing_revisions(missing) | |||
|
74 | .map_err(|e| GraphError::pynew(py, e))?; | |||
|
75 | Ok(py.None()) | |||
|
76 | } | |||
|
77 | ||||
|
78 | def hasinfo(&self) -> PyResult<bool> { | |||
|
79 | Ok(self.inner(py).borrow().has_info()) | |||
|
80 | } | |||
|
81 | ||||
|
82 | def iscomplete(&self) -> PyResult<bool> { | |||
|
83 | Ok(self.inner(py).borrow().is_complete()) | |||
|
84 | } | |||
|
85 | ||||
|
86 | def commonheads(&self) -> PyResult<PyObject> { | |||
|
87 | py_set( | |||
|
88 | py, | |||
|
89 | &self.inner(py).borrow().common_heads() | |||
|
90 | .map_err(|e| GraphError::pynew(py, e))? | |||
|
91 | ) | |||
|
92 | } | |||
|
93 | }); | |||
|
94 | ||||
|
95 | /// Create the module, with __package__ given from parent | |||
|
96 | pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { | |||
|
97 | let dotted_name = &format!("{}.discovery", package); | |||
|
98 | let m = PyModule::new(py, dotted_name)?; | |||
|
99 | m.add(py, "__package__", package)?; | |||
|
100 | m.add( | |||
|
101 | py, | |||
|
102 | "__doc__", | |||
|
103 | "Discovery of common node sets - Rust implementation", | |||
|
104 | )?; | |||
|
105 | m.add_class::<PartialDiscovery>(py)?; | |||
|
106 | ||||
|
107 | let sys = PyModule::import(py, "sys")?; | |||
|
108 | let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | |||
|
109 | sys_modules.set_item(py, dotted_name, &m)?; | |||
|
110 | // Example C code (see pyexpat.c and import.c) will "give away the | |||
|
111 | // reference", but we won't because it will be consumed once the | |||
|
112 | // Rust PyObject is dropped. | |||
|
113 | Ok(m) | |||
|
114 | } |
@@ -0,0 +1,103 | |||||
|
1 | from __future__ import absolute_import | |||
|
2 | import unittest | |||
|
3 | ||||
|
4 | try: | |||
|
5 | from mercurial import rustext | |||
|
6 | rustext.__name__ # trigger immediate actual import | |||
|
7 | except ImportError: | |||
|
8 | rustext = None | |||
|
9 | else: | |||
|
10 | # this would fail already without appropriate ancestor.__package__ | |||
|
11 | from mercurial.rustext.discovery import ( | |||
|
12 | PartialDiscovery, | |||
|
13 | ) | |||
|
14 | ||||
|
15 | try: | |||
|
16 | from mercurial.cext import parsers as cparsers | |||
|
17 | except ImportError: | |||
|
18 | cparsers = None | |||
|
19 | ||||
|
20 | # picked from test-parse-index2, copied rather than imported | |||
|
21 | # so that it stays stable even if test-parse-index2 changes or disappears. | |||
|
22 | data_non_inlined = ( | |||
|
23 | b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' | |||
|
24 | b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' | |||
|
25 | b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' | |||
|
26 | b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |||
|
27 | b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' | |||
|
28 | b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' | |||
|
29 | b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' | |||
|
30 | b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |||
|
31 | b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' | |||
|
32 | b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' | |||
|
33 | b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' | |||
|
34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' | |||
|
35 | b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' | |||
|
36 | b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' | |||
|
37 | b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' | |||
|
38 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' | |||
|
39 | ) | |||
|
40 | ||||
|
41 | ||||
|
42 | @unittest.skipIf(rustext is None or cparsers is None, | |||
|
43 | "rustext or the C Extension parsers module " | |||
|
44 | "discovery relies on is not available") | |||
|
45 | class rustdiscoverytest(unittest.TestCase): | |||
|
46 | """Test the correctness of binding to Rust code. | |||
|
47 | ||||
|
48 | This test is merely for the binding to Rust itself: extraction of | |||
|
49 | Python variable, giving back the results etc. | |||
|
50 | ||||
|
51 | It is not meant to test the algorithmic correctness of the provided | |||
|
52 | methods. Hence the very simple embedded index data is good enough. | |||
|
53 | ||||
|
54 | Algorithmic correctness is asserted by the Rust unit tests. | |||
|
55 | """ | |||
|
56 | ||||
|
57 | def parseindex(self): | |||
|
58 | return cparsers.parse_index2(data_non_inlined, False)[0] | |||
|
59 | ||||
|
60 | def testindex(self): | |||
|
61 | idx = self.parseindex() | |||
|
62 | # checking our assumptions 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 | ||||
|
69 | def testaddcommonsmissings(self): | |||
|
70 | idx = self.parseindex() | |||
|
71 | disco = PartialDiscovery(idx, [3]) | |||
|
72 | self.assertFalse(disco.hasinfo()) | |||
|
73 | self.assertFalse(disco.iscomplete()) | |||
|
74 | ||||
|
75 | disco.addcommons([1]) | |||
|
76 | self.assertTrue(disco.hasinfo()) | |||
|
77 | self.assertFalse(disco.iscomplete()) | |||
|
78 | ||||
|
79 | disco.addmissings([2]) | |||
|
80 | self.assertTrue(disco.hasinfo()) | |||
|
81 | self.assertTrue(disco.iscomplete()) | |||
|
82 | ||||
|
83 | self.assertEqual(disco.commonheads(), {1}) | |||
|
84 | ||||
|
85 | def testaddinfocommonfirst(self): | |||
|
86 | idx = self.parseindex() | |||
|
87 | disco = PartialDiscovery(idx, [3]) | |||
|
88 | disco.addinfo([(1, True), (2, False)]) | |||
|
89 | self.assertTrue(disco.hasinfo()) | |||
|
90 | self.assertTrue(disco.iscomplete()) | |||
|
91 | self.assertEqual(disco.commonheads(), {1}) | |||
|
92 | ||||
|
93 | def testaddinfomissingfirst(self): | |||
|
94 | idx = self.parseindex() | |||
|
95 | disco = PartialDiscovery(idx, [3]) | |||
|
96 | disco.addinfo([(2, False), (1, True)]) | |||
|
97 | self.assertTrue(disco.hasinfo()) | |||
|
98 | self.assertTrue(disco.iscomplete()) | |||
|
99 | self.assertEqual(disco.commonheads(), {1}) | |||
|
100 | ||||
|
101 | if __name__ == '__main__': | |||
|
102 | import silenttestrunner | |||
|
103 | silenttestrunner.main(__name__) |
@@ -1,45 +1,47 | |||||
1 | // lib.rs |
|
1 | // lib.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 | //! Python bindings of `hg-core` objects using the `cpython` crate. |
|
8 | //! Python bindings of `hg-core` objects using the `cpython` crate. | |
9 | //! Once compiled, the resulting single shared library object can be placed in |
|
9 | //! Once compiled, the resulting single shared library object can be placed in | |
10 | //! the `mercurial` package directly as `rustext.so` or `rustext.dll`. |
|
10 | //! the `mercurial` package directly as `rustext.so` or `rustext.dll`. | |
11 | //! It holds several modules, so that from the point of view of Python, |
|
11 | //! It holds several modules, so that from the point of view of Python, | |
12 | //! it behaves as the `cext` package. |
|
12 | //! it behaves as the `cext` package. | |
13 | //! |
|
13 | //! | |
14 | //! Example: |
|
14 | //! Example: | |
15 | //! |
|
15 | //! | |
16 | //! ```text |
|
16 | //! ```text | |
17 | //! >>> from mercurial.rustext import ancestor |
|
17 | //! >>> from mercurial.rustext import ancestor | |
18 | //! >>> ancestor.__doc__ |
|
18 | //! >>> ancestor.__doc__ | |
19 | //! 'Generic DAG ancestor algorithms - Rust implementation' |
|
19 | //! 'Generic DAG ancestor algorithms - Rust implementation' | |
20 | //! ``` |
|
20 | //! ``` | |
21 |
|
21 | |||
22 | #[macro_use] |
|
22 | #[macro_use] | |
23 | extern crate cpython; |
|
23 | extern crate cpython; | |
24 | extern crate hg; |
|
24 | extern crate hg; | |
25 | extern crate libc; |
|
25 | extern crate libc; | |
26 |
|
26 | |||
27 | pub mod ancestors; |
|
27 | pub mod ancestors; | |
28 | mod cindex; |
|
28 | mod cindex; | |
29 | mod conversion; |
|
29 | mod conversion; | |
30 | pub mod dagops; |
|
30 | pub mod dagops; | |
|
31 | pub mod discovery; | |||
31 | pub mod exceptions; |
|
32 | pub mod exceptions; | |
32 |
|
33 | |||
33 | py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| { |
|
34 | py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| { | |
34 | m.add( |
|
35 | m.add( | |
35 | py, |
|
36 | py, | |
36 | "__doc__", |
|
37 | "__doc__", | |
37 | "Mercurial core concepts - Rust implementation", |
|
38 | "Mercurial core concepts - Rust implementation", | |
38 | )?; |
|
39 | )?; | |
39 |
|
40 | |||
40 | let dotted_name: String = m.get(py, "__name__")?.extract(py)?; |
|
41 | let dotted_name: String = m.get(py, "__name__")?.extract(py)?; | |
41 | m.add(py, "ancestor", ancestors::init_module(py, &dotted_name)?)?; |
|
42 | m.add(py, "ancestor", ancestors::init_module(py, &dotted_name)?)?; | |
42 | m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?; |
|
43 | m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?; | |
|
44 | m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?; | |||
43 | m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?; |
|
45 | m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?; | |
44 | Ok(()) |
|
46 | Ok(()) | |
45 | }); |
|
47 | }); |
General Comments 0
You need to be logged in to leave comments.
Login now