Browse Source

add flowy ot

appflowy 3 năm trước cách đây
mục cha
commit
b449707021

+ 1 - 0
rust-lib/Cargo.toml

@@ -15,6 +15,7 @@ members = [
   "flowy-observable",
   "flowy-document",
   "flowy-editor",
+  "flowy-ot",
 ]
 
 [profile.dev]

+ 13 - 0
rust-lib/flowy-ot/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "flowy-ot"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bytecount = "0.6.0"
+
+[dev-dependencies]
+criterion = "0.3"
+rand = "0.7.3"

+ 97 - 0
rust-lib/flowy-ot/src/attributes.rs

@@ -0,0 +1,97 @@
+use std::collections::{hash_map::RandomState, HashMap};
+
+#[derive(Debug, Clone, Default, PartialEq)]
+pub struct Attributes {
+    inner: HashMap<String, String>,
+}
+
+impl Attributes {
+    pub fn new() -> Self {
+        Attributes {
+            inner: HashMap::new(),
+        }
+    }
+
+    pub fn remove_empty_value(&mut self) { self.inner.retain((|_, v| v.is_empty())); }
+
+    pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); }
+}
+
+impl std::convert::From<HashMap<String, String>> for Attributes {
+    fn from(attributes: HashMap<String, String, RandomState>) -> Self {
+        Attributes { inner: attributes }
+    }
+}
+
+impl std::ops::Deref for Attributes {
+    type Target = HashMap<String, String>;
+
+    fn deref(&self) -> &Self::Target { &self.inner }
+}
+
+impl std::ops::DerefMut for Attributes {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
+}
+
+pub fn compose_attributes(
+    mut a: Attributes,
+    b: Attributes,
+    keep_empty: bool,
+) -> Option<Attributes> {
+    a.extend(b);
+    let mut result = a;
+    if !keep_empty {
+        result.remove_empty_value()
+    }
+
+    return if result.is_empty() {
+        None
+    } else {
+        Some(result)
+    };
+}
+
+pub fn transform_attributes(a: Attributes, b: Attributes, priority: bool) -> Option<Attributes> {
+    if a.is_empty() {
+        return Some(b);
+    }
+
+    if b.is_empty() {
+        return None;
+    }
+
+    if !priority {
+        return Some(b);
+    }
+
+    let result = b.iter().fold(Attributes::new(), |mut attributes, (k, v)| {
+        if a.contains_key(k) == false {
+            attributes.insert(k.clone(), v.clone());
+        }
+        attributes
+    });
+    Some(result)
+}
+
+pub fn invert_attributes(attr: Option<Attributes>, base: Option<Attributes>) -> Attributes {
+    let attr = attr.unwrap_or(Attributes::new());
+    let base = base.unwrap_or(Attributes::new());
+
+    let base_inverted = base
+        .iter()
+        .fold(Attributes::new(), |mut attributes, (k, v)| {
+            if base.get(k) != attr.get(k) && attr.contains_key(k) {
+                attributes.insert(k.clone(), v.clone());
+            }
+            attributes
+        });
+
+    let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, v)| {
+        if base.get(k) != attr.get(k) && !base.contains_key(k) {
+            attributes.insert(k.clone(), "".to_owned());
+        }
+        attributes
+    });
+
+    return inverted;
+}

+ 443 - 0
rust-lib/flowy-ot/src/delta.rs

@@ -0,0 +1,443 @@
+use crate::{errors::OTError, operation::*};
+use bytecount::num_chars;
+use std::{cmp::Ordering, error::Error, fmt, iter::FromIterator};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Delta {
+    pub ops: Vec<Operation>,
+    pub base_len: usize,
+    pub target_len: usize,
+}
+
+impl Default for Delta {
+    fn default() -> Self {
+        Self {
+            ops: Vec::new(),
+            base_len: 0,
+            target_len: 0,
+        }
+    }
+}
+
+impl FromIterator<OpType> for Delta {
+    fn from_iter<T: IntoIterator<Item = OpType>>(ops: T) -> Self {
+        let mut operations = Delta::default();
+        for op in ops {
+            operations.add(op);
+        }
+        operations
+    }
+}
+
+impl Delta {
+    #[inline]
+    pub fn with_capacity(capacity: usize) -> Self {
+        Self {
+            ops: Vec::with_capacity(capacity),
+            base_len: 0,
+            target_len: 0,
+        }
+    }
+
+    fn add(&mut self, op: OpType) {
+        match op {
+            OpType::Delete(i) => self.delete(i),
+            OpType::Insert(s) => self.insert(&s),
+            OpType::Retain(i) => self.retain(i),
+        }
+    }
+
+    pub fn delete(&mut self, n: u64) {
+        if n == 0 {
+            return;
+        }
+        self.base_len += n as usize;
+
+        if let Some(operation) = self.ops.last_mut() {
+            if operation.ty.is_delete() {
+                operation.delete(n);
+                return;
+            }
+        }
+
+        self.ops.push(OperationBuilder::delete(n).build());
+    }
+
+    pub fn insert(&mut self, s: &str) {
+        if s.is_empty() {
+            return;
+        }
+        self.target_len += num_chars(s.as_bytes());
+        let new_last = match self
+            .ops
+            .iter_mut()
+            .map(|op| &mut op.ty)
+            .collect::<Vec<&mut OpType>>()
+            .as_mut_slice()
+        {
+            [.., OpType::Insert(s_last)] => {
+                *s_last += &s;
+                return;
+            },
+            [.., OpType::Insert(s_pre_last), OpType::Delete(_)] => {
+                *s_pre_last += s;
+                return;
+            },
+            [.., op_last @ OpType::Delete(_)] => {
+                let new_last = op_last.clone();
+                *(*op_last) = OpType::Insert(s.to_owned());
+                new_last
+            },
+            _ => OpType::Insert(s.to_owned()),
+        };
+        self.ops.push(OperationBuilder::new(new_last).build());
+    }
+
+    pub fn retain(&mut self, n: u64) {
+        if n == 0 {
+            return;
+        }
+        self.base_len += n as usize;
+        self.target_len += n as usize;
+
+        if let Some(operation) = self.ops.last_mut() {
+            if operation.ty.is_retain() {
+                operation.retain(n);
+                return;
+            }
+        }
+
+        self.ops.push(OperationBuilder::retain(n).build());
+    }
+
+    /// Merges the operation with `other` into one operation while preserving
+    /// the changes of both. Or, in other words, for each input string S and a
+    /// pair of consecutive operations A and B.
+    ///     `apply(apply(S, A), B) = apply(S, compose(A, B))`
+    /// must hold.
+    ///
+    /// # Error
+    ///
+    /// Returns an `OTError` if the operations are not composable due to length
+    /// conflicts.
+    pub fn compose(&self, other: &Self) -> Result<Self, OTError> {
+        if self.target_len != other.base_len {
+            return Err(OTError);
+        }
+
+        let mut new_delta = Delta::default();
+        let mut ops1 = self.ops.iter().cloned();
+        let mut ops2 = other.ops.iter().cloned();
+
+        let mut maybe_op1 = ops1.next();
+        let mut maybe_op2 = ops2.next();
+        loop {
+            match (
+                &maybe_op1.as_ref().map(|o| &o.ty),
+                &maybe_op2.as_ref().map(|o| &o.ty),
+            ) {
+                (None, None) => break,
+                (Some(OpType::Delete(i)), _) => {
+                    new_delta.delete(*i);
+                    maybe_op1 = ops1.next();
+                },
+                (_, Some(OpType::Insert(s))) => {
+                    new_delta.insert(s);
+                    maybe_op2 = ops2.next();
+                },
+                (None, _) | (_, None) => {
+                    return Err(OTError);
+                },
+                (Some(OpType::Retain(i)), Some(OpType::Retain(j))) => match i.cmp(&j) {
+                    Ordering::Less => {
+                        new_delta.retain(*i);
+                        maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
+                        maybe_op1 = ops1.next();
+                    },
+                    std::cmp::Ordering::Equal => {
+                        new_delta.retain(*i);
+                        maybe_op1 = ops1.next();
+                        maybe_op2 = ops2.next();
+                    },
+                    std::cmp::Ordering::Greater => {
+                        new_delta.retain(*j);
+                        maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
+                        maybe_op2 = ops2.next();
+                    },
+                },
+                (Some(OpType::Insert(s)), Some(OpType::Delete(j))) => {
+                    match (num_chars(s.as_bytes()) as u64).cmp(j) {
+                        Ordering::Less => {
+                            maybe_op2 = Some(
+                                OperationBuilder::delete(*j - num_chars(s.as_bytes()) as u64)
+                                    .build(),
+                            );
+                            maybe_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            maybe_op1 = ops1.next();
+                            maybe_op2 = ops2.next();
+                        },
+                        Ordering::Greater => {
+                            maybe_op1 = Some(
+                                OperationBuilder::insert(s.chars().skip(*j as usize).collect())
+                                    .build(),
+                            );
+                            maybe_op2 = ops2.next();
+                        },
+                    }
+                },
+                (Some(OpType::Insert(s)), Some(OpType::Retain(j))) => {
+                    match (num_chars(s.as_bytes()) as u64).cmp(j) {
+                        Ordering::Less => {
+                            new_delta.insert(s);
+                            maybe_op2 = Some(
+                                OperationBuilder::retain(*j - num_chars(s.as_bytes()) as u64)
+                                    .build(),
+                            );
+                            maybe_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            new_delta.insert(s);
+                            maybe_op1 = ops1.next();
+                            maybe_op2 = ops2.next();
+                        },
+                        Ordering::Greater => {
+                            let chars = &mut s.chars();
+                            new_delta.insert(&chars.take(*j as usize).collect::<String>());
+                            maybe_op1 = Some(OperationBuilder::insert(chars.collect()).build());
+                            maybe_op2 = ops2.next();
+                        },
+                    }
+                },
+                (Some(OpType::Retain(i)), Some(OpType::Delete(j))) => match i.cmp(&j) {
+                    Ordering::Less => {
+                        new_delta.delete(*i);
+                        maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
+                        maybe_op1 = ops1.next();
+                    },
+                    Ordering::Equal => {
+                        new_delta.delete(*j);
+                        maybe_op2 = ops2.next();
+                        maybe_op1 = ops1.next();
+                    },
+                    Ordering::Greater => {
+                        new_delta.delete(*j);
+                        maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
+                        maybe_op2 = ops2.next();
+                    },
+                },
+            };
+        }
+        Ok(new_delta)
+    }
+
+    /// Transforms two operations A and B that happened concurrently and
+    /// produces two operations A' and B' (in an array) such that
+    ///     `apply(apply(S, A), B') = apply(apply(S, B), A')`.
+    /// This function is the heart of OT.
+    ///
+    /// # Error
+    ///
+    /// Returns an `OTError` if the operations cannot be transformed due to
+    /// length conflicts.
+    pub fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> {
+        if self.base_len != other.base_len {
+            return Err(OTError);
+        }
+
+        let mut a_prime = Delta::default();
+        let mut b_prime = Delta::default();
+
+        let mut ops1 = self.ops.iter().cloned();
+        let mut ops2 = other.ops.iter().cloned();
+
+        let mut maybe_op1 = ops1.next();
+        let mut maybe_op2 = ops2.next();
+        loop {
+            match (
+                &maybe_op1.as_ref().map(|o| &o.ty),
+                &maybe_op2.as_ref().map(|o| &o.ty),
+            ) {
+                (None, None) => break,
+                (Some(OpType::Insert(s)), _) => {
+                    a_prime.insert(s);
+                    b_prime.retain(num_chars(s.as_bytes()) as _);
+                    maybe_op1 = ops1.next();
+                },
+                (_, Some(OpType::Insert(s))) => {
+                    a_prime.retain(num_chars(s.as_bytes()) as _);
+                    b_prime.insert(s);
+                    maybe_op2 = ops2.next();
+                },
+                (None, _) => {
+                    return Err(OTError);
+                },
+                (_, None) => {
+                    return Err(OTError);
+                },
+                (Some(OpType::Retain(i)), Some(OpType::Retain(j))) => {
+                    match i.cmp(&j) {
+                        Ordering::Less => {
+                            a_prime.retain(*i);
+                            b_prime.retain(*i);
+                            maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
+                            maybe_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            a_prime.retain(*i);
+                            b_prime.retain(*i);
+                            maybe_op1 = ops1.next();
+                            maybe_op2 = ops2.next();
+                        },
+                        Ordering::Greater => {
+                            a_prime.retain(*j);
+                            b_prime.retain(*j);
+                            maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
+                            maybe_op2 = ops2.next();
+                        },
+                    };
+                },
+                (Some(OpType::Delete(i)), Some(OpType::Delete(j))) => match i.cmp(&j) {
+                    Ordering::Less => {
+                        maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
+                        maybe_op1 = ops1.next();
+                    },
+                    Ordering::Equal => {
+                        maybe_op1 = ops1.next();
+                        maybe_op2 = ops2.next();
+                    },
+                    Ordering::Greater => {
+                        maybe_op1 = Some(OperationBuilder::delete(*i - *j).build());
+                        maybe_op2 = ops2.next();
+                    },
+                },
+                (Some(OpType::Delete(i)), Some(OpType::Retain(j))) => {
+                    match i.cmp(&j) {
+                        Ordering::Less => {
+                            a_prime.delete(*i);
+                            maybe_op2 = Some(OperationBuilder::retain(*j - *i).build());
+                            maybe_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            a_prime.delete(*i);
+                            maybe_op1 = ops1.next();
+                            maybe_op2 = ops2.next();
+                        },
+                        Ordering::Greater => {
+                            a_prime.delete(*j);
+                            maybe_op1 = Some(OperationBuilder::delete(*i - *j).build());
+                            maybe_op2 = ops2.next();
+                        },
+                    };
+                },
+                (Some(OpType::Retain(i)), Some(OpType::Delete(j))) => {
+                    match i.cmp(&j) {
+                        Ordering::Less => {
+                            b_prime.delete(*i);
+                            maybe_op2 = Some(OperationBuilder::delete(*j - *i).build());
+                            maybe_op1 = ops1.next();
+                        },
+                        Ordering::Equal => {
+                            b_prime.delete(*i);
+                            maybe_op1 = ops1.next();
+                            maybe_op2 = ops2.next();
+                        },
+                        Ordering::Greater => {
+                            b_prime.delete(*j);
+                            maybe_op1 = Some(OperationBuilder::retain(*i - *j).build());
+                            maybe_op2 = ops2.next();
+                        },
+                    };
+                },
+            }
+        }
+
+        Ok((a_prime, b_prime))
+    }
+
+    /// Applies an operation to a string, returning a new string.
+    ///
+    /// # Error
+    ///
+    /// Returns an error if the operation cannot be applied due to length
+    /// conflicts.
+    pub fn apply(&self, s: &str) -> Result<String, OTError> {
+        if num_chars(s.as_bytes()) != self.base_len {
+            return Err(OTError);
+        }
+        let mut new_s = String::new();
+        let chars = &mut s.chars();
+        for op in &self.ops {
+            match &op.ty {
+                OpType::Retain(retain) => {
+                    for c in chars.take(*retain as usize) {
+                        new_s.push(c);
+                    }
+                },
+                OpType::Delete(delete) => {
+                    for _ in 0..*delete {
+                        chars.next();
+                    }
+                },
+                OpType::Insert(insert) => {
+                    new_s += insert;
+                },
+            }
+        }
+        Ok(new_s)
+    }
+
+    /// Computes the inverse of an operation. The inverse of an operation is the
+    /// operation that reverts the effects of the operation
+    pub fn invert(&self, s: &str) -> Self {
+        let mut inverse = Delta::default();
+        let chars = &mut s.chars();
+        for op in &self.ops {
+            match &op.ty {
+                OpType::Retain(retain) => {
+                    inverse.retain(*retain);
+                    for _ in 0..*retain {
+                        chars.next();
+                    }
+                },
+                OpType::Insert(insert) => {
+                    inverse.delete(num_chars(insert.as_bytes()) as u64);
+                },
+                OpType::Delete(delete) => {
+                    inverse.insert(&chars.take(*delete as usize).collect::<String>());
+                },
+            }
+        }
+        inverse
+    }
+
+    /// Checks if this operation has no effect.
+    #[inline]
+    pub fn is_noop(&self) -> bool {
+        match self
+            .ops
+            .iter()
+            .map(|op| &op.ty)
+            .collect::<Vec<&OpType>>()
+            .as_slice()
+        {
+            [] => true,
+            [OpType::Retain(_)] => true,
+            _ => false,
+        }
+    }
+
+    /// Returns the length of a string these operations can be applied to
+    #[inline]
+    pub fn base_len(&self) -> usize { self.base_len }
+
+    /// Returns the length of the resulting string after the operations have
+    /// been applied.
+    #[inline]
+    pub fn target_len(&self) -> usize { self.target_len }
+
+    /// Returns the wrapped sequence of operations.
+    #[inline]
+    pub fn ops(&self) -> &[Operation] { &self.ops }
+}

+ 12 - 0
rust-lib/flowy-ot/src/errors.rs

@@ -0,0 +1,12 @@
+use std::{error::Error, fmt};
+
+#[derive(Clone, Debug)]
+pub struct OTError;
+
+impl fmt::Display for OTError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "incompatible lengths") }
+}
+
+impl Error for OTError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> { None }
+}

+ 4 - 0
rust-lib/flowy-ot/src/lib.rs

@@ -0,0 +1,4 @@
+mod attributes;
+pub mod delta;
+pub mod errors;
+pub mod operation;

+ 103 - 0
rust-lib/flowy-ot/src/operation.rs

@@ -0,0 +1,103 @@
+use crate::attributes::Attributes;
+use std::{
+    cmp::Ordering,
+    collections::{hash_map::RandomState, HashMap},
+    ops::Deref,
+};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct Operation {
+    pub ty: OpType,
+    pub attrs: Attributes,
+}
+
+impl Operation {
+    pub fn delete(&mut self, n: u64) { self.ty.delete(n); }
+
+    pub fn retain(&mut self, n: u64) { self.ty.retain(n); }
+
+    pub fn is_plain(&self) -> bool { self.attrs.is_empty() }
+
+    pub fn is_noop(&self) -> bool {
+        match self.ty {
+            OpType::Retain(_) => true,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum OpType {
+    Delete(u64),
+    Retain(u64),
+    Insert(String),
+}
+
+impl OpType {
+    pub fn is_delete(&self) -> bool {
+        match self {
+            OpType::Delete(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_retain(&self) -> bool {
+        match self {
+            OpType::Retain(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_insert(&self) -> bool {
+        match self {
+            OpType::Insert(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn delete(&mut self, n: u64) {
+        debug_assert_eq!(self.is_delete(), true);
+        if let OpType::Delete(n_last) = self {
+            *n_last += n;
+        }
+    }
+
+    pub fn retain(&mut self, n: u64) {
+        debug_assert_eq!(self.is_retain(), true);
+        if let OpType::Retain(i_last) = self {
+            *i_last += n;
+        }
+    }
+}
+
+pub struct OperationBuilder {
+    ty: OpType,
+    attrs: Attributes,
+}
+
+impl OperationBuilder {
+    pub fn new(ty: OpType) -> OperationBuilder {
+        OperationBuilder {
+            ty,
+            attrs: Attributes::default(),
+        }
+    }
+
+    pub fn retain(n: u64) -> OperationBuilder { OperationBuilder::new(OpType::Retain(n)) }
+
+    pub fn delete(n: u64) -> OperationBuilder { OperationBuilder::new(OpType::Delete(n)) }
+
+    pub fn insert(s: String) -> OperationBuilder { OperationBuilder::new(OpType::Insert(s)) }
+
+    pub fn with_attrs(mut self, attrs: Attributes) -> OperationBuilder {
+        self.attrs = attrs;
+        self
+    }
+
+    pub fn build(self) -> Operation {
+        Operation {
+            ty: self.ty,
+            attrs: self.attrs,
+        }
+    }
+}

+ 46 - 0
rust-lib/flowy-ot/tests/helper/mod.rs

@@ -0,0 +1,46 @@
+use flowy_ot::delta::Delta;
+use rand::{prelude::*, Rng as WrappedRng};
+
+pub struct Rng(StdRng);
+
+impl Default for Rng {
+    fn default() -> Self { Rng(StdRng::from_rng(thread_rng()).unwrap()) }
+}
+
+impl Rng {
+    pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
+
+    pub fn gen_string(&mut self, len: usize) -> String {
+        (0..len).map(|_| self.0.gen::<char>()).collect()
+    }
+
+    pub fn gen_delta(&mut self, s: &str) -> Delta {
+        let mut op = Delta::default();
+        loop {
+            let left = s.chars().count() - op.base_len();
+            if left == 0 {
+                break;
+            }
+            let i = if left == 1 {
+                1
+            } else {
+                1 + self.0.gen_range(0, std::cmp::min(left - 1, 20))
+            };
+            match self.0.gen_range(0.0, 1.0) {
+                f if f < 0.2 => {
+                    op.insert(&self.gen_string(i));
+                },
+                f if f < 0.4 => {
+                    op.delete(i as u64);
+                },
+                _ => {
+                    op.retain(i as u64);
+                },
+            }
+        }
+        if self.0.gen_range(0.0, 1.0) < 0.3 {
+            op.insert(&("1".to_owned() + &self.gen_string(10)));
+        }
+        op
+    }
+}

+ 161 - 0
rust-lib/flowy-ot/tests/op_test.rs

@@ -0,0 +1,161 @@
+mod helper;
+
+use crate::helper::Rng;
+use bytecount::num_chars;
+use flowy_ot::{
+    delta::Delta,
+    operation::{OpType, OperationBuilder},
+};
+
+#[test]
+fn lengths() {
+    let mut delta = Delta::default();
+    assert_eq!(delta.base_len, 0);
+    assert_eq!(delta.target_len, 0);
+    delta.retain(5);
+    assert_eq!(delta.base_len, 5);
+    assert_eq!(delta.target_len, 5);
+    delta.insert("abc");
+    assert_eq!(delta.base_len, 5);
+    assert_eq!(delta.target_len, 8);
+    delta.retain(2);
+    assert_eq!(delta.base_len, 7);
+    assert_eq!(delta.target_len, 10);
+    delta.delete(2);
+    assert_eq!(delta.base_len, 9);
+    assert_eq!(delta.target_len, 10);
+}
+#[test]
+fn sequence() {
+    let mut delta = Delta::default();
+    delta.retain(5);
+    delta.retain(0);
+    delta.insert("lorem");
+    delta.insert("");
+    delta.delete(3);
+    delta.delete(0);
+    assert_eq!(delta.ops.len(), 3);
+}
+
+#[test]
+fn apply() {
+    for _ in 0..1000 {
+        let mut rng = Rng::default();
+        let s = rng.gen_string(50);
+        let delta = rng.gen_delta(&s);
+        assert_eq!(num_chars(s.as_bytes()), delta.base_len);
+        assert_eq!(delta.apply(&s).unwrap().chars().count(), delta.target_len);
+    }
+}
+#[test]
+fn invert() {
+    for _ in 0..1000 {
+        let mut rng = Rng::default();
+        let s = rng.gen_string(50);
+        let delta_a = rng.gen_delta(&s);
+        let delta_b = delta_a.invert(&s);
+        assert_eq!(delta_a.base_len, delta_b.target_len);
+        assert_eq!(delta_a.target_len, delta_b.base_len);
+        assert_eq!(delta_b.apply(&delta_a.apply(&s).unwrap()).unwrap(), s);
+    }
+}
+#[test]
+fn empty_ops() {
+    let mut delta = Delta::default();
+    delta.retain(0);
+    delta.insert("");
+    delta.delete(0);
+    assert_eq!(delta.ops.len(), 0);
+}
+#[test]
+fn eq() {
+    let mut delta_a = Delta::default();
+    delta_a.delete(1);
+    delta_a.insert("lo");
+    delta_a.retain(2);
+    delta_a.retain(3);
+    let mut delta_b = Delta::default();
+    delta_b.delete(1);
+    delta_b.insert("l");
+    delta_b.insert("o");
+    delta_b.retain(5);
+    assert_eq!(delta_a, delta_b);
+    delta_a.delete(1);
+    delta_b.retain(1);
+    assert_ne!(delta_a, delta_b);
+}
+#[test]
+fn ops_merging() {
+    let mut delta = Delta::default();
+    assert_eq!(delta.ops.len(), 0);
+    delta.retain(2);
+    assert_eq!(delta.ops.len(), 1);
+    assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(2).build()));
+    delta.retain(3);
+    assert_eq!(delta.ops.len(), 1);
+    assert_eq!(delta.ops.last(), Some(&OperationBuilder::retain(5).build()));
+    delta.insert("abc");
+    assert_eq!(delta.ops.len(), 2);
+    assert_eq!(
+        delta.ops.last(),
+        Some(&OperationBuilder::insert("abc".to_owned()).build())
+    );
+    delta.insert("xyz");
+    assert_eq!(delta.ops.len(), 2);
+    assert_eq!(
+        delta.ops.last(),
+        Some(&OperationBuilder::insert("abcxyz".to_owned()).build())
+    );
+    delta.delete(1);
+    assert_eq!(delta.ops.len(), 3);
+    assert_eq!(delta.ops.last(), Some(&OperationBuilder::delete(1).build()));
+    delta.delete(1);
+    assert_eq!(delta.ops.len(), 3);
+    assert_eq!(delta.ops.last(), Some(&OperationBuilder::delete(2).build()));
+}
+#[test]
+fn is_noop() {
+    let mut delta = Delta::default();
+    assert!(delta.is_noop());
+    delta.retain(5);
+    assert!(delta.is_noop());
+    delta.retain(3);
+    assert!(delta.is_noop());
+    delta.insert("lorem");
+    assert!(!delta.is_noop());
+}
+#[test]
+fn compose() {
+    for _ in 0..1000 {
+        let mut rng = Rng::default();
+        let s = rng.gen_string(20);
+        let a = rng.gen_delta(&s);
+        let after_a = a.apply(&s).unwrap();
+        assert_eq!(a.target_len, num_chars(after_a.as_bytes()));
+
+        let b = rng.gen_delta(&after_a);
+        let after_b = b.apply(&after_a).unwrap();
+        assert_eq!(b.target_len, num_chars(after_b.as_bytes()));
+
+        let ab = a.compose(&b).unwrap();
+        assert_eq!(ab.target_len, b.target_len);
+        let after_ab = ab.apply(&s).unwrap();
+        assert_eq!(after_b, after_ab);
+    }
+}
+#[test]
+fn transform() {
+    for _ in 0..1000 {
+        let mut rng = Rng::default();
+        let s = rng.gen_string(20);
+        let a = rng.gen_delta(&s);
+        let b = rng.gen_delta(&s);
+        let (a_prime, b_prime) = a.transform(&b).unwrap();
+        let ab_prime = a.compose(&b_prime).unwrap();
+        let ba_prime = b.compose(&a_prime).unwrap();
+        let after_ab_prime = ab_prime.apply(&s).unwrap();
+        let after_ba_prime = ba_prime.apply(&s).unwrap();
+        assert_eq!(ab_prime, ba_prime);
+        assert_eq!(after_ab_prime, after_ba_prime);
+    }
+}