use std::borrow::Borrow;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::hash::Hash;
use std::io::Write;
use std::iter::zip;
use std::sync::Arc;
use itertools::Itertools;
use crate::backend;
use crate::backend::{BackendError, FileId, ObjectId, TreeId, TreeValue};
use crate::content_hash::ContentHash;
use crate::repo_path::RepoPath;
use crate::store::Store;
use crate::tree::Tree;
pub fn trivial_merge<'a, T>(removes: &'a [T], adds: &'a [T]) -> Option<&'a T>
where
T: Eq + Hash,
{
assert_eq!(
adds.len(),
removes.len() + 1,
"trivial_merge() requires exactly one more adds than removes"
);
if adds.len() == 1 {
return Some(&adds[0]);
} else if adds.len() == 2 {
return if adds[0] == adds[1] {
Some(&adds[0])
} else if adds[0] == removes[0] {
Some(&adds[1])
} else if adds[1] == removes[0] {
Some(&adds[0])
} else {
None
};
}
let mut counts: HashMap<&T, i32> = HashMap::new();
for value in adds.iter() {
counts.entry(value).and_modify(|e| *e += 1).or_insert(1);
}
for value in removes.iter() {
counts.entry(value).and_modify(|e| *e -= 1).or_insert(-1);
}
let counts = counts
.into_iter()
.filter(|&(_, count)| count != 0)
.collect_vec();
match counts[..] {
[(value, 1)] => {
Some(value)
}
[(value1, count1), (value2, count2)] => {
assert_eq!(count1 + count2, 1);
if count1 > 0 {
Some(value1)
} else {
Some(value2)
}
}
_ => None,
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct Merge<T> {
removes: Vec<T>,
adds: Vec<T>,
}
impl<T: Debug> Debug for Merge<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
if self.removes.is_empty() {
f.debug_tuple("Resolved").field(&self.adds[0]).finish()
} else {
f.debug_struct("Conflicted")
.field("removes", &self.removes)
.field("adds", &self.adds)
.finish()
}
}
}
impl<T> Merge<T> {
pub fn new(removes: Vec<T>, adds: Vec<T>) -> Self {
assert_eq!(adds.len(), removes.len() + 1);
Merge { removes, adds }
}
pub fn resolved(value: T) -> Self {
Merge::new(vec![], vec![value])
}
pub fn from_legacy_form(
removes: impl IntoIterator<Item = T>,
adds: impl IntoIterator<Item = T>,
) -> Merge<Option<T>> {
let mut removes = removes.into_iter().map(Some).collect_vec();
let mut adds = adds.into_iter().map(Some).collect_vec();
while removes.len() + 1 < adds.len() {
removes.push(None);
}
while adds.len() < removes.len() + 1 {
adds.push(None);
}
Merge::new(removes, adds)
}
pub fn take(self) -> (Vec<T>, Vec<T>) {
(self.removes, self.adds)
}
pub fn removes(&self) -> &[T] {
&self.removes
}
pub fn adds(&self) -> &[T] {
&self.adds
}
pub fn num_sides(&self) -> usize {
self.adds.len()
}
pub fn is_resolved(&self) -> bool {
self.removes.is_empty()
}
pub fn as_resolved(&self) -> Option<&T> {
if let [value] = &self.adds[..] {
Some(value)
} else {
None
}
}
pub fn into_resolved(mut self) -> Result<T, Merge<T>> {
if self.removes.is_empty() {
Ok(self.adds.pop().unwrap())
} else {
Err(self)
}
}
pub fn simplify(mut self) -> Self
where
T: PartialEq,
{
let mut add_index = 0;
while add_index < self.adds.len() {
let add = &self.adds[add_index];
if let Some(remove_index) = self.removes.iter().position(|remove| remove == add) {
self.adds.swap(remove_index + 1, add_index);
self.removes.remove(remove_index);
self.adds.remove(remove_index + 1);
} else {
add_index += 1;
}
}
self
}
pub fn resolve_trivial(&self) -> Option<&T>
where
T: Eq + Hash,
{
trivial_merge(&self.removes, &self.adds)
}
pub fn pad_to(&mut self, num_sides: usize, value: &T)
where
T: Clone,
{
for _ in self.num_sides()..num_sides {
self.removes.push(value.clone());
self.adds.push(value.clone());
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
itertools::interleave(&self.adds, &self.removes)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
itertools::interleave(self.adds.iter_mut(), self.removes.iter_mut())
}
pub fn map<'a, U>(&'a self, f: impl FnMut(&'a T) -> U) -> Merge<U> {
let builder: MergeBuilder<U> = self.iter().map(f).collect();
builder.build()
}
pub fn maybe_map<'a, U>(&'a self, f: impl FnMut(&'a T) -> Option<U>) -> Option<Merge<U>> {
let builder: Option<MergeBuilder<U>> = self.iter().map(f).collect();
builder.map(MergeBuilder::build)
}
pub fn try_map<'a, U, E>(
&'a self,
f: impl FnMut(&'a T) -> Result<U, E>,
) -> Result<Merge<U>, E> {
let builder: MergeBuilder<U> = self.iter().map(f).try_collect()?;
Ok(builder.build())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MergeBuilder<T> {
removes: Vec<T>,
adds: Vec<T>,
}
impl<T> Default for MergeBuilder<T> {
fn default() -> Self {
Self {
removes: Default::default(),
adds: Default::default(),
}
}
}
impl<T> MergeBuilder<T> {
pub fn build(self) -> Merge<T> {
Merge::new(self.removes, self.adds)
}
}
impl<T> IntoIterator for Merge<T> {
type Item = T;
type IntoIter = itertools::Interleave<std::vec::IntoIter<T>, std::vec::IntoIter<T>>;
fn into_iter(self) -> Self::IntoIter {
itertools::interleave(self.adds, self.removes)
}
}
impl<T> FromIterator<T> for MergeBuilder<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut builder = MergeBuilder::default();
builder.extend(iter);
builder
}
}
impl<T> Extend<T> for MergeBuilder<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let (mut curr, mut next) = if self.adds.len() != self.removes.len() {
(&mut self.removes, &mut self.adds)
} else {
(&mut self.adds, &mut self.removes)
};
for item in iter {
curr.push(item);
std::mem::swap(&mut curr, &mut next);
}
}
}
impl<T> Merge<Option<T>> {
pub fn absent() -> Self {
Self::resolved(None)
}
pub fn normal(value: T) -> Self {
Self::resolved(Some(value))
}
pub fn is_absent(&self) -> bool {
matches!(self.as_resolved(), Some(None))
}
pub fn is_present(&self) -> bool {
!self.is_absent()
}
pub fn into_legacy_form(self) -> (Vec<T>, Vec<T>) {
(
self.removes.into_iter().flatten().collect(),
self.adds.into_iter().flatten().collect(),
)
}
}
impl<T> Merge<Merge<T>> {
pub fn flatten(mut self) -> Merge<T> {
self.removes.reverse();
self.adds.reverse();
let mut result = self.adds.pop().unwrap();
while let Some(mut remove) = self.removes.pop() {
let first_add = remove.adds.remove(0);
result.removes.extend(remove.adds);
result.removes.push(first_add);
result.adds.extend(remove.removes);
let add = self.adds.pop().unwrap();
result.removes.extend(add.removes);
result.adds.extend(add.adds);
}
assert!(self.adds.is_empty());
result
}
}
impl<T: ContentHash> ContentHash for Merge<T> {
fn hash(&self, state: &mut impl digest::Update) {
self.removes().hash(state);
self.adds().hash(state);
}
}
pub type MergedTreeValue = Merge<Option<TreeValue>>;
impl MergedTreeValue {
pub fn from_backend_conflict(conflict: backend::Conflict) -> Self {
let removes = conflict.removes.into_iter().map(|term| term.value);
let adds = conflict.adds.into_iter().map(|term| term.value);
Merge::from_legacy_form(removes, adds)
}
pub fn into_backend_conflict(self) -> backend::Conflict {
let (removes, adds) = self.into_legacy_form();
let removes = removes
.into_iter()
.map(|value| backend::ConflictTerm { value })
.collect();
let adds = adds
.into_iter()
.map(|value| backend::ConflictTerm { value })
.collect();
backend::Conflict { removes, adds }
}
pub fn is_tree(&self) -> bool {
self.is_present()
&& self
.iter()
.all(|value| matches!(value, Some(TreeValue::Tree(_)) | None))
}
pub fn to_file_merge(&self) -> Option<Merge<Option<FileId>>> {
self.maybe_map(|term| match term {
None => Some(None),
Some(TreeValue::File { id, executable: _ }) => Some(Some(id.clone())),
_ => None,
})
}
pub fn with_new_file_ids(&self, file_ids: &Merge<Option<FileId>>) -> Self {
assert_eq!(self.removes.len(), file_ids.removes.len());
let builder: MergeBuilder<Option<TreeValue>> = zip(self.iter(), file_ids.iter())
.map(|(tree_value, file_id)| {
if let Some(TreeValue::File { id: _, executable }) = tree_value {
Some(TreeValue::File {
id: file_id.as_ref().unwrap().clone(),
executable: *executable,
})
} else {
assert!(tree_value.is_none());
assert!(file_id.is_none());
None
}
})
.collect();
builder.build()
}
pub fn describe(&self, file: &mut dyn Write) -> std::io::Result<()> {
file.write_all(b"Conflict:\n")?;
for term in self.removes().iter().flatten() {
file.write_all(format!(" Removing {}\n", describe_conflict_term(term)).as_bytes())?;
}
for term in self.adds().iter().flatten() {
file.write_all(format!(" Adding {}\n", describe_conflict_term(term)).as_bytes())?;
}
Ok(())
}
}
impl<T> Merge<Option<T>>
where
T: Borrow<TreeValue>,
{
pub fn to_tree_merge(
&self,
store: &Arc<Store>,
dir: &RepoPath,
) -> Result<Option<Merge<Tree>>, BackendError> {
let tree_id_merge = self.maybe_map(|term| match term {
None => Some(None),
Some(value) => {
if let TreeValue::Tree(id) = value.borrow() {
Some(Some(id))
} else {
None
}
}
});
if let Some(tree_id_merge) = tree_id_merge {
let get_tree = |id: &Option<&TreeId>| -> Result<Tree, BackendError> {
if let Some(id) = id {
store.get_tree(dir, id)
} else {
Ok(Tree::null(store.clone(), dir.clone()))
}
};
Ok(Some(tree_id_merge.try_map(get_tree)?))
} else {
Ok(None)
}
}
}
fn describe_conflict_term(value: &TreeValue) -> String {
match value {
TreeValue::File {
id,
executable: false,
} => {
format!("file with id {}", id.hex())
}
TreeValue::File {
id,
executable: true,
} => {
format!("executable file with id {}", id.hex())
}
TreeValue::Symlink(id) => {
format!("symlink with id {}", id.hex())
}
TreeValue::Tree(id) => {
format!("tree with id {}", id.hex())
}
TreeValue::GitSubmodule(id) => {
format!("Git submodule with id {}", id.hex())
}
TreeValue::Conflict(id) => {
format!("Conflict with id {}", id.hex())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn c<T: Clone>(removes: &[T], adds: &[T]) -> Merge<T> {
Merge::new(removes.to_vec(), adds.to_vec())
}
#[test]
fn test_trivial_merge() {
assert_eq!(trivial_merge(&[], &[0]), Some(&0));
assert_eq!(trivial_merge(&[0], &[0, 0]), Some(&0));
assert_eq!(trivial_merge(&[0], &[0, 1]), Some(&1));
assert_eq!(trivial_merge(&[0], &[1, 0]), Some(&1));
assert_eq!(trivial_merge(&[0], &[1, 1]), Some(&1));
assert_eq!(trivial_merge(&[0], &[1, 2]), None);
assert_eq!(trivial_merge(&[0, 0], &[0, 0, 0]), Some(&0));
assert_eq!(trivial_merge(&[0, 0], &[0, 0, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 0]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[0, 1, 2]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 0]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[1, 0, 2]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 0]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 0], &[1, 1, 2]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 0]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 1]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 2]), None);
assert_eq!(trivial_merge(&[0, 0], &[1, 2, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 0]), Some(&0));
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 1]), Some(&0));
assert_eq!(trivial_merge(&[0, 1], &[0, 0, 2]), None);
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 0]), Some(&0));
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 1], &[0, 1, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 0]), None);
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 1]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[0, 2, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 0]), Some(&0));
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 1], &[1, 0, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 0]), Some(&1));
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 1]), Some(&1));
assert_eq!(trivial_merge(&[0, 1], &[1, 1, 2]), None);
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 0]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 1]), None);
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[1, 2, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 0]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 1]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 0, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 0]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 1]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 2]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 1, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 0]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 1]), Some(&2));
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 2]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 2, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 0]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 1]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 2]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 3]), None);
assert_eq!(trivial_merge(&[0, 1], &[2, 3, 4]), None);
}
#[test]
fn test_legacy_form_conversion() {
fn test_equivalent<T>(legacy_form: (Vec<T>, Vec<T>), merge: Merge<Option<T>>)
where
T: Clone + PartialEq + std::fmt::Debug,
{
assert_eq!(merge.clone().into_legacy_form(), legacy_form);
assert_eq!(Merge::from_legacy_form(legacy_form.0, legacy_form.1), merge);
}
test_equivalent((vec![], vec![0]), Merge::new(vec![], vec![Some(0)]));
test_equivalent(
(vec![0], vec![1, 2]),
Merge::new(vec![Some(0)], vec![Some(1), Some(2)]),
);
test_equivalent(
(vec![0], vec![1]),
Merge::new(vec![Some(0)], vec![Some(1), None]),
);
test_equivalent(
(vec![], vec![0, 1]),
Merge::new(vec![None], vec![Some(0), Some(1)]),
);
test_equivalent(
(vec![0, 1], vec![2, 3, 4]),
Merge::new(vec![Some(0), Some(1)], vec![Some(2), Some(3), Some(4)]),
);
test_equivalent(
(vec![0, 1], vec![]),
Merge::new(vec![Some(0), Some(1)], vec![None, None, None]),
);
}
#[test]
fn test_as_resolved() {
assert_eq!(Merge::new(vec![], vec![0]).as_resolved(), Some(&0));
assert_eq!(Merge::new(vec![0], vec![0, 1]).as_resolved(), None);
}
#[test]
fn test_simplify() {
assert_eq!(c(&[], &[0]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0], &[0, 0]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0], &[0, 1]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0], &[1, 0]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0], &[1, 1]).simplify(), c(&[0], &[1, 1]));
assert_eq!(c(&[0], &[1, 2]).simplify(), c(&[0], &[1, 2]));
assert_eq!(c(&[0, 0], &[0, 0, 0]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0, 0], &[0, 0, 1]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 0], &[0, 1, 0]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 0], &[0, 1, 1]).simplify(), c(&[0], &[1, 1]));
assert_eq!(c(&[0, 0], &[0, 1, 2]).simplify(), c(&[0], &[1, 2]));
assert_eq!(c(&[0, 0], &[1, 0, 0]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 0], &[1, 0, 1]).simplify(), c(&[0], &[1, 1]));
assert_eq!(c(&[0, 0], &[1, 0, 2]).simplify(), c(&[0], &[1, 2]));
assert_eq!(c(&[0, 0], &[1, 1, 0]).simplify(), c(&[0], &[1, 1]));
assert_eq!(c(&[0, 0], &[1, 1, 1]).simplify(), c(&[0, 0], &[1, 1, 1]));
assert_eq!(c(&[0, 0], &[1, 1, 2]).simplify(), c(&[0, 0], &[1, 1, 2]));
assert_eq!(c(&[0, 0], &[1, 2, 0]).simplify(), c(&[0], &[1, 2]));
assert_eq!(c(&[0, 0], &[1, 2, 1]).simplify(), c(&[0, 0], &[1, 2, 1]));
assert_eq!(c(&[0, 0], &[1, 2, 2]).simplify(), c(&[0, 0], &[1, 2, 2]));
assert_eq!(c(&[0, 0], &[1, 2, 3]).simplify(), c(&[0, 0], &[1, 2, 3]));
assert_eq!(c(&[0, 1], &[0, 0, 0]).simplify(), c(&[1], &[0, 0]));
assert_eq!(c(&[0, 1], &[0, 0, 1]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0, 1], &[0, 0, 2]).simplify(), c(&[1], &[0, 2]));
assert_eq!(c(&[0, 1], &[0, 1, 0]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0, 1], &[0, 1, 1]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 1], &[0, 1, 2]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[0, 2, 0]).simplify(), c(&[1], &[2, 0]));
assert_eq!(c(&[0, 1], &[0, 2, 1]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[0, 2, 2]).simplify(), c(&[1], &[2, 2]));
assert_eq!(c(&[0, 1], &[0, 2, 3]).simplify(), c(&[1], &[2, 3]));
assert_eq!(c(&[0, 1], &[1, 0, 0]).simplify(), c(&[], &[0]));
assert_eq!(c(&[0, 1], &[1, 0, 1]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 1], &[1, 0, 2]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[1, 1, 0]).simplify(), c(&[], &[1]));
assert_eq!(c(&[0, 1], &[1, 1, 1]).simplify(), c(&[0], &[1, 1]));
assert_eq!(c(&[0, 1], &[1, 1, 2]).simplify(), c(&[0], &[2, 1]));
assert_eq!(c(&[0, 1], &[1, 2, 0]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[1, 2, 1]).simplify(), c(&[0], &[1, 2]));
assert_eq!(c(&[0, 1], &[1, 2, 2]).simplify(), c(&[0], &[2, 2]));
assert_eq!(c(&[0, 1], &[1, 2, 3]).simplify(), c(&[0], &[3, 2]));
assert_eq!(c(&[0, 1], &[2, 0, 0]).simplify(), c(&[1], &[2, 0]));
assert_eq!(c(&[0, 1], &[2, 0, 1]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[2, 0, 2]).simplify(), c(&[1], &[2, 2]));
assert_eq!(c(&[0, 1], &[2, 0, 3]).simplify(), c(&[1], &[2, 3]));
assert_eq!(c(&[0, 1], &[2, 1, 0]).simplify(), c(&[], &[2]));
assert_eq!(c(&[0, 1], &[2, 1, 1]).simplify(), c(&[0], &[2, 1]));
assert_eq!(c(&[0, 1], &[2, 1, 2]).simplify(), c(&[0], &[2, 2]));
assert_eq!(c(&[0, 1], &[2, 1, 3]).simplify(), c(&[0], &[2, 3]));
assert_eq!(c(&[0, 1], &[2, 2, 0]).simplify(), c(&[1], &[2, 2]));
assert_eq!(c(&[0, 1], &[2, 2, 1]).simplify(), c(&[0], &[2, 2]));
assert_eq!(c(&[0, 1], &[2, 2, 2]).simplify(), c(&[0, 1], &[2, 2, 2]));
assert_eq!(c(&[0, 1], &[2, 2, 3]).simplify(), c(&[0, 1], &[2, 2, 3]));
assert_eq!(c(&[0, 1], &[2, 3, 0]).simplify(), c(&[1], &[2, 3]));
assert_eq!(c(&[0, 1], &[2, 3, 1]).simplify(), c(&[0], &[2, 3]));
assert_eq!(c(&[0, 1], &[2, 3, 2]).simplify(), c(&[0, 1], &[2, 3, 2]));
assert_eq!(c(&[0, 1], &[2, 3, 3]).simplify(), c(&[0, 1], &[2, 3, 3]));
assert_eq!(c(&[0, 1], &[2, 3, 4]).simplify(), c(&[0, 1], &[2, 3, 4]));
assert_eq!(
c(&[0, 1, 2], &[3, 4, 5, 0]).simplify(),
c(&[1, 2], &[3, 5, 4])
);
}
#[test]
fn test_merge_invariants() {
fn check_invariants(removes: &[u32], adds: &[u32]) {
let merge = Merge::new(removes.to_vec(), adds.to_vec());
assert_eq!(
merge.clone().simplify().simplify(),
merge.clone().simplify(),
"simplify() not idempotent for {merge:?}"
);
assert_eq!(
merge.clone().simplify().resolve_trivial(),
merge.resolve_trivial(),
"simplify() changed result of resolve_trivial() for {merge:?}"
);
}
check_invariants(&[], &[0]);
for i in 0..=1 {
for j in 0..=i + 1 {
check_invariants(&[0], &[i, j]);
for k in 0..=j + 1 {
for l in 0..=k + 1 {
check_invariants(&[0, i], &[j, k, l]);
}
}
}
}
}
#[test]
fn test_pad_to() {
let mut x = c(&[], &[1]);
x.pad_to(3, &2);
assert_eq!(x, c(&[2, 2], &[1, 2, 2]));
x.pad_to(1, &3);
assert_eq!(x, c(&[2, 2], &[1, 2, 2]));
}
#[test]
fn test_iter() {
assert_eq!(c(&[], &[1]).iter().collect_vec(), vec![&1]);
assert_eq!(
c(&[1, 2], &[3, 4, 5]).iter().collect_vec(),
vec![&3, &1, &4, &2, &5]
);
}
#[test]
fn test_from_iter() {
assert_eq!(MergeBuilder::from_iter([1]).build(), c(&[], &[1]));
assert_eq!(
MergeBuilder::from_iter([1, 2, 3, 4, 5]).build(),
c(&[2, 4], &[1, 3, 5])
);
}
#[test]
#[should_panic]
fn test_from_iter_empty() {
MergeBuilder::from_iter([1; 0]).build();
}
#[test]
#[should_panic]
fn test_from_iter_even() {
MergeBuilder::from_iter([1, 2]).build();
}
#[test]
fn test_extend() {
let mut builder: MergeBuilder<i32> = Default::default();
builder.extend([1]);
assert_eq!(builder.build(), c(&[], &[1]));
let mut builder: MergeBuilder<i32> = Default::default();
builder.extend([1, 2]);
builder.extend([3, 4, 5]);
assert_eq!(builder.build(), c(&[2, 4], &[1, 3, 5]));
}
#[test]
fn test_map() {
fn increment(i: &i32) -> i32 {
i + 1
}
assert_eq!(c(&[], &[1]).map(increment), c(&[], &[2]));
assert_eq!(c(&[1], &[3, 5]).map(increment), c(&[2], &[4, 6]));
}
#[test]
fn test_maybe_map() {
fn sqrt(i: &i32) -> Option<i32> {
if *i >= 0 {
Some((*i as f64).sqrt() as i32)
} else {
None
}
}
assert_eq!(c(&[], &[1]).maybe_map(sqrt), Some(c(&[], &[1])));
assert_eq!(c(&[], &[-1]).maybe_map(sqrt), None);
assert_eq!(c(&[1], &[4, 9]).maybe_map(sqrt), Some(c(&[1], &[2, 3])));
assert_eq!(c(&[-1], &[4, 9]).maybe_map(sqrt), None);
assert_eq!(c(&[1], &[-4, 9]).maybe_map(sqrt), None);
}
#[test]
fn test_try_map() {
fn sqrt(i: &i32) -> Result<i32, ()> {
if *i >= 0 {
Ok((*i as f64).sqrt() as i32)
} else {
Err(())
}
}
assert_eq!(c(&[], &[1]).try_map(sqrt), Ok(c(&[], &[1])));
assert_eq!(c(&[], &[-1]).try_map(sqrt), Err(()));
assert_eq!(c(&[1], &[4, 9]).try_map(sqrt), Ok(c(&[1], &[2, 3])));
assert_eq!(c(&[-1], &[4, 9]).try_map(sqrt), Err(()));
assert_eq!(c(&[1], &[-4, 9]).try_map(sqrt), Err(()));
}
#[test]
fn test_flatten() {
assert_eq!(c(&[], &[c(&[], &[0])]).flatten(), c(&[], &[0]));
assert_eq!(c(&[], &[c(&[0], &[1, 2])]).flatten(), c(&[0], &[1, 2]));
assert_eq!(
c(&[c(&[], &[0])], &[c(&[], &[1]), c(&[], &[2])]).flatten(),
c(&[0], &[1, 2])
);
assert_eq!(
c(&[c(&[0], &[1, 2])], &[c(&[3], &[4, 5]), c(&[6], &[7, 8])]).flatten(),
c(&[3, 2, 1, 6], &[4, 5, 0, 7, 8])
);
}
}