Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cob: Start testing history permutations
Alexis Sellier committed 3 years ago
commit 67f9c3554d388605adf8aca6c4a7a56cf0262939
parent 139f095d515643ee822b16c146a6e0cf0406d3c4
4 files changed +87 -8
modified radicle/src/cob/op.rs
@@ -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,
modified radicle/src/cob/store.rs
@@ -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.
modified radicle/src/cob/test.rs
@@ -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)
}
modified radicle/src/cob/thread.rs
@@ -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)]
@@ -642,6 +643,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 {
            let t = Thread::default();