##// END OF EJS Templates
rust: adapted hg-core tests for iteration over Result...
Georges Racinet -
r40965:43ca24b7 default
parent child Browse files
Show More
@@ -1,261 +1,261
1 1 // ancestors.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 //! Rust versions of generic DAG ancestors algorithms for Mercurial
9 9
10 10 use super::{Graph, GraphError, Revision, NULL_REVISION};
11 11 use std::collections::{BinaryHeap, HashSet};
12 12
13 13 /// Iterator over the ancestors of a given list of revisions
14 14 /// This is a generic type, defined and implemented for any Graph, so that
15 15 /// it's easy to
16 16 ///
17 17 /// - unit test in pure Rust
18 18 /// - bind to main Mercurial code, potentially in several ways and have these
19 19 /// bindings evolve over time
20 20 pub struct AncestorsIterator<G: Graph> {
21 21 graph: G,
22 22 visit: BinaryHeap<Revision>,
23 23 seen: HashSet<Revision>,
24 24 stoprev: Revision,
25 25 }
26 26
27 27 impl<G: Graph> AncestorsIterator<G> {
28 28 /// Constructor.
29 29 ///
30 30 /// if `inclusive` is true, then the init revisions are emitted in
31 31 /// particular, otherwise iteration starts from their parents.
32 32 pub fn new<I>(
33 33 graph: G,
34 34 initrevs: I,
35 35 stoprev: Revision,
36 36 inclusive: bool,
37 37 ) -> Result<Self, GraphError>
38 38 where
39 39 I: IntoIterator<Item = Revision>,
40 40 {
41 41 let filtered_initrevs = initrevs.into_iter().filter(|&r| r >= stoprev);
42 42 if inclusive {
43 43 let visit: BinaryHeap<Revision> = filtered_initrevs.collect();
44 44 let seen = visit.iter().map(|&x| x).collect();
45 45 return Ok(AncestorsIterator {
46 46 visit: visit,
47 47 seen: seen,
48 48 stoprev: stoprev,
49 49 graph: graph,
50 50 });
51 51 }
52 52 let mut this = AncestorsIterator {
53 53 visit: BinaryHeap::new(),
54 54 seen: HashSet::new(),
55 55 stoprev: stoprev,
56 56 graph: graph,
57 57 };
58 58 this.seen.insert(NULL_REVISION);
59 59 for rev in filtered_initrevs {
60 60 let parents = this.graph.parents(rev)?;
61 61 this.conditionally_push_rev(parents.0);
62 62 this.conditionally_push_rev(parents.1);
63 63 }
64 64 Ok(this)
65 65 }
66 66
67 67 #[inline]
68 68 fn conditionally_push_rev(&mut self, rev: Revision) {
69 69 if self.stoprev <= rev && !self.seen.contains(&rev) {
70 70 self.seen.insert(rev);
71 71 self.visit.push(rev);
72 72 }
73 73 }
74 74
75 75 /// Consumes partially the iterator to tell if the given target
76 76 /// revision
77 77 /// is in the ancestors it emits.
78 78 /// This is meant for iterators actually dedicated to that kind of
79 79 /// purpose
80 80 pub fn contains(&mut self, target: Revision) -> Result<bool, GraphError> {
81 81 if self.seen.contains(&target) && target != NULL_REVISION {
82 82 return Ok(true);
83 83 }
84 84 for item in self {
85 85 let rev = item?;
86 86 if rev == target {
87 87 return Ok(true);
88 88 }
89 89 if rev < target {
90 90 return Ok(false);
91 91 }
92 92 }
93 93 Ok(false)
94 94 }
95 95 }
96 96
97 97 /// Main implementation.
98 98 ///
99 99 /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py`
100 100 /// with a few non crucial differences:
101 101 ///
102 102 /// - there's no filtering of invalid parent revisions. Actually, it should be
103 103 /// consistent and more efficient to filter them from the end caller.
104 104 /// - we don't have the optimization for adjacent revs
105 105 /// (case where p1 == rev-1), because it amounts to update the first element
106 106 /// of the heap without sifting, which Rust's BinaryHeap doesn't let us do.
107 107 /// - we save a few pushes by comparing with `stoprev` before pushing
108 108 impl<G: Graph> Iterator for AncestorsIterator<G> {
109 109 type Item = Result<Revision, GraphError>;
110 110
111 111 fn next(&mut self) -> Option<Self::Item> {
112 112 let current = match self.visit.peek() {
113 113 None => {
114 114 return None;
115 115 }
116 116 Some(c) => *c,
117 117 };
118 118 let (p1, p2) = match self.graph.parents(current) {
119 119 Ok(ps) => ps,
120 120 Err(e) => return Some(Err(e)),
121 121 };
122 122 if p1 < self.stoprev || self.seen.contains(&p1) {
123 123 self.visit.pop();
124 124 } else {
125 125 *(self.visit.peek_mut().unwrap()) = p1;
126 126 self.seen.insert(p1);
127 127 };
128 128
129 129 self.conditionally_push_rev(p2);
130 130 Some(Ok(current))
131 131 }
132 132 }
133 133
134 134 #[cfg(test)]
135 135 mod tests {
136 136
137 137 use super::*;
138 138
139 139 #[derive(Clone, Debug)]
140 140 struct Stub;
141 141
142 142 /// This is the same as the dict from test-ancestors.py
143 143 impl Graph for Stub {
144 144 fn parents(
145 145 &self,
146 146 rev: Revision,
147 147 ) -> Result<(Revision, Revision), GraphError> {
148 148 match rev {
149 149 0 => Ok((-1, -1)),
150 150 1 => Ok((0, -1)),
151 151 2 => Ok((1, -1)),
152 152 3 => Ok((1, -1)),
153 153 4 => Ok((2, -1)),
154 154 5 => Ok((4, -1)),
155 155 6 => Ok((4, -1)),
156 156 7 => Ok((4, -1)),
157 157 8 => Ok((-1, -1)),
158 158 9 => Ok((6, 7)),
159 159 10 => Ok((5, -1)),
160 160 11 => Ok((3, 7)),
161 161 12 => Ok((9, -1)),
162 162 13 => Ok((8, -1)),
163 163 r => Err(GraphError::ParentOutOfRange(r)),
164 164 }
165 165 }
166 166 }
167 167
168 168 fn list_ancestors<G: Graph>(
169 169 graph: G,
170 170 initrevs: Vec<Revision>,
171 171 stoprev: Revision,
172 172 inclusive: bool,
173 173 ) -> Vec<Revision> {
174 174 AncestorsIterator::new(graph, initrevs, stoprev, inclusive)
175 175 .unwrap()
176 .map(|res| res.unwrap())
176 177 .collect()
177 178 }
178 179
179 180 #[test]
180 181 /// Same tests as test-ancestor.py, without membership
181 182 /// (see also test-ancestor.py.out)
182 183 fn test_list_ancestor() {
183 184 assert_eq!(list_ancestors(Stub, vec![], 0, false), vec![]);
184 185 assert_eq!(
185 186 list_ancestors(Stub, vec![11, 13], 0, false),
186 187 vec![8, 7, 4, 3, 2, 1, 0]
187 188 );
188 189 assert_eq!(list_ancestors(Stub, vec![1, 3], 0, false), vec![1, 0]);
189 190 assert_eq!(
190 191 list_ancestors(Stub, vec![11, 13], 0, true),
191 192 vec![13, 11, 8, 7, 4, 3, 2, 1, 0]
192 193 );
193 194 assert_eq!(list_ancestors(Stub, vec![11, 13], 6, false), vec![8, 7]);
194 195 assert_eq!(
195 196 list_ancestors(Stub, vec![11, 13], 6, true),
196 197 vec![13, 11, 8, 7]
197 198 );
198 199 assert_eq!(list_ancestors(Stub, vec![11, 13], 11, true), vec![13, 11]);
199 200 assert_eq!(list_ancestors(Stub, vec![11, 13], 12, true), vec![13]);
200 201 assert_eq!(
201 202 list_ancestors(Stub, vec![10, 1], 0, true),
202 203 vec![10, 5, 4, 2, 1, 0]
203 204 );
204 205 }
205 206
206 207 #[test]
207 208 /// Corner case that's not directly in test-ancestors.py, but
208 209 /// that happens quite often, as demonstrated by running the whole
209 210 /// suite.
210 211 /// For instance, run tests/test-obsolete-checkheads.t
211 212 fn test_nullrev_input() {
212 213 let mut iter =
213 214 AncestorsIterator::new(Stub, vec![-1], 0, false).unwrap();
214 215 assert_eq!(iter.next(), None)
215 216 }
216 217
217 218 #[test]
218 219 fn test_contains() {
219 220 let mut lazy =
220 221 AncestorsIterator::new(Stub, vec![10, 1], 0, true).unwrap();
221 assert!(lazy.contains(1));
222 assert!(!lazy.contains(3));
222 assert!(lazy.contains(1).unwrap());
223 assert!(!lazy.contains(3).unwrap());
223 224
224 225 let mut lazy =
225 226 AncestorsIterator::new(Stub, vec![0], 0, false).unwrap();
226 assert!(!lazy.contains(NULL_REVISION));
227 assert!(!lazy.contains(NULL_REVISION).unwrap());
227 228 }
228 229
229 230 /// A corrupted Graph, supporting error handling tests
230 231 struct Corrupted;
231 232
232 233 impl Graph for Corrupted {
233 234 fn parents(
234 235 &self,
235 236 rev: Revision,
236 237 ) -> Result<(Revision, Revision), GraphError> {
237 238 match rev {
238 239 1 => Ok((0, -1)),
239 240 r => Err(GraphError::ParentOutOfRange(r)),
240 241 }
241 242 }
242 243 }
243 244
244 245 #[test]
245 246 fn test_initrev_out_of_range() {
246 247 // inclusive=false looks up initrev's parents right away
247 248 match AncestorsIterator::new(Stub, vec![25], 0, false) {
248 249 Ok(_) => panic!("Should have been ParentOutOfRange"),
249 250 Err(e) => assert_eq!(e, GraphError::ParentOutOfRange(25)),
250 251 }
251 252 }
252 253
253 254 #[test]
254 255 fn test_next_out_of_range() {
255 256 // inclusive=false looks up initrev's parents right away
256 257 let mut iter =
257 258 AncestorsIterator::new(Corrupted, vec![1], 0, false).unwrap();
258 assert_eq!(iter.next(), Some(0));
259 assert_eq!(iter.next(), None);
259 assert_eq!(iter.next(), Some(Err(GraphError::ParentOutOfRange(0))));
260 260 }
261 261 }
General Comments 0
You need to be logged in to leave comments. Login now