##// END OF EJS Templates
rust: propagate error of index_get_parents() properly...
Yuya Nishihara -
r41244:443eb4bc default
parent child Browse files
Show More
@@ -1,272 +1,273 b''
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 pub fn contains(&mut self, target: Revision) -> bool {
80 pub fn contains(&mut self, target: Revision) -> Result<bool, GraphError> {
81 81 if self.seen.contains(&target) && target != NULL_REVISION {
82 return true;
82 return Ok(true);
83 83 }
84 for rev in self {
84 for item in self {
85 let rev = item?;
85 86 if rev == target {
86 return true;
87 return Ok(true);
87 88 }
88 89 if rev < target {
89 return false;
90 return Ok(false);
90 91 }
91 92 }
92 false
93 Ok(false)
93 94 }
94 95 }
95 96
96 97 /// Main implementation.
97 98 ///
98 99 /// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py`
99 100 /// with a few non crucial differences:
100 101 ///
101 102 /// - there's no filtering of invalid parent revisions. Actually, it should be
102 103 /// consistent and more efficient to filter them from the end caller.
103 104 /// - we don't have the optimization for adjacent revs
104 105 /// (case where p1 == rev-1), because it amounts to update the first element
105 106 /// of the heap without sifting, which Rust's BinaryHeap doesn't let us do.
106 107 /// - we save a few pushes by comparing with `stoprev` before pushing
107 108 ///
108 109 /// Error treatment:
109 110 /// We swallow the possible GraphError of conditionally_push_parents() to
110 111 /// respect the Iterator trait in a simple manner: never emitting parents
111 112 /// for the returned revision. We finds this good enough for now, because:
112 113 ///
113 114 /// - there's a good chance that invalid revisionss are fed from the start,
114 115 /// and `new()` doesn't swallow the error result.
115 116 /// - this is probably what the Python implementation produces anyway, due
116 117 /// to filtering at each step, and Python code is currently the only
117 118 /// concrete caller we target, so we shouldn't need a finer error treatment
118 119 /// for the time being.
119 120 impl<G: Graph> Iterator for AncestorsIterator<G> {
120 type Item = Revision;
121 type Item = Result<Revision, GraphError>;
121 122
122 fn next(&mut self) -> Option<Revision> {
123 fn next(&mut self) -> Option<Self::Item> {
123 124 let current = match self.visit.peek() {
124 125 None => {
125 126 return None;
126 127 }
127 128 Some(c) => *c,
128 129 };
129 let (p1, p2) = self
130 .graph
131 .parents(current)
132 .unwrap_or((NULL_REVISION, NULL_REVISION));
130 let (p1, p2) = match self.graph.parents(current) {
131 Ok(ps) => ps,
132 Err(e) => return Some(Err(e)),
133 };
133 134 if p1 < self.stoprev || self.seen.contains(&p1) {
134 135 self.visit.pop();
135 136 } else {
136 137 *(self.visit.peek_mut().unwrap()) = p1;
137 138 self.seen.insert(p1);
138 139 };
139 140
140 141 self.conditionally_push_rev(p2);
141 Some(current)
142 Some(Ok(current))
142 143 }
143 144 }
144 145
145 146 #[cfg(test)]
146 147 mod tests {
147 148
148 149 use super::*;
149 150
150 151 #[derive(Clone, Debug)]
151 152 struct Stub;
152 153
153 154 /// This is the same as the dict from test-ancestors.py
154 155 impl Graph for Stub {
155 156 fn parents(
156 157 &self,
157 158 rev: Revision,
158 159 ) -> Result<(Revision, Revision), GraphError> {
159 160 match rev {
160 161 0 => Ok((-1, -1)),
161 162 1 => Ok((0, -1)),
162 163 2 => Ok((1, -1)),
163 164 3 => Ok((1, -1)),
164 165 4 => Ok((2, -1)),
165 166 5 => Ok((4, -1)),
166 167 6 => Ok((4, -1)),
167 168 7 => Ok((4, -1)),
168 169 8 => Ok((-1, -1)),
169 170 9 => Ok((6, 7)),
170 171 10 => Ok((5, -1)),
171 172 11 => Ok((3, 7)),
172 173 12 => Ok((9, -1)),
173 174 13 => Ok((8, -1)),
174 175 r => Err(GraphError::ParentOutOfRange(r)),
175 176 }
176 177 }
177 178 }
178 179
179 180 fn list_ancestors<G: Graph>(
180 181 graph: G,
181 182 initrevs: Vec<Revision>,
182 183 stoprev: Revision,
183 184 inclusive: bool,
184 185 ) -> Vec<Revision> {
185 186 AncestorsIterator::new(graph, initrevs, stoprev, inclusive)
186 187 .unwrap()
187 188 .collect()
188 189 }
189 190
190 191 #[test]
191 192 /// Same tests as test-ancestor.py, without membership
192 193 /// (see also test-ancestor.py.out)
193 194 fn test_list_ancestor() {
194 195 assert_eq!(list_ancestors(Stub, vec![], 0, false), vec![]);
195 196 assert_eq!(
196 197 list_ancestors(Stub, vec![11, 13], 0, false),
197 198 vec![8, 7, 4, 3, 2, 1, 0]
198 199 );
199 200 assert_eq!(list_ancestors(Stub, vec![1, 3], 0, false), vec![1, 0]);
200 201 assert_eq!(
201 202 list_ancestors(Stub, vec![11, 13], 0, true),
202 203 vec![13, 11, 8, 7, 4, 3, 2, 1, 0]
203 204 );
204 205 assert_eq!(list_ancestors(Stub, vec![11, 13], 6, false), vec![8, 7]);
205 206 assert_eq!(
206 207 list_ancestors(Stub, vec![11, 13], 6, true),
207 208 vec![13, 11, 8, 7]
208 209 );
209 210 assert_eq!(list_ancestors(Stub, vec![11, 13], 11, true), vec![13, 11]);
210 211 assert_eq!(list_ancestors(Stub, vec![11, 13], 12, true), vec![13]);
211 212 assert_eq!(
212 213 list_ancestors(Stub, vec![10, 1], 0, true),
213 214 vec![10, 5, 4, 2, 1, 0]
214 215 );
215 216 }
216 217
217 218 #[test]
218 219 /// Corner case that's not directly in test-ancestors.py, but
219 220 /// that happens quite often, as demonstrated by running the whole
220 221 /// suite.
221 222 /// For instance, run tests/test-obsolete-checkheads.t
222 223 fn test_nullrev_input() {
223 224 let mut iter =
224 225 AncestorsIterator::new(Stub, vec![-1], 0, false).unwrap();
225 226 assert_eq!(iter.next(), None)
226 227 }
227 228
228 229 #[test]
229 230 fn test_contains() {
230 231 let mut lazy =
231 232 AncestorsIterator::new(Stub, vec![10, 1], 0, true).unwrap();
232 233 assert!(lazy.contains(1));
233 234 assert!(!lazy.contains(3));
234 235
235 236 let mut lazy =
236 237 AncestorsIterator::new(Stub, vec![0], 0, false).unwrap();
237 238 assert!(!lazy.contains(NULL_REVISION));
238 239 }
239 240
240 241 /// A corrupted Graph, supporting error handling tests
241 242 struct Corrupted;
242 243
243 244 impl Graph for Corrupted {
244 245 fn parents(
245 246 &self,
246 247 rev: Revision,
247 248 ) -> Result<(Revision, Revision), GraphError> {
248 249 match rev {
249 250 1 => Ok((0, -1)),
250 251 r => Err(GraphError::ParentOutOfRange(r)),
251 252 }
252 253 }
253 254 }
254 255
255 256 #[test]
256 257 fn test_initrev_out_of_range() {
257 258 // inclusive=false looks up initrev's parents right away
258 259 match AncestorsIterator::new(Stub, vec![25], 0, false) {
259 260 Ok(_) => panic!("Should have been ParentOutOfRange"),
260 261 Err(e) => assert_eq!(e, GraphError::ParentOutOfRange(25)),
261 262 }
262 263 }
263 264
264 265 #[test]
265 266 fn test_next_out_of_range() {
266 267 // inclusive=false looks up initrev's parents right away
267 268 let mut iter =
268 269 AncestorsIterator::new(Corrupted, vec![1], 0, false).unwrap();
269 270 assert_eq!(iter.next(), Some(0));
270 271 assert_eq!(iter.next(), None);
271 272 }
272 273 }
@@ -1,267 +1,271 b''
1 1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5
6 6 //! Bindings for CPython extension code
7 7 //!
8 8 //! This exposes methods to build and use a `rustlazyancestors` iterator
9 9 //! from C code, using an index and its parents function that are passed
10 10 //! from the caller at instantiation.
11 11
12 12 use hg::AncestorsIterator;
13 13 use hg::{Graph, GraphError, Revision, NULL_REVISION};
14 14 use libc::{c_int, c_long, c_void, ssize_t};
15 15 use std::ptr::null_mut;
16 16 use std::slice;
17 17
18 18 type IndexPtr = *mut c_void;
19 19
20 20 extern "C" {
21 21 fn HgRevlogIndex_GetParents(
22 22 op: IndexPtr,
23 23 rev: c_int,
24 24 parents: *mut [c_int; 2],
25 25 ) -> c_int;
26 26 }
27 27
28 28 /// A Graph backed up by objects and functions from revlog.c
29 29 ///
30 30 /// This implementation of the Graph trait, relies on (pointers to)
31 31 /// - the C index object (`index` member)
32 32 /// - the `index_get_parents()` function (`parents` member)
33 33 pub struct Index {
34 34 index: IndexPtr,
35 35 }
36 36
37 37 impl Index {
38 38 pub fn new(index: IndexPtr) -> Self {
39 39 Index {
40 40 index: index,
41 41 }
42 42 }
43 43 }
44 44
45 45 impl Graph for Index {
46 46 /// wrap a call to the C extern parents function
47 47 fn parents(&self, rev: Revision) -> Result<(Revision, Revision), GraphError> {
48 48 let mut res: [c_int; 2] = [0; 2];
49 49 let code =
50 50 unsafe { HgRevlogIndex_GetParents(self.index, rev, &mut res as *mut [c_int; 2]) };
51 51 match code {
52 52 0 => Ok((res[0], res[1])),
53 53 _ => Err(GraphError::ParentOutOfRange(rev)),
54 54 }
55 55 }
56 56 }
57 57
58 58 /// Wrapping of AncestorsIterator<Index> constructor, for C callers.
59 59 ///
60 60 /// Besides `initrevs`, `stoprev` and `inclusive`, that are converted
61 61 /// we receive the index and the parents function as pointers
62 62 #[no_mangle]
63 63 pub extern "C" fn rustlazyancestors_init(
64 64 index: IndexPtr,
65 65 initrevslen: ssize_t,
66 66 initrevs: *mut c_long,
67 67 stoprev: c_long,
68 68 inclusive: c_int,
69 69 ) -> *mut AncestorsIterator<Index> {
70 70 assert!(initrevslen >= 0);
71 71 unsafe {
72 72 raw_init(
73 73 Index::new(index),
74 74 initrevslen as usize,
75 75 initrevs,
76 76 stoprev,
77 77 inclusive,
78 78 )
79 79 }
80 80 }
81 81
82 82 /// Testable (for any Graph) version of rustlazyancestors_init
83 83 #[inline]
84 84 unsafe fn raw_init<G: Graph>(
85 85 graph: G,
86 86 initrevslen: usize,
87 87 initrevs: *mut c_long,
88 88 stoprev: c_long,
89 89 inclusive: c_int,
90 90 ) -> *mut AncestorsIterator<G> {
91 91 let inclb = match inclusive {
92 92 0 => false,
93 93 1 => true,
94 94 _ => {
95 95 return null_mut();
96 96 }
97 97 };
98 98
99 99 let slice = slice::from_raw_parts(initrevs, initrevslen);
100 100
101 101 Box::into_raw(Box::new(match AncestorsIterator::new(
102 102 graph,
103 103 slice.into_iter().map(|&r| r as Revision),
104 104 stoprev as Revision,
105 105 inclb,
106 106 ) {
107 107 Ok(it) => it,
108 108 Err(_) => {
109 109 return null_mut();
110 110 }
111 111 }))
112 112 }
113 113
114 114 /// Deallocator to be called from C code
115 115 #[no_mangle]
116 116 pub extern "C" fn rustlazyancestors_drop(raw_iter: *mut AncestorsIterator<Index>) {
117 117 raw_drop(raw_iter);
118 118 }
119 119
120 120 /// Testable (for any Graph) version of rustlazayancestors_drop
121 121 #[inline]
122 122 fn raw_drop<G: Graph>(raw_iter: *mut AncestorsIterator<G>) {
123 123 unsafe {
124 124 Box::from_raw(raw_iter);
125 125 }
126 126 }
127 127
128 128 /// Iteration main method to be called from C code
129 129 ///
130 130 /// We convert the end of iteration into NULL_REVISION,
131 131 /// it will be up to the C wrapper to convert that back into a Python end of
132 132 /// iteration
133 133 #[no_mangle]
134 134 pub extern "C" fn rustlazyancestors_next(raw: *mut AncestorsIterator<Index>) -> c_long {
135 135 raw_next(raw)
136 136 }
137 137
138 138 /// Testable (for any Graph) version of rustlazayancestors_next
139 139 #[inline]
140 140 fn raw_next<G: Graph>(raw: *mut AncestorsIterator<G>) -> c_long {
141 141 let as_ref = unsafe { &mut *raw };
142 as_ref.next().unwrap_or(NULL_REVISION) as c_long
142 let rev = match as_ref.next() {
143 Some(Ok(rev)) => rev,
144 Some(Err(_)) | None => NULL_REVISION,
145 };
146 rev as c_long
143 147 }
144 148
145 149 #[no_mangle]
146 150 pub extern "C" fn rustlazyancestors_contains(
147 151 raw: *mut AncestorsIterator<Index>,
148 152 target: c_long,
149 153 ) -> c_int {
150 154 raw_contains(raw, target)
151 155 }
152 156
153 157 /// Testable (for any Graph) version of rustlazayancestors_next
154 158 #[inline]
155 159 fn raw_contains<G: Graph>(
156 160 raw: *mut AncestorsIterator<G>,
157 161 target: c_long,
158 162 ) -> c_int {
159 163 let as_ref = unsafe { &mut *raw };
160 if as_ref.contains(target as Revision) {
161 return 1;
164 match as_ref.contains(target as Revision) {
165 Ok(r) => r as c_int,
166 Err(_) => -1,
162 167 }
163 0
164 168 }
165 169
166 170 #[cfg(test)]
167 171 mod tests {
168 172 use super::*;
169 173 use std::thread;
170 174
171 175 #[derive(Clone, Debug)]
172 176 struct Stub;
173 177
174 178 impl Graph for Stub {
175 179 fn parents(&self, r: Revision) -> Result<(Revision, Revision), GraphError> {
176 180 match r {
177 181 25 => Err(GraphError::ParentOutOfRange(25)),
178 182 _ => Ok((1, 2)),
179 183 }
180 184 }
181 185 }
182 186
183 187 /// Helper for test_init_next()
184 188 fn stub_raw_init(
185 189 initrevslen: usize,
186 190 initrevs: usize,
187 191 stoprev: c_long,
188 192 inclusive: c_int,
189 193 ) -> usize {
190 194 unsafe {
191 195 raw_init(
192 196 Stub,
193 197 initrevslen,
194 198 initrevs as *mut c_long,
195 199 stoprev,
196 200 inclusive,
197 201 ) as usize
198 202 }
199 203 }
200 204
201 205 fn stub_raw_init_from_vec(
202 206 mut initrevs: Vec<c_long>,
203 207 stoprev: c_long,
204 208 inclusive: c_int,
205 209 ) -> *mut AncestorsIterator<Stub> {
206 210 unsafe {
207 211 raw_init(
208 212 Stub,
209 213 initrevs.len(),
210 214 initrevs.as_mut_ptr(),
211 215 stoprev,
212 216 inclusive,
213 217 )
214 218 }
215 219 }
216 220
217 221 #[test]
218 222 // Test what happens when we init an Iterator as with the exposed C ABI
219 223 // and try to use it afterwards
220 224 // We spawn new threads, in order to make memory consistency harder
221 225 // but this forces us to convert the pointers into shareable usizes.
222 226 fn test_init_next() {
223 227 let mut initrevs: Vec<c_long> = vec![11, 13];
224 228 let initrevs_len = initrevs.len();
225 229 let initrevs_ptr = initrevs.as_mut_ptr() as usize;
226 230 let handler = thread::spawn(move || stub_raw_init(initrevs_len, initrevs_ptr, 0, 1));
227 231 let raw = handler.join().unwrap() as *mut AncestorsIterator<Stub>;
228 232
229 233 assert_eq!(raw_next(raw), 13);
230 234 assert_eq!(raw_next(raw), 11);
231 235 assert_eq!(raw_next(raw), 2);
232 236 assert_eq!(raw_next(raw), 1);
233 237 assert_eq!(raw_next(raw), NULL_REVISION as c_long);
234 238 raw_drop(raw);
235 239 }
236 240
237 241 #[test]
238 242 fn test_init_wrong_bool() {
239 243 assert_eq!(stub_raw_init_from_vec(vec![11, 13], 0, 2), null_mut());
240 244 }
241 245
242 246 #[test]
243 247 fn test_empty() {
244 248 let raw = stub_raw_init_from_vec(vec![], 0, 1);
245 249 assert_eq!(raw_next(raw), NULL_REVISION as c_long);
246 250 raw_drop(raw);
247 251 }
248 252
249 253 #[test]
250 254 fn test_init_err_out_of_range() {
251 255 assert!(stub_raw_init_from_vec(vec![25], 0, 0).is_null());
252 256 }
253 257
254 258 #[test]
255 259 fn test_contains() {
256 260 let raw = stub_raw_init_from_vec(vec![5, 6], 0, 1);
257 261 assert_eq!(raw_contains(raw, 5), 1);
258 262 assert_eq!(raw_contains(raw, 2), 1);
259 263 }
260 264
261 265 #[test]
262 266 fn test_contains_exclusive() {
263 267 let raw = stub_raw_init_from_vec(vec![5, 6], 0, 0);
264 268 assert_eq!(raw_contains(raw, 5), 0);
265 269 assert_eq!(raw_contains(raw, 2), 1);
266 270 }
267 271 }
General Comments 0
You need to be logged in to leave comments. Login now