ancestors.rs
846 lines
| 26.7 KiB
| application/rls-services+xml
|
RustLexer
Georges Racinet
|
r40307 | // ancestors.rs | ||
// | ||||
// Copyright 2018 Georges Racinet <gracinet@anybox.fr> | ||||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
//! Rust versions of generic DAG ancestors algorithms for Mercurial | ||||
use super::{Graph, GraphError, Revision, NULL_REVISION}; | ||||
Raphaël Gomès
|
r42514 | use crate::dagops; | ||
Georges Racinet
|
r40995 | use std::cmp::max; | ||
Georges Racinet
|
r40307 | use std::collections::{BinaryHeap, HashSet}; | ||
/// Iterator over the ancestors of a given list of revisions | ||||
/// This is a generic type, defined and implemented for any Graph, so that | ||||
/// it's easy to | ||||
/// | ||||
/// - unit test in pure Rust | ||||
/// - bind to main Mercurial code, potentially in several ways and have these | ||||
/// bindings evolve over time | ||||
pub struct AncestorsIterator<G: Graph> { | ||||
graph: G, | ||||
visit: BinaryHeap<Revision>, | ||||
seen: HashSet<Revision>, | ||||
stoprev: Revision, | ||||
} | ||||
Georges Racinet
|
r40995 | pub struct MissingAncestors<G: Graph> { | ||
graph: G, | ||||
bases: HashSet<Revision>, | ||||
Georges Racinet
|
r41866 | max_base: Revision, | ||
Georges Racinet
|
r40995 | } | ||
Georges Racinet
|
r40307 | impl<G: Graph> AncestorsIterator<G> { | ||
/// Constructor. | ||||
/// | ||||
/// if `inclusive` is true, then the init revisions are emitted in | ||||
/// particular, otherwise iteration starts from their parents. | ||||
Yuya Nishihara
|
r41138 | pub fn new( | ||
Georges Racinet
|
r40307 | graph: G, | ||
Yuya Nishihara
|
r41138 | initrevs: impl IntoIterator<Item = Revision>, | ||
Georges Racinet
|
r40307 | stoprev: Revision, | ||
inclusive: bool, | ||||
Yuya Nishihara
|
r41138 | ) -> Result<Self, GraphError> { | ||
Georges Racinet
|
r40307 | let filtered_initrevs = initrevs.into_iter().filter(|&r| r >= stoprev); | ||
if inclusive { | ||||
let visit: BinaryHeap<Revision> = filtered_initrevs.collect(); | ||||
Raphaël Gomès
|
r45500 | let seen = visit.iter().cloned().collect(); | ||
Georges Racinet
|
r40307 | return Ok(AncestorsIterator { | ||
Raphaël Gomès
|
r45500 | visit, | ||
seen, | ||||
stoprev, | ||||
graph, | ||||
Georges Racinet
|
r40307 | }); | ||
} | ||||
let mut this = AncestorsIterator { | ||||
visit: BinaryHeap::new(), | ||||
seen: HashSet::new(), | ||||
Raphaël Gomès
|
r45500 | stoprev, | ||
graph, | ||||
Georges Racinet
|
r40307 | }; | ||
this.seen.insert(NULL_REVISION); | ||||
for rev in filtered_initrevs { | ||||
Georges Racinet
|
r40969 | for parent in this.graph.parents(rev)?.iter().cloned() { | ||
this.conditionally_push_rev(parent); | ||||
} | ||||
Georges Racinet
|
r40307 | } | ||
Ok(this) | ||||
} | ||||
#[inline] | ||||
fn conditionally_push_rev(&mut self, rev: Revision) { | ||||
Georges Racinet
|
r41863 | if self.stoprev <= rev && self.seen.insert(rev) { | ||
Georges Racinet
|
r40307 | self.visit.push(rev); | ||
} | ||||
} | ||||
Georges Racinet
|
r40336 | /// Consumes partially the iterator to tell if the given target | ||
/// revision | ||||
/// is in the ancestors it emits. | ||||
/// This is meant for iterators actually dedicated to that kind of | ||||
/// purpose | ||||
Yuya Nishihara
|
r40898 | pub fn contains(&mut self, target: Revision) -> Result<bool, GraphError> { | ||
Georges Racinet
|
r40336 | if self.seen.contains(&target) && target != NULL_REVISION { | ||
Yuya Nishihara
|
r40898 | return Ok(true); | ||
Georges Racinet
|
r40336 | } | ||
Yuya Nishihara
|
r40898 | for item in self { | ||
let rev = item?; | ||||
Georges Racinet
|
r40336 | if rev == target { | ||
Yuya Nishihara
|
r40898 | return Ok(true); | ||
Georges Racinet
|
r40336 | } | ||
if rev < target { | ||||
Yuya Nishihara
|
r40898 | return Ok(false); | ||
Georges Racinet
|
r40336 | } | ||
} | ||||
Yuya Nishihara
|
r40898 | Ok(false) | ||
Georges Racinet
|
r40336 | } | ||
Georges Racinet
|
r41084 | |||
pub fn peek(&self) -> Option<Revision> { | ||||
Raphaël Gomès
|
r45500 | self.visit.peek().cloned() | ||
Georges Racinet
|
r41084 | } | ||
/// Tell if the iterator is about an empty set | ||||
/// | ||||
/// The result does not depend whether the iterator has been consumed | ||||
/// or not. | ||||
/// This is mostly meant for iterators backing a lazy ancestors set | ||||
pub fn is_empty(&self) -> bool { | ||||
if self.visit.len() > 0 { | ||||
return false; | ||||
} | ||||
if self.seen.len() > 1 { | ||||
return false; | ||||
} | ||||
// at this point, the seen set is at most a singleton. | ||||
// If not `self.inclusive`, it's still possible that it has only | ||||
// the null revision | ||||
self.seen.is_empty() || self.seen.contains(&NULL_REVISION) | ||||
} | ||||
Georges Racinet
|
r40307 | } | ||
Georges Racinet
|
r41084 | /// Main implementation for the iterator | ||
Georges Racinet
|
r40307 | /// | ||
/// The algorithm is the same as in `_lazyancestorsiter()` from `ancestors.py` | ||||
/// with a few non crucial differences: | ||||
/// | ||||
/// - there's no filtering of invalid parent revisions. Actually, it should be | ||||
/// consistent and more efficient to filter them from the end caller. | ||||
Georges Racinet
|
r40968 | /// - we don't have the optimization for adjacent revisions (i.e., the case | ||
/// where `p1 == rev - 1`), because it amounts to update the first element of | ||||
/// the heap without sifting, which Rust's BinaryHeap doesn't let us do. | ||||
Georges Racinet
|
r40307 | /// - we save a few pushes by comparing with `stoprev` before pushing | ||
impl<G: Graph> Iterator for AncestorsIterator<G> { | ||||
Yuya Nishihara
|
r40898 | type Item = Result<Revision, GraphError>; | ||
Georges Racinet
|
r40307 | |||
Yuya Nishihara
|
r40898 | fn next(&mut self) -> Option<Self::Item> { | ||
Georges Racinet
|
r40847 | let current = match self.visit.peek() { | ||
Georges Racinet
|
r40307 | None => { | ||
return None; | ||||
} | ||||
Georges Racinet
|
r40847 | Some(c) => *c, | ||
Georges Racinet
|
r40307 | }; | ||
Georges Racinet
|
r40969 | let [p1, p2] = match self.graph.parents(current) { | ||
Yuya Nishihara
|
r40898 | Ok(ps) => ps, | ||
Err(e) => return Some(Err(e)), | ||||
}; | ||||
Georges Racinet
|
r41863 | if p1 < self.stoprev || !self.seen.insert(p1) { | ||
Georges Racinet
|
r40847 | self.visit.pop(); | ||
} else { | ||||
*(self.visit.peek_mut().unwrap()) = p1; | ||||
}; | ||||
Georges Racinet
|
r40867 | self.conditionally_push_rev(p2); | ||
Yuya Nishihara
|
r40898 | Some(Ok(current)) | ||
Georges Racinet
|
r40307 | } | ||
} | ||||
Georges Racinet
|
r40995 | impl<G: Graph> MissingAncestors<G> { | ||
pub fn new(graph: G, bases: impl IntoIterator<Item = Revision>) -> Self { | ||||
Georges Racinet
|
r41866 | let mut created = MissingAncestors { | ||
Raphaël Gomès
|
r45500 | graph, | ||
Georges Racinet
|
r41866 | bases: HashSet::new(), | ||
max_base: NULL_REVISION, | ||||
}; | ||||
created.add_bases(bases); | ||||
created | ||||
Georges Racinet
|
r40995 | } | ||
pub fn has_bases(&self) -> bool { | ||||
Georges Racinet
|
r41865 | !self.bases.is_empty() | ||
Georges Racinet
|
r40995 | } | ||
/// Return a reference to current bases. | ||||
/// | ||||
/// This is useful in unit tests, but also setdiscovery.py does | ||||
/// read the bases attribute of a ancestor.missingancestors instance. | ||||
Raphaël Gomès
|
r50825 | pub fn get_bases(&self) -> &HashSet<Revision> { | ||
Georges Racinet
|
r40995 | &self.bases | ||
} | ||||
Georges Racinet
|
r41282 | /// Computes the relative heads of current bases. | ||
/// | ||||
/// The object is still usable after this. | ||||
pub fn bases_heads(&self) -> Result<HashSet<Revision>, GraphError> { | ||||
dagops::heads(&self.graph, self.bases.iter()) | ||||
} | ||||
/// Consumes the object and returns the relative heads of its bases. | ||||
Georges Racinet
|
r41866 | pub fn into_bases_heads( | ||
mut self, | ||||
) -> Result<HashSet<Revision>, GraphError> { | ||||
Georges Racinet
|
r41282 | dagops::retain_heads(&self.graph, &mut self.bases)?; | ||
Ok(self.bases) | ||||
} | ||||
Georges Racinet
|
r41866 | /// Add some revisions to `self.bases` | ||
/// | ||||
/// Takes care of keeping `self.max_base` up to date. | ||||
Georges Racinet
|
r40995 | pub fn add_bases( | ||
&mut self, | ||||
new_bases: impl IntoIterator<Item = Revision>, | ||||
) { | ||||
Georges Racinet
|
r41866 | let mut max_base = self.max_base; | ||
self.bases.extend( | ||||
new_bases | ||||
.into_iter() | ||||
.filter(|&rev| rev != NULL_REVISION) | ||||
Raphaël Gomès
|
r53188 | .inspect(|&r| { | ||
Georges Racinet
|
r41866 | if r > max_base { | ||
max_base = r; | ||||
} | ||||
}), | ||||
); | ||||
self.max_base = max_base; | ||||
Georges Racinet
|
r40995 | } | ||
/// Remove all ancestors of self.bases from the revs set (in place) | ||||
pub fn remove_ancestors_from( | ||||
&mut self, | ||||
revs: &mut HashSet<Revision>, | ||||
) -> Result<(), GraphError> { | ||||
revs.retain(|r| !self.bases.contains(r)); | ||||
Georges Racinet
|
r41865 | // the null revision is always an ancestor. Logically speaking | ||
// it's debatable in case bases is empty, but the Python | ||||
// implementation always adds NULL_REVISION to bases, making it | ||||
// unconditionnally true. | ||||
Georges Racinet
|
r40995 | revs.remove(&NULL_REVISION); | ||
if revs.is_empty() { | ||||
return Ok(()); | ||||
} | ||||
// anything in revs > start is definitely not an ancestor of bases | ||||
// revs <= start need to be investigated | ||||
Georges Racinet
|
r41866 | if self.max_base == NULL_REVISION { | ||
return Ok(()); | ||||
} | ||||
Georges Racinet
|
r40995 | // whatever happens, we'll keep at least keepcount of them | ||
// knowing this gives us a earlier stop condition than | ||||
// going all the way to the root | ||||
Georges Racinet
|
r41866 | let keepcount = revs.iter().filter(|r| **r > self.max_base).count(); | ||
Georges Racinet
|
r40995 | |||
Georges Racinet
|
r41866 | let mut curr = self.max_base; | ||
Georges Racinet
|
r40995 | while curr != NULL_REVISION && revs.len() > keepcount { | ||
if self.bases.contains(&curr) { | ||||
revs.remove(&curr); | ||||
self.add_parents(curr)?; | ||||
} | ||||
Raphaël Gomès
|
r51872 | // We know this revision is safe because we've checked the bounds | ||
// before. | ||||
curr = Revision(curr.0 - 1); | ||||
Georges Racinet
|
r40995 | } | ||
Ok(()) | ||||
} | ||||
Georges Racinet
|
r41866 | /// Add the parents of `rev` to `self.bases` | ||
/// | ||||
/// This has no effect on `self.max_base` | ||||
Georges Racinet
|
r40995 | #[inline] | ||
fn add_parents(&mut self, rev: Revision) -> Result<(), GraphError> { | ||||
Georges Racinet
|
r41866 | if rev == NULL_REVISION { | ||
return Ok(()); | ||||
} | ||||
Georges Racinet
|
r40995 | for p in self.graph.parents(rev)?.iter().cloned() { | ||
Georges Racinet
|
r41866 | // No need to bother the set with inserting NULL_REVISION over and | ||
// over | ||||
Georges Racinet
|
r40995 | if p != NULL_REVISION { | ||
self.bases.insert(p); | ||||
} | ||||
} | ||||
Ok(()) | ||||
} | ||||
/// Return all the ancestors of revs that are not ancestors of self.bases | ||||
/// | ||||
/// This may include elements from revs. | ||||
/// | ||||
/// Equivalent to the revset (::revs - ::self.bases). Revs are returned in | ||||
/// revision number order, which is a topological order. | ||||
pub fn missing_ancestors( | ||||
&mut self, | ||||
revs: impl IntoIterator<Item = Revision>, | ||||
) -> Result<Vec<Revision>, GraphError> { | ||||
// just for convenience and comparison with Python version | ||||
let bases_visit = &mut self.bases; | ||||
let mut revs: HashSet<Revision> = revs | ||||
.into_iter() | ||||
.filter(|r| !bases_visit.contains(r)) | ||||
.collect(); | ||||
let revs_visit = &mut revs; | ||||
let mut both_visit: HashSet<Revision> = | ||||
Raphaël Gomès
|
r50825 | revs_visit.intersection(bases_visit).cloned().collect(); | ||
Georges Racinet
|
r40995 | if revs_visit.is_empty() { | ||
return Ok(Vec::new()); | ||||
} | ||||
Georges Racinet
|
r41866 | let max_revs = revs_visit.iter().cloned().max().unwrap(); | ||
let start = max(self.max_base, max_revs); | ||||
Georges Racinet
|
r40995 | |||
// TODO heuristics for with_capacity()? | ||||
let mut missing: Vec<Revision> = Vec::new(); | ||||
Raphaël Gomès
|
r51872 | for curr in (0..=start.0).rev() { | ||
Georges Racinet
|
r40995 | if revs_visit.is_empty() { | ||
break; | ||||
} | ||||
Raphaël Gomès
|
r51872 | if both_visit.remove(&Revision(curr)) { | ||
Georges Racinet
|
r40995 | // curr's parents might have made it into revs_visit through | ||
// another path | ||||
Raphaël Gomès
|
r51872 | for p in self.graph.parents(Revision(curr))?.iter().cloned() { | ||
Georges Racinet
|
r40995 | if p == NULL_REVISION { | ||
continue; | ||||
} | ||||
revs_visit.remove(&p); | ||||
bases_visit.insert(p); | ||||
both_visit.insert(p); | ||||
} | ||||
Raphaël Gomès
|
r51872 | } else if revs_visit.remove(&Revision(curr)) { | ||
missing.push(Revision(curr)); | ||||
for p in self.graph.parents(Revision(curr))?.iter().cloned() { | ||||
Yuya Nishihara
|
r41167 | if p == NULL_REVISION { | ||
continue; | ||||
} | ||||
Georges Racinet
|
r41864 | if bases_visit.contains(&p) { | ||
// p is already known to be an ancestor of revs_visit | ||||
revs_visit.remove(&p); | ||||
both_visit.insert(p); | ||||
} else if both_visit.contains(&p) { | ||||
// p should have been in bases_visit | ||||
Yuya Nishihara
|
r41167 | revs_visit.remove(&p); | ||
Georges Racinet
|
r40995 | bases_visit.insert(p); | ||
Yuya Nishihara
|
r41167 | } else { | ||
// visit later | ||||
Yuya Nishihara
|
r41169 | revs_visit.insert(p); | ||
Georges Racinet
|
r40995 | } | ||
} | ||||
Raphaël Gomès
|
r51872 | } else if bases_visit.contains(&Revision(curr)) { | ||
for p in self.graph.parents(Revision(curr))?.iter().cloned() { | ||||
Yuya Nishihara
|
r41168 | if p == NULL_REVISION { | ||
continue; | ||||
} | ||||
Georges Racinet
|
r41864 | if revs_visit.remove(&p) || both_visit.contains(&p) { | ||
Yuya Nishihara
|
r41170 | // p is an ancestor of bases_visit, and is implicitly | ||
// in revs_visit, which means p is ::revs & ::bases. | ||||
Yuya Nishihara
|
r41168 | bases_visit.insert(p); | ||
both_visit.insert(p); | ||||
} else { | ||||
Yuya Nishihara
|
r41169 | bases_visit.insert(p); | ||
Yuya Nishihara
|
r41168 | } | ||
} | ||||
Georges Racinet
|
r40995 | } | ||
} | ||||
missing.reverse(); | ||||
Ok(missing) | ||||
} | ||||
} | ||||
Georges Racinet
|
r40307 | #[cfg(test)] | ||
mod tests { | ||||
use super::*; | ||||
Raphaël Gomès
|
r51872 | use crate::{ | ||
testing::{SampleGraph, VecGraph}, | ||||
BaseRevision, | ||||
}; | ||||
impl From<BaseRevision> for Revision { | ||||
fn from(value: BaseRevision) -> Self { | ||||
if !cfg!(test) { | ||||
panic!("should only be used in tests") | ||||
} | ||||
Revision(value) | ||||
} | ||||
} | ||||
impl PartialEq<BaseRevision> for Revision { | ||||
fn eq(&self, other: &BaseRevision) -> bool { | ||||
if !cfg!(test) { | ||||
panic!("should only be used in tests") | ||||
} | ||||
self.0.eq(other) | ||||
} | ||||
} | ||||
impl PartialEq<u32> for Revision { | ||||
fn eq(&self, other: &u32) -> bool { | ||||
if !cfg!(test) { | ||||
panic!("should only be used in tests") | ||||
} | ||||
let check: Result<u32, _> = self.0.try_into(); | ||||
match check { | ||||
Ok(value) => value.eq(other), | ||||
Err(_) => false, | ||||
} | ||||
} | ||||
} | ||||
Georges Racinet
|
r40307 | |||
fn list_ancestors<G: Graph>( | ||||
graph: G, | ||||
initrevs: Vec<Revision>, | ||||
stoprev: Revision, | ||||
inclusive: bool, | ||||
) -> Vec<Revision> { | ||||
AncestorsIterator::new(graph, initrevs, stoprev, inclusive) | ||||
.unwrap() | ||||
Georges Racinet
|
r40965 | .map(|res| res.unwrap()) | ||
Georges Racinet
|
r40307 | .collect() | ||
} | ||||
#[test] | ||||
/// Same tests as test-ancestor.py, without membership | ||||
/// (see also test-ancestor.py.out) | ||||
fn test_list_ancestor() { | ||||
Raphaël Gomès
|
r51872 | assert_eq!( | ||
list_ancestors(SampleGraph, vec![], 0.into(), false), | ||||
Vec::<Revision>::new() | ||||
); | ||||
Georges Racinet
|
r40307 | assert_eq!( | ||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
0.into(), | ||||
false | ||||
), | ||||
Georges Racinet
|
r40307 | vec![8, 7, 4, 3, 2, 1, 0] | ||
); | ||||
Georges Racinet
|
r52512 | // it works as well on references, because &Graph implements Graph | ||
// this is needed as of this writing by RHGitaly | ||||
assert_eq!( | ||||
list_ancestors( | ||||
&SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
0.into(), | ||||
false | ||||
), | ||||
vec![8, 7, 4, 3, 2, 1, 0] | ||||
); | ||||
Georges Racinet
|
r40307 | assert_eq!( | ||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![1.into(), 3.into()], | ||||
0.into(), | ||||
false | ||||
), | ||||
Georges Racinet
|
r41277 | vec![1, 0] | ||
); | ||||
assert_eq!( | ||||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
0.into(), | ||||
true | ||||
), | ||||
Georges Racinet
|
r40307 | vec![13, 11, 8, 7, 4, 3, 2, 1, 0] | ||
); | ||||
assert_eq!( | ||||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
6.into(), | ||||
false | ||||
), | ||||
Georges Racinet
|
r41277 | vec![8, 7] | ||
); | ||||
assert_eq!( | ||||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
6.into(), | ||||
true | ||||
), | ||||
Georges Racinet
|
r40307 | vec![13, 11, 8, 7] | ||
); | ||||
Georges Racinet
|
r41277 | assert_eq!( | ||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
11.into(), | ||||
true | ||||
), | ||||
Georges Racinet
|
r41277 | vec![13, 11] | ||
); | ||||
Georges Racinet
|
r40307 | assert_eq!( | ||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![11.into(), 13.into()], | ||||
12.into(), | ||||
true | ||||
), | ||||
Georges Racinet
|
r41277 | vec![13] | ||
); | ||||
assert_eq!( | ||||
Raphaël Gomès
|
r51872 | list_ancestors( | ||
SampleGraph, | ||||
vec![10.into(), 1.into()], | ||||
0.into(), | ||||
true | ||||
), | ||||
Georges Racinet
|
r40307 | vec![10, 5, 4, 2, 1, 0] | ||
); | ||||
} | ||||
#[test] | ||||
/// Corner case that's not directly in test-ancestors.py, but | ||||
/// that happens quite often, as demonstrated by running the whole | ||||
/// suite. | ||||
/// For instance, run tests/test-obsolete-checkheads.t | ||||
fn test_nullrev_input() { | ||||
Raphaël Gomès
|
r51872 | let mut iter = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![Revision(-1)], | ||||
0.into(), | ||||
false, | ||||
) | ||||
.unwrap(); | ||||
Georges Racinet
|
r40307 | assert_eq!(iter.next(), None) | ||
} | ||||
Georges Racinet
|
r40336 | #[test] | ||
fn test_contains() { | ||||
Raphaël Gomès
|
r51872 | let mut lazy = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![10.into(), 1.into()], | ||||
0.into(), | ||||
true, | ||||
) | ||||
.unwrap(); | ||||
assert!(lazy.contains(1.into()).unwrap()); | ||||
assert!(!lazy.contains(3.into()).unwrap()); | ||||
Georges Racinet
|
r40336 | |||
Raphaël Gomès
|
r51872 | let mut lazy = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![0.into()], | ||||
0.into(), | ||||
false, | ||||
) | ||||
.unwrap(); | ||||
Georges Racinet
|
r40965 | assert!(!lazy.contains(NULL_REVISION).unwrap()); | ||
Georges Racinet
|
r40336 | } | ||
Georges Racinet
|
r41084 | #[test] | ||
fn test_peek() { | ||||
Raphaël Gomès
|
r51872 | let mut iter = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![10.into()], | ||||
0.into(), | ||||
true, | ||||
) | ||||
.unwrap(); | ||||
Georges Racinet
|
r41084 | // peek() gives us the next value | ||
Raphaël Gomès
|
r51872 | assert_eq!(iter.peek(), Some(10.into())); | ||
Georges Racinet
|
r41084 | // but it's not been consumed | ||
Raphaël Gomès
|
r51872 | assert_eq!(iter.next(), Some(Ok(10.into()))); | ||
Georges Racinet
|
r41084 | // and iteration resumes normally | ||
Raphaël Gomès
|
r51872 | assert_eq!(iter.next(), Some(Ok(5.into()))); | ||
Georges Racinet
|
r41084 | |||
// let's drain the iterator to test peek() at the end | ||||
while iter.next().is_some() {} | ||||
assert_eq!(iter.peek(), None); | ||||
} | ||||
#[test] | ||||
fn test_empty() { | ||||
Raphaël Gomès
|
r51872 | let mut iter = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![10.into()], | ||||
0.into(), | ||||
true, | ||||
) | ||||
.unwrap(); | ||||
Georges Racinet
|
r41084 | assert!(!iter.is_empty()); | ||
while iter.next().is_some() {} | ||||
assert!(!iter.is_empty()); | ||||
Raphaël Gomès
|
r51872 | let iter = AncestorsIterator::new(SampleGraph, vec![], 0.into(), true) | ||
.unwrap(); | ||||
Georges Racinet
|
r41084 | assert!(iter.is_empty()); | ||
// case where iter.seen == {NULL_REVISION} | ||||
Raphaël Gomès
|
r51872 | let iter = AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![0.into()], | ||||
0.into(), | ||||
false, | ||||
) | ||||
.unwrap(); | ||||
Georges Racinet
|
r41084 | assert!(iter.is_empty()); | ||
} | ||||
Georges Racinet
|
r40307 | /// A corrupted Graph, supporting error handling tests | ||
Georges Racinet
|
r41084 | #[derive(Clone, Debug)] | ||
Georges Racinet
|
r40307 | struct Corrupted; | ||
impl Graph for Corrupted { | ||||
Raphaël Gomès
|
r51872 | // FIXME what to do about this? Are we just not supposed to get them | ||
// anymore? | ||||
Georges Racinet
|
r40969 | fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError> { | ||
Georges Racinet
|
r40307 | match rev { | ||
Raphaël Gomès
|
r51872 | Revision(1) => Ok([0.into(), (-1).into()]), | ||
Georges Racinet
|
r40307 | r => Err(GraphError::ParentOutOfRange(r)), | ||
} | ||||
} | ||||
} | ||||
#[test] | ||||
fn test_initrev_out_of_range() { | ||||
// inclusive=false looks up initrev's parents right away | ||||
Raphaël Gomès
|
r51872 | match AncestorsIterator::new( | ||
SampleGraph, | ||||
vec![25.into()], | ||||
0.into(), | ||||
false, | ||||
) { | ||||
Georges Racinet
|
r40307 | Ok(_) => panic!("Should have been ParentOutOfRange"), | ||
Raphaël Gomès
|
r51872 | Err(e) => assert_eq!(e, GraphError::ParentOutOfRange(25.into())), | ||
Georges Racinet
|
r40307 | } | ||
} | ||||
#[test] | ||||
fn test_next_out_of_range() { | ||||
// inclusive=false looks up initrev's parents right away | ||||
let mut iter = | ||||
Raphaël Gomès
|
r51872 | AncestorsIterator::new(Corrupted, vec![1.into()], 0.into(), false) | ||
.unwrap(); | ||||
assert_eq!( | ||||
iter.next(), | ||||
Some(Err(GraphError::ParentOutOfRange(0.into()))) | ||||
); | ||||
Georges Racinet
|
r40307 | } | ||
Georges Racinet
|
r40995 | |||
#[test] | ||||
Georges Racinet
|
r41282 | /// Test constructor, add/get bases and heads | ||
fn test_missing_bases() -> Result<(), GraphError> { | ||||
Raphaël Gomès
|
r51872 | let mut missing_ancestors = MissingAncestors::new( | ||
SampleGraph, | ||||
[5.into(), 3.into(), 1.into(), 3.into()].iter().cloned(), | ||||
); | ||||
Georges Racinet
|
r40995 | let mut as_vec: Vec<Revision> = | ||
missing_ancestors.get_bases().iter().cloned().collect(); | ||||
Raphaël Gomès
|
r50825 | as_vec.sort_unstable(); | ||
Georges Racinet
|
r40995 | assert_eq!(as_vec, [1, 3, 5]); | ||
Georges Racinet
|
r41866 | assert_eq!(missing_ancestors.max_base, 5); | ||
Georges Racinet
|
r40995 | |||
Raphaël Gomès
|
r51872 | missing_ancestors | ||
.add_bases([3.into(), 7.into(), 8.into()].iter().cloned()); | ||||
Georges Racinet
|
r40995 | as_vec = missing_ancestors.get_bases().iter().cloned().collect(); | ||
Raphaël Gomès
|
r50825 | as_vec.sort_unstable(); | ||
Georges Racinet
|
r40995 | assert_eq!(as_vec, [1, 3, 5, 7, 8]); | ||
Georges Racinet
|
r41866 | assert_eq!(missing_ancestors.max_base, 8); | ||
Georges Racinet
|
r41282 | |||
as_vec = missing_ancestors.bases_heads()?.iter().cloned().collect(); | ||||
Raphaël Gomès
|
r50825 | as_vec.sort_unstable(); | ||
Georges Racinet
|
r41282 | assert_eq!(as_vec, [3, 5, 7, 8]); | ||
Ok(()) | ||||
Georges Racinet
|
r40995 | } | ||
fn assert_missing_remove( | ||||
Raphaël Gomès
|
r51872 | bases: &[BaseRevision], | ||
revs: &[BaseRevision], | ||||
expected: &[BaseRevision], | ||||
Georges Racinet
|
r40995 | ) { | ||
Raphaël Gomès
|
r51872 | let mut missing_ancestors = MissingAncestors::new( | ||
SampleGraph, | ||||
bases.iter().map(|r| Revision(*r)), | ||||
); | ||||
let mut revset: HashSet<Revision> = | ||||
revs.iter().map(|r| Revision(*r)).collect(); | ||||
Georges Racinet
|
r40995 | missing_ancestors | ||
.remove_ancestors_from(&mut revset) | ||||
.unwrap(); | ||||
let mut as_vec: Vec<Revision> = revset.into_iter().collect(); | ||||
Raphaël Gomès
|
r50825 | as_vec.sort_unstable(); | ||
Georges Racinet
|
r40995 | assert_eq!(as_vec.as_slice(), expected); | ||
} | ||||
#[test] | ||||
fn test_missing_remove() { | ||||
assert_missing_remove( | ||||
&[1, 2, 3, 4, 7], | ||||
Vec::from_iter(1..10).as_slice(), | ||||
&[5, 6, 8, 9], | ||||
); | ||||
assert_missing_remove(&[10], &[11, 12, 13, 14], &[11, 12, 13, 14]); | ||||
assert_missing_remove(&[7], &[1, 2, 3, 4, 5], &[3, 5]); | ||||
} | ||||
fn assert_missing_ancestors( | ||||
Raphaël Gomès
|
r51872 | bases: &[BaseRevision], | ||
revs: &[BaseRevision], | ||||
expected: &[BaseRevision], | ||||
Georges Racinet
|
r40995 | ) { | ||
Raphaël Gomès
|
r51872 | let mut missing_ancestors = MissingAncestors::new( | ||
SampleGraph, | ||||
bases.iter().map(|r| Revision(*r)), | ||||
); | ||||
Georges Racinet
|
r40995 | let missing = missing_ancestors | ||
Raphaël Gomès
|
r51872 | .missing_ancestors(revs.iter().map(|r| Revision(*r))) | ||
Georges Racinet
|
r40995 | .unwrap(); | ||
assert_eq!(missing.as_slice(), expected); | ||||
} | ||||
#[test] | ||||
fn test_missing_ancestors() { | ||||
// examples taken from test-ancestors.py by having it run | ||||
// on the same graph (both naive and fast Python algs) | ||||
assert_missing_ancestors(&[10], &[11], &[3, 7, 11]); | ||||
assert_missing_ancestors(&[11], &[10], &[5, 10]); | ||||
assert_missing_ancestors(&[7], &[9, 11], &[3, 6, 9, 11]); | ||||
} | ||||
/// An interesting case found by a random generator similar to | ||||
/// the one in test-ancestor.py. An early version of Rust MissingAncestors | ||||
/// failed this, yet none of the integration tests of the whole suite | ||||
/// catched it. | ||||
Raphaël Gomès
|
r50825 | #[allow(clippy::unnecessary_cast)] | ||
Georges Racinet
|
r40995 | #[test] | ||
fn test_remove_ancestors_from_case1() { | ||||
Raphaël Gomès
|
r51872 | const FAKE_NULL_REVISION: BaseRevision = -1; | ||
assert_eq!(FAKE_NULL_REVISION, NULL_REVISION.0); | ||||
Georges Racinet
|
r40995 | let graph: VecGraph = vec![ | ||
Raphaël Gomès
|
r51872 | [FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||
[0, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [1, 0], | ||
[2, 1], | ||||
Raphaël Gomès
|
r51872 | [3, FAKE_NULL_REVISION], | ||
[4, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [5, 1], | ||
Raphaël Gomès
|
r51872 | [2, FAKE_NULL_REVISION], | ||
[7, FAKE_NULL_REVISION], | ||||
[8, FAKE_NULL_REVISION], | ||||
[9, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [10, 1], | ||
Raphaël Gomès
|
r51872 | [3, FAKE_NULL_REVISION], | ||
[12, FAKE_NULL_REVISION], | ||||
[13, FAKE_NULL_REVISION], | ||||
[14, FAKE_NULL_REVISION], | ||||
[4, FAKE_NULL_REVISION], | ||||
[16, FAKE_NULL_REVISION], | ||||
[17, FAKE_NULL_REVISION], | ||||
[18, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [19, 11], | ||
Raphaël Gomès
|
r51872 | [20, FAKE_NULL_REVISION], | ||
[21, FAKE_NULL_REVISION], | ||||
[22, FAKE_NULL_REVISION], | ||||
[23, FAKE_NULL_REVISION], | ||||
[2, FAKE_NULL_REVISION], | ||||
[3, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [26, 24], | ||
Raphaël Gomès
|
r51872 | [27, FAKE_NULL_REVISION], | ||
[28, FAKE_NULL_REVISION], | ||||
[12, FAKE_NULL_REVISION], | ||||
[1, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [1, 9], | ||
Raphaël Gomès
|
r51872 | [32, FAKE_NULL_REVISION], | ||
[33, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [34, 31], | ||
Raphaël Gomès
|
r51872 | [35, FAKE_NULL_REVISION], | ||
Georges Racinet
|
r40995 | [36, 26], | ||
Raphaël Gomès
|
r51872 | [37, FAKE_NULL_REVISION], | ||
[38, FAKE_NULL_REVISION], | ||||
[39, FAKE_NULL_REVISION], | ||||
[40, FAKE_NULL_REVISION], | ||||
[41, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [42, 26], | ||
Raphaël Gomès
|
r51872 | [0, FAKE_NULL_REVISION], | ||
[44, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [45, 4], | ||
Raphaël Gomès
|
r51872 | [40, FAKE_NULL_REVISION], | ||
[47, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [36, 0], | ||
Raphaël Gomès
|
r51872 | [49, FAKE_NULL_REVISION], | ||
[FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||||
[51, FAKE_NULL_REVISION], | ||||
[52, FAKE_NULL_REVISION], | ||||
[53, FAKE_NULL_REVISION], | ||||
[14, FAKE_NULL_REVISION], | ||||
[55, FAKE_NULL_REVISION], | ||||
[15, FAKE_NULL_REVISION], | ||||
[23, FAKE_NULL_REVISION], | ||||
[58, FAKE_NULL_REVISION], | ||||
[59, FAKE_NULL_REVISION], | ||||
[2, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [61, 59], | ||
Raphaël Gomès
|
r51872 | [62, FAKE_NULL_REVISION], | ||
[63, FAKE_NULL_REVISION], | ||||
[FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||||
[65, FAKE_NULL_REVISION], | ||||
[66, FAKE_NULL_REVISION], | ||||
[67, FAKE_NULL_REVISION], | ||||
[68, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [37, 28], | ||
[69, 25], | ||||
Raphaël Gomès
|
r51872 | [71, FAKE_NULL_REVISION], | ||
[72, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [50, 2], | ||
Raphaël Gomès
|
r51872 | [74, FAKE_NULL_REVISION], | ||
[12, FAKE_NULL_REVISION], | ||||
[18, FAKE_NULL_REVISION], | ||||
[77, FAKE_NULL_REVISION], | ||||
[78, FAKE_NULL_REVISION], | ||||
[79, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [43, 33], | ||
Raphaël Gomès
|
r51872 | [81, FAKE_NULL_REVISION], | ||
[82, FAKE_NULL_REVISION], | ||||
[83, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [84, 45], | ||
Raphaël Gomès
|
r51872 | [85, FAKE_NULL_REVISION], | ||
[86, FAKE_NULL_REVISION], | ||||
[FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||||
[88, FAKE_NULL_REVISION], | ||||
[FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [76, 83], | ||
Raphaël Gomès
|
r51872 | [44, FAKE_NULL_REVISION], | ||
[92, FAKE_NULL_REVISION], | ||||
[93, FAKE_NULL_REVISION], | ||||
[9, FAKE_NULL_REVISION], | ||||
Georges Racinet
|
r40995 | [95, 67], | ||
Raphaël Gomès
|
r51872 | [96, FAKE_NULL_REVISION], | ||
[97, FAKE_NULL_REVISION], | ||||
[FAKE_NULL_REVISION, FAKE_NULL_REVISION], | ||||
] | ||||
.into_iter() | ||||
.map(|[a, b]| [Revision(a), Revision(b)]) | ||||
.collect(); | ||||
let problem_rev = 28.into(); | ||||
let problem_base = 70.into(); | ||||
Georges Racinet
|
r40995 | // making the problem obvious: problem_rev is a parent of problem_base | ||
assert_eq!(graph.parents(problem_base).unwrap()[1], problem_rev); | ||||
let mut missing_ancestors: MissingAncestors<VecGraph> = | ||||
MissingAncestors::new( | ||||
graph, | ||||
[60, 26, 70, 3, 96, 19, 98, 49, 97, 47, 1, 6] | ||||
.iter() | ||||
Raphaël Gomès
|
r51872 | .map(|r| Revision(*r)), | ||
Georges Racinet
|
r40995 | ); | ||
assert!(missing_ancestors.bases.contains(&problem_base)); | ||||
let mut revs: HashSet<Revision> = | ||||
[4, 12, 41, 28, 68, 38, 1, 30, 56, 44] | ||||
.iter() | ||||
Raphaël Gomès
|
r51872 | .map(|r| Revision(*r)) | ||
Georges Racinet
|
r40995 | .collect(); | ||
missing_ancestors.remove_ancestors_from(&mut revs).unwrap(); | ||||
assert!(!revs.contains(&problem_rev)); | ||||
} | ||||
Georges Racinet
|
r40307 | } | ||