Add homework 01 guarantees assignment
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
target
|
||||
**/target
|
||||
**/__pycache__
|
||||
**/.pytest_cache
|
||||
*.pyc
|
||||
Generated
+990
@@ -0,0 +1,990 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anysystem"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01badd3bc0d042e4399f8f685c394a79a72e7f233b3d3b5eabb3b94734f4dbe"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"downcast-rs",
|
||||
"dyn-clone",
|
||||
"indexmap 2.14.0",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"ordered-float",
|
||||
"pyo3",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"regex",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simcore",
|
||||
"sugars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assertables"
|
||||
version = "3.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d84b91d63c67e6c0b1a74728dad0057c4a6124b74e671ff91b7fe9f8bbd671aa"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap 1.9.3",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distsys-guarantees"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anysystem",
|
||||
"assertables",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"indexmap 2.14.0",
|
||||
"log",
|
||||
"pyo3",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"sugars",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
|
||||
dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.106",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_type_name"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92acc4cd6ae20767c54d6cf1a075624e7f4d9e99d7ebc685398ff243144d8714"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simcore"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a20dfec85e94e569fa5b810b2d992c68b361f31bfba471447427536488c9c292"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"downcast-rs",
|
||||
"dyn-clone",
|
||||
"erased-serde",
|
||||
"futures",
|
||||
"log",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_type_name",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "sugars"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc0db74f9ee706e039d031a560bd7d110c7022f016051b3d33eeff9583e3e67a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "distsys-guarantees"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
resolver = "3"
|
||||
|
||||
[dependencies]
|
||||
anysystem = "=0.3.0"
|
||||
indexmap = "2.12"
|
||||
assertables = "3.2.2"
|
||||
clap = { version = "3.1.17", features = ["cargo", "derive"] }
|
||||
env_logger = "0.9.0"
|
||||
log = "0.4.14"
|
||||
pyo3 = { version = "=0.29.2", features = ["auto-initialize"] }
|
||||
rand = "0.8.5"
|
||||
rand_pcg = "0.3.1"
|
||||
sugars = "3.0.0"
|
||||
@@ -0,0 +1,19 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM rust:1.97.1-bookworm@sha256:0e2bcaef56d041a486784e54104a81aebe0da44bd03019bd70bc0401e42e4a97 AS builder
|
||||
|
||||
RUN apt-get update && apt-get install -y build-essential python3-dev
|
||||
|
||||
WORKDIR /tests
|
||||
COPY . .
|
||||
RUN --mount=type=cache,id=distsys-course-cargo-registry,target=/usr/local/cargo/registry,sharing=locked \
|
||||
--mount=type=cache,id=distsys-course-cargo-target-rust-1-97-1,target=/tests/target,sharing=locked \
|
||||
cargo install --locked --path . --root /opt/course
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y python3-dev && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /opt/course/bin/distsys-guarantees /usr/local/bin/distsys-guarantees
|
||||
|
||||
WORKDIR /solution
|
||||
ENTRYPOINT ["timeout", "-k", "10", "300", "distsys-guarantees", "-i", "guarantees.py"]
|
||||
@@ -0,0 +1,2 @@
|
||||
max_width = 120
|
||||
newline_style = "Unix"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,285 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use assertables::{assume, assume_eq};
|
||||
use sugars::boxed;
|
||||
|
||||
use anysystem::python::PyProcessFactory;
|
||||
use anysystem::test::TestResult;
|
||||
use anysystem::{Message, System};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TestConfig<'a> {
|
||||
pub impl_path: &'a str,
|
||||
pub sender_class: &'a str,
|
||||
pub receiver_class: &'a str,
|
||||
pub seed: u64,
|
||||
pub monkeys: u32,
|
||||
pub reliable: bool,
|
||||
pub once: bool,
|
||||
pub ordered: bool,
|
||||
}
|
||||
|
||||
pub fn build_system(config: &TestConfig, measure_max_size: bool) -> System {
|
||||
let mut sys = System::new(config.seed);
|
||||
sys.add_node("sender-node");
|
||||
sys.add_node("receiver-node");
|
||||
|
||||
let sender_f = PyProcessFactory::new(config.impl_path, config.sender_class);
|
||||
let mut sender = sender_f.build(("sender", "receiver"), config.seed);
|
||||
if measure_max_size {
|
||||
sender.set_max_size_freq(100);
|
||||
}
|
||||
sys.add_process("sender", boxed!(sender), "sender-node");
|
||||
|
||||
let receiver_f = PyProcessFactory::new(config.impl_path, config.receiver_class);
|
||||
let mut receiver = receiver_f.build(("receiver",), config.seed);
|
||||
if measure_max_size {
|
||||
receiver.set_max_size_freq(100);
|
||||
}
|
||||
sys.add_process("receiver", boxed!(receiver), "receiver-node");
|
||||
|
||||
sys
|
||||
}
|
||||
|
||||
pub fn generate_message_texts(sys: &mut System, message_count: usize) -> Vec<String> {
|
||||
if message_count == 5 {
|
||||
["distributed", "systems", "need", "some", "guarantees"]
|
||||
.map(String::from)
|
||||
.to_vec()
|
||||
} else {
|
||||
let mut messages = Vec::new();
|
||||
for _i in 0..message_count {
|
||||
let msg = if message_count == 10 {
|
||||
format!("{}C", sys.gen_range(20..30))
|
||||
} else {
|
||||
sys.random_string(100)
|
||||
};
|
||||
messages.push(msg);
|
||||
}
|
||||
messages
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_messages(sys: &mut System, message_count: usize) -> Vec<Message> {
|
||||
let texts = generate_message_texts(sys, message_count);
|
||||
let mut messages = Vec::new();
|
||||
for text in texts {
|
||||
let msg = Message::new("MESSAGE", &format!(r#"{{"text": "{text}"}}"#));
|
||||
sys.send_local_message("sender", msg.clone());
|
||||
if message_count <= 50 {
|
||||
let steps = sys.gen_range(0..2);
|
||||
if steps > 0 {
|
||||
sys.steps(steps);
|
||||
}
|
||||
} else {
|
||||
let duration = sys.gen_range(0.0..2.0);
|
||||
sys.step_for_duration(duration);
|
||||
};
|
||||
messages.push(msg);
|
||||
}
|
||||
messages
|
||||
}
|
||||
|
||||
pub fn check_delivered_messages(
|
||||
delivered: &[Message],
|
||||
expected_msg_count: &HashMap<String, i32>,
|
||||
expected_tip: &String,
|
||||
) -> Result<HashMap<String, i32>, String> {
|
||||
assert!(!expected_msg_count.is_empty());
|
||||
let mut delivered_msg_count = HashMap::default();
|
||||
for msg in delivered.iter() {
|
||||
// assuming all messages have the same type
|
||||
assume_eq!(msg.tip, *expected_tip, format!("Wrong message type {}", msg.tip))?;
|
||||
assume!(
|
||||
expected_msg_count.contains_key(&msg.data),
|
||||
format!("Wrong message data: {}", msg.data)
|
||||
)?;
|
||||
*delivered_msg_count.entry(msg.data.clone()).or_insert(0) += 1;
|
||||
}
|
||||
Ok(delivered_msg_count)
|
||||
}
|
||||
|
||||
pub fn check_message_delivery_reliable(
|
||||
delivered_msg_count: &HashMap<String, i32>,
|
||||
expected_msg_count: &HashMap<String, i32>,
|
||||
) -> TestResult {
|
||||
for (data, expected_count) in expected_msg_count {
|
||||
let delivered_count = delivered_msg_count.get(data).unwrap_or(&0);
|
||||
assume!(
|
||||
delivered_count >= expected_count,
|
||||
format!(
|
||||
"Message {} is not delivered (observed count {} < expected count {})",
|
||||
data, delivered_count, expected_count
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn check_message_delivery_once(
|
||||
delivered_msg_count: &HashMap<String, i32>,
|
||||
expected_msg_count: &HashMap<String, i32>,
|
||||
) -> TestResult {
|
||||
for (data, delivered_count) in delivered_msg_count {
|
||||
if expected_msg_count.contains_key(data) {
|
||||
let expected_count = expected_msg_count[data];
|
||||
assume!(
|
||||
*delivered_count <= expected_count,
|
||||
format!(
|
||||
"Message {} is delivered more than once (observed count {} > expected count {})",
|
||||
data, delivered_count, expected_count
|
||||
)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn check_message_delivery_ordered(delivered: &[Message], sent: &[Message]) -> TestResult {
|
||||
let mut next_idx = 0;
|
||||
for i in 0..delivered.len() {
|
||||
let msg = &delivered[i];
|
||||
let mut matched = false;
|
||||
while !matched && next_idx < sent.len() {
|
||||
if msg.data == sent[next_idx].data {
|
||||
matched = true;
|
||||
} else {
|
||||
next_idx += 1;
|
||||
}
|
||||
}
|
||||
assume!(
|
||||
matched,
|
||||
format!("Order violation: {} after {}", msg.data, &delivered[i - 1].data)
|
||||
)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn check_guarantees(sys: &mut System, sent: &[Message], config: &TestConfig) -> TestResult {
|
||||
let mut expected_msg_count = HashMap::new();
|
||||
for msg in sent {
|
||||
*expected_msg_count.entry(msg.data.clone()).or_insert(0) += 1;
|
||||
}
|
||||
let delivered = sys.read_local_messages("receiver");
|
||||
|
||||
// check that delivered messages have expected type and data
|
||||
let delivered_msg_count = check_delivered_messages(&delivered, &expected_msg_count, &sent[0].tip)?;
|
||||
|
||||
// check delivered message count according to expected guarantees
|
||||
if config.reliable {
|
||||
check_message_delivery_reliable(&delivered_msg_count, &expected_msg_count)?;
|
||||
}
|
||||
if config.once {
|
||||
check_message_delivery_once(&delivered_msg_count, &expected_msg_count)?;
|
||||
}
|
||||
if config.ordered {
|
||||
check_message_delivery_ordered(&delivered, sent)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_overhead(
|
||||
guarantee: &str,
|
||||
faulty: bool,
|
||||
message_count: usize,
|
||||
sender_mem: u64,
|
||||
receiver_mem: u64,
|
||||
net_message_count: u64,
|
||||
net_traffic: u64,
|
||||
throughput: f64,
|
||||
) -> TestResult {
|
||||
let (sender_mem_limit, receiver_mem_limit, net_message_count_limit, net_traffic_limit, throughput_limit) =
|
||||
match guarantee {
|
||||
"AMO" => match message_count {
|
||||
100 => {
|
||||
if !faulty {
|
||||
(800, 1500, 100, 20000, 0.6)
|
||||
} else {
|
||||
(800, 3500, 100, 20000, 0.6)
|
||||
}
|
||||
}
|
||||
1000 => {
|
||||
if !faulty {
|
||||
(800, 1500, 1000, 200000, 0.6)
|
||||
} else {
|
||||
(800, 30000, 1000, 200000, 0.6)
|
||||
}
|
||||
}
|
||||
_ => (u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0.),
|
||||
},
|
||||
"ALO" => match message_count {
|
||||
100 => {
|
||||
if !faulty {
|
||||
(1700, 600, 200, 20000, 0.6)
|
||||
} else {
|
||||
(5000, 600, 500, 40000, 0.6)
|
||||
}
|
||||
}
|
||||
1000 => {
|
||||
if !faulty {
|
||||
(2200, 600, 2000, 200000, 0.6)
|
||||
} else {
|
||||
(6000, 600, 5000, 400000, 0.6)
|
||||
}
|
||||
}
|
||||
_ => (u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0.),
|
||||
},
|
||||
"EO" => match message_count {
|
||||
100 => {
|
||||
if !faulty {
|
||||
(1700, 1500, 200, 20000, 0.6)
|
||||
} else {
|
||||
(5000, 2200, 500, 40000, 0.6)
|
||||
}
|
||||
}
|
||||
1000 => {
|
||||
if !faulty {
|
||||
(2200, 1500, 2000, 200000, 0.6)
|
||||
} else {
|
||||
(6000, 2200, 5000, 400000, 0.6)
|
||||
}
|
||||
}
|
||||
_ => (u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0.),
|
||||
},
|
||||
"EOO" => match message_count {
|
||||
100 => {
|
||||
if !faulty {
|
||||
(2900, 1200, 200, 25000, 0.4)
|
||||
} else {
|
||||
(9000, 2500, 500, 45000, 0.4)
|
||||
}
|
||||
}
|
||||
1000 => {
|
||||
if !faulty {
|
||||
(3400, 1200, 2000, 250000, 0.4)
|
||||
} else {
|
||||
(55000, 4000, 5000, 450000, 0.4)
|
||||
}
|
||||
}
|
||||
_ => (u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0.),
|
||||
},
|
||||
_ => (u64::MAX, u64::MAX, u64::MAX, u64::MAX, 0.),
|
||||
};
|
||||
assume!(
|
||||
sender_mem <= sender_mem_limit,
|
||||
format!("Sender memory > {}", sender_mem_limit)
|
||||
)?;
|
||||
assume!(
|
||||
receiver_mem <= receiver_mem_limit,
|
||||
format!("Receiver memory > {}", receiver_mem_limit)
|
||||
)?;
|
||||
assume!(
|
||||
net_message_count <= net_message_count_limit,
|
||||
format!("Message count > {}", net_message_count_limit)
|
||||
)?;
|
||||
assume!(
|
||||
net_traffic <= net_traffic_limit,
|
||||
format!("Traffic > {}", net_traffic_limit)
|
||||
)?;
|
||||
assume!(
|
||||
throughput >= throughput_limit,
|
||||
format!("Throughput < {}", throughput_limit)
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
mod common;
|
||||
mod shared_state;
|
||||
mod tests;
|
||||
mod tests_mc;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
|
||||
use clap::Parser;
|
||||
use env_logger::Builder;
|
||||
use log::LevelFilter;
|
||||
|
||||
use anysystem::test::{TestResult, TestSuite};
|
||||
|
||||
use crate::common::TestConfig;
|
||||
use crate::tests::*;
|
||||
use crate::tests_mc::*;
|
||||
|
||||
/// Guarantees Homework Tests
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to Python file with solution
|
||||
#[clap(long = "impl", short = 'i', default_value = "solution/guarantees.py")]
|
||||
solution_path: String,
|
||||
|
||||
/// Test to run (optional)
|
||||
#[clap(long = "test", short)]
|
||||
test: Option<String>,
|
||||
|
||||
/// Print execution trace
|
||||
#[clap(long, short)]
|
||||
debug: bool,
|
||||
|
||||
/// Guarantee to check
|
||||
#[clap(long, short, possible_values = ["AMO", "ALO", "EO", "EOO"])]
|
||||
guarantee: Option<String>,
|
||||
|
||||
/// Random seed used in tests
|
||||
#[clap(long, short, default_value = "123")]
|
||||
seed: u64,
|
||||
|
||||
/// Number of chaos monkey runs
|
||||
#[clap(long, short, default_value = "0")]
|
||||
monkeys: u32,
|
||||
|
||||
/// Run overhead tests
|
||||
#[clap(long, short)]
|
||||
overhead: bool,
|
||||
|
||||
/// Run model checking tests
|
||||
#[clap(long, short = 'c')]
|
||||
model_checking: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
if !shared_state::validate_or_report(
|
||||
&args.solution_path,
|
||||
&[
|
||||
"AtMostOnceSender",
|
||||
"AtMostOnceReceiver",
|
||||
"AtLeastOnceSender",
|
||||
"AtLeastOnceReceiver",
|
||||
"ExactlyOnceSender",
|
||||
"ExactlyOnceReceiver",
|
||||
"ExactlyOnceOrderedSender",
|
||||
"ExactlyOnceOrderedReceiver",
|
||||
],
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if args.debug {
|
||||
Builder::new()
|
||||
.filter(Some("anysystem"), LevelFilter::Debug)
|
||||
.format(|buf, record| writeln!(buf, "{}", record.args()))
|
||||
.init();
|
||||
}
|
||||
let guarantee = args.guarantee.as_deref();
|
||||
|
||||
env::set_var("PYTHONHASHSEED", args.seed.to_string());
|
||||
let mut config = TestConfig {
|
||||
impl_path: &args.solution_path,
|
||||
sender_class: "",
|
||||
receiver_class: "",
|
||||
seed: args.seed,
|
||||
monkeys: args.monkeys,
|
||||
reliable: false,
|
||||
once: false,
|
||||
ordered: false,
|
||||
};
|
||||
let mut tests = TestSuite::new();
|
||||
|
||||
// At most once
|
||||
if guarantee.is_none() || guarantee == Some("AMO") {
|
||||
config.sender_class = "AtMostOnceSender";
|
||||
config.receiver_class = "AtMostOnceReceiver";
|
||||
config.once = true;
|
||||
// without drops should be reliable
|
||||
config.reliable = true;
|
||||
tests.add("[AT MOST ONCE] NORMAL", test_normal, config);
|
||||
tests.add("[AT MOST ONCE] NORMAL NON-UNIQUE", test_normal_non_unique, config);
|
||||
tests.add("[AT MOST ONCE] DELAYED", test_delayed, config);
|
||||
tests.add("[AT MOST ONCE] DUPLICATED", test_duplicated, config);
|
||||
tests.add("[AT MOST ONCE] DELAYED+DUPLICATED", test_delayed_duplicated, config);
|
||||
tests.add("[AT MOST ONCE] OLD DUPLICATE", test_old_duplicate, config);
|
||||
// with drops is not reliable
|
||||
config.reliable = false;
|
||||
tests.add("[AT MOST ONCE] DROPPED", test_dropped, config);
|
||||
if args.monkeys > 0 {
|
||||
tests.add("[AT MOST ONCE] CHAOS MONKEY", test_chaos_monkey, config);
|
||||
}
|
||||
if args.overhead {
|
||||
config.reliable = true;
|
||||
tests.add(
|
||||
"[AT MOST ONCE] OVERHEAD NORMAL",
|
||||
|x| test_overhead(x, "AMO", false),
|
||||
config,
|
||||
);
|
||||
config.reliable = false;
|
||||
tests.add(
|
||||
"[AT MOST ONCE] OVERHEAD FAULTY",
|
||||
|x| test_overhead(x, "AMO", true),
|
||||
config,
|
||||
);
|
||||
}
|
||||
if args.model_checking {
|
||||
tests.add("[AT MOST ONCE] MODEL CHECKING", test_mc_reliable_network, config);
|
||||
tests.add(
|
||||
"[AT MOST ONCE] MODEL CHECKING MESSAGE DROPS",
|
||||
test_mc_message_drops,
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[AT MOST ONCE] MODEL CHECKING UNSTABLE NETWORK",
|
||||
test_mc_unstable_network,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// At least once
|
||||
if guarantee.is_none() || guarantee == Some("ALO") {
|
||||
config.sender_class = "AtLeastOnceSender";
|
||||
config.receiver_class = "AtLeastOnceReceiver";
|
||||
config.reliable = true;
|
||||
config.once = false;
|
||||
tests.add("[AT LEAST ONCE] NORMAL", test_normal, config);
|
||||
tests.add("[AT LEAST ONCE] NORMAL NON-UNIQUE", test_normal_non_unique, config);
|
||||
tests.add("[AT LEAST ONCE] DELAYED", test_delayed, config);
|
||||
tests.add("[AT LEAST ONCE] DUPLICATED", test_duplicated, config);
|
||||
tests.add("[AT LEAST ONCE] DELAYED+DUPLICATED", test_delayed_duplicated, config);
|
||||
tests.add("[AT LEAST ONCE] DROPPED", test_dropped, config);
|
||||
if args.monkeys > 0 {
|
||||
tests.add("[AT LEAST ONCE] CHAOS MONKEY", test_chaos_monkey, config);
|
||||
}
|
||||
if args.overhead {
|
||||
tests.add(
|
||||
"[AT LEAST ONCE] OVERHEAD NORMAL",
|
||||
|x| test_overhead(x, "ALO", false),
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[AT LEAST ONCE] OVERHEAD FAULTY",
|
||||
|x| test_overhead(x, "ALO", true),
|
||||
config,
|
||||
);
|
||||
}
|
||||
if args.model_checking {
|
||||
tests.add("[AT LEAST ONCE] MODEL CHECKING", test_mc_reliable_network, config);
|
||||
tests.add(
|
||||
"[AT LEAST ONCE] MODEL CHECKING MESSAGE DROPS",
|
||||
test_mc_message_drops,
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[AT LEAST ONCE] MODEL CHECKING UNSTABLE NETWORK",
|
||||
test_mc_unstable_network,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Exactly once
|
||||
if guarantee.is_none() || guarantee == Some("EO") {
|
||||
config.sender_class = "ExactlyOnceSender";
|
||||
config.receiver_class = "ExactlyOnceReceiver";
|
||||
config.reliable = true;
|
||||
config.once = true;
|
||||
tests.add("[EXACTLY ONCE] NORMAL", test_normal, config);
|
||||
tests.add("[EXACTLY ONCE] NORMAL NON-UNIQUE", test_normal_non_unique, config);
|
||||
tests.add("[EXACTLY ONCE] DELAYED", test_delayed, config);
|
||||
tests.add("[EXACTLY ONCE] DUPLICATED", test_duplicated, config);
|
||||
tests.add("[EXACTLY ONCE] DELAYED+DUPLICATED", test_delayed_duplicated, config);
|
||||
tests.add("[EXACTLY ONCE] DROPPED", test_dropped, config);
|
||||
if args.monkeys > 0 {
|
||||
tests.add("[EXACTLY ONCE] CHAOS MONKEY", test_chaos_monkey, config);
|
||||
}
|
||||
if args.overhead {
|
||||
tests.add(
|
||||
"[EXACTLY ONCE] OVERHEAD NORMAL",
|
||||
|x| test_overhead(x, "EO", false),
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE] OVERHEAD FAULTY",
|
||||
|x| test_overhead(x, "EO", true),
|
||||
config,
|
||||
);
|
||||
}
|
||||
if args.model_checking {
|
||||
tests.add("[EXACTLY ONCE] MODEL CHECKING", test_mc_reliable_network, config);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE] MODEL CHECKING MESSAGE DROPS",
|
||||
test_mc_message_drops,
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE] MODEL CHECKING UNSTABLE NETWORK",
|
||||
test_mc_unstable_network,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// EXACTLY ONCE ORDERED
|
||||
if guarantee.is_none() || guarantee == Some("EOO") {
|
||||
config.sender_class = "ExactlyOnceOrderedSender";
|
||||
config.receiver_class = "ExactlyOnceOrderedReceiver";
|
||||
config.reliable = true;
|
||||
config.once = true;
|
||||
config.ordered = true;
|
||||
tests.add("[EXACTLY ONCE ORDERED] NORMAL", test_normal, config);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] NORMAL NON-UNIQUE",
|
||||
test_normal_non_unique,
|
||||
config,
|
||||
);
|
||||
tests.add("[EXACTLY ONCE ORDERED] DELAYED", test_delayed, config);
|
||||
tests.add("[EXACTLY ONCE ORDERED] DUPLICATED", test_duplicated, config);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] DELAYED+DUPLICATED",
|
||||
test_delayed_duplicated,
|
||||
config,
|
||||
);
|
||||
tests.add("[EXACTLY ONCE ORDERED] DROPPED", test_dropped, config);
|
||||
if args.monkeys > 0 {
|
||||
tests.add("[EXACTLY ONCE ORDERED] CHAOS MONKEY", test_chaos_monkey, config);
|
||||
}
|
||||
if args.overhead {
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] OVERHEAD NORMAL",
|
||||
|x| test_overhead(x, "EOO", false),
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] OVERHEAD FAULTY",
|
||||
|x| test_overhead(x, "EOO", true),
|
||||
config,
|
||||
);
|
||||
}
|
||||
if args.model_checking {
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] MODEL CHECKING",
|
||||
test_mc_reliable_network,
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] MODEL CHECKING MESSAGE DROPS",
|
||||
test_mc_message_drops,
|
||||
config,
|
||||
);
|
||||
tests.add(
|
||||
"[EXACTLY ONCE ORDERED] MODEL CHECKING UNSTABLE NETWORK",
|
||||
test_mc_unstable_network,
|
||||
config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(test) = &args.test {
|
||||
tests.run_test(test);
|
||||
} else {
|
||||
let (_, results) = tests.run();
|
||||
let score = score(results);
|
||||
println!("SCORE: {score}\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn score(results: IndexMap<String, TestResult>) -> f32 {
|
||||
let guarantees = HashSet::from(["AT MOST ONCE", "AT LEAST ONCE", "EXACTLY ONCE", "EXACTLY ONCE ORDERED"]);
|
||||
let mut failed_guarantees: HashSet<&str> = HashSet::new();
|
||||
let mut failed_overheads: HashSet<&str> = HashSet::new();
|
||||
for (test, result) in results {
|
||||
if result.is_err() {
|
||||
for guarantee in guarantees.iter() {
|
||||
if test.contains(format!("[{guarantee}]").as_str()) {
|
||||
if test.contains("OVERHEAD") {
|
||||
failed_overheads.insert(guarantee);
|
||||
} else {
|
||||
failed_guarantees.insert(guarantee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9. - failed_guarantees.len() as f32 * 2. - f32::from(!failed_overheads.is_empty())
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyList, PyModule};
|
||||
|
||||
const VALIDATOR_CODE: &str = include_str!("../shared_state_validator.py");
|
||||
|
||||
fn validator_module<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyModule>> {
|
||||
let code = CString::new(VALIDATOR_CODE).expect("validator source contains a null byte");
|
||||
PyModule::from_code(py, &code, c"shared_state_validator.py", c"shared_state_validator")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn validate_source(source: &str, filename: &str, class_names: &[&str]) -> Result<Vec<String>, String> {
|
||||
Python::attach(|py| {
|
||||
let module = validator_module(py).map_err(|error| error.to_string())?;
|
||||
let class_names = PyList::new(py, class_names).map_err(|error| error.to_string())?;
|
||||
module
|
||||
.getattr("validate_source")
|
||||
.and_then(|function| function.call1((source, filename, class_names)))
|
||||
.and_then(|result| result.extract::<Vec<String>>())
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_solution(path: &str, class_names: &[&str]) -> Result<Vec<String>, String> {
|
||||
Python::attach(|py| {
|
||||
let module = validator_module(py).map_err(|error| error.to_string())?;
|
||||
let class_names = PyList::new(py, class_names).map_err(|error| error.to_string())?;
|
||||
module
|
||||
.getattr("validate_solution")
|
||||
.and_then(|function| function.call1((path, class_names)))
|
||||
.and_then(|result| result.extract::<Vec<String>>())
|
||||
.map_err(|error| error.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_or_report(path: &str, class_names: &[&str]) -> bool {
|
||||
match validate_solution(path, class_names) {
|
||||
Ok(violations) if violations.is_empty() => true,
|
||||
Ok(violations) => {
|
||||
println!("ERROR: shared state between AnySystem processes is forbidden");
|
||||
for violation in violations {
|
||||
println!("{violation}");
|
||||
}
|
||||
println!("\nSCORE: 0\n");
|
||||
false
|
||||
}
|
||||
Err(error) => panic!("failed to validate solution for shared state: {error}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{validate_solution, validate_source};
|
||||
|
||||
const PROCESS: &[&str] = &["ProcessImpl"];
|
||||
|
||||
fn violations(source: &str) -> Vec<String> {
|
||||
validate_source(source, "solution.py", PROCESS).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_imports_helpers_constants_and_instance_state() {
|
||||
let source = r#"
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
from types import MappingProxyType
|
||||
|
||||
LIMIT = 2 ** 10
|
||||
NAMES = ("a", "b")
|
||||
LOOKUP = MappingProxyType({"a": 1, "b": 2})
|
||||
|
||||
@dataclass
|
||||
class Helper:
|
||||
values: list[str] = field(default_factory=list)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Status:
|
||||
ok: bool
|
||||
|
||||
class Kind(Enum):
|
||||
READY = auto()
|
||||
|
||||
class ProcessImpl:
|
||||
RETRIES = 3
|
||||
READY = Status(True)
|
||||
|
||||
def __init__(self):
|
||||
self.values = []
|
||||
self.helper = Helper()
|
||||
|
||||
def choose(self):
|
||||
return random.choice(NAMES)
|
||||
"#;
|
||||
assert_eq!(violations(source), Vec::<String>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_local_imports_without_executing_them() {
|
||||
let suffix = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let directory = std::env::temp_dir().join(format!(
|
||||
"distsys-shared-state-validator-{}-{suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&directory).unwrap();
|
||||
let entrypoint = directory.join("solution.py");
|
||||
std::fs::write(&entrypoint, "import helper\n\nclass ProcessImpl:\n pass\n").unwrap();
|
||||
std::fs::write(
|
||||
directory.join("helper.py"),
|
||||
"class Shared:\n data = {}\n\nraise RuntimeError('must not execute')\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = validate_solution(entrypoint.to_str().unwrap(), PROCESS);
|
||||
std::fs::remove_dir_all(directory).unwrap();
|
||||
|
||||
let violations = result.unwrap();
|
||||
assert_eq!(violations.len(), 1);
|
||||
assert!(violations[0].contains("Shared.data"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_shared_state_hidden_behind_helper_class() {
|
||||
let source = r#"
|
||||
from enum import Enum
|
||||
|
||||
class Shared:
|
||||
data = {}
|
||||
|
||||
@classmethod
|
||||
def data_ref(cls):
|
||||
return cls.data
|
||||
|
||||
class UnsafeKind(Enum):
|
||||
VALUES = []
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self):
|
||||
Shared.data_ref()["key"] = "value"
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 2);
|
||||
assert!(violations.iter().any(|item| item.contains("Shared.data")));
|
||||
assert!(violations.iter().any(|item| item.contains("UnsafeKind.VALUES")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_module_and_process_class_state() {
|
||||
let source = r#"
|
||||
CACHE = {}
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrozenButMutable:
|
||||
values: list[int] = field(default_factory=list)
|
||||
|
||||
BROKEN = FrozenButMutable()
|
||||
|
||||
class Base:
|
||||
pending = set()
|
||||
|
||||
class ProcessImpl(Base):
|
||||
values = []
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 4);
|
||||
assert!(violations.iter().any(|message| message.contains("CACHE")));
|
||||
assert!(violations.iter().any(|message| message.contains("BROKEN")));
|
||||
assert!(violations.iter().any(|message| message.contains("Base.pending")));
|
||||
assert!(violations.iter().any(|message| message.contains("ProcessImpl.values")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_module_level_dynamic_shared_state() {
|
||||
let source = r#"
|
||||
def helper():
|
||||
pass
|
||||
|
||||
class ProcessImpl:
|
||||
COUNT = 0
|
||||
|
||||
alias = ProcessImpl
|
||||
alias.cache = {}
|
||||
helper.cache = []
|
||||
setattr(ProcessImpl, "pending", set())
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 3);
|
||||
assert!(violations
|
||||
.iter()
|
||||
.all(|message| message.contains("module-level shared state")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_mutable_defaults_and_scope_state() {
|
||||
let source = r#"
|
||||
class ProcessImpl:
|
||||
def on_message(self, message, cache={}):
|
||||
global counter
|
||||
def next_value():
|
||||
nonlocal message
|
||||
return message
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_shared_but_accepts_local_closure_state() {
|
||||
let source = r#"
|
||||
def make_counter():
|
||||
count = 0
|
||||
def next_value():
|
||||
nonlocal count
|
||||
count += 1
|
||||
return count
|
||||
return next_value
|
||||
|
||||
SHARED_COUNTER = make_counter()
|
||||
|
||||
class ProcessImpl:
|
||||
def local_counter(self):
|
||||
count = 0
|
||||
def next_value():
|
||||
nonlocal count
|
||||
count += 1
|
||||
return count
|
||||
return next_value()
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 2);
|
||||
assert!(violations.iter().any(|message| message.contains("shared closure")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_getattr_eval_and_shared_cache_decorator() {
|
||||
let source = r#"
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache
|
||||
def cached_value(key):
|
||||
return key
|
||||
|
||||
class ProcessImpl:
|
||||
cache = ()
|
||||
|
||||
def on_message(self, message):
|
||||
getattr(type(self), "cache").clear()
|
||||
eval("globals()")
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 3);
|
||||
assert!(violations.iter().any(|message| message.contains("lru_cache")));
|
||||
assert!(violations.iter().any(|message| message.contains("clear")));
|
||||
assert!(violations.iter().any(|message| message.contains("eval")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_direct_aliased_and_reflective_mutation() {
|
||||
let source = r#"
|
||||
class ProcessImpl:
|
||||
COUNT = 0
|
||||
|
||||
def on_message(self, message):
|
||||
cls = type(self)
|
||||
state = cls.__dict__
|
||||
cls.COUNT += 1
|
||||
state.update({"x": 1})
|
||||
setattr(cls, "other", [])
|
||||
globals()["hidden"] = {}
|
||||
exec("hidden = {}")
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.len() >= 5);
|
||||
assert!(violations.iter().any(|message| message.contains("assignment mutates")));
|
||||
assert!(violations.iter().any(|message| message.contains("setattr")));
|
||||
assert!(violations.iter().any(|message| message.contains("globals")));
|
||||
assert!(violations.iter().any(|message| message.contains("exec")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_spoofed_safe_names_and_aliased_cache_decorator() {
|
||||
let source = r#"
|
||||
def tuple():
|
||||
return []
|
||||
|
||||
def MappingProxyType(value):
|
||||
return value
|
||||
|
||||
def dataclass(*args, **kwargs):
|
||||
def wrap(cls):
|
||||
return cls
|
||||
return wrap
|
||||
|
||||
SHARED_TUPLE = tuple()
|
||||
SHARED_MAPPING = MappingProxyType({})
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Mutable:
|
||||
pass
|
||||
|
||||
SHARED_OBJECT = Mutable()
|
||||
|
||||
from functools import lru_cache as memo
|
||||
|
||||
@memo
|
||||
def cached(value):
|
||||
return value
|
||||
|
||||
class ProcessImpl:
|
||||
pass
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.iter().any(|message| message.contains("SHARED_TUPLE")));
|
||||
assert!(violations.iter().any(|message| message.contains("SHARED_MAPPING")));
|
||||
assert!(violations.iter().any(|message| message.contains("SHARED_OBJECT")));
|
||||
assert!(violations.iter().any(|message| message.contains("lru_cache")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_imported_and_function_object_state() {
|
||||
let source = r#"
|
||||
import math
|
||||
import sys
|
||||
|
||||
SHARED = sys.modules
|
||||
holder = lambda: None
|
||||
module_alias = math
|
||||
math.shared = {}
|
||||
holder.shared = {}
|
||||
module_alias.other = {}
|
||||
|
||||
class ProcessImpl:
|
||||
def __init__(self):
|
||||
self.shared = sys.modules
|
||||
|
||||
def write(self):
|
||||
local_alias = math
|
||||
local_alias.more = {}
|
||||
self.shared["hidden"] = {}
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.iter().any(|message| message.contains("SHARED")));
|
||||
assert!(violations.len() >= 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unknown_constructor_as_mutable_default() {
|
||||
let source = r#"
|
||||
class Box:
|
||||
def __init__(self):
|
||||
self.values = {}
|
||||
|
||||
class ProcessImpl:
|
||||
def on_message(self, message, box=Box()):
|
||||
pass
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations
|
||||
.iter()
|
||||
.any(|message| message.contains("mutable default argument")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_dunder_and_class_namespace_reflection() {
|
||||
let source = r#"
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Frozen:
|
||||
value: int
|
||||
|
||||
SHARED = Frozen(1)
|
||||
|
||||
class ProcessImpl:
|
||||
locals()["shared"] = {}
|
||||
|
||||
def on_message(self, message):
|
||||
type.__setattr__(ProcessImpl, "cache", {})
|
||||
object.__setattr__(SHARED, "value", 2)
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations
|
||||
.iter()
|
||||
.any(|message| message.contains("class-level shared state")));
|
||||
assert!(violations.iter().any(|message| message.contains("__setattr__")));
|
||||
assert!(violations.len() >= 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_mutating_frozen_dataclass_methods() {
|
||||
let source = r#"
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Frozen:
|
||||
value: int = 1
|
||||
|
||||
def __post_init__(self):
|
||||
object.__setattr__(self, "state", {})
|
||||
|
||||
def put(self, key, value):
|
||||
self.state[key] = value
|
||||
|
||||
GLOBAL = Frozen()
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self):
|
||||
GLOBAL.put("x", 1)
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.iter().any(|message| message.contains("GLOBAL")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_dunder_and_operator_item_mutation() {
|
||||
let source = r#"
|
||||
import math
|
||||
import operator
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self):
|
||||
math.__dict__.__setitem__("shared", {})
|
||||
operator.setitem(math.__dict__, "other", {})
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.len() >= 2);
|
||||
assert!(violations.iter().any(|message| message.contains("__setitem__")));
|
||||
assert!(violations.iter().any(|message| message.contains("operator")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_class_local_and_star_cache_decorators() {
|
||||
let source = r#"
|
||||
from functools import *
|
||||
|
||||
@lru_cache
|
||||
def module_cached():
|
||||
return []
|
||||
|
||||
class ProcessImpl:
|
||||
from functools import lru_cache as memo
|
||||
|
||||
@staticmethod
|
||||
@memo
|
||||
def shared():
|
||||
return []
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(
|
||||
violations
|
||||
.iter()
|
||||
.filter(|message| message.contains("lru_cache"))
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_literal_dynamic_local_imports() {
|
||||
let suffix = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let directory = std::env::temp_dir().join(format!(
|
||||
"distsys-dynamic-import-validator-{}-{suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&directory).unwrap();
|
||||
let entrypoint = directory.join("solution.py");
|
||||
std::fs::write(
|
||||
&entrypoint,
|
||||
"from importlib import import_module as load\n\nPREFIX = 'hel'\nMODULE = PREFIX + 'per'\n\nclass ProcessImpl:\n def __init__(self):\n self.shared = load(MODULE).CACHE\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(directory.join("helper.py"), "CACHE = {}\n").unwrap();
|
||||
|
||||
let result = validate_solution(entrypoint.to_str().unwrap(), PROCESS);
|
||||
std::fs::remove_dir_all(directory).unwrap();
|
||||
|
||||
let violations = result.unwrap();
|
||||
assert_eq!(violations.len(), 1);
|
||||
assert!(violations[0].contains("CACHE"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_shared_values_returned_by_wrappers_and_wrapped_cache() {
|
||||
let source = r#"
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
|
||||
def get_shared():
|
||||
return sys.modules
|
||||
|
||||
def memo(function):
|
||||
return lru_cache(function)
|
||||
|
||||
@memo
|
||||
def shared():
|
||||
return []
|
||||
|
||||
class ProcessImpl:
|
||||
def __init__(self):
|
||||
self.shared = get_shared()
|
||||
|
||||
def write(self):
|
||||
self.shared["key"] = shared()
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert!(violations.iter().any(|message| message.contains("assignment mutates")));
|
||||
assert!(violations.iter().any(|message| message.contains("memo")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_aliased_mutator_callables() {
|
||||
let source = r#"
|
||||
import math
|
||||
import operator
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self):
|
||||
mutate = operator.setitem
|
||||
mutate(math.__dict__, "first", {})
|
||||
bound_mutate = math.__dict__.__setitem__
|
||||
bound_mutate("second", {})
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 2);
|
||||
assert!(violations.iter().any(|message| message.contains("operator")));
|
||||
assert!(violations.iter().any(|message| message.contains("aliased")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unresolved_dynamic_local_imports() {
|
||||
let suffix = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let directory = std::env::temp_dir().join(format!(
|
||||
"distsys-unresolved-import-validator-{}-{suffix}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&directory).unwrap();
|
||||
let entrypoint = directory.join("solution.py");
|
||||
std::fs::write(
|
||||
&entrypoint,
|
||||
"from importlib import import_module as load\n\nclass ProcessImpl:\n def write(self):\n name = ''.join(['helper'])\n state = load(name).CACHE\n state['key'] = 1\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(directory.join("helper.py"), "CACHE = {}\n").unwrap();
|
||||
|
||||
let result = validate_solution(entrypoint.to_str().unwrap(), PROCESS);
|
||||
std::fs::remove_dir_all(directory).unwrap();
|
||||
|
||||
let violations = result.unwrap();
|
||||
assert_eq!(violations.len(), 1);
|
||||
assert!(violations[0].contains("cannot be resolved statically"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_stateful_imported_api_but_allows_reviewed_calls() {
|
||||
let source = r#"
|
||||
import random
|
||||
from random import seed as direct_seed
|
||||
|
||||
random.seed(1)
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self, value):
|
||||
direct_seed(value)
|
||||
reseed = random.seed
|
||||
reseed(value)
|
||||
|
||||
def read(self):
|
||||
return random.getstate()
|
||||
|
||||
def choose(self):
|
||||
return random.choice((1, 2))
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 4);
|
||||
assert!(violations.iter().any(|message| message.contains("random.seed")));
|
||||
assert!(violations.iter().any(|message| message.contains("random.getstate")));
|
||||
assert!(!violations.iter().any(|message| message.contains("random.choice")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_passthrough_and_lambda_shared_references() {
|
||||
let source = r#"
|
||||
import sys
|
||||
|
||||
def identity(value):
|
||||
return value
|
||||
|
||||
get_shared = lambda: sys.modules
|
||||
lambda_identity = lambda value: value
|
||||
|
||||
class ProcessImpl:
|
||||
def __init__(self):
|
||||
self.first = identity(sys.modules)
|
||||
self.second = get_shared()
|
||||
self.third = lambda_identity(sys.modules)
|
||||
|
||||
def write(self):
|
||||
self.first["first"] = {}
|
||||
self.second["second"] = {}
|
||||
self.third["third"] = {}
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 3);
|
||||
assert!(violations.iter().all(|message| message.contains("assignment mutates")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_getattr_hidden_imported_api_and_mutator() {
|
||||
let source = r#"
|
||||
import math
|
||||
import random
|
||||
|
||||
class ProcessImpl:
|
||||
def write(self, value):
|
||||
getattr(random, "seed")(value)
|
||||
getattr(math.__dict__, "__setitem__")("shared", {})
|
||||
"#;
|
||||
let violations = violations(source);
|
||||
assert_eq!(violations.len(), 2);
|
||||
assert!(violations.iter().any(|message| message.contains("random.seed")));
|
||||
assert!(violations.iter().any(|message| message.contains("__setitem__")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::fs;
|
||||
|
||||
use assertables::assume;
|
||||
use rand::prelude::*;
|
||||
use rand_pcg::Pcg64;
|
||||
|
||||
use anysystem::test::TestResult;
|
||||
use anysystem::Message;
|
||||
|
||||
use crate::common::{build_system, check_guarantees, check_overhead, send_messages, TestConfig};
|
||||
|
||||
pub fn test_normal(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
let messages = send_messages(&mut sys, 5);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)?;
|
||||
// We expect no more than 5 messages from sender in normal network conditions
|
||||
let sent_count = sys.sent_message_count("sender");
|
||||
assume!(
|
||||
sent_count <= 5,
|
||||
format!("Sender sent {} messages, expected at most 5", sent_count)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_normal_non_unique(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
let messages = send_messages(&mut sys, 10);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)?;
|
||||
// We expect no more than 10 messages from sender in normal network conditions (stable delay, no loss).
|
||||
// If solution sends multiple messages without or with too small (<RTT) delay, this results in extra redundant
|
||||
// traffic. We want to avoid wasting network resources, assuming that normal conditions happen most of the time.
|
||||
let sent_count = sys.sent_message_count("sender");
|
||||
assume!(
|
||||
sent_count <= 10,
|
||||
format!("Sender sent {} messages, expected at most 10", sent_count)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_delayed(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_delays(1., 3.);
|
||||
let messages = send_messages(&mut sys, 5);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)
|
||||
}
|
||||
|
||||
pub fn test_duplicated(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_dupl_rate(0.3);
|
||||
let messages = send_messages(&mut sys, 5);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)
|
||||
}
|
||||
|
||||
pub fn test_delayed_duplicated(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_delays(1., 3.);
|
||||
sys.network().set_dupl_rate(0.3);
|
||||
let messages = send_messages(&mut sys, 5);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)
|
||||
}
|
||||
|
||||
pub fn test_old_duplicate(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_delays(1., 3.);
|
||||
sys.network().set_dupl_rate(1.);
|
||||
|
||||
let first = Message::new("MESSAGE", r#"{"text": "first"}"#);
|
||||
let mut messages = vec![first.clone()];
|
||||
sys.send_local_message("sender", first);
|
||||
|
||||
// Stop after the first copy is delivered.
|
||||
while sys.local_outbox("receiver").is_empty() {
|
||||
if !sys.step() {
|
||||
return Err("The first message was not delivered".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the old duplicate pending while newer messages are delivered first.
|
||||
// Zero-delay messages are processed before the delayed copy.
|
||||
sys.network().set_dupl_rate(0.);
|
||||
sys.network().set_delay(0.);
|
||||
for i in 0..50 {
|
||||
let msg = Message::new("MESSAGE", &format!(r#"{{"text": "message-{i}"}}"#));
|
||||
sys.send_local_message("sender", msg.clone());
|
||||
messages.push(msg);
|
||||
}
|
||||
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)
|
||||
}
|
||||
|
||||
pub fn test_dropped(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_drop_rate(0.3);
|
||||
let messages = send_messages(&mut sys, 5);
|
||||
sys.step_until_no_events();
|
||||
check_guarantees(&mut sys, &messages, config)
|
||||
}
|
||||
|
||||
pub fn test_chaos_monkey(config: &TestConfig) -> TestResult {
|
||||
let mut rand = Pcg64::seed_from_u64(config.seed);
|
||||
for i in 1..=config.monkeys {
|
||||
let mut run_config = *config;
|
||||
run_config.seed = rand.next_u64();
|
||||
println!("Run {} (seed: {})", i, run_config.seed);
|
||||
let mut sys = build_system(&run_config, false);
|
||||
sys.network().set_delays(1., 3.);
|
||||
sys.network().set_dupl_rate(0.3);
|
||||
sys.network().set_drop_rate(0.3);
|
||||
let messages = send_messages(&mut sys, 50);
|
||||
sys.step_until_no_events();
|
||||
let res = check_guarantees(&mut sys, &messages, &run_config);
|
||||
res.as_ref()?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn test_overhead(config: &TestConfig, guarantee: &str, faulty: bool) -> TestResult {
|
||||
for message_count in [100, 500, 1000] {
|
||||
let mut sys = build_system(config, true);
|
||||
if faulty {
|
||||
sys.network().set_delays(1., 3.);
|
||||
sys.network().set_dupl_rate(0.3);
|
||||
sys.network().set_drop_rate(0.3);
|
||||
}
|
||||
let messages = send_messages(&mut sys, message_count);
|
||||
sys.step_until_no_events();
|
||||
let res = check_guarantees(&mut sys, &messages, config);
|
||||
res.as_ref()?;
|
||||
let sender_mem = sys.max_size("sender");
|
||||
let receiver_mem = sys.max_size("receiver");
|
||||
let net_message_count = sys.network().network_message_count();
|
||||
let net_traffic = sys.network().traffic();
|
||||
let throughput = message_count as f64 / sys.time();
|
||||
println!(
|
||||
"{message_count:<6} Send Mem: {sender_mem:<8} Recv Mem: {receiver_mem:<8} Messages: {net_message_count:<8} Traffic: {net_traffic:<8} Throughput: {throughput:.3}"
|
||||
);
|
||||
check_overhead(
|
||||
guarantee,
|
||||
faulty,
|
||||
message_count,
|
||||
sender_mem,
|
||||
receiver_mem,
|
||||
net_message_count,
|
||||
net_traffic,
|
||||
throughput,
|
||||
)?;
|
||||
}
|
||||
let impl_code = fs::read_to_string(config.impl_path).unwrap();
|
||||
assume!(
|
||||
!impl_code.contains("<<") && !impl_code.contains(">>"),
|
||||
"Implementation contains bitwise shift operators"
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use sugars::boxed;
|
||||
|
||||
use anysystem::logger::LogEntry;
|
||||
use anysystem::mc::{
|
||||
predicates::{goals, invariants, prunes},
|
||||
strategies::Bfs,
|
||||
InvariantFn, ModelChecker, StrategyConfig,
|
||||
};
|
||||
use anysystem::test::TestResult;
|
||||
use anysystem::Message;
|
||||
|
||||
use crate::common::{
|
||||
build_system, check_delivered_messages, check_message_delivery_once, check_message_delivery_ordered,
|
||||
check_message_delivery_reliable, generate_message_texts, TestConfig,
|
||||
};
|
||||
|
||||
fn mc_invariant_guarantees(messages_expected: Vec<Message>, config: TestConfig) -> InvariantFn {
|
||||
boxed!(move |state| {
|
||||
let mut expected_msg_count = HashMap::new();
|
||||
for msg in &messages_expected {
|
||||
*expected_msg_count.entry(msg.data.clone()).or_insert(0) += 1;
|
||||
}
|
||||
let delivered = &state.node_states["receiver-node"].proc_states["receiver"].local_outbox;
|
||||
|
||||
// check that delivered messages have expected type and data
|
||||
let delivered_msg_count = check_delivered_messages(delivered, &expected_msg_count, &messages_expected[0].tip)?;
|
||||
|
||||
// check delivered message count according to expected guarantees
|
||||
if config.reliable && state.events.is_empty() {
|
||||
check_message_delivery_reliable(&delivered_msg_count, &expected_msg_count)?;
|
||||
}
|
||||
if config.once {
|
||||
check_message_delivery_once(&delivered_msg_count, &expected_msg_count)?;
|
||||
}
|
||||
if config.ordered {
|
||||
check_message_delivery_ordered(delivered, &messages_expected)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn test_mc_reliable_network(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
let messages: Vec<Message> = generate_message_texts(&mut sys, 2)
|
||||
.into_iter()
|
||||
.map(|text| Message::new("MESSAGE", &format!(r#"{{"text": "{text}"}}"#)))
|
||||
.collect();
|
||||
let strategy_config = StrategyConfig::default()
|
||||
.prune(prunes::sent_messages_limit(4))
|
||||
.goal(goals::got_n_local_messages("receiver-node", "receiver", 2))
|
||||
.invariant(invariants::all_invariants(vec![
|
||||
invariants::state_depth(20),
|
||||
mc_invariant_guarantees(messages.clone(), *config),
|
||||
]));
|
||||
let mut mc = ModelChecker::new(&sys);
|
||||
let res = mc.run_with_change::<Bfs>(strategy_config, move |sys| {
|
||||
for message in messages {
|
||||
sys.send_local_message("sender-node", "sender", message);
|
||||
}
|
||||
});
|
||||
if let Err(e) = res {
|
||||
e.print_trace();
|
||||
Err(e.message())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_mc_message_drops(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_drop_rate(0.1);
|
||||
let messages: Vec<Message> = generate_message_texts(&mut sys, 2)
|
||||
.into_iter()
|
||||
.map(|text| Message::new("MESSAGE", &format!(r#"{{"text": "{text}"}}"#)))
|
||||
.collect();
|
||||
let strategy_config = StrategyConfig::default()
|
||||
.prune(prunes::state_depth(7))
|
||||
.goal(goals::any_goal(vec![
|
||||
goals::got_n_local_messages("receiver-node", "receiver", 2),
|
||||
goals::no_events(),
|
||||
]))
|
||||
.invariant(mc_invariant_guarantees(messages.clone(), *config));
|
||||
let mut mc = ModelChecker::new(&sys);
|
||||
let res = mc.run_with_change::<Bfs>(strategy_config, move |sys| {
|
||||
for message in messages {
|
||||
sys.send_local_message("sender-node", "sender", message);
|
||||
}
|
||||
});
|
||||
if let Err(e) = res {
|
||||
e.print_trace();
|
||||
Err(e.message())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_mc_unstable_network(config: &TestConfig) -> TestResult {
|
||||
let mut sys = build_system(config, false);
|
||||
sys.network().set_drop_rate(0.1);
|
||||
sys.network().set_dupl_rate(0.1);
|
||||
let msg_count = if config.ordered { 3 } else { 2 };
|
||||
let messages: Vec<Message> = generate_message_texts(&mut sys, msg_count)
|
||||
.into_iter()
|
||||
.map(|text| Message::new("MESSAGE", &format!(r#"{{"text": "{text}"}}"#)))
|
||||
.collect();
|
||||
let num_drops_allowed = 1;
|
||||
let num_duplication_allowed = 1;
|
||||
let goal = if config.reliable && config.once {
|
||||
goals::all_goals(vec![
|
||||
goals::got_n_local_messages("receiver-node", "receiver", msg_count),
|
||||
goals::no_events(),
|
||||
])
|
||||
} else {
|
||||
goals::no_events()
|
||||
};
|
||||
let mut invariants = vec![
|
||||
invariants::state_depth(20),
|
||||
mc_invariant_guarantees(messages.clone(), *config),
|
||||
];
|
||||
if config.ordered {
|
||||
invariants.push(invariants::time_limit(Duration::from_secs(80)))
|
||||
};
|
||||
let strategy_config = StrategyConfig::default()
|
||||
.prune(prunes::any_prune(vec![
|
||||
prunes::events_limit(LogEntry::is_mc_message_dropped, num_drops_allowed),
|
||||
prunes::events_limit(LogEntry::is_mc_message_duplicated, num_duplication_allowed),
|
||||
prunes::events_limit(LogEntry::is_mc_timer_fired, 1),
|
||||
prunes::events_limit(LogEntry::is_mc_message_received, msg_count + num_drops_allowed),
|
||||
]))
|
||||
.goal(goal)
|
||||
.invariant(invariants::all_invariants(invariants));
|
||||
let mut mc = ModelChecker::new(&sys);
|
||||
|
||||
let res = mc.run_with_change::<Bfs>(strategy_config, |sys| {
|
||||
for msg in messages {
|
||||
sys.send_local_message("sender-node", "sender", msg.clone());
|
||||
}
|
||||
});
|
||||
if let Err(e) = res {
|
||||
e.print_trace();
|
||||
Err(e.message())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user