From f6ffcc70392542556c6f625ff04c89c25ea0bb1a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Wed, 1 Jan 2020 17:45:43 -0500 Subject: [PATCH] add proptest for action sequences --- TODO.txt | 3 +- tests/sync_action_sequences.rs | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/sync_action_sequences.rs diff --git a/TODO.txt b/TODO.txt index 056c26e24..11597abd5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -5,11 +5,10 @@ - annotations: `annotation. = "annotation"` - tags: `tags. = ""` * add HTTP API +* abstract server, storage, etc. into traits * implement snapshot requests * implement backups * implement client-side encryption * design expiration - need to be sure that create / delete operations don't get reversed * cli tools -* prop testing for DB modifications - - generate a non-failing sequence of operations with syncs interspersed diff --git a/tests/sync_action_sequences.rs b/tests/sync_action_sequences.rs new file mode 100644 index 000000000..dd4a7e364 --- /dev/null +++ b/tests/sync_action_sequences.rs @@ -0,0 +1,69 @@ +use chrono::Utc; +use ot::{Operation, Server, DB}; +use proptest::prelude::*; +use uuid::Uuid; + +#[derive(Debug)] +enum Action { + Op(Operation), + Sync, +} + +fn action_sequence_strategy() -> impl Strategy> { + // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs + "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { + let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); + seq.as_bytes() + .chunks(2) + .map(|action_on| { + let action = match action_on[0] { + b'C' => Action::Op(Operation::Create { uuid }), + b'U' => Action::Op(Operation::Update { + uuid, + property: "title".into(), + value: Some("foo".into()), + timestamp: Utc::now(), + }), + b'D' => Action::Op(Operation::Delete { uuid }), + b'S' => Action::Sync, + _ => unreachable!(), + }; + let acton = action_on[1] - b'1'; + (action, acton) + }) + .collect::>() + }) +} + +proptest! { + #[test] + // check that various sequences of operations on mulitple db's do not get the db's into an + // incompatible state. The main concern here is that there might be a sequence of create + // and delete operations that results in a task existing in one DB but not existing in + // another. So, the generated sequences focus on a single task UUID. + fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { + let mut server = Server::new(); + let mut dbs = [DB::new(), DB::new(), DB::new()]; + + for (action, db) in action_sequence { + println!("{:?} on db {}", action, db); + + let db = &mut dbs[db as usize]; + match action { + Action::Op(op) => { + if let Err(e) = db.apply(op) { + println!(" {:?} (ignored)", e); + } + }, + Action::Sync => db.sync("me", &mut server), + } + } + + println!("{:?}", dbs[0].tasks()); + println!("{:?}", dbs[1].tasks()); + println!("{:?}", dbs[2].tasks()); + + assert_eq!(dbs[0].tasks(), dbs[1].tasks()); + assert_eq!(dbs[1].tasks(), dbs[2].tasks()); + } +}