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