cob: Start testing history permutations

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2023-01-01 23:28:44 +01:00
parent 139f095d51
commit 67f9c3554d
No known key found for this signature in database
4 changed files with 87 additions and 8 deletions

View File

@ -62,6 +62,18 @@ pub struct Op<A> {
pub timestamp: clock::Physical,
}
impl<A: Eq> PartialOrd for Op<A> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.id().partial_cmp(&other.id())
}
}
impl<A: Eq> Ord for Op<A> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.id().cmp(&other.id())
}
}
impl<A: Serialize> Op<A> {
pub fn new(
action: A,

View File

@ -54,6 +54,15 @@ pub trait FromHistory: Sized + Default {
Ok((obj, history.clock().into()))
}
/// Create an object from individual operations.
/// Returns an error if any of the operations fails to apply.
fn from_ops(ops: impl IntoIterator<Item = Op<Self::Action>>) -> Result<Self, Self::Error> {
let mut state = Self::default();
state.apply(ops)?;
Ok(state)
}
}
/// Store error.

View File

@ -1,25 +1,31 @@
use std::collections::BTreeSet;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ops::{ControlFlow, Deref};
use nonempty::NonEmpty;
use serde::Serialize;
use crate::cob::op::Op;
use crate::cob::op::{Op, Ops};
use crate::cob::store::encoding;
use crate::cob::History;
use crate::git::Oid;
use crate::test::arbitrary;
use super::store::FromHistory;
/// Convenience type for building histories.
#[derive(Debug, Clone)]
pub struct HistoryBuilder<A> {
pub struct HistoryBuilder<T> {
history: History,
witness: PhantomData<A>,
resource: Oid,
witness: PhantomData<T>,
}
impl<A: Serialize> HistoryBuilder<A> {
pub fn new(op: &Op<A>) -> HistoryBuilder<A> {
impl<T: FromHistory> HistoryBuilder<T>
where
T::Action: Serialize + Eq,
{
pub fn new(op: &Op<T::Action>) -> HistoryBuilder<T> {
let entry = arbitrary::oid();
let resource = arbitrary::oid();
let contents = encoding::encode(&op.action).unwrap();
@ -37,7 +43,7 @@ impl<A: Serialize> HistoryBuilder<A> {
}
}
pub fn append(&mut self, op: &Op<A>) -> &mut Self {
pub fn append(&mut self, op: &Op<T::Action>) -> &mut Self {
self.history.extend(
arbitrary::oid(),
op.author,
@ -51,6 +57,27 @@ impl<A: Serialize> HistoryBuilder<A> {
pub fn merge(&mut self, other: Self) {
self.history.merge(other.history);
}
/// Return a sorted list of operations by traversing the history in topological order.
pub fn sorted(&self) -> Vec<Op<T::Action>> {
self.history.traverse(Vec::new(), |mut acc, entry| {
let Ops(ops) =
Ops::try_from(entry).expect("HistoryBuilder::sorted: operations must be valid");
acc.extend(ops);
ControlFlow::Continue(acc)
})
}
/// Return `n` permutations of the topological ordering of operations.
/// *This function will never return if less than `n` permutations exist.*
pub fn permutations(&self, n: usize) -> impl IntoIterator<Item = Vec<Op<T::Action>>> {
let mut permutations = BTreeSet::new();
while permutations.len() < n {
permutations.insert(self.sorted());
}
permutations.into_iter()
}
}
impl<A> Deref for HistoryBuilder<A> {
@ -62,6 +89,9 @@ impl<A> Deref for HistoryBuilder<A> {
}
/// Create a new test history.
pub fn history<A: Serialize>(op: &Op<A>) -> HistoryBuilder<A> {
pub fn history<T: FromHistory>(op: &Op<T::Action>) -> HistoryBuilder<T>
where
T::Action: Serialize + Eq,
{
HistoryBuilder::new(op)
}

View File

@ -373,6 +373,7 @@ mod tests {
use super::*;
use crate as radicle;
use crate::cob::store::FromHistory;
use crate::cob::test;
use crate::crypto::test::signer::MockSigner;
#[derive(Clone)]
@ -641,6 +642,33 @@ mod tests {
);
}
#[test]
fn test_histories() {
let mut alice = Actor::<MockSigner>::default();
let mut bob = Actor::<MockSigner>::default();
let mut eve = Actor::<MockSigner>::default();
let a0 = alice.comment("Alice's comment", None);
let b0 = bob.comment("Bob's reply", Some(a0.id())); // Bob and Eve's replies are concurrent.
let e0 = eve.comment("Eve's reply", Some(a0.id()));
let mut a = test::history::<Thread>(&a0);
let mut b = a.clone();
let mut e = a.clone();
b.append(&b0);
e.append(&e0);
a.merge(b);
a.merge(e);
let (expected, _) = Thread::from_history(&a).unwrap();
for permutation in a.permutations(2) {
let actual = Thread::from_ops(permutation).unwrap();
assert_eq!(actual, expected);
}
}
#[test]
fn prop_invariants() {
fn property(log: Changes<3>) -> TestResult {