diff --git a/Cargo.lock b/Cargo.lock index 61e1c4a..1a18f3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base64ct" version = "1.6.0" @@ -42,6 +48,21 @@ dependencies = [ "rand", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -164,6 +185,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -198,12 +225,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -215,11 +254,22 @@ name = "mast" version = "0.1.0" dependencies = [ "blake3", + "proptest", "redb", "tempfile", "varu64", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -246,6 +296,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.33" @@ -285,6 +361,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "redb" version = "1.4.0" @@ -303,6 +388,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustix" version = "0.38.28" @@ -316,6 +407,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "snafu" version = "0.6.10" @@ -404,6 +507,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -425,6 +534,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/mast/Cargo.toml b/mast/Cargo.toml index 06ef672..17b4ff9 100644 --- a/mast/Cargo.toml +++ b/mast/Cargo.toml @@ -11,4 +11,5 @@ redb = "1.4.0" varu64 = "0.7.0" [dev-dependencies] +proptest = "1.4.0" tempfile = "3.8.1" diff --git a/mast/proptest-regressions/operations/insert.txt b/mast/proptest-regressions/operations/insert.txt new file mode 100644 index 0000000..93591fb --- /dev/null +++ b/mast/proptest-regressions/operations/insert.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 7912e0f85e7a1a9892a9c85944f3392f6e1fe1a33d2ea00b3809400d75166743 # shrinks to random_entries = [([135], [150]), ([25], [0]), ([129], [0]), ([135], [150])] diff --git a/mast/src/operations/insert.rs b/mast/src/operations/insert.rs index a6cb1ce..03eeb39 100644 --- a/mast/src/operations/insert.rs +++ b/mast/src/operations/insert.rs @@ -116,7 +116,12 @@ pub fn insert( if let Some(mut existing) = path.existing { if existing.value() == value { // There is really nothing to update. Skip traversing upwards. - return path.upper_path.pop().map(|(n, _)| n).unwrap_or(existing); + + return path + .upper_path + .first() + .map(|(n, _)| n.clone()) + .unwrap_or(existing); } // Decrement the old version. @@ -222,6 +227,35 @@ fn binary_search_path( #[cfg(test)] mod test { use crate::test::{test_operations, Entry}; + use proptest::prelude::*; + + proptest! { + #[test] + /// Test that upserting an entry with the same key in different tree shapes results in the + /// expected structure + fn test_upsert(random_entries in prop::collection::vec( + (prop::collection::vec(any::(), 1), prop::collection::vec(any::(), 1)), + 1..10, + )) { + let operations = random_entries.into_iter().map(|(key, value)| { + Entry::insert(&key, &value) + }).collect::>(); + + test_operations(&operations, None); + } + + #[test] + fn test_general_insertiong(random_entries in prop::collection::vec( + (prop::collection::vec(any::(), 32), prop::collection::vec(any::(), 32)), + 1..50, + )) { + let operations = random_entries.into_iter().map(|(key, value)| { + Entry::insert(&key, &value) + }).collect::>(); + + test_operations(&operations, None); + } + } #[test] fn insert_single_entry() { diff --git a/mast/src/test.rs b/mast/src/test.rs index 8d2a2be..5c0efbf 100644 --- a/mast/src/test.rs +++ b/mast/src/test.rs @@ -162,12 +162,12 @@ fn into_mermaid_graph(treap: &HashTreap) -> String { } fn build_graph_string(treap: &HashTreap, node: &Node, graph: &mut String) { - let key = bytes_to_string(node.key()); + let key = format_key(node.key()); let node_label = format!("{}(({}))", node.hash(), key); // graph.push_str(&format!("## START node {}\n", node_label)); if let Some(child) = treap.get_node(node.left()) { - let key = bytes_to_string(child.key()); + let key = format_key(child.key()); let child_label = format!("{}(({}))", child.hash(), key); graph.push_str(&format!(" {} --l--> {};\n", node_label, child_label)); @@ -178,7 +178,7 @@ fn build_graph_string(treap: &HashTreap, node: &Node, graph: &mut String) { } if let Some(child) = treap.get_node(node.right()) { - let key = bytes_to_string(child.key()); + let key = format_key(child.key()); let child_label = format!("{}(({}))", child.hash(), key); graph.push_str(&format!(" {} --r--> {};\n", node_label, child_label)); @@ -189,6 +189,6 @@ fn build_graph_string(treap: &HashTreap, node: &Node, graph: &mut String) { } } -fn bytes_to_string(byte: &[u8]) -> String { - String::from_utf8(byte.to_vec()).expect("Invalid utf8 key in test with mermaig graph") +fn format_key(bytes: &[u8]) -> String { + format!("\"{:?}\"", bytes) }