##// END OF EJS Templates
rust-discovery: implementing and exposing stats()...
Georges Racinet -
r42357:1b0be75c default
parent child Browse files
Show More
@@ -1,196 +1,209 b''
1 // discovery.rs
1 // discovery.rs
2 //
2 //
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
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 //! Discovery operations
8 //! Discovery operations
9 //!
9 //!
10 //! This is a Rust counterpart to the `partialdiscovery` class of
10 //! This is a Rust counterpart to the `partialdiscovery` class of
11 //! `mercurial.setdiscovery`
11 //! `mercurial.setdiscovery`
12
12
13 use super::{Graph, GraphError, Revision};
13 use super::{Graph, GraphError, Revision};
14 use crate::ancestors::MissingAncestors;
14 use crate::ancestors::MissingAncestors;
15 use crate::dagops;
15 use crate::dagops;
16 use std::collections::HashSet;
16 use std::collections::HashSet;
17
17
18 pub struct PartialDiscovery<G: Graph + Clone> {
18 pub struct PartialDiscovery<G: Graph + Clone> {
19 target_heads: Option<Vec<Revision>>,
19 target_heads: Option<Vec<Revision>>,
20 graph: G, // plays the role of self._repo
20 graph: G, // plays the role of self._repo
21 common: MissingAncestors<G>,
21 common: MissingAncestors<G>,
22 undecided: Option<HashSet<Revision>>,
22 undecided: Option<HashSet<Revision>>,
23 missing: HashSet<Revision>,
23 missing: HashSet<Revision>,
24 }
24 }
25
25
26 pub struct DiscoveryStats {
27 pub undecided: Option<usize>,
28 }
29
26 impl<G: Graph + Clone> PartialDiscovery<G> {
30 impl<G: Graph + Clone> PartialDiscovery<G> {
27 /// Create a PartialDiscovery object, with the intent
31 /// Create a PartialDiscovery object, with the intent
28 /// of comparing our `::<target_heads>` revset to the contents of another
32 /// of comparing our `::<target_heads>` revset to the contents of another
29 /// repo.
33 /// repo.
30 ///
34 ///
31 /// For now `target_heads` is passed as a vector, and will be used
35 /// For now `target_heads` is passed as a vector, and will be used
32 /// at the first call to `ensure_undecided()`.
36 /// at the first call to `ensure_undecided()`.
33 ///
37 ///
34 /// If we want to make the signature more flexible,
38 /// If we want to make the signature more flexible,
35 /// we'll have to make it a type argument of `PartialDiscovery` or a trait
39 /// we'll have to make it a type argument of `PartialDiscovery` or a trait
36 /// object since we'll keep it in the meanwhile
40 /// object since we'll keep it in the meanwhile
37 pub fn new(graph: G, target_heads: Vec<Revision>) -> Self {
41 pub fn new(graph: G, target_heads: Vec<Revision>) -> Self {
38 PartialDiscovery {
42 PartialDiscovery {
39 undecided: None,
43 undecided: None,
40 target_heads: Some(target_heads),
44 target_heads: Some(target_heads),
41 graph: graph.clone(),
45 graph: graph.clone(),
42 common: MissingAncestors::new(graph, vec![]),
46 common: MissingAncestors::new(graph, vec![]),
43 missing: HashSet::new(),
47 missing: HashSet::new(),
44 }
48 }
45 }
49 }
46
50
47 /// Register revisions known as being common
51 /// Register revisions known as being common
48 pub fn add_common_revisions(
52 pub fn add_common_revisions(
49 &mut self,
53 &mut self,
50 common: impl IntoIterator<Item = Revision>,
54 common: impl IntoIterator<Item = Revision>,
51 ) -> Result<(), GraphError> {
55 ) -> Result<(), GraphError> {
52 self.common.add_bases(common);
56 self.common.add_bases(common);
53 if let Some(ref mut undecided) = self.undecided {
57 if let Some(ref mut undecided) = self.undecided {
54 self.common.remove_ancestors_from(undecided)?;
58 self.common.remove_ancestors_from(undecided)?;
55 }
59 }
56 Ok(())
60 Ok(())
57 }
61 }
58
62
59 /// Register revisions known as being missing
63 /// Register revisions known as being missing
60 pub fn add_missing_revisions(
64 pub fn add_missing_revisions(
61 &mut self,
65 &mut self,
62 missing: impl IntoIterator<Item = Revision>,
66 missing: impl IntoIterator<Item = Revision>,
63 ) -> Result<(), GraphError> {
67 ) -> Result<(), GraphError> {
64 self.ensure_undecided()?;
68 self.ensure_undecided()?;
65 let range = dagops::range(
69 let range = dagops::range(
66 &self.graph,
70 &self.graph,
67 missing,
71 missing,
68 self.undecided.as_ref().unwrap().iter().cloned(),
72 self.undecided.as_ref().unwrap().iter().cloned(),
69 )?;
73 )?;
70 let undecided_mut = self.undecided.as_mut().unwrap();
74 let undecided_mut = self.undecided.as_mut().unwrap();
71 for missrev in range {
75 for missrev in range {
72 self.missing.insert(missrev);
76 self.missing.insert(missrev);
73 undecided_mut.remove(&missrev);
77 undecided_mut.remove(&missrev);
74 }
78 }
75 Ok(())
79 Ok(())
76 }
80 }
77
81
78 /// Do we have any information about the peer?
82 /// Do we have any information about the peer?
79 pub fn has_info(&self) -> bool {
83 pub fn has_info(&self) -> bool {
80 self.common.has_bases()
84 self.common.has_bases()
81 }
85 }
82
86
83 /// Did we acquire full knowledge of our Revisions that the peer has?
87 /// Did we acquire full knowledge of our Revisions that the peer has?
84 pub fn is_complete(&self) -> bool {
88 pub fn is_complete(&self) -> bool {
85 self.undecided.as_ref().map_or(false, |s| s.is_empty())
89 self.undecided.as_ref().map_or(false, |s| s.is_empty())
86 }
90 }
87
91
88 /// Return the heads of the currently known common set of revisions.
92 /// Return the heads of the currently known common set of revisions.
89 ///
93 ///
90 /// If the discovery process is not complete (see `is_complete()`), the
94 /// If the discovery process is not complete (see `is_complete()`), the
91 /// caller must be aware that this is an intermediate state.
95 /// caller must be aware that this is an intermediate state.
92 ///
96 ///
93 /// On the other hand, if it is complete, then this is currently
97 /// On the other hand, if it is complete, then this is currently
94 /// the only way to retrieve the end results of the discovery process.
98 /// the only way to retrieve the end results of the discovery process.
95 ///
99 ///
96 /// We may introduce in the future an `into_common_heads` call that
100 /// We may introduce in the future an `into_common_heads` call that
97 /// would be more appropriate for normal Rust callers, dropping `self`
101 /// would be more appropriate for normal Rust callers, dropping `self`
98 /// if it is complete.
102 /// if it is complete.
99 pub fn common_heads(&self) -> Result<HashSet<Revision>, GraphError> {
103 pub fn common_heads(&self) -> Result<HashSet<Revision>, GraphError> {
100 self.common.bases_heads()
104 self.common.bases_heads()
101 }
105 }
102
106
103 /// Force first computation of `self.undecided`
107 /// Force first computation of `self.undecided`
104 ///
108 ///
105 /// After this, `self.undecided.as_ref()` and `.as_mut()` can be
109 /// After this, `self.undecided.as_ref()` and `.as_mut()` can be
106 /// unwrapped to get workable immutable or mutable references without
110 /// unwrapped to get workable immutable or mutable references without
107 /// any panic.
111 /// any panic.
108 ///
112 ///
109 /// This is an imperative call instead of an access with added lazyness
113 /// This is an imperative call instead of an access with added lazyness
110 /// to reduce easily the scope of mutable borrow for the caller,
114 /// to reduce easily the scope of mutable borrow for the caller,
111 /// compared to undecided(&'a mut self) -> &'a… that would keep it
115 /// compared to undecided(&'a mut self) -> &'a… that would keep it
112 /// as long as the resulting immutable one.
116 /// as long as the resulting immutable one.
113 fn ensure_undecided(&mut self) -> Result<(), GraphError> {
117 fn ensure_undecided(&mut self) -> Result<(), GraphError> {
114 if self.undecided.is_some() {
118 if self.undecided.is_some() {
115 return Ok(());
119 return Ok(());
116 }
120 }
117 let tgt = self.target_heads.take().unwrap();
121 let tgt = self.target_heads.take().unwrap();
118 self.undecided =
122 self.undecided =
119 Some(self.common.missing_ancestors(tgt)?.into_iter().collect());
123 Some(self.common.missing_ancestors(tgt)?.into_iter().collect());
120 Ok(())
124 Ok(())
121 }
125 }
126
127 /// Provide statistics about the current state of the discovery process
128 pub fn stats(&self) -> DiscoveryStats {
129 DiscoveryStats {
130 undecided: self.undecided.as_ref().map(|s| s.len()),
131 }
132 }
122 }
133 }
123
134
124 #[cfg(test)]
135 #[cfg(test)]
125 mod tests {
136 mod tests {
126 use super::*;
137 use super::*;
127 use crate::testing::SampleGraph;
138 use crate::testing::SampleGraph;
128
139
129 /// A PartialDiscovery as for pushing all the heads of `SampleGraph`
140 /// A PartialDiscovery as for pushing all the heads of `SampleGraph`
130 fn full_disco() -> PartialDiscovery<SampleGraph> {
141 fn full_disco() -> PartialDiscovery<SampleGraph> {
131 PartialDiscovery::new(SampleGraph, vec![10, 11, 12, 13])
142 PartialDiscovery::new(SampleGraph, vec![10, 11, 12, 13])
132 }
143 }
133
144
134 fn sorted_undecided(
145 fn sorted_undecided(
135 disco: &PartialDiscovery<SampleGraph>,
146 disco: &PartialDiscovery<SampleGraph>,
136 ) -> Vec<Revision> {
147 ) -> Vec<Revision> {
137 let mut as_vec: Vec<Revision> =
148 let mut as_vec: Vec<Revision> =
138 disco.undecided.as_ref().unwrap().iter().cloned().collect();
149 disco.undecided.as_ref().unwrap().iter().cloned().collect();
139 as_vec.sort();
150 as_vec.sort();
140 as_vec
151 as_vec
141 }
152 }
142
153
143 fn sorted_missing(disco: &PartialDiscovery<SampleGraph>) -> Vec<Revision> {
154 fn sorted_missing(disco: &PartialDiscovery<SampleGraph>) -> Vec<Revision> {
144 let mut as_vec: Vec<Revision> =
155 let mut as_vec: Vec<Revision> =
145 disco.missing.iter().cloned().collect();
156 disco.missing.iter().cloned().collect();
146 as_vec.sort();
157 as_vec.sort();
147 as_vec
158 as_vec
148 }
159 }
149
160
150 fn sorted_common_heads(
161 fn sorted_common_heads(
151 disco: &PartialDiscovery<SampleGraph>,
162 disco: &PartialDiscovery<SampleGraph>,
152 ) -> Result<Vec<Revision>, GraphError> {
163 ) -> Result<Vec<Revision>, GraphError> {
153 let mut as_vec: Vec<Revision> =
164 let mut as_vec: Vec<Revision> =
154 disco.common_heads()?.iter().cloned().collect();
165 disco.common_heads()?.iter().cloned().collect();
155 as_vec.sort();
166 as_vec.sort();
156 Ok(as_vec)
167 Ok(as_vec)
157 }
168 }
158
169
159 #[test]
170 #[test]
160 fn test_add_common_get_undecided() -> Result<(), GraphError> {
171 fn test_add_common_get_undecided() -> Result<(), GraphError> {
161 let mut disco = full_disco();
172 let mut disco = full_disco();
162 assert_eq!(disco.undecided, None);
173 assert_eq!(disco.undecided, None);
163 assert!(!disco.has_info());
174 assert!(!disco.has_info());
175 assert_eq!(disco.stats().undecided, None);
164
176
165 disco.add_common_revisions(vec![11, 12])?;
177 disco.add_common_revisions(vec![11, 12])?;
166 assert!(disco.has_info());
178 assert!(disco.has_info());
167 assert!(!disco.is_complete());
179 assert!(!disco.is_complete());
168 assert!(disco.missing.is_empty());
180 assert!(disco.missing.is_empty());
169
181
170 // add_common_revisions did not trigger a premature computation
182 // add_common_revisions did not trigger a premature computation
171 // of `undecided`, let's check that and ask for them
183 // of `undecided`, let's check that and ask for them
172 assert_eq!(disco.undecided, None);
184 assert_eq!(disco.undecided, None);
173 disco.ensure_undecided()?;
185 disco.ensure_undecided()?;
174 assert_eq!(sorted_undecided(&disco), vec![5, 8, 10, 13]);
186 assert_eq!(sorted_undecided(&disco), vec![5, 8, 10, 13]);
187 assert_eq!(disco.stats().undecided, Some(4));
175 Ok(())
188 Ok(())
176 }
189 }
177
190
178 /// in this test, we pretend that our peer misses exactly (8+10)::
191 /// in this test, we pretend that our peer misses exactly (8+10)::
179 /// and we're comparing all our repo to it (as in a bare push)
192 /// and we're comparing all our repo to it (as in a bare push)
180 #[test]
193 #[test]
181 fn test_discovery() -> Result<(), GraphError> {
194 fn test_discovery() -> Result<(), GraphError> {
182 let mut disco = full_disco();
195 let mut disco = full_disco();
183 disco.add_common_revisions(vec![11, 12])?;
196 disco.add_common_revisions(vec![11, 12])?;
184 disco.add_missing_revisions(vec![8, 10])?;
197 disco.add_missing_revisions(vec![8, 10])?;
185 assert_eq!(sorted_undecided(&disco), vec![5]);
198 assert_eq!(sorted_undecided(&disco), vec![5]);
186 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
199 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
187 assert!(!disco.is_complete());
200 assert!(!disco.is_complete());
188
201
189 disco.add_common_revisions(vec![5])?;
202 disco.add_common_revisions(vec![5])?;
190 assert_eq!(sorted_undecided(&disco), vec![]);
203 assert_eq!(sorted_undecided(&disco), vec![]);
191 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
204 assert_eq!(sorted_missing(&disco), vec![8, 10, 13]);
192 assert!(disco.is_complete());
205 assert!(disco.is_complete());
193 assert_eq!(sorted_common_heads(&disco)?, vec![5, 11, 12]);
206 assert_eq!(sorted_common_heads(&disco)?, vec![5, 11, 12]);
194 Ok(())
207 Ok(())
195 }
208 }
196 }
209 }
@@ -1,114 +1,125 b''
1 // discovery.rs
1 // discovery.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::discovery` module provided by the
8 //! Bindings for the `hg::discovery` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as `rustext.discovery`
9 //! `hg-core` crate. From Python, this will be seen as `rustext.discovery`
10 //!
10 //!
11 //! # Classes visible from Python:
11 //! # Classes visible from Python:
12 //! - [`PartialDiscover`] is the Rust implementation of
12 //! - [`PartialDiscover`] is the Rust implementation of
13 //! `mercurial.setdiscovery.partialdiscovery`.
13 //! `mercurial.setdiscovery.partialdiscovery`.
14
14
15 use crate::conversion::{py_set, rev_pyiter_collect};
15 use crate::conversion::{py_set, rev_pyiter_collect};
16 use cindex::Index;
16 use cindex::Index;
17 use cpython::{ObjectProtocol, PyDict, PyModule, PyObject, PyResult, Python};
17 use cpython::{
18 ObjectProtocol, PyDict, PyModule, PyObject, PyResult, Python, ToPyObject,
19 };
18 use exceptions::GraphError;
20 use exceptions::GraphError;
19 use hg::discovery::PartialDiscovery as CorePartialDiscovery;
21 use hg::discovery::PartialDiscovery as CorePartialDiscovery;
20 use hg::Revision;
22 use hg::Revision;
21
23
22 use std::cell::RefCell;
24 use std::cell::RefCell;
23
25
24 py_class!(pub class PartialDiscovery |py| {
26 py_class!(pub class PartialDiscovery |py| {
25 data inner: RefCell<Box<CorePartialDiscovery<Index>>>;
27 data inner: RefCell<Box<CorePartialDiscovery<Index>>>;
26
28
27 def __new__(
29 def __new__(
28 _cls,
30 _cls,
29 index: PyObject,
31 index: PyObject,
30 targetheads: PyObject
32 targetheads: PyObject
31 ) -> PyResult<PartialDiscovery> {
33 ) -> PyResult<PartialDiscovery> {
32 Self::create_instance(
34 Self::create_instance(
33 py,
35 py,
34 RefCell::new(Box::new(CorePartialDiscovery::new(
36 RefCell::new(Box::new(CorePartialDiscovery::new(
35 Index::new(py, index)?,
37 Index::new(py, index)?,
36 rev_pyiter_collect(py, &targetheads)?,
38 rev_pyiter_collect(py, &targetheads)?,
37 )))
39 )))
38 )
40 )
39 }
41 }
40
42
41 def addcommons(&self, commons: PyObject) -> PyResult<PyObject> {
43 def addcommons(&self, commons: PyObject) -> PyResult<PyObject> {
42 let mut inner = self.inner(py).borrow_mut();
44 let mut inner = self.inner(py).borrow_mut();
43 let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?;
45 let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?;
44 inner.add_common_revisions(commons_vec)
46 inner.add_common_revisions(commons_vec)
45 .map_err(|e| GraphError::pynew(py, e))?;
47 .map_err(|e| GraphError::pynew(py, e))?;
46 Ok(py.None())
48 Ok(py.None())
47 }
49 }
48
50
49 def addmissings(&self, missings: PyObject) -> PyResult<PyObject> {
51 def addmissings(&self, missings: PyObject) -> PyResult<PyObject> {
50 let mut inner = self.inner(py).borrow_mut();
52 let mut inner = self.inner(py).borrow_mut();
51 let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?;
53 let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?;
52 inner.add_missing_revisions(missings_vec)
54 inner.add_missing_revisions(missings_vec)
53 .map_err(|e| GraphError::pynew(py, e))?;
55 .map_err(|e| GraphError::pynew(py, e))?;
54 Ok(py.None())
56 Ok(py.None())
55 }
57 }
56
58
57 def addinfo(&self, sample: PyObject) -> PyResult<PyObject> {
59 def addinfo(&self, sample: PyObject) -> PyResult<PyObject> {
58 let mut missing: Vec<Revision> = Vec::new();
60 let mut missing: Vec<Revision> = Vec::new();
59 let mut common: Vec<Revision> = Vec::new();
61 let mut common: Vec<Revision> = Vec::new();
60 for info in sample.iter(py)? { // info is a pair (Revision, bool)
62 for info in sample.iter(py)? { // info is a pair (Revision, bool)
61 let mut revknown = info?.iter(py)?;
63 let mut revknown = info?.iter(py)?;
62 let rev: Revision = revknown.next().unwrap()?.extract(py)?;
64 let rev: Revision = revknown.next().unwrap()?.extract(py)?;
63 let known: bool = revknown.next().unwrap()?.extract(py)?;
65 let known: bool = revknown.next().unwrap()?.extract(py)?;
64 if known {
66 if known {
65 common.push(rev);
67 common.push(rev);
66 } else {
68 } else {
67 missing.push(rev);
69 missing.push(rev);
68 }
70 }
69 }
71 }
70 let mut inner = self.inner(py).borrow_mut();
72 let mut inner = self.inner(py).borrow_mut();
71 inner.add_common_revisions(common)
73 inner.add_common_revisions(common)
72 .map_err(|e| GraphError::pynew(py, e))?;
74 .map_err(|e| GraphError::pynew(py, e))?;
73 inner.add_missing_revisions(missing)
75 inner.add_missing_revisions(missing)
74 .map_err(|e| GraphError::pynew(py, e))?;
76 .map_err(|e| GraphError::pynew(py, e))?;
75 Ok(py.None())
77 Ok(py.None())
76 }
78 }
77
79
78 def hasinfo(&self) -> PyResult<bool> {
80 def hasinfo(&self) -> PyResult<bool> {
79 Ok(self.inner(py).borrow().has_info())
81 Ok(self.inner(py).borrow().has_info())
80 }
82 }
81
83
82 def iscomplete(&self) -> PyResult<bool> {
84 def iscomplete(&self) -> PyResult<bool> {
83 Ok(self.inner(py).borrow().is_complete())
85 Ok(self.inner(py).borrow().is_complete())
84 }
86 }
85
87
88 def stats(&self) -> PyResult<PyDict> {
89 let stats = self.inner(py).borrow().stats();
90 let as_dict: PyDict = PyDict::new(py);
91 as_dict.set_item(py, "undecided",
92 stats.undecided.map(|l| l.to_py_object(py))
93 .unwrap_or_else(|| py.None()))?;
94 Ok(as_dict)
95 }
96
86 def commonheads(&self) -> PyResult<PyObject> {
97 def commonheads(&self) -> PyResult<PyObject> {
87 py_set(
98 py_set(
88 py,
99 py,
89 &self.inner(py).borrow().common_heads()
100 &self.inner(py).borrow().common_heads()
90 .map_err(|e| GraphError::pynew(py, e))?
101 .map_err(|e| GraphError::pynew(py, e))?
91 )
102 )
92 }
103 }
93 });
104 });
94
105
95 /// Create the module, with __package__ given from parent
106 /// Create the module, with __package__ given from parent
96 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
107 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
97 let dotted_name = &format!("{}.discovery", package);
108 let dotted_name = &format!("{}.discovery", package);
98 let m = PyModule::new(py, dotted_name)?;
109 let m = PyModule::new(py, dotted_name)?;
99 m.add(py, "__package__", package)?;
110 m.add(py, "__package__", package)?;
100 m.add(
111 m.add(
101 py,
112 py,
102 "__doc__",
113 "__doc__",
103 "Discovery of common node sets - Rust implementation",
114 "Discovery of common node sets - Rust implementation",
104 )?;
115 )?;
105 m.add_class::<PartialDiscovery>(py)?;
116 m.add_class::<PartialDiscovery>(py)?;
106
117
107 let sys = PyModule::import(py, "sys")?;
118 let sys = PyModule::import(py, "sys")?;
108 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
119 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
109 sys_modules.set_item(py, dotted_name, &m)?;
120 sys_modules.set_item(py, dotted_name, &m)?;
110 // Example C code (see pyexpat.c and import.c) will "give away the
121 // 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
122 // reference", but we won't because it will be consumed once the
112 // Rust PyObject is dropped.
123 // Rust PyObject is dropped.
113 Ok(m)
124 Ok(m)
114 }
125 }
@@ -1,103 +1,111 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2 import unittest
2 import unittest
3
3
4 try:
4 try:
5 from mercurial import rustext
5 from mercurial import rustext
6 rustext.__name__ # trigger immediate actual import
6 rustext.__name__ # trigger immediate actual import
7 except ImportError:
7 except ImportError:
8 rustext = None
8 rustext = None
9 else:
9 else:
10 # this would fail already without appropriate ancestor.__package__
10 # this would fail already without appropriate ancestor.__package__
11 from mercurial.rustext.discovery import (
11 from mercurial.rustext.discovery import (
12 PartialDiscovery,
12 PartialDiscovery,
13 )
13 )
14
14
15 try:
15 try:
16 from mercurial.cext import parsers as cparsers
16 from mercurial.cext import parsers as cparsers
17 except ImportError:
17 except ImportError:
18 cparsers = None
18 cparsers = None
19
19
20 # picked from test-parse-index2, copied rather than imported
20 # picked from test-parse-index2, copied rather than imported
21 # so that it stays stable even if test-parse-index2 changes or disappears.
21 # so that it stays stable even if test-parse-index2 changes or disappears.
22 data_non_inlined = (
22 data_non_inlined = (
23 b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
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'
38 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00'
39 )
39 )
40
40
41
41
42 @unittest.skipIf(rustext is None or cparsers is None,
42 @unittest.skipIf(rustext is None or cparsers is None,
43 "rustext or the C Extension parsers module "
43 "rustext or the C Extension parsers module "
44 "discovery relies on is not available")
44 "discovery relies on is not available")
45 class rustdiscoverytest(unittest.TestCase):
45 class rustdiscoverytest(unittest.TestCase):
46 """Test the correctness of binding to Rust code.
46 """Test the correctness of binding to Rust code.
47
47
48 This test is merely for the binding to Rust itself: extraction of
48 This test is merely for the binding to Rust itself: extraction of
49 Python variable, giving back the results etc.
49 Python variable, giving back the results etc.
50
50
51 It is not meant to test the algorithmic correctness of the provided
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.
52 methods. Hence the very simple embedded index data is good enough.
53
53
54 Algorithmic correctness is asserted by the Rust unit tests.
54 Algorithmic correctness is asserted by the Rust unit tests.
55 """
55 """
56
56
57 def parseindex(self):
57 def parseindex(self):
58 return cparsers.parse_index2(data_non_inlined, False)[0]
58 return cparsers.parse_index2(data_non_inlined, False)[0]
59
59
60 def testindex(self):
60 def testindex(self):
61 idx = self.parseindex()
61 idx = self.parseindex()
62 # checking our assumptions about the index binary data:
62 # checking our assumptions about the index binary data:
63 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
63 self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)},
64 {0: (-1, -1),
64 {0: (-1, -1),
65 1: (0, -1),
65 1: (0, -1),
66 2: (1, -1),
66 2: (1, -1),
67 3: (2, -1)})
67 3: (2, -1)})
68
68
69 def testaddcommonsmissings(self):
69 def testaddcommonsmissings(self):
70 idx = self.parseindex()
70 idx = self.parseindex()
71 disco = PartialDiscovery(idx, [3])
71 disco = PartialDiscovery(idx, [3])
72 self.assertFalse(disco.hasinfo())
72 self.assertFalse(disco.hasinfo())
73 self.assertFalse(disco.iscomplete())
73 self.assertFalse(disco.iscomplete())
74
74
75 disco.addcommons([1])
75 disco.addcommons([1])
76 self.assertTrue(disco.hasinfo())
76 self.assertTrue(disco.hasinfo())
77 self.assertFalse(disco.iscomplete())
77 self.assertFalse(disco.iscomplete())
78
78
79 disco.addmissings([2])
79 disco.addmissings([2])
80 self.assertTrue(disco.hasinfo())
80 self.assertTrue(disco.hasinfo())
81 self.assertTrue(disco.iscomplete())
81 self.assertTrue(disco.iscomplete())
82
82
83 self.assertEqual(disco.commonheads(), {1})
83 self.assertEqual(disco.commonheads(), {1})
84
84
85 def testaddmissingsstats(self):
86 idx = self.parseindex()
87 disco = PartialDiscovery(idx, [3])
88 self.assertIsNone(disco.stats()['undecided'], None)
89
90 disco.addmissings([2])
91 self.assertEqual(disco.stats()['undecided'], 2)
92
85 def testaddinfocommonfirst(self):
93 def testaddinfocommonfirst(self):
86 idx = self.parseindex()
94 idx = self.parseindex()
87 disco = PartialDiscovery(idx, [3])
95 disco = PartialDiscovery(idx, [3])
88 disco.addinfo([(1, True), (2, False)])
96 disco.addinfo([(1, True), (2, False)])
89 self.assertTrue(disco.hasinfo())
97 self.assertTrue(disco.hasinfo())
90 self.assertTrue(disco.iscomplete())
98 self.assertTrue(disco.iscomplete())
91 self.assertEqual(disco.commonheads(), {1})
99 self.assertEqual(disco.commonheads(), {1})
92
100
93 def testaddinfomissingfirst(self):
101 def testaddinfomissingfirst(self):
94 idx = self.parseindex()
102 idx = self.parseindex()
95 disco = PartialDiscovery(idx, [3])
103 disco = PartialDiscovery(idx, [3])
96 disco.addinfo([(2, False), (1, True)])
104 disco.addinfo([(2, False), (1, True)])
97 self.assertTrue(disco.hasinfo())
105 self.assertTrue(disco.hasinfo())
98 self.assertTrue(disco.iscomplete())
106 self.assertTrue(disco.iscomplete())
99 self.assertEqual(disco.commonheads(), {1})
107 self.assertEqual(disco.commonheads(), {1})
100
108
101 if __name__ == '__main__':
109 if __name__ == '__main__':
102 import silenttestrunner
110 import silenttestrunner
103 silenttestrunner.main(__name__)
111 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now