From be789a2feeb89a4029ec4f7cd5575beb78be6327 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 2 Aug 2022 03:01:27 -0400 Subject: [PATCH] Add another test for for idempotency --- Cargo.toml | 7 ++++++ src/lib.rs | 5 +++++ src/proptest.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ src/test.rs | 22 +++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/proptest.rs diff --git a/Cargo.toml b/Cargo.toml index 9c6c24b..95de544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,10 @@ license = "Apache License 2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +[dependencies.proptest] +version = "1.0.0" +optional = true + +[features] +default = [] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9304caf..13d7abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,8 @@ mod node; pub enum EdgeError { NoSuchDependents, } +// TODO(jwall): In order to avoid copies it is probably smart to have some concept of +// a node pool. /// A Merkle-DAG implementation. This is a modification on the standard Merkle Tree data structure /// but instead of a tree it is a DAG and as a result can have multiple roots. A merkle-dag specifies @@ -114,3 +116,6 @@ where #[cfg(test)] mod test; + +#[cfg(all(test, feature = "proptest"))] +mod proptest; diff --git a/src/proptest.rs b/src/proptest.rs new file mode 100644 index 0000000..62fd453 --- /dev/null +++ b/src/proptest.rs @@ -0,0 +1,58 @@ +// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use proptest::prelude::*; +use std::collections::{hash_map::DefaultHasher, BTreeMap, BTreeSet}; + +use crate::DAG; + +fn edge_strategy(nodes_count: usize) -> impl Strategy, BTreeSet)> { + prop::collection::vec(".*", 4..nodes_count).prop_flat_map(|payloads| { + let nodes_len = payloads.len(); + // TODO(jwall): Generate valid DAGs + // select a random set of payloads to be roots. + // select a random set of non root payloads to be dependencies + ( + // our total list of nodes. + Just(payloads), + // our random list of roots. + prop::collection::btree_set(1..nodes_len, 1..(nodes_len / 2)), + ) + }) +} + +proptest! { + #[test] + fn test_dag_add_node_properties((nodes, parent_idxs) in edge_strategy(100)) { + // TODO implement the tests now + let mut dag = DAG::::new(); + let parent_count = parent_idxs.len(); + let mut dependents = BTreeMap::new(); + for (idx, n) in nodes.iter().cloned().enumerate() { + if !parent_idxs.contains(&idx) { + let node_id = dag.add_node(n, BTreeSet::new()).unwrap(); + let parent = idx % parent_count; + if dependents.contains_key(&parent) { + dependents.get_mut(&parent).map(|v: &mut BTreeSet<[u8; 8]>| v.insert(node_id)); + } else { + dependents.insert(parent, BTreeSet::from([node_id])); + } + } + } + for (pidx, dep_ids) in dependents { + dag.add_node(nodes[pidx].clone(), dep_ids).unwrap(); + } + assert!(dag.get_roots().len() <= parent_count); + assert!(dag.get_nodes().len() <= nodes.len()); + } +} diff --git a/src/test.rs b/src/test.rs index b0166a0..e41349c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -61,3 +61,25 @@ fn test_adding_nodes_is_idempotent() { assert_eq!(root_size, dag.get_roots().len()); assert_eq!(nodes_size, dag.get_nodes().len()); } + +#[test] +fn test_adding_nodes_is_idempotent_regardless_of_dep_order() { + let mut dag = DAG::<&str, DefaultHasher, 8>::new(); + let quake_node_id = dag.add_node("quake", BTreeSet::new()).unwrap(); + let qualm_node_id = dag.add_node("qualm", BTreeSet::new()).unwrap(); + let quell_node_id = dag.add_node("quell", BTreeSet::new()).unwrap(); + let dep_ids = BTreeSet::from([quake_node_id, qualm_node_id, quell_node_id]); + dag.add_node("foo", dep_ids).unwrap(); + let root_size = dag.get_roots().len(); + let nodes_size = dag.get_nodes().len(); + + let dep_ids = BTreeSet::from([quell_node_id, quake_node_id, qualm_node_id]); + dag.add_node("foo", dep_ids).unwrap(); + assert_eq!(root_size, dag.get_roots().len()); + assert_eq!(nodes_size, dag.get_nodes().len()); + + let dep_ids = BTreeSet::from([qualm_node_id, quell_node_id, quake_node_id]); + dag.add_node("foo", dep_ids).unwrap(); + assert_eq!(root_size, dag.get_roots().len()); + assert_eq!(nodes_size, dag.get_nodes().len()); +}