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