diff --git a/proptest-regressions/proptest.txt b/proptest-regressions/proptest.txt new file mode 100644 index 0000000..601f38b --- /dev/null +++ b/proptest-regressions/proptest.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc c088b01c1a4b9a492a590d2502f262cf18cb5a8212cacbe45f498931013a57d9 # shrinks to dag = DAG { roots: {[12, 209, 112, 144, 111, 102, 222, 65], [61, 88, 109, 99, 208, 29, 32, 124], [66, 28, 1, 12, 253, 41, 177, 169], [66, 109, 117, 33, 29, 147, 107, 79], [68, 23, 235, 211, 128, 39, 104, 92], [68, 41, 65, 186, 2, 225, 244, 191], [74, 205, 130, 150, 139, 42, 92, 139], [84, 73, 215, 73, 180, 169, 178, 246], [100, 95, 200, 229, 49, 88, 55, 243], [120, 188, 47, 100, 174, 95, 207, 54], [126, 2, 38, 152, 195, 97, 41, 195], [170, 5, 151, 159, 243, 149, 102, 226], [205, 86, 229, 238, 33, 109, 223, 30], [238, 104, 89, 107, 101, 113, 236, 239]}, nodes: {[12, 209, 112, 144, 111, 102, 222, 65]: Node { id: [12, 209, 112, 144, 111, 102, 222, 65], item: "\u{77066}\u{839a6}Ѩx$\u{f5d90}\u{7f}¥", item_id: [43, 159, 87, 213, 170, 161, 96, 158], dependency_ids: {[29, 81, 198, 58, 220, 71, 244, 47], [130, 35, 86, 166, 76, 64, 98, 51], [217, 244, 181, 147, 15, 1, 101, 57]}, _phantom: PhantomData }, [29, 81, 198, 58, 220, 71, 244, 47]: Node { id: [29, 81, 198, 58, 220, 71, 244, 47], item: ".'\\/", item_id: [124, 116, 6, 14, 68, 117, 197, 151], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [38, 58, 79, 42, 215, 174, 243, 58]: Node { id: [38, 58, 79, 42, 215, 174, 243, 58], item: "\u{7e521}\"", item_id: [38, 58, 79, 42, 215, 174, 243, 58], dependency_ids: {}, _phantom: PhantomData }, [61, 88, 109, 99, 208, 29, 32, 124]: Node { id: [61, 88, 109, 99, 208, 29, 32, 124], item: "", item_id: [44, 83, 12, 21, 98, 167, 251, 209], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [66, 28, 1, 12, 253, 41, 177, 169]: Node { id: [66, 28, 1, 12, 253, 41, 177, 169], item: "\u{76554}\t'¥\u{0}{/", item_id: [51, 32, 26, 216, 20, 4, 219, 114], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [66, 109, 117, 33, 29, 147, 107, 79]: Node { id: [66, 109, 117, 33, 29, 147, 107, 79], item: "d'�\u{202e}dȺ", item_id: [7, 95, 82, 2, 141, 239, 196, 12], dependency_ids: {[29, 81, 198, 58, 220, 71, 244, 47], [130, 35, 86, 166, 76, 64, 98, 51], [217, 244, 181, 147, 15, 1, 101, 57]}, _phantom: PhantomData }, [68, 23, 235, 211, 128, 39, 104, 92]: Node { id: [68, 23, 235, 211, 128, 39, 104, 92], item: "\u{1b}🕴\t𣐲\\\u{989bb}<搥<\u{101e36}<¥\u{da20a}\"\u{441fa}\u{acc17}\u{e40a6}/", item_id: [147, 190, 178, 24, 90, 81, 245, 188], dependency_ids: {[29, 81, 198, 58, 220, 71, 244, 47], [130, 35, 86, 166, 76, 64, 98, 51], [217, 244, 181, 147, 15, 1, 101, 57]}, _phantom: PhantomData }, [68, 41, 65, 186, 2, 225, 244, 191]: Node { id: [68, 41, 65, 186, 2, 225, 244, 191], item: "\u{cd092}'G\u{feff}z&3Ⱥ\u{1b}.*}\u{80b59}\u{4c6a5}S\u{c7ad3}:?\\'`\",'`*\u{5c163}$&=\u{1a1f5}C", item_id: [176, 68, 50, 212, 76, 136, 83, 160], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [74, 205, 130, 150, 139, 42, 92, 139]: Node { id: [74, 205, 130, 150, 139, 42, 92, 139], item: "\"\u{86f00}\u{ffe2c}{ \u{b} \u{2ee2c}\u{67247}\u{3}Î\u{97a98}=E\u{a0}g\"_?\u{edcde}", item_id: [14, 243, 82, 209, 194, 148, 16, 177], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [82, 78, 44, 253, 111, 110, 254, 23]: Node { id: [82, 78, 44, 253, 111, 110, 254, 23], item: "", item_id: [44, 83, 12, 21, 98, 167, 251, 209], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [84, 73, 215, 73, 180, 169, 178, 246]: Node { id: [84, 73, 215, 73, 180, 169, 178, 246], item: "@\u{7}\"=", item_id: [185, 147, 55, 106, 149, 0, 212, 130], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [100, 95, 200, 229, 49, 88, 55, 243]: Node { id: [100, 95, 200, 229, 49, 88, 55, 243], item: "^\u{ef93c}🕴\u{feff}<Ѩ.¥", item_id: [193, 47, 240, 147, 90, 133, 125, 157], dependency_ids: {}, _phantom: PhantomData }, [205, 86, 229, 238, 33, 109, 223, 30]: Node { id: [205, 86, 229, 238, 33, 109, 223, 30], item: "🕴¥\u{ee16a}\\-\u{0}.\r麭ORL\u{1b}\u{feff}\u{a9114}\u{1}=\u{b}\u{73544}`'`�%¥\u{9fef0}¥\u{108d50}\u{1b}\u{99505}\tx", item_id: [146, 80, 183, 143, 248, 118, 243, 240], dependency_ids: {[38, 58, 79, 42, 215, 174, 243, 58], [82, 78, 44, 253, 111, 110, 254, 23], [193, 47, 240, 147, 90, 133, 125, 157]}, _phantom: PhantomData }, [217, 244, 181, 147, 15, 1, 101, 57]: Node { id: [217, 244, 181, 147, 15, 1, 101, 57], item: "?\u{ba089}H*\u{0}&1\u{7f}&\u{0}\\\" where N: ByteEncoder, diff --git a/src/node.rs b/src/node.rs index dc2e2ca..87b7383 100644 --- a/src/node.rs +++ b/src/node.rs @@ -25,7 +25,7 @@ use crate::hash::{ByteEncoder, HashWriter}; /// Nodes are tied to a specific implementation of the HashWriter trait which is itself tied /// to the DAG they are stored in guaranteeing that the same Hashing implementation is used /// for each node in the DAG. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Node where N: ByteEncoder, diff --git a/src/proptest.rs b/src/proptest.rs index 7efa9b6..9ac3264 100644 --- a/src/proptest.rs +++ b/src/proptest.rs @@ -14,25 +14,61 @@ use proptest::prelude::*; use std::collections::{hash_map::DefaultHasher, BTreeMap, BTreeSet}; -use crate::DAG; +use crate::{NodeCompare, DAG}; -fn edge_strategy(nodes_count: usize) -> impl Strategy, BTreeSet)> { +fn simple_edge_strategy( + nodes_count: usize, +) -> impl Strategy, BTreeSet)> { prop::collection::vec(".*", 4..nodes_count).prop_flat_map(|payloads| { let nodes_len = payloads.len(); - // 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. + // our list of roots. prop::collection::btree_set(1..nodes_len, 1..(nodes_len / 2)), ) }) } +fn complex_dag_strategy( + nodes_count: usize, + depth: usize, + branch: usize, +) -> impl Strategy> { + prop::collection::vec(".*", depth..nodes_count).prop_flat_map(move |payloads| { + let nodes_len = payloads.len(); + let mut dag = DAG::::new(); + // partition the payloads into depth pieces + let mut id_stack: Vec<[u8; 8]> = Vec::new(); + for chunk in payloads.chunks(nodes_len / depth) { + // loop through the partions adding each partions nodes to the dag. + let dep_sets: Vec> = if id_stack.is_empty() { + vec![BTreeSet::new()] + } else { + let mut dep_sets = Vec::new(); + for id_chunk in id_stack.chunks(branch) { + let id_set = id_chunk.iter().fold(BTreeSet::new(), |mut acc, item| { + acc.insert(item.clone()); + acc + }); + dep_sets.push(id_set); + } + dep_sets + }; + let dep_set_len = dep_sets.len(); + for (idx, p) in chunk.iter().enumerate() { + let dep_idx = idx % dep_set_len; + let dep_set = dep_sets[dep_idx].clone(); + id_stack.push(dag.add_node(p.clone(), dep_set).unwrap().clone()); + } + } + Just(dag) + }) +} + proptest! { #[test] - fn test_dag_add_node_properties((nodes, parent_idxs) in edge_strategy(100)) { + fn test_dag_add_node_properties((nodes, parent_idxs) in simple_edge_strategy(100)) { // TODO implement the tests now let mut dag = DAG::::new(); let parent_count = parent_idxs.len(); @@ -58,3 +94,37 @@ proptest! { assert!(dag.get_nodes().len() == node_set.len()); } } + +proptest! { + #[test] + fn test_complex_dag_node_properties(dag in complex_dag_strategy(100, 10, 3)) { + // TODO(jwall): We can assert much more about the DAG if we get more clever in what we return. + let nodes = dag.get_nodes(); + assert!(nodes.len() <= 100); + + let roots = dag.get_roots(); + assert!(roots.len() < dag.get_nodes().len()); + + for node_id in nodes.keys() { + let mut is_descendant = false; + if roots.contains(node_id) { + continue; + } + for root in roots.iter() { + if let NodeCompare::After = dag.compare(root, node_id) { + // success + is_descendant = true; + } + } + assert!(is_descendant); + } + // Check that every root node is uncomparable. + for left_root in roots.iter() { + for right_root in roots.iter() { + if left_root != right_root { + assert_eq!(dag.compare(left_root, right_root), NodeCompare::Uncomparable); + } + } + } + } +}