##// END OF EJS Templates
rust-discovery: cpython bindings for the core logic...
Georges Racinet -
r42356:13b64247 default
parent child Browse files
Show More
@@ -0,0 +1,114 b''
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 b''
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__)
@@ -28,6 +28,7 b' 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| {
@@ -40,6 +41,7 b' py_module_initializer!(rustext, initrust'
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