Merge pull request #39 from djmitche/issue14

Refactor into several crates
This commit is contained in:
Dustin J. Mitchell
2020-11-23 16:10:18 -05:00
committed by GitHub
25 changed files with 765 additions and 536 deletions

316
Cargo.lock generated
View File

@@ -1,5 +1,20 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "addr2line"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "ansi_term"
version = "0.11.0"
@@ -11,10 +26,11 @@ dependencies = [
[[package]]
name = "atty"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
@@ -26,41 +42,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "backtrace"
version = "0.3.40"
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
dependencies = [
"backtrace-sys",
"cfg-if",
"addr2line",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtrace-sys"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "bit-set"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.5.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb"
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"
[[package]]
name = "bitflags"
@@ -70,24 +84,15 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.3.2"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
[[package]]
name = "c2-chacha"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
dependencies = [
"ppv-lite86",
]
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.48"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
[[package]]
name = "cfg-if"
@@ -96,22 +101,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.10"
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi",
]
[[package]]
name = "clap"
version = "2.33.0"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
@@ -133,9 +146,9 @@ dependencies = [
[[package]]
name = "failure"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
"backtrace",
"failure_derive",
@@ -143,9 +156,9 @@ dependencies = [
[[package]]
name = "failure_derive"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote",
@@ -155,9 +168,9 @@ dependencies = [
[[package]]
name = "fnv"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuchsia-cprng"
@@ -167,20 +180,35 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "getrandom"
version = "0.1.13"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [
"cfg-if 0.1.10",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "gimli"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
[[package]]
name = "hermit-abi"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "itoa"
version = "0.4.4"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "kv"
@@ -203,9 +231,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.66"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
[[package]]
name = "lmdb-rkv"
@@ -231,50 +259,66 @@ dependencies = [
]
[[package]]
name = "num-integer"
version = "0.1.41"
name = "miniz_oxide"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
dependencies = [
"autocfg",
"adler",
"autocfg 1.0.1",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg 1.0.1",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.10"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
"autocfg 1.0.1",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
name = "object"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.6"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro2"
version = "1.0.7"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "proptest"
version = "0.9.4"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf147e022eacf0c8a054ab864914a7602618adba841d800a9a9868a5237a529f"
checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b"
dependencies = [
"bit-set",
"bitflags",
@@ -292,15 +336,15 @@ dependencies = [
[[package]]
name = "quick-error"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.2"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
@@ -324,7 +368,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg",
"autocfg 0.1.7",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
@@ -339,13 +383,13 @@ dependencies = [
[[package]]
name = "rand"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha 0.2.1",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
@@ -356,17 +400,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg",
"autocfg 0.1.7",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"c2-chacha",
"ppv-lite86",
"rand_core 0.5.1",
]
@@ -452,7 +496,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg",
"autocfg 0.1.7",
"rand_core 0.4.2",
]
@@ -476,21 +520,21 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.1.56"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "regex-syntax"
version = "0.6.12"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
[[package]]
name = "remove_dir_all"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
@@ -507,9 +551,9 @@ dependencies = [
[[package]]
name = "rmp-serde"
version = "0.14.0"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a31c0798045f039ace94e0166f76478b3ba83116ec7c9d4bc934c5b13b8df21"
checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8"
dependencies = [
"byteorder",
"rmp",
@@ -518,9 +562,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.16"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
[[package]]
name = "rusty-fork"
@@ -536,24 +580,24 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.2"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.104"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
dependencies = [
"proc-macro2",
"quote",
@@ -562,9 +606,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.44"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
dependencies = [
"itoa",
"ryu",
@@ -579,20 +623,24 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.12"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc157159e2a7df58cd67b1cace10b8ed256a404fb0070593f137d8ba6bef4de"
checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "sync-server"
version = "0.1.0"
[[package]]
name = "synstructure"
version = "0.12.3"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote",
@@ -605,7 +653,6 @@ name = "taskchampion"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"failure",
"kv",
"lmdb-rkv",
@@ -616,6 +663,15 @@ dependencies = [
"uuid",
]
[[package]]
name = "taskchampion-cli"
version = "0.1.0"
dependencies = [
"clap",
"taskchampion",
"uuid",
]
[[package]]
name = "tempdir"
version = "0.3.7"
@@ -632,9 +688,9 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if",
"cfg-if 0.1.10",
"libc",
"rand 0.7.2",
"rand 0.7.3",
"redox_syscall",
"remove_dir_all",
"winapi",
@@ -651,18 +707,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.9"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e"
checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.9"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
dependencies = [
"proc-macro2",
"quote",
@@ -671,35 +727,35 @@ dependencies = [
[[package]]
name = "time"
version = "0.1.42"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"redox_syscall",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "toml"
version = "0.5.5"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
[[package]]
name = "unicode-width"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "uuid"
@@ -707,15 +763,15 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand 0.7.2",
"rand 0.7.3",
"serde",
]
[[package]]
name = "vec_map"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "wait-timeout"
@@ -728,15 +784,21 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.7.0"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",

View File

@@ -1,19 +1,7 @@
[package]
name = "taskchampion"
version = "0.1.0"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2018"
[workspace]
[dependencies]
uuid = { version = "0.8.1", features = ["serde", "v4"] }
serde = "1.0.104"
serde_json = "1.0"
chrono = { version = "0.4.10", features = ["serde"] }
failure = {version = "0.1.5", features = ["derive"] }
clap = "~2.33.0"
kv = {version = "0.10.0", features = ["msgpack-value"]}
lmdb-rkv = {version = "0.12.3"}
[dev-dependencies]
proptest = "0.9.4"
tempdir = "0.3.7"
members = [
"taskchampion",
"cli",
"sync-server"
]

10
cli/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "taskchampion-cli"
version = "0.1.0"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2018"
[dependencies]
clap = "~2.33.0"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
taskchampion = { path = "../taskchampion" }

View File

@@ -1,7 +1,6 @@
extern crate clap;
use clap::{App, Arg, SubCommand};
use std::path::Path;
use taskchampion::{taskstorage, Replica, Status, DB};
use taskchampion::{taskstorage, Replica, Status};
use uuid::Uuid;
fn main() {
@@ -21,12 +20,9 @@ fn main() {
.subcommand(SubCommand::with_name("gc").about("run garbage collection"))
.get_matches();
let mut replica = Replica::new(
DB::new(Box::new(
taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(),
))
.into(),
);
let mut replica = Replica::new(Box::new(
taskstorage::KVStorage::new(Path::new("/tmp/tasks")).unwrap(),
));
match matches.subcommand() {
("add", Some(matches)) => {

View File

@@ -1,22 +0,0 @@
// TODO: remove this eventually when there's an API
#![allow(dead_code)]
#![allow(unused_variables)]
#[macro_use]
extern crate failure;
mod errors;
mod operation;
mod replica;
mod server;
mod task;
mod taskdb;
pub mod taskstorage;
pub use operation::Operation;
pub use replica::Replica;
pub use server::Server;
pub use task::Priority;
pub use task::Status;
pub use task::Task;
pub use taskdb::DB;

9
sync-server/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "sync-server"
version = "0.1.0"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

18
taskchampion/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "taskchampion"
version = "0.1.0"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2018"
[dependencies]
uuid = { version = "0.8.1", features = ["serde", "v4"] }
serde = "1.0.104"
serde_json = "1.0"
chrono = { version = "0.4.10", features = ["serde"] }
failure = {version = "0.1.5", features = ["derive"] }
kv = {version = "0.10.0", features = ["msgpack-value"]}
lmdb-rkv = {version = "0.12.3"}
[dev-dependencies]
proptest = "0.9.4"
tempdir = "0.3.7"

View File

@@ -4,7 +4,4 @@ use failure::Fail;
pub enum Error {
#[fail(display = "Task Database Error: {}", _0)]
DBError(String),
#[fail(display = "Replica Error: {}", _0)]
ReplicaError(String),
}

38
taskchampion/src/lib.rs Normal file
View File

@@ -0,0 +1,38 @@
/*!
This crate implements the core of TaskChampion, the [replica](crate::Replica).
A TaskChampion replica is a local copy of a user's task data. As the name suggests, several
replicas of the same data can exist (such as on a user's laptop and on their phone) and can
synchronize with one another.
# Task Storage
The [`taskstorage`](crate::taskstorage) module supports pluggable storage for a replica's data.
An implementation is provided, but users of this crate can provide their own implementation as well.
# Server
Replica synchronization takes place against a server.
The [`server`](crate::server) module defines the interface a server must meet.
# See Also
See the [TaskChampion Book](https://github.com/djmitche/taskchampion/blob/main/docs/src/SUMMARY.md)
for more information about the design and usage of the tool.
*/
mod errors;
mod replica;
pub mod server;
mod task;
mod taskdb;
pub mod taskstorage;
pub use replica::Replica;
pub use task::Priority;
pub use task::Status;
pub use task::{Task, TaskMut};
#[cfg(test)]
pub(crate) mod testing;

View File

@@ -1,21 +1,29 @@
use crate::errors::Error;
use crate::operation::Operation;
use crate::server::Server;
use crate::task::{Status, Task};
use crate::taskdb::DB;
use crate::taskstorage::TaskMap;
use crate::taskdb::TaskDB;
use crate::taskstorage::{Operation, TaskMap, TaskStorage};
use chrono::Utc;
use failure::Fallible;
use std::collections::HashMap;
use uuid::Uuid;
/// A replica represents an instance of a user's task data.
/// A replica represents an instance of a user's task data, providing an easy interface
/// for querying and modifying that data.
pub struct Replica {
taskdb: Box<DB>,
taskdb: TaskDB,
}
impl Replica {
pub fn new(taskdb: Box<DB>) -> Replica {
return Replica { taskdb };
pub fn new(storage: Box<dyn TaskStorage>) -> Replica {
return Replica {
taskdb: TaskDB::new(storage),
};
}
#[cfg(test)]
pub fn new_inmemory() -> Replica {
Replica::new(Box::new(crate::taskstorage::InMemoryStorage::new()))
}
/// Update an existing task. If the value is Some, the property is added or updated. If the
@@ -45,7 +53,7 @@ impl Replica {
}
/// Get all tasks represented as a map keyed by UUID
pub fn all_tasks<'a>(&'a mut self) -> Fallible<HashMap<Uuid, Task>> {
pub fn all_tasks(&mut self) -> Fallible<HashMap<Uuid, Task>> {
let mut res = HashMap::new();
for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) {
res.insert(uuid.clone(), Task::new(uuid.clone(), tm));
@@ -54,7 +62,7 @@ impl Replica {
}
/// Get the UUIDs of all tasks
pub fn all_task_uuids<'a>(&'a mut self) -> Fallible<Vec<Uuid>> {
pub fn all_task_uuids(&mut self) -> Fallible<Vec<Uuid>> {
self.taskdb.all_task_uuids()
}
@@ -124,6 +132,11 @@ impl Replica {
self.taskdb.apply(Operation::Delete { uuid })
}
/// Synchronize this replica against the given server.
pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> {
self.taskdb.sync(username, server)
}
/// Perform "garbage collection" on this replica. In particular, this renumbers the working
/// set to contain only pending tasks.
pub fn gc(&mut self) -> Fallible<()> {
@@ -138,12 +151,11 @@ impl Replica {
mod tests {
use super::*;
use crate::task::Status;
use crate::taskdb::DB;
use uuid::Uuid;
#[test]
fn new_task() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let t = rep
@@ -156,7 +168,7 @@ mod tests {
#[test]
fn modify_task() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let t = rep
@@ -183,7 +195,7 @@ mod tests {
#[test]
fn delete_task() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
rep.new_task(uuid.clone(), Status::Pending, "a task".into())
@@ -195,7 +207,7 @@ mod tests {
#[test]
fn get_and_modify() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
rep.new_task(uuid.clone(), Status::Pending, "another task".into())
@@ -215,7 +227,7 @@ mod tests {
#[test]
fn new_pending_adds_to_working_set() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
rep.new_task(uuid.clone(), Status::Pending, "to-be-pending".into())
@@ -233,7 +245,7 @@ mod tests {
#[test]
fn get_does_not_exist() {
let mut rep = Replica::new(DB::new_inmemory().into());
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
assert_eq!(rep.get_task(&uuid).unwrap(), None);
}

View File

@@ -0,0 +1,20 @@
pub type Blob = Vec<u8>;
pub enum VersionAdd {
// OK, version added
Ok,
// Rejected, must be based on the the given version
ExpectedVersion(u64),
}
/// A value implementing this trait can act as a server against which a replica can sync.
pub trait Server {
/// Get a vector of all versions after `since_version`
fn get_versions(&self, username: &str, since_version: u64) -> Vec<Blob>;
/// Add a new version. If the given version number is incorrect, this responds with the
/// appropriate version and expects the caller to try again.
fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd;
fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob);
}

View File

@@ -2,33 +2,36 @@ use crate::replica::Replica;
use crate::taskstorage::TaskMap;
use chrono::prelude::*;
use failure::Fallible;
use std::convert::TryFrom;
use uuid::Uuid;
pub type Timestamp = DateTime<Utc>;
/// The priority of a task
#[derive(Debug, PartialEq)]
pub enum Priority {
/// Low
L,
/// Medium
M,
/// High
H,
}
impl TryFrom<&str> for Priority {
type Error = failure::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
#[allow(dead_code)]
impl Priority {
/// Get a Priority from the 1-character value in a TaskMap,
/// defaulting to M
pub(crate) fn from_taskmap(s: &str) -> Priority {
match s {
"L" => Ok(Priority::L),
"M" => Ok(Priority::M),
"H" => Ok(Priority::H),
_ => Err(format_err!("invalid status {}", s)),
"L" => Priority::L,
"M" => Priority::M,
"H" => Priority::H,
_ => Priority::M,
}
}
}
impl AsRef<str> for Priority {
fn as_ref(&self) -> &str {
/// Get the 1-character value for this priority to use in the TaskMap.
pub(crate) fn to_taskmap(&self) -> &str {
match self {
Priority::L => "L",
Priority::M => "M",
@@ -36,6 +39,8 @@ impl AsRef<str> for Priority {
}
}
}
/// The status of a task. The default status in "Pending".
#[derive(Debug, PartialEq)]
pub enum Status {
Pending,
@@ -86,8 +91,8 @@ pub struct Task {
taskmap: TaskMap,
}
/// A mutable task, with setter methods. Calling a setter will update the Replica, as well as the
/// included Task.
/// A mutable task, with setter methods. Most methods are simple setters and not further
/// described. Calling a setter will update the Replica, as well as the included Task.
pub struct TaskMut<'r> {
task: Task,
replica: &'r mut Replica,
@@ -145,7 +150,8 @@ impl Task {
}
impl<'r> TaskMut<'r> {
/// Get the immutable task
/// Get the immutable version of this object. Note that TaskMut [`std::ops::Deref`]s to
/// [`crate::task::Task`], so all of that struct's getter methods can be used on TaskMut.
pub fn into_immut(self) -> Task {
self.task
}
@@ -160,12 +166,10 @@ impl<'r> TaskMut<'r> {
self.set_string("status", Some(String::from(status.to_taskmap())))
}
/// Set the task's description
pub fn set_description(&mut self, description: String) -> Fallible<()> {
self.set_string("description", Some(description))
}
/// Set the task's description
pub fn set_modified(&mut self, modified: DateTime<Utc>) -> Fallible<()> {
self.set_timestamp("modified", Some(modified))
}
@@ -213,3 +217,28 @@ impl<'r> std::ops::Deref for TaskMut<'r> {
&self.task
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_priority() {
assert_eq!(Priority::L.to_taskmap(), "L");
assert_eq!(Priority::M.to_taskmap(), "M");
assert_eq!(Priority::H.to_taskmap(), "H");
assert_eq!(Priority::from_taskmap("L"), Priority::L);
assert_eq!(Priority::from_taskmap("M"), Priority::M);
assert_eq!(Priority::from_taskmap("H"), Priority::H);
}
#[test]
fn test_status() {
assert_eq!(Status::Pending.to_taskmap(), "P");
assert_eq!(Status::Completed.to_taskmap(), "C");
assert_eq!(Status::Deleted.to_taskmap(), "D");
assert_eq!(Status::from_taskmap("P"), Status::Pending);
assert_eq!(Status::from_taskmap("C"), Status::Completed);
assert_eq!(Status::from_taskmap("D"), Status::Deleted);
}
}

View File

@@ -1,14 +1,13 @@
use crate::errors::Error;
use crate::operation::Operation;
use crate::server::{Server, VersionAdd};
use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn};
use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn};
use failure::Fallible;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::str;
use uuid::Uuid;
pub struct DB {
pub struct TaskDB {
storage: Box<dyn TaskStorage>,
}
@@ -18,24 +17,24 @@ struct Version {
operations: Vec<Operation>,
}
impl DB {
/// Create a new DB with the given backend storage
pub fn new(storage: Box<dyn TaskStorage>) -> DB {
DB { storage }
impl TaskDB {
/// Create a new TaskDB with the given backend storage
pub fn new(storage: Box<dyn TaskStorage>) -> TaskDB {
TaskDB { storage }
}
#[cfg(test)]
pub fn new_inmemory() -> DB {
DB::new(Box::new(crate::taskstorage::InMemoryStorage::new()))
pub fn new_inmemory() -> TaskDB {
TaskDB::new(Box::new(crate::taskstorage::InMemoryStorage::new()))
}
/// Apply an operation to the DB. Aside from synchronization operations, this is the only way
/// to modify the DB. In cases where an operation does not make sense, this function will do
/// nothing and return an error (but leave the DB in a consistent state).
/// Apply an operation to the TaskDB. Aside from synchronization operations, this is the only way
/// to modify the TaskDB. In cases where an operation does not make sense, this function will do
/// nothing and return an error (but leave the TaskDB in a consistent state).
pub fn apply(&mut self, op: Operation) -> Fallible<()> {
// TODO: differentiate error types here?
let mut txn = self.storage.txn()?;
if let err @ Err(_) = DB::apply_op(txn.as_mut(), &op) {
if let err @ Err(_) = TaskDB::apply_op(txn.as_mut(), &op) {
return err;
}
txn.add_operation(op)?;
@@ -170,7 +169,7 @@ impl DB {
}
/// Sync to the given server, pulling remote changes and pushing local changes.
pub fn sync(&mut self, username: &str, server: &mut Server) -> Fallible<()> {
pub fn sync(&mut self, username: &str, server: &mut dyn Server) -> Fallible<()> {
let mut txn = self.storage.txn()?;
// retry synchronizing until the server accepts our version (this allows for races between
@@ -184,7 +183,7 @@ impl DB {
assert_eq!(version.version, txn.base_version()? + 1);
println!("applying version {:?} from server", version.version);
DB::apply_version(txn.as_mut(), version)?;
TaskDB::apply_version(txn.as_mut(), version)?;
}
let operations: Vec<Operation> = txn.operations()?.iter().map(|o| o.clone()).collect();
@@ -255,7 +254,7 @@ impl DB {
}
}
if let Some(o) = svr_op {
if let Err(e) = DB::apply_op(txn, &o) {
if let Err(e) = TaskDB::apply_op(txn, &o) {
println!("Invalid operation when syncing: {} (ignored)", e);
}
}
@@ -268,7 +267,8 @@ impl DB {
// functions for supporting tests
pub fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> {
#[cfg(test)]
pub(crate) fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> {
let mut res: Vec<(Uuid, Vec<(String, String)>)> = self
.all_tasks()
.unwrap()
@@ -286,7 +286,8 @@ impl DB {
res
}
pub fn operations(&mut self) -> Vec<Operation> {
#[cfg(test)]
pub(crate) fn operations(&mut self) -> Vec<Operation> {
let mut txn = self.storage.txn().unwrap();
txn.operations()
.unwrap()
@@ -299,13 +300,16 @@ impl DB {
#[cfg(test)]
mod tests {
use super::*;
use crate::taskstorage::InMemoryStorage;
use crate::testing::testserver::TestServer;
use chrono::Utc;
use proptest::prelude::*;
use std::collections::HashMap;
use uuid::Uuid;
#[test]
fn test_apply_create() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op = Operation::Create { uuid };
db.apply(op.clone()).unwrap();
@@ -316,7 +320,7 @@ mod tests {
#[test]
fn test_apply_create_exists() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op = Operation::Create { uuid };
db.apply(op.clone()).unwrap();
@@ -331,7 +335,7 @@ mod tests {
#[test]
fn test_apply_create_update() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid };
db.apply(op1.clone()).unwrap();
@@ -352,7 +356,7 @@ mod tests {
#[test]
fn test_apply_create_update_delete_prop() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid };
db.apply(op1.clone()).unwrap();
@@ -394,7 +398,7 @@ mod tests {
#[test]
fn test_apply_update_does_not_exist() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op = Operation::Update {
uuid,
@@ -413,7 +417,7 @@ mod tests {
#[test]
fn test_apply_create_delete() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op1 = Operation::Create { uuid };
db.apply(op1.clone()).unwrap();
@@ -427,7 +431,7 @@ mod tests {
#[test]
fn test_apply_delete_not_present() {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuid = Uuid::new_v4();
let op1 = Operation::Delete { uuid };
@@ -442,7 +446,7 @@ mod tests {
#[test]
fn rebuild_working_set() -> Fallible<()> {
let mut db = DB::new_inmemory();
let mut db = TaskDB::new_inmemory();
let uuids = vec![
Uuid::new_v4(), // 0: pending, not already in working set
Uuid::new_v4(), // 1: pending, already in working set
@@ -451,7 +455,7 @@ mod tests {
Uuid::new_v4(), // 4: pending, already in working set
];
// add everything to the DB
// add everything to the TaskDB
for uuid in &uuids {
db.apply(Operation::Create { uuid: uuid.clone() })?;
}
@@ -508,4 +512,182 @@ mod tests {
Ok(())
}
fn newdb() -> TaskDB {
TaskDB::new(Box::new(InMemoryStorage::new()))
}
#[test]
fn test_sync() {
let mut server = TestServer::new();
let mut db1 = newdb();
db1.sync("me", &mut server).unwrap();
let mut db2 = newdb();
db2.sync("me", &mut server).unwrap();
// make some changes in parallel to db1 and db2..
let uuid1 = Uuid::new_v4();
db1.apply(Operation::Create { uuid: uuid1 }).unwrap();
db1.apply(Operation::Update {
uuid: uuid1,
property: "title".into(),
value: Some("my first task".into()),
timestamp: Utc::now(),
})
.unwrap();
let uuid2 = Uuid::new_v4();
db2.apply(Operation::Create { uuid: uuid2 }).unwrap();
db2.apply(Operation::Update {
uuid: uuid2,
property: "title".into(),
value: Some("my second task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// now make updates to the same task on both sides
db1.apply(Operation::Update {
uuid: uuid2,
property: "priority".into(),
value: Some("H".into()),
timestamp: Utc::now(),
})
.unwrap();
db2.apply(Operation::Update {
uuid: uuid2,
property: "project".into(),
value: Some("personal".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}
#[test]
fn test_sync_create_delete() {
let mut server = TestServer::new();
let mut db1 = newdb();
db1.sync("me", &mut server).unwrap();
let mut db2 = newdb();
db2.sync("me", &mut server).unwrap();
// create and update a task..
let uuid = Uuid::new_v4();
db1.apply(Operation::Create { uuid }).unwrap();
db1.apply(Operation::Update {
uuid: uuid,
property: "title".into(),
value: Some("my first task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// delete and re-create the task on db1
db1.apply(Operation::Delete { uuid }).unwrap();
db1.apply(Operation::Create { uuid }).unwrap();
db1.apply(Operation::Update {
uuid: uuid,
property: "title".into(),
value: Some("my second task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and on db2, update a property of the task
db2.apply(Operation::Update {
uuid: uuid,
property: "project".into(),
value: Some("personal".into()),
timestamp: Utc::now(),
})
.unwrap();
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}
#[derive(Debug)]
enum Action {
Op(Operation),
Sync,
}
fn action_sequence_strategy() -> impl Strategy<Value = Vec<(Action, u8)>> {
// 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::<Vec<(Action, u8)>>()
})
}
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 TaskDB 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 = TestServer::new();
let mut dbs = [newdb(), newdb(), newdb()];
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).unwrap(),
}
}
assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks());
assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks());
}
}
}

View File

@@ -1,6 +1,5 @@
use crate::operation::Operation;
use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn};
use failure::Fallible;
use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn};
use failure::{format_err, Fallible};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use uuid::Uuid;
@@ -139,6 +138,8 @@ impl<'t> TaskStorageTxn for Txn<'t> {
}
}
/// InMemoryStorage is a simple in-memory task storage implementation. It is not useful for
/// production data, but is useful for testing purposes.
#[derive(PartialEq, Debug, Clone)]
pub struct InMemoryStorage {
data: Data,

View File

@@ -1,6 +1,5 @@
use crate::operation::Operation;
use crate::taskstorage::{TaskMap, TaskStorage, TaskStorageTxn};
use failure::Fallible;
use crate::taskstorage::{Operation, TaskMap, TaskStorage, TaskStorageTxn};
use failure::{format_err, Fallible};
use kv::msgpack::Msgpack;
use kv::{Bucket, Config, Error, Integer, Serde, Store, ValueBuf};
use std::convert::TryInto;
@@ -177,7 +176,6 @@ impl<'t> TaskStorageTxn for Txn<'t> {
fn all_tasks(&mut self) -> Fallible<Vec<(Uuid, TaskMap)>> {
let bucket = self.tasks_bucket();
let kvtxn = self.kvtxn();
let curs = kvtxn.read_cursor(bucket)?;
let all_tasks: Result<Vec<(Uuid, TaskMap)>, Error> = kvtxn
.read_cursor(bucket)?
.iter()
@@ -189,7 +187,6 @@ impl<'t> TaskStorageTxn for Txn<'t> {
fn all_task_uuids(&mut self) -> Fallible<Vec<Uuid>> {
let bucket = self.tasks_bucket();
let kvtxn = self.kvtxn();
let curs = kvtxn.read_cursor(bucket)?;
Ok(kvtxn
.read_cursor(bucket)?
.iter()
@@ -224,7 +221,6 @@ impl<'t> TaskStorageTxn for Txn<'t> {
fn operations(&mut self) -> Fallible<Vec<Operation>> {
let bucket = self.operations_bucket();
let kvtxn = self.kvtxn();
let curs = kvtxn.read_cursor(bucket)?;
let all_ops: Result<Vec<(u64, Operation)>, Error> = kvtxn
.read_cursor(bucket)?
.iter()
@@ -299,7 +295,6 @@ impl<'t> TaskStorageTxn for Txn<'t> {
res.push(None)
}
let curs = kvtxn.read_cursor(working_set_bucket)?;
for (i, u) in kvtxn.read_cursor(working_set_bucket)?.iter() {
let i: u64 = i.into();
res[i as usize] = Some(u.inner()?.to_serde());

View File

@@ -1,14 +1,16 @@
use crate::Operation;
use failure::Fallible;
use std::collections::HashMap;
use uuid::Uuid;
mod inmemory;
mod kv;
mod operation;
pub use self::kv::KVStorage;
pub use inmemory::InMemoryStorage;
pub use operation::Operation;
/// An in-memory representation of a task as a simple hashmap
pub type TaskMap = HashMap<String, String>;
@@ -22,10 +24,18 @@ fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap {
}
/// A TaskStorage transaction, in which storage operations are performed.
/// Serializable consistency is maintained, and implementations do not optimize
/// for concurrent access so some may simply apply a mutex to limit access to
/// one transaction at a time. Transactions are aborted if they are dropped.
/// It's safe to drop transactions that did not modify any data.
///
/// # Concurrency
///
/// Serializable consistency must be maintained. Concurrent access is unusual
/// and some implementations may simply apply a mutex to limit access to
/// one transaction at a time.
///
/// # Commiting and Aborting
///
/// A transaction is not visible to other readers until it is committed with
/// [`crate::taskstorage::TaskStorageTxn::commit`]. Transactions are aborted if they are dropped.
/// It is safe and performant to drop transactions that did not modify any data without committing.
pub trait TaskStorageTxn {
/// Get an (immutable) task, if it is in the storage
fn get_task(&mut self, uuid: &Uuid) -> Fallible<Option<TaskMap>>;
@@ -58,7 +68,7 @@ pub trait TaskStorageTxn {
fn operations<'a>(&mut self) -> Fallible<Vec<Operation>>;
/// Add an operation to the end of the list of operations in the storage. Note that this
/// merely *stores* the operation; it is up to the DB to apply it.
/// merely *stores* the operation; it is up to the TaskDB to apply it.
fn add_operation(&mut self, op: Operation) -> Fallible<()>;
/// Replace the current list of operations with a new list.
@@ -83,9 +93,8 @@ pub trait TaskStorageTxn {
fn commit(&mut self) -> Fallible<()>;
}
/// A trait for objects able to act as backing storage for a DB. This API is optimized to be
/// easy to implement, with all of the semantic meaning of the data located in the DB
/// implementation, which is the sole consumer of this trait.
/// A trait for objects able to act as task storage. Most of the interesting behavior is in the
/// [`crate::taskstorage::TaskStorageTxn`] trait.
pub trait TaskStorage {
/// Begin a transaction
fn txn<'a>(&'a mut self) -> Fallible<Box<dyn TaskStorageTxn + 'a>>;

View File

@@ -128,8 +128,10 @@ impl Operation {
#[cfg(test)]
mod test {
use super::*;
use crate::taskdb::DB;
use crate::taskdb::TaskDB;
use crate::taskstorage::InMemoryStorage;
use chrono::{Duration, Utc};
use proptest::prelude::*;
// note that `tests/operation_transform_invariant.rs` tests the transform function quite
// thoroughly, so this testing is light.
@@ -146,7 +148,7 @@ mod test {
// check that the two operation sequences have the same effect, enforcing the invariant of
// the transform function.
let mut db1 = DB::new_inmemory();
let mut db1 = TaskDB::new_inmemory();
if let Some(ref o) = setup {
db1.apply(o.clone()).unwrap();
}
@@ -155,7 +157,7 @@ mod test {
db1.apply(o).unwrap();
}
let mut db2 = DB::new_inmemory();
let mut db2 = TaskDB::new_inmemory();
if let Some(ref o) = setup {
db2.apply(o.clone()).unwrap();
}
@@ -273,4 +275,81 @@ mod test {
None,
);
}
fn uuid_strategy() -> impl Strategy<Value = Uuid> {
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<Value = Operation> {
prop_oneof![
uuid_strategy().prop_map(|uuid| Operation::Create { uuid }),
uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }),
(uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| {
Operation::Update {
uuid,
property,
value: Some("true".into()),
timestamp: Utc::now(),
}
}),
]
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 1024, .. ProptestConfig::default()
})]
#[test]
// check that the two operation sequences have the same effect, enforcing the invariant of
// the transform function.
fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) {
let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone());
let mut db1 = TaskDB::new(Box::new(InMemoryStorage::new()));
let mut db2 = TaskDB::new(Box::new(InMemoryStorage::new()));
// Ensure that any expected tasks already exist
if let Operation::Update{ ref uuid, .. } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Update{ ref uuid, .. } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Delete{ ref uuid } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Delete{ ref uuid } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
// if applying the initial operations fail, that indicates the operation was invalid
// in the base state, so consider the case successful.
if let Err(_) = db1.apply(o1) {
return Ok(());
}
if let Err(_) = db2.apply(o2) {
return Ok(());
}
if let Some(o) = o2p {
db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?;
}
if let Some(o) = o1p {
db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?;
}
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}
}
}

View File

@@ -0,0 +1 @@
pub mod testserver;

View File

@@ -0,0 +1,81 @@
use crate::server::{Blob, Server, VersionAdd};
use std::collections::HashMap;
pub(crate) struct TestServer {
users: HashMap<String, User>,
}
struct User {
// versions, indexed at v-1
versions: Vec<Blob>,
snapshots: HashMap<u64, Blob>,
}
impl TestServer {
pub fn new() -> TestServer {
TestServer {
users: HashMap::new(),
}
}
fn get_user_mut(&mut self, username: &str) -> &mut User {
self.users
.entry(username.to_string())
.or_insert_with(User::new)
}
}
impl Server for TestServer {
/// Get a vector of all versions after `since_version`
fn get_versions(&self, username: &str, since_version: u64) -> Vec<Blob> {
self.users
.get(username)
.map(|user| user.get_versions(since_version))
.unwrap_or_else(|| vec![])
}
/// Add a new version. If the given version number is incorrect, this responds with the
/// appropriate version and expects the caller to try again.
fn add_version(&mut self, username: &str, version: u64, blob: Blob) -> VersionAdd {
self.get_user_mut(username).add_version(version, blob)
}
fn add_snapshot(&mut self, username: &str, version: u64, blob: Blob) {
self.get_user_mut(username).add_snapshot(version, blob);
}
}
impl User {
fn new() -> User {
User {
versions: vec![],
snapshots: HashMap::new(),
}
}
fn get_versions(&self, since_version: u64) -> Vec<Blob> {
let last_version = self.versions.len();
if last_version == since_version as usize {
return vec![];
}
self.versions[since_version as usize..last_version]
.iter()
.map(|r| r.clone())
.collect::<Vec<Blob>>()
}
fn add_version(&mut self, version: u64, blob: Blob) -> VersionAdd {
// of by one here: client wants to send version 1 first
let expected_version = self.versions.len() as u64 + 1;
if version != expected_version {
return VersionAdd::ExpectedVersion(expected_version);
}
self.versions.push(blob);
VersionAdd::Ok
}
fn add_snapshot(&mut self, version: u64, blob: Blob) {
self.snapshots.insert(version, blob);
}
}

View File

@@ -1,2 +0,0 @@
[description:"https:\/\/phabricator.services.example.com\/D7364 &open;taskgraph&close; Download debian packages" end:"1541705209" entry:"1538520624" modified:"1541705209" phabricatorid:"D7364" priority:"M" project:"moz" status:"completed" tags:"phabricator,respond" uuid:"ca33f6d6-1688-4503-90be-3b3526a32b5a" wait:"1570118809"]
[annotation_1541461824:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" description:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3 More changes" end:"1541702602" entry:"1541451283" githubbody:"some notes:\n\n1. This is a huge PR, so I'm not expecting a quick turn around at all. If you have questions, let me know and I can hope on Vidyo.\n1. Data persistence is written in a semi-janky way. My intention is to use the time while this is under review to make progress on postgres stuff so that the next review cycle will be using new Postgres things. Which means.... There's a lot of bugs in the concurrency because there's no synchronisation at all in this janky-ish model.\n1. The API is the minimum api required to get provisioning-ish things working\n1. I intend to write a system for automatically testing provider and bidding strategy implementations, so that you can do instantiate a provider\/strategy, stub\/spy it as needed then run a test suite against it and have it do its thing. The idea is that each provider will need to mock their underlying api system in their own way, but the set of tests we run for Provider API conformance would be pretty standardized. This should make writing tests for new providers a lot easier.\n1. The provider\/strategy loading system is intentionally simple. The idea is that these aren't general purpose plugins, but rather special ones. The idea is that the config files would essentially declare instances and then provide constructor arguments to initialize them all... This would make enabling\/disabling providers\/strategies fairly trivial\n1. I decided to drop fake implementations of providers and strategies for testing the provisioning logic and instead opt for Sinon stubs, which I think give us a better testing story\n1. I still intend to have fake providers and bidding strategies for doing API testing.\n\nLet me know, and again, I don't expect or need a quick turn around on this PR.\n" githubcreatedon:"1541451283" githubnamespace:"djmitche" githubnumber:"3.000000" githubrepo:"taskcluster\/taskcluster-worker-manager" githubtitle:"More changes" githubtype:"pull_request" githubupdatedat:"1541699191" githuburl:"https:\/\/github.com\/taskcluster\/taskcluster-worker-manager\/pull\/3" githubuser:"jhford" modified:"1541702602" priority:"H" project:"moz" status:"completed" tags:"respond" uuid:"2186f981-d1f5-4642-b833-5b16b3a2d334"]

View File

@@ -1,85 +0,0 @@
use chrono::Utc;
use proptest::prelude::*;
use taskchampion::{taskstorage, Operation, DB};
use uuid::Uuid;
fn newdb() -> DB {
DB::new(Box::new(taskstorage::InMemoryStorage::new()))
}
fn uuid_strategy() -> impl Strategy<Value = Uuid> {
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<Value = Operation> {
prop_oneof![
uuid_strategy().prop_map(|uuid| Operation::Create { uuid }),
uuid_strategy().prop_map(|uuid| Operation::Delete { uuid }),
(uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| {
Operation::Update {
uuid,
property,
value: Some("true".into()),
timestamp: Utc::now(),
}
}),
]
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 1024, .. ProptestConfig::default()
})]
#[test]
// check that the two operation sequences have the same effect, enforcing the invariant of
// the transform function.
fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) {
let (o1p, o2p) = Operation::transform(o1.clone(), o2.clone());
let mut db1 = newdb();
let mut db2 = newdb();
// Ensure that any expected tasks already exist
if let Operation::Update{ ref uuid, .. } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Update{ ref uuid, .. } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Delete{ ref uuid } = o1 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
if let Operation::Delete{ ref uuid } = o2 {
let _ = db1.apply(Operation::Create{uuid: uuid.clone()});
let _ = db2.apply(Operation::Create{uuid: uuid.clone()});
}
// if applying the initial operations fail, that indicates the operation was invalid
// in the base state, so consider the case successful.
if let Err(_) = db1.apply(o1) {
return Ok(());
}
if let Err(_) = db2.apply(o2) {
return Ok(());
}
if let Some(o) = o2p {
db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?;
}
if let Some(o) = o1p {
db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?;
}
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}
}

View File

@@ -1,120 +0,0 @@
use chrono::Utc;
use taskchampion::{taskstorage, Operation, Server, DB};
use uuid::Uuid;
fn newdb() -> DB {
DB::new(Box::new(taskstorage::InMemoryStorage::new()))
}
#[test]
fn test_sync() {
let mut server = Server::new();
let mut db1 = newdb();
db1.sync("me", &mut server).unwrap();
let mut db2 = newdb();
db2.sync("me", &mut server).unwrap();
// make some changes in parallel to db1 and db2..
let uuid1 = Uuid::new_v4();
db1.apply(Operation::Create { uuid: uuid1 }).unwrap();
db1.apply(Operation::Update {
uuid: uuid1,
property: "title".into(),
value: Some("my first task".into()),
timestamp: Utc::now(),
})
.unwrap();
let uuid2 = Uuid::new_v4();
db2.apply(Operation::Create { uuid: uuid2 }).unwrap();
db2.apply(Operation::Update {
uuid: uuid2,
property: "title".into(),
value: Some("my second task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// now make updates to the same task on both sides
db1.apply(Operation::Update {
uuid: uuid2,
property: "priority".into(),
value: Some("H".into()),
timestamp: Utc::now(),
})
.unwrap();
db2.apply(Operation::Update {
uuid: uuid2,
property: "project".into(),
value: Some("personal".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}
#[test]
fn test_sync_create_delete() {
let mut server = Server::new();
let mut db1 = newdb();
db1.sync("me", &mut server).unwrap();
let mut db2 = newdb();
db2.sync("me", &mut server).unwrap();
// create and update a task..
let uuid = Uuid::new_v4();
db1.apply(Operation::Create { uuid }).unwrap();
db1.apply(Operation::Update {
uuid: uuid,
property: "title".into(),
value: Some("my first task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and synchronize those around
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
// delete and re-create the task on db1
db1.apply(Operation::Delete { uuid }).unwrap();
db1.apply(Operation::Create { uuid }).unwrap();
db1.apply(Operation::Update {
uuid: uuid,
property: "title".into(),
value: Some("my second task".into()),
timestamp: Utc::now(),
})
.unwrap();
// and on db2, update a property of the task
db2.apply(Operation::Update {
uuid: uuid,
property: "project".into(),
value: Some("personal".into()),
timestamp: Utc::now(),
})
.unwrap();
db1.sync("me", &mut server).unwrap();
db2.sync("me", &mut server).unwrap();
db1.sync("me", &mut server).unwrap();
assert_eq!(db1.sorted_tasks(), db2.sorted_tasks());
}

View File

@@ -1,69 +0,0 @@
use chrono::Utc;
use proptest::prelude::*;
use taskchampion::{taskstorage, Operation, Server, DB};
use uuid::Uuid;
fn newdb() -> DB {
DB::new(Box::new(taskstorage::InMemoryStorage::new()))
}
#[derive(Debug)]
enum Action {
Op(Operation),
Sync,
}
fn action_sequence_strategy() -> impl Strategy<Value = Vec<(Action, u8)>> {
// 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::<Vec<(Action, u8)>>()
})
}
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 = [newdb(), newdb(), newdb()];
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).unwrap(),
}
}
assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks());
assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks());
}
}