From 0a2293a9c56721291f4c5153145f9a89946ab3a6 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Sat, 28 Dec 2019 14:31:37 -0500 Subject: [PATCH] use proptest to check invariants --- Cargo.lock | 246 +++++++++++++++++++++++++ Cargo.toml | 3 + src/lib.rs | 3 + src/operation.rs | 111 ++++++++++- src/taskdb.rs | 71 ++++--- tests/operation_transform_invariant.rs | 60 ++++++ 6 files changed, 448 insertions(+), 46 deletions(-) create mode 100644 tests/operation_transform_invariant.rs diff --git a/Cargo.lock b/Cargo.lock index c839c94c8..828a6238d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,29 @@ dependencies = [ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bit-set" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "c2-chacha" version = "0.2.3" @@ -53,6 +76,14 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.6" @@ -73,6 +104,16 @@ dependencies = [ "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "getrandom" version = "0.1.13" @@ -88,6 +129,11 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.66" @@ -116,6 +162,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -133,6 +180,30 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proptest" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "quote" version = "1.0.2" @@ -141,6 +212,24 @@ dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.7.2" @@ -153,6 +242,15 @@ dependencies = [ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_chacha" version = "0.2.1" @@ -162,6 +260,19 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand_core" version = "0.5.1" @@ -170,6 +281,14 @@ dependencies = [ "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -178,16 +297,96 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rusty-fork" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ryu" version = "1.0.2" @@ -242,6 +441,19 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -266,6 +478,14 @@ dependencies = [ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.7.0" @@ -294,35 +514,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" "checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" +"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" "checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" "checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" +"checksum proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rusty-fork 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +"checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 2862faab0..f5e337fa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,6 @@ uuid = { version = "0.8.1", features = ["serde", "v4"] } serde_json = "1.0" chrono = "0.4.10" failure = {version = "0.1.5", features = ["derive"] } + +[dev-dependencies] +proptest = "0.9.4" diff --git a/src/lib.rs b/src/lib.rs index 3ef1ecf6e..98390d7c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,6 @@ mod errors; mod operation; mod taskdb; + +pub use operation::Operation; +pub use taskdb::DB; diff --git a/src/operation.rs b/src/operation.rs index 3d8258b8d..8aafbce5b 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -72,6 +72,7 @@ impl Operation { (None, Some(operation2)) } else if timestamp1 > timestamp2 { // prefer the later modification + //(Some(operation1), None) (Some(operation1), None) } else { // arbitrarily resolve in favor of the first operation @@ -89,6 +90,10 @@ impl Operation { mod test { use super::*; use crate::taskdb::DB; + use chrono::{Duration, Utc}; + + // note that `tests/operation_transform_invariant.rs` tests the transform function quite + // thoroughly, so this testing is light. fn test_transform( o1: Operation, @@ -102,14 +107,14 @@ mod test { // check that the two operation sequences have the same effect, enforcing the invariant of // the transform function. let mut db1 = DB::new(); - db1.apply(o1).unwrap(); + db1.apply(o1); if let Some(o) = o2p { - db1.apply(o).unwrap(); + db1.apply(o); } let mut db2 = DB::new(); - db2.apply(o2).unwrap(); + db2.apply(o2); if let Some(o) = o1p { - db2.apply(o).unwrap(); + db2.apply(o); } assert_eq!(db1.tasks(), db2.tasks()); } @@ -120,10 +125,100 @@ mod test { let uuid2 = Uuid::new_v4(); test_transform( - Operation::Create { uuid: uuid1 }, - Operation::Create { uuid: uuid2 }, - Some(Operation::Create { uuid: uuid1 }), - Some(Operation::Create { uuid: uuid2 }), + Create { uuid: uuid1 }, + Create { uuid: uuid2 }, + Some(Create { uuid: uuid1 }), + Some(Create { uuid: uuid2 }), + ); + } + + #[test] + fn test_related_updates_different_props() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }, + Update { + uuid, + property: "def".into(), + value: false.into(), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }), + Some(Update { + uuid, + property: "def".into(), + value: false.into(), + timestamp, + }), + ); + } + + #[test] + fn test_related_updates_same_prop() { + let uuid = Uuid::new_v4(); + let timestamp1 = Utc::now(); + let timestamp2 = timestamp1 + Duration::seconds(10); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp: timestamp1, + }, + Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp: timestamp2, + }, + None, + Some(Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp: timestamp2, + }), + ); + } + + #[test] + fn test_related_updates_same_prop_same_time() { + let uuid = Uuid::new_v4(); + let timestamp = Utc::now(); + + test_transform( + Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }, + Update { + uuid, + property: "abc".into(), + value: false.into(), + timestamp, + }, + Some(Update { + uuid, + property: "abc".into(), + value: true.into(), + timestamp, + }), + None, ); } } diff --git a/src/taskdb.rs b/src/taskdb.rs index acc555c0b..3417aa020 100644 --- a/src/taskdb.rs +++ b/src/taskdb.rs @@ -1,11 +1,10 @@ -use crate::errors::Error; use crate::operation::Operation; use serde_json::Value; use std::collections::hash_map::Entry; use std::collections::HashMap; use uuid::Uuid; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct DB { // The current state, with all pending operations applied tasks: HashMap>, @@ -30,18 +29,15 @@ impl DB { } /// Apply an operation to the DB. Aside from synchronization operations, this - /// is the only way to modify the DB. - pub fn apply(&mut self, op: Operation) -> Result<(), Error> { + /// is the only way to modify the DB. In cases where an operation does not + /// make sense, this function will ignore the operation. + pub fn apply(&mut self, op: Operation) { match op { Operation::Create { uuid } => { - match self.tasks.entry(uuid) { - Entry::Occupied(_) => { - return Err(Error::DBError(format!("Task {} already exists", uuid))); - } - ent @ Entry::Vacant(_) => { - ent.or_insert(HashMap::new()); - } - }; + // insert if the task does not already exist + if let ent @ Entry::Vacant(_) = self.tasks.entry(uuid) { + ent.or_insert(HashMap::new()); + } } Operation::Update { ref uuid, @@ -49,18 +45,13 @@ impl DB { ref value, timestamp: _, } => { - match self.tasks.get_mut(uuid) { - Some(task) => { - task.insert(property.clone(), value.clone()); - } - None => { - return Err(Error::DBError(format!("Task {} does not exist", uuid))); - } - }; + // update if this task exists, otherwise ignore + if let Some(task) = self.tasks.get_mut(uuid) { + task.insert(property.clone(), value.clone()); + } } }; self.operations.push(op); - Ok(()) } /// Get a read-only reference to the underlying set of tasks. @@ -82,7 +73,7 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op = Operation::Create { uuid }; - db.apply(op.clone()).unwrap(); + db.apply(op.clone()); let mut exp = HashMap::new(); exp.insert(uuid, HashMap::new()); @@ -94,11 +85,14 @@ mod tests { fn test_apply_create_exists() { let mut db = DB::new(); let uuid = Uuid::new_v4(); - db.apply(Operation::Create { uuid }).unwrap(); - assert_eq!( - db.apply(Operation::Create { uuid }), - Err(Error::DBError(format!("Task {} already exists", uuid))) - ); + let op = Operation::Create { uuid }; + db.apply(op.clone()); + db.apply(op.clone()); + + let mut exp = HashMap::new(); + exp.insert(uuid, HashMap::new()); + assert_eq!(db.tasks(), &exp); + assert_eq!(db.operations, vec![op.clone(), op]); } #[test] @@ -106,14 +100,14 @@ mod tests { let mut db = DB::new(); let uuid = Uuid::new_v4(); let op1 = Operation::Create { uuid }; - db.apply(op1.clone()).unwrap(); + db.apply(op1.clone()); let op2 = Operation::Update { uuid, property: String::from("title"), value: Value::from("\"my task\""), timestamp: Utc::now(), }; - db.apply(op2.clone()).unwrap(); + db.apply(op2.clone()); let mut exp = HashMap::new(); let mut task = HashMap::new(); @@ -127,14 +121,15 @@ mod tests { fn test_apply_update_does_not_exist() { let mut db = DB::new(); let uuid = Uuid::new_v4(); - assert_eq!( - db.apply(Operation::Update { - uuid, - property: String::from("title"), - value: Value::from("\"my task\""), - timestamp: Utc::now(), - }), - Err(Error::DBError(format!("Task {} does not exist", uuid))) - ); + let op = Operation::Update { + uuid, + property: String::from("title"), + value: Value::from("\"my task\""), + timestamp: Utc::now(), + }; + db.apply(op.clone()); + + assert_eq!(db.tasks(), &HashMap::new()); + assert_eq!(db.operations, vec![op]); } } diff --git a/tests/operation_transform_invariant.rs b/tests/operation_transform_invariant.rs new file mode 100644 index 000000000..61b0d9816 --- /dev/null +++ b/tests/operation_transform_invariant.rs @@ -0,0 +1,60 @@ +use chrono::Utc; +use ot::Operation; +use ot::DB; +use proptest::prelude::*; +use uuid::Uuid; + +fn uuid_strategy() -> impl Strategy { + prop_oneof![ + Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), + Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), + Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), + Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), + ] +} + +fn operation_strategy() -> impl Strategy { + prop_oneof![ + uuid_strategy().prop_map(|uuid| Operation::Create { uuid }), + (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { + Operation::Update { + uuid, + property, + value: true.into(), + timestamp: Utc::now(), + } + }), + ] +} + +proptest! { + #[test] + fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { + let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone()); + + // check that the two operation sequences have the same effect, enforcing the invariant of + // the transform function. This needs some care as if either of the operations is + // an Update then we must ensure the task already exists in the DB. + let mut db1 = DB::new(); + + if let Operation::Update{ ref uuid, .. } = o1 { + db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + if let Operation::Update{ ref uuid, .. } = o2 { + db1.apply(Operation::Create{uuid: uuid.clone()}); + } + + let mut db2 = db1.clone(); + + db1.apply(o1); + if let Some(o) = o2p { + db1.apply(o); + } + db2.apply(o2); + if let Some(o) = o1p { + db2.apply(o); + } + assert_eq!(db1.tasks(), db2.tasks()); + } +}