rspec-1.0.0/.cargo_vcs_info.json0000644000000001121400753047700122050ustar { "git": { "sha1": "7879c21f970d7dfbe0a668b636bc9fa5842ffbb4" } } rspec-1.0.0/.gitignore010064400007650000024000000017251400751413100127740ustar 00000000000000 # Created by https://www.gitignore.io/api/rust ### Rust ### # Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # These are temporary files generated by racer *.racertmp # End of https://www.gitignore.io/api/rust # Created by https://www.gitignore.io/api/osx ### OSX ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # End of https://www.gitignore.io/api/osx .idea/* *.iml rspec-1.0.0/.travis.yml010064400007650000024000000024661400752777600131430ustar 00000000000000sudo: false language: rust addons: apt: packages: - libcurl4-openssl-dev - libelf-dev - libdw-dev - binutils-dev rust: - nightly - beta - stable matrix: allow_failures: - rust: nightly - rust: beta before_script: - | pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - | travis-cargo build && travis-cargo test && travis-cargo bench && travis-cargo --only stable doc after_success: - travis-cargo --only stable doc-upload - travis-cargo coveralls --no-sudo --verify env: global: - TRAVIS_CARGO_NIGHTLY_FEATURE=clippy - secure: y5nerEobHN6w+QKBfP+ks5PsqDMGWFacxk3+HN/iPwqN7tcg0j72iq7/TaYEQUYtsmTuX6ifFuxpMZNsI6mNmjF0Cb8XQdUsS+mocAadjje+tavzKY2eGPmozRisCEJeybIJEnSUC4jwwdJS+pwXgIbri2bWGssi4Nc+9ojdeA15tL6Zyy079wv978Ucp+oj6XSRXq1PXqZ+CRP+kU2AGBWCPbhW03TFn4y1Sn4RvSQEXHtcHBef1F6Z+MIyn7gLK1rX0R+FgJe+XqO08rwefaIgXJP99hDESxQS1BqJD2fW9oLed0ITyA1pE6k5qTpLhiF73alVDSIaxLNiIXINg62pmZEFJ7yy+A8nwiEW2Qc0oq3TZwLW94GifADVgBRgRjbYidGz8UvTaYxfdJapvuMD0GmOEZuXFPA3E0jTmjBlClgtQircVbl9XgOVbBCrjmyihJ/wkjCVLRuoSj/+2T1zsZmLqRkhKj0dDPZSPVcmaWSzJkevEYXyi5aglnRC2nssf5kgZ3ctUh4/APFRvt2QnVVowIYi9fLEW/bUxxD5BGzMsP9yrM1KECbdFPob3g1LOZN4VwPJIDp1vbdW0/Wim/akR2Mujxm0L9+oSaB6taWUq5NYTl2vfFmOa6xJonpsgZpFanxSxyiBjXlBVeVC6hiHnjD12nAXONFmLJE= rspec-1.0.0/Cargo.lock0000644000000453471400753047700102030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[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.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base-x" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" [[package]] name = "bitflags" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" [[package]] name = "bumpalo" version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" [[package]] name = "cargo_metadata" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" dependencies = [ "serde", "serde_derive", "serde_json", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clippy" version = "0.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b86ea33b6f775111137966a997e3ca49f99c724a7e05efb386d9018649acac" dependencies = [ "cargo_metadata", "clippy_lints", ] [[package]] name = "clippy_lints" version = "0.0.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a95723fc71cacda0de649f8f23d894e99ee45f1df34c2c19c9f712c36233c08" dependencies = [ "itertools", "lazy_static 0.2.11", "matches", "pulldown-cmark", "quine-mc_cluskey", "regex-syntax", "semver 0.6.0", "serde", "serde_derive", "toml", "unicode-normalization", ] [[package]] name = "colored" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" dependencies = [ "atty", "lazy_static 1.4.0", "winapi", ] [[package]] name = "const_fn" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" [[package]] name = "crossbeam-channel" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ "cfg-if", "const_fn", "crossbeam-utils", "lazy_static 1.4.0", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg", "cfg-if", "lazy_static 1.4.0", ] [[package]] name = "darling" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "derive-new" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ "darling", "derive_builder_core", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_core" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "discard" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "expectest" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78817c0b64aab8153fbd7532339fbac1a54e43c715802715a288163ca6024978" dependencies = [ "num-traits", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "hermit-abi" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "itertools" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "lazy_static" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memoffset" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" dependencies = [ "autocfg", ] [[package]] name = "num-traits" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "pulldown-cmark" version = "0.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" dependencies = [ "bitflags", "getopts", ] [[package]] name = "quine-mc_cluskey" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" [[package]] name = "quote" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ "autocfg", "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "lazy_static 1.4.0", "num_cpus", ] [[package]] name = "regex-syntax" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" [[package]] name = "rspec" version = "1.0.0" dependencies = [ "clippy", "colored", "derive-new", "derive_builder", "expectest", "rayon", "time", ] [[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ "semver 0.9.0", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ "semver-parser", ] [[package]] name = "semver" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" [[package]] name = "serde_derive" version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "sha1" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "standback" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" dependencies = [ "version_check", ] [[package]] name = "stdweb" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", "rustc_version", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", "wasm-bindgen", ] [[package]] name = "stdweb-derive" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", "serde", "serde_derive", "syn", ] [[package]] name = "stdweb-internal-macros" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", "proc-macro2", "quote", "serde", "serde_derive", "serde_json", "sha1", "syn", ] [[package]] name = "stdweb-internal-runtime" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "strsim" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "syn" version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "time" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7" dependencies = [ "const_fn", "libc", "standback", "stdweb", "time-macros", "version_check", "winapi", ] [[package]] name = "time-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" dependencies = [ "proc-macro-hack", "time-macros-impl", ] [[package]] name = "time-macros-impl" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" dependencies = [ "proc-macro-hack", "proc-macro2", "quote", "standback", "syn", ] [[package]] name = "tinyvec" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "toml" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" dependencies = [ "serde", ] [[package]] name = "unicode-normalization" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "wasm-bindgen" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" dependencies = [ "bumpalo", "lazy_static 1.4.0", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" [[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-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" rspec-1.0.0/Cargo.toml0000644000000025771400753047700102240ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "rspec" version = "1.0.0" authors = ["Thomas Wickham ", "Vincent Esche "] description = "Write Rspec-like tests with stable rust" homepage = "https://mackwic.github.io/rspec" readme = "README.md" keywords = ["rspec", "test", "harness", "tdd", "bdd"] categories = ["development-tools", "development-tools::testing"] license = "MPL-2.0" repository = "https://github.com/rust-rspec/rspec" [dependencies.colored] version = "2.0" [dependencies.derive-new] version = "0.5" [dependencies.derive_builder] version = "0.9" [dependencies.expectest] version = "0.12" optional = true [dependencies.rayon] version = "1.5" [dependencies.time] version = "0.2" [build-dependencies.clippy] version = "0.0.153" optional = true [features] default = [] expectest_compat = ["expectest"] [badges.maintenance] status = "passively-maintained" rspec-1.0.0/Cargo.toml.orig010064400007650000024000000015011400752777600137060ustar 00000000000000[package] name = "rspec" description = "Write Rspec-like tests with stable rust" version = "1.0.0" readme = "README.md" repository = "https://github.com/rust-rspec/rspec" homepage = "https://mackwic.github.io/rspec" license = "MPL-2.0" authors = [ "Thomas Wickham ", "Vincent Esche ", ] keywords = [ "rspec", "test", "harness", "tdd", "bdd", ] categories = [ "development-tools", "development-tools::testing" ] [build-dependencies.clippy] optional = true version = "0.0.153" [dependencies] colored = "2.0" derive-new = "0.5" derive_builder = "0.9" rayon = "1.5" time = "0.2" [dependencies.expectest] optional = true version = "0.12" [features] default = [] expectest_compat = ["expectest"] [badges] maintenance = { status = "passively-maintained" } rspec-1.0.0/LICENSE010064400007650000024000000405251400751413100120120ustar 00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. rspec-1.0.0/README.md010064400007650000024000000255351400752777600123130ustar 00000000000000# rspec - a BDD test harness that works with stable Rust [![Build Status](https://travis-ci.org/rust-rspec/rspec.svg?branch=master)](https://travis-ci.org/rust-rspec/rspec) [![Coverage Status](https://coveralls.io/repos/github/rust-rspec/rspec/badge.svg)](https://coveralls.io/github/rust-rspec/rspec) [![Crates.io](https://img.shields.io/crates/v/rspec.svg?maxAge=2592000)](https://crates.io/crates/rspec) [![Crates.io](https://img.shields.io/crates/l/rspec.svg?maxAge=2592000)](https://github.com/rust-rspec/rspec/blob/master/LICENSE) When you like BDD, and all the nested `describe/context/it` way of testing, but you also like when your code compiles every day 👌. If you don't know what is Rust, are confused by the terms BDD, TDD, or just want a gently beginner introduction, [please go to the Beginner Section](#beginners). The last stable documentation is available for consultation at [docs.rs/rspec](https://docs.rs/rspec). ## How to use Add this in your `Cargo.toml`: ```toml [dev_dependencies] rspec = "1.0" ``` and add this to your `src/lib.rs` or `src/main.rs`: ```rust #[cfg(test)] extern crate rspec; ``` You can see complete examples in the [`examples/`](https://github.com/rust-rspec/rspec/tree/master/examples) directory. ```rust extern crate rspec; pub fn main() { // Use a local struct to provide the test contexts with an environment. // The environment will contain the subject that is to be tested // along with any additional data you might need during the test run: #[derive(Clone, Default, Debug)] struct Environment { // ... } // `rspec::run(…)` is a convenience wrapper that takes care of setting up // a runner, logger, configuration and running the test suite for you. // If you want more direct control, you can manually set up those things, too. rspec::run(&rspec::describe("rspec, a BDD testing framework", Environment::default(), |ctx| { // `describe`, or any of its equivalents, opens the root context // of your test suite. Within you can then either define test examples: ctx.it("can define top-level tests", |_| true); // or make use of sub-contexts to add some structure to your test suite: ctx.specify("contexts give your tests structure and reduce redundancy", |ctx| { ctx.before(|_| { // Executed once, before any of the contexts/examples is entered. }); ctx.after(|_| { // Executed once, after all of the contexts/examples have been exited. }); ctx.specify("rspec can handle results", |ctx| { ctx.it("passes if the return is_ok()", |_| Ok(()) as Result<(),()>); ctx.it("failes if the return is_err()", |_| Err(()) as Result<(),()>); }); ctx.specify("rspec can handle bools", |ctx| { ctx.it("should pass if true", |_| true); ctx.it("should fail if false", |_| false); ctx.it("is convenient for comparisons", |_| (42 % 37 + 2) > 3); }); ctx.specify("rspec can handle units", |ctx| { ctx.it("should pass if the return is ()", |_| {}); }); ctx.specify("rspec can handle panics", |ctx| { ctx.it("is convenient for asserts", |_| assert_eq!(1, 1)); }); }); })); // exits the process with a failure code if one of the tests failed. } ``` ### Suites, Contexts & Examples rspec provides three variants for each of the structural elements: | | Variant A | Variant B | Variant C | | --------- | --------- | ---------- | --------- | | Suites: | `suite` | `describe` | `given` | | Contexts: | `context` | `specify` | `when` | | Examples: | `example` | `it` | `then` | **Note:** While the intended use is to stick to a single variant per test suite it is possible to freely mix structural elements across variants. #### Variant A: `suite`, `context` & `example` ```rust runner.run(&rspec::suite("opens a suite", /* environment */, |ctx| { ctx.context("opens a context", |ctx| { ctx.example("opens an example", |env| /* test condition */ ); }); })); ``` #### Variant B: `describe`, `specify` & `it` ```rust runner.run(&rspec::describe("opens a suite", /* environment */, |ctx| { ctx.specify("opens a context", |ctx| { ctx.it("opens an example", |env| /* test condition */ ); }); })); ``` #### Variant C: `given`, `when` & `then` ```rust runner.run(&rspec::given("opens a suite", /* environment */, |ctx| { ctx.when("opens a context", |ctx| { ctx.then("opens an example", |env| /* test condition */ ); }); })); ``` ### Before & After | | All | Each | | ------- | --------------------- | ------------- | | Before: | `before`/`before_all` | `before_each` | | After: | `after` /`after_all` | `after_each` | #### All The "All" variants of before and after blocks are executed once upon entering (or exiting, respectively) the given context. #### Each `before_each` and `after_each` blocks are executed once before each of the given context's sub-contexts or examples. ### More Examples Again, you can see complete examples in the [`examples/`](https://github.com/rust-rspec/rspec/tree/master/examples) directory. ## Documentation The last stable documentation is available for consultation at [https://docs.rs/rspec](https://docs.rs/rspec). ## Contributions ... are greatly welcome! Contributions follow the standard Github workflow, which is: 1. Fork this repository 2. Create a feature branch 3. Code and commit inside this branch. I have a personnal preference for small atomic commits, but that's not a hard rule. 4. Make sure you have written tests for your feature. If you don't know how to do that, push the PR with `[WIP]` in the title and we'll gladly help you. 5. When tests are ok, and you features/bug fixes are covered, we will review the code together, make it better together. 6. When everyone agrees that the code is right, and the tests pass, you will have the privilege to merge your PR and become a mighty Contributor. Congrats. Take a look at [the issues](https://github.com/rust-rspec/rspec/issues) if you want to help without knowing how. Some issues are mentored! ## Contributors - Thomas WICKHAM [@mackwic](https://github.com/mackwic) - Pascal HERTLEIF [@killercup](https://github.com/killercup) - Matthias BOURG [@pol0nium](https://github.com/pol0nium) - Vincent ESCHE [@regexident](https://github.com/regexident) ## Beginners #### About Rust Welcome in the Rust community! Here are some links which can hopefully help: - [**Rust is a system programming language**](https://www.rust-lang.org). Check the rust-lang link for a detailed description, you can install it [with rustup](https://www.rustup.rs/). - **Newcomers**: [Rust by Example](http://rustbyexample.com/) is a fantastic resource to get started and grasp most of the Rust semantic. Check it out! - **Intermediate**: [The Rust Book](https://doc.rust-lang.org/book/) is the best resource to begin the journey to learn Rust. - **Advanced**: [Learning Rust with too many linked lists](http://cglab.ca/~abeinges/blah/too-many-lists/book/) is a great book which explains by examples and with great details the system of ownership and how to use it. [The Rustonomicon](https://doc.rust-lang.org/nomicon/) Is another resoure helpful to understand how the compiler thinks and will help you converge quickly to a compilable code. #### About TDD TDD, short for Tests Driven Development, is a methodology of development where your code is always working, where refactoring is easy, and you know exactly what piece of code do _and what it doesn't_. With TDD, legacy code is limited, your build is always green and you don't have regresssions. This is a wonderful and magical land, a well-keeped secret where only the best of the best are admitted, people who don't compromise on quality because they know the cost of the absence of quality. People who know that over-quality is a non-sense. Here are some useful links: - [Uncle Bob's 3 rules of TDD](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd) - [The Art of Agile: Test Driven Development](http://www.jamesshore.com/Agile-Book/test_driven_development.html) - TDD also enable [simple and emergeant designs](http://www.jamesshore.com/Agile-Book/simple_design.html) by reducing the number of decisions you have to take at each step. #### About BDD BDD, short for Behavior Driven Development, is a variation on TDD; some would say that BDD is simply TDD but refined. BDD states that tests are not the center of the methodology. They are one of the most usefull tool available, but we should not look at them in too high regard. What matters is the contract they seal, the _described behavior_. Thus, there is enough tests when the behavior of the `struct` is sufficiently described. Thinking in term of behavior has two benefits: - When doing TDD, it helps to make incremential steps. Just write examples of how to use the functions, and make this example pass, then go to the next one. Your tests will naturally have one clear intent, and so will be easy to debug / rely on when refactoring. This is the describe/it approach, which this crate hopes to fill. - By describing behavior, we are doing an _analysis_ of our program. This analysis can be very useful! Say... an User Story, for example. Given the formalism _As a X, When Y, I want Z_, you can assign scenarii describing the high-level behavior of your units. The Gherkin formalism is often employed for this, it use a _Given X, When Y, Then Z_ structure. This project does not aim to help on high-level BDD, see [the cucumber for Rust port](https://github.com/acmcarther/cucumbe://github.com/acmcarther/cucumber) for that. The foundation of BDD [is well explained here](https://dannorth.net/introducing-bdd/) and [also here](http://blog.daveastels.com.s3-website-us-west-2.amazonaws.com/2014/09/29/a-new-look-at-test-driven-development.html). BDD written with the Gherkin formalism can really gain from a layer of DDD (Domain Driven Development), [but this is another story...](https://msdn.microsoft.com/en-us/magazine/dd419654.aspx). ## Licence Mozilla Public Licence 2.0. See the LICENCE file at the root of the repository. In non legal terms it means that: - if you fix a bug, you MUST give back the code of the fix (it's only fair, see the [Contributing Section](#contributions)). - if you change/extend the API, you MUST give back the code you changed in the files under MPL2. The [Contributing Section](#contributions) can help there. - you CAN'T sue the authors of this project for anything about this code - appart from that, you can do almost whatever you want. See the LICENCE file for details. This section DOES NOT REPLACE NOR COMPLETE the LICENCE files. The LICENCE file is the only place where the licence of this project is defined. rspec-1.0.0/examples/advanced.rs010064400007650000024000000030321400751413100147260ustar 00000000000000extern crate rspec; use std::collections::BTreeSet; pub fn main() { #[derive(Clone, Debug)] struct Environment { set: BTreeSet, len_before: usize, } let environment = Environment { set: BTreeSet::new(), len_before: 0, }; rspec::run(&rspec::given("a BTreeSet", environment, |ctx| { ctx.when("not having added any items", |ctx| { ctx.then("it is empty", |env| assert!(env.set.is_empty())); }); ctx.when("adding an new item", |ctx| { ctx.before_all(|env| { env.len_before = env.set.len(); env.set.insert(42); }); ctx.then("it is not empty any more", |env| { assert!(!env.set.is_empty()); }); ctx.then("its len increases by 1", |env| { assert_eq!(env.set.len(), env.len_before + 1); }); ctx.when("adding it again", |ctx| { ctx.before_all(|env| { env.len_before = env.set.len(); env.set.insert(42); }); ctx.then("its len remains the same", |env| { assert_eq!(env.set.len(), env.len_before); }); }); }); ctx.when("returning to outer context", |ctx| { ctx.then("it is still empty", |env| assert!(env.set.is_empty())); }); ctx.then("panic!(…) fails", |_env| { panic!("Some reason for failure.") }); })); } rspec-1.0.0/examples/multi.rs010064400007650000024000000024641400751413100143230ustar 00000000000000extern crate rspec; use std::io; use std::sync::Arc; // An example of a single runner running multiple semantically equivalent, // yet syntactically different test suites in succession: pub fn main() { let logger = Arc::new(rspec::Logger::new(io::stdout())); let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); let runner = rspec::Runner::new(configuration, vec![logger]); // A test suite using the `suite`, `context`, `example` syntax family: runner.run(&rspec::suite("an value of ten", 10, |ctx| { ctx.context("adding 5 to it", |ctx| { ctx.example("results in fifteen", |num| { assert_eq!(*num, 15); }); }); })); // A test suite using the `describe`, `specify`, `it` syntax family: runner.run(&rspec::describe("an value of ten", 10, |ctx| { ctx.specify("adding 5 to it", |ctx| { ctx.it("results in fifteen", |num| { assert_eq!(*num, 15); }); }); })); // A test suite using the `given`, `when`, `then` syntax family: runner.run(&rspec::given("an value of ten", 10, |ctx| { ctx.when("adding 5 to it", |ctx| { ctx.then("results in fifteen", |num| { assert_eq!(*num, 15); }); }); })); } rspec-1.0.0/examples/simple.rs010064400007650000024000000031371400751413100144600ustar 00000000000000extern crate rspec; pub fn main() { // The easiest way to open a suite is by calling the `rspec::run(…)` function, // passing it the result of one of these functions: // // - `rspec::suite`, // - `rspec::describe` // - `rspec::given` // // which all behave the same and only differ in the label // that is printed the the est suite's log. // // One then passes the following arguments to aforementioned function: // // - a name (to add some more meaning to the runner's output) // - an initial value (to base the tests on) // - a closure (to provide the suite's test logic) rspec::run(&rspec::given("a value of zero", 0, |ctx| { ctx.then("it is zero", |value| { assert_eq!(*value, 0); }); ctx.when("multiplying by itself", |ctx| { // Any time one wants to mutate the value being tested // one does so by calling `ctx.before(…)` (or `ctx.after(…)`), // which will execute the provided closure before any other // sub-context (e.g. `ctx.when(…)`) or example (e.g. `ctx.then(…)`) // is executed: ctx.before(|value| { *value *= *value; }); ctx.then("it remains zero", |value| { assert_eq!(*value, 0); }); }); ctx.when("adding a value to it", |ctx| { ctx.before(|value| { *value += 42; }); ctx.then("it becomes said value", |value| { assert_eq!(*value, 42); }); }); })); } rspec-1.0.0/src/block/context.rs010064400007650000024000000454071400752777600147470ustar 00000000000000//! The Context module holds all the functionalities for the test declaration, that is: //! `before`, `after`, `suite`, `context`, `it` and their variants. //! //! A Context can also holds reference to children Contextes, for which the `before` //! [closures](https://rustbyexample.com/fn/closures.html) will be executed after the `before` //! [closures](https://rustbyexample.com/fn/closures.html) of the current context. //! //! Running these tests and doing asserts is not the job of the Context, but the Runner. //! use block::{Block, Example}; use header::{ContextHeader, ContextLabel, ExampleHeader, ExampleLabel}; use report::ExampleResult; /// Test contexts are a convenient tool for adding structure and code sharing to a test suite. pub struct Context { pub(crate) header: Option, pub(crate) blocks: Vec>, pub(crate) before_all: Vec>, pub(crate) before_each: Vec>, pub(crate) after_all: Vec>, pub(crate) after_each: Vec>, } impl Context { pub(crate) fn new(header: Option) -> Self { Context { header, blocks: vec![], before_all: vec![], before_each: vec![], after_all: vec![], after_each: vec![], } } pub fn num_blocks(&self) -> usize { self.blocks.len() } pub fn num_examples(&self) -> usize { self.blocks.iter().map(|b| b.num_examples()).sum() } pub fn is_empty(&self) -> bool { self.blocks.is_empty() } } // Both `Send` and `Sync` are necessary for parallel threaded execution. unsafe impl Send for Context where T: Send {} // Both `Send` and `Sync` are necessary for parallel threaded execution. unsafe impl Sync for Context where T: Sync {} impl Context where T: Clone, { /// Open and name a new context within the current context. /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.context("opens a context labeled 'context'", |_ctx| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Context "opens a sub-context": /// … /// ``` /// /// Available aliases: /// /// - [`specify`](struct.Context.html#method.specify). /// - [`when`](struct.Context.html#method.when). pub fn context(&mut self, name: &'static str, body: F) where F: FnOnce(&mut Context), T: ::std::fmt::Debug, { let header = ContextHeader { label: ContextLabel::Context, name, }; self.context_internal(Some(header), body) } /// Alias for [`context`](struct.Context.html#method.context), see for more info. /// /// Available further aliases: /// /// - [`when`](struct.Context.html#method.when). pub fn specify(&mut self, name: &'static str, body: F) where F: FnOnce(&mut Context), T: ::std::fmt::Debug, { let header = ContextHeader { label: ContextLabel::Specify, name, }; self.context_internal(Some(header), body) } /// Alias for [`context`](struct.Context.html#method.context), see for more info. /// /// Available further aliases: /// /// - [`specify`](struct.Context.html#method.specify). pub fn when(&mut self, name: &'static str, body: F) where F: FnOnce(&mut Context), T: ::std::fmt::Debug, { let header = ContextHeader { label: ContextLabel::When, name, }; self.context_internal(Some(header), body) } /// Open a new name-less context within the current context which won't show up in the logs. /// /// This can be useful for adding additional structure (and `before`/`after` blocks) to your /// tests without their labels showing up as noise in the console output. /// As such one might want to selectively assign two contexts/examples an additional `before` /// block without them getting visually separated from their neighboring contexts/examples. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a suite",(), |ctx| { /// ctx.context("a context", |ctx| { /// ctx.scope(|ctx| { /// ctx.example("an example", |_env| { /// // … /// }); /// }); /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a suite": /// Context "a context": /// Example "an example" /// ``` /// /// The `before_each(…)` block gets executed before `'It "tests a"'` and `'It "tests a"'`, /// but not before `'It "tests c"'`. pub fn scope(&mut self, body: F) where F: FnOnce(&mut Context), T: ::std::fmt::Debug, { self.context_internal(None, body) } fn context_internal(&mut self, header: Option, body: F) where F: FnOnce(&mut Context), T: ::std::fmt::Debug, { let mut child = Context::new(header); body(&mut child); self.blocks.push(Block::Context(child)) } /// Open and name a new example within the current context. /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.example("an example", |_env| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Example "an example": /// … /// ``` /// /// Available aliases: /// /// - [`it`](struct.Context.html#method.it). /// - [`then`](struct.Context.html#method.then). pub fn example(&mut self, name: &'static str, body: F) where F: 'static + Fn(&T) -> U, U: Into, { let header = ExampleHeader::new(ExampleLabel::Example, name); self.example_internal(header, body) } /// Alias for [`example`](struct.Context.html#method.example), see for more info. /// /// Available further aliases: /// /// - [`it`](struct.Context.html#method.it). pub fn it(&mut self, name: &'static str, body: F) where F: 'static + Fn(&T) -> U, U: Into, { let header = ExampleHeader::new(ExampleLabel::It, name); self.example_internal(header, body) } /// Alias for [`example`](struct.Context.html#method.example), see for more info. /// /// Available further aliases: /// /// - [`it`](struct.Context.html#method.it). pub fn then(&mut self, name: &'static str, body: F) where F: 'static + Fn(&T) -> U, U: Into, { let header = ExampleHeader::new(ExampleLabel::Then, name); self.example_internal(header, body) } fn example_internal(&mut self, header: ExampleHeader, body: F) where F: 'static + Fn(&T) -> U, U: Into, { use std::panic::{catch_unwind, AssertUnwindSafe}; let example = Example::new(header, move |environment| { let result = catch_unwind(AssertUnwindSafe(|| body(&environment).into())); match result { Ok(result) => result, Err(error) => { use std::borrow::Cow; let error_as_str = error.downcast_ref::<&str>().map(|s| Cow::from(*s)); let error_as_string = error.downcast_ref::().map(|s| Cow::from(s.clone())); let message = error_as_str .or(error_as_string) .map(|cow| format!("thread panicked at '{:?}'.", cow.to_string())); ExampleResult::Failure(message) } } }); self.blocks.push(Block::Example(example)) } /// Declares a closure that will be executed once before any /// of the context's children (context or example blocks) are being executed. /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.before_all(|_env| { /// // … /// }); /// /// ctx.example("an example", |_env| { /// // … /// }); /// /// ctx.example("another example", |_env| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Example "an example": /// … /// Example "another example": /// … /// ``` /// /// Available aliases: /// /// - [`before`](struct.Context.html#method.before). pub fn before_all(&mut self, body: F) where F: 'static + Fn(&mut T), { self.before_all.push(Box::new(body)) } /// Alias for [`before_all`](struct.Context.html#method.before_all), see for more info. pub fn before(&mut self, body: F) where F: 'static + Fn(&mut T), { self.before_all(body) } /// Declares a closure that will be executed once before each /// of the context's children (context or example blocks). /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.before_each(|_env| { /// // … /// }); /// /// ctx.example("an example", |_env| { /// // … /// }); /// /// ctx.example("another example", |_env| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Example "an example": /// … /// Example "another example": /// … /// ``` pub fn before_each(&mut self, body: F) where F: 'static + Fn(&mut T), { self.before_each.push(Box::new(body)) } /// Declares a closure that will be executed once after any /// of the context's children (context or example blocks) have been executed. /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.after_all(|_env| { /// // … /// }); /// /// ctx.example("an example", |_env| { /// // … /// }); /// /// ctx.example("another example", |_env| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Example "an example": /// … /// Example "another example": /// … /// ``` /// /// Available aliases: /// /// - [`after`](struct.Context.html#method.after). pub fn after_all(&mut self, body: F) where F: 'static + Fn(&mut T), { self.after_all.push(Box::new(body)) } /// Alias for [`after_all`](struct.Context.html#method.after_all), see for more info. pub fn after(&mut self, body: F) where F: 'static + Fn(&mut T), { self.after_all(body) } /// Declares a closure that will be executed once after each /// of the context's children (context or example blocks). /// /// Note that the order of execution **IS NOT** guaranteed to match the declaration order. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |ctx| { /// ctx.after_each(|_env| { /// // … /// }); /// /// ctx.example("an example", |_env| { /// // … /// }); /// /// ctx.example("another example", |_env| { /// // … /// }); /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests: /// Suite "a test suite": /// Example "an example": /// … /// Example "another example": /// … /// ``` pub fn after_each(&mut self, body: F) where F: 'static + Fn(&mut T), { self.after_each.push(Box::new(body)) } } #[cfg(test)] impl Default for Context { /// Used for testing fn default() -> Self { Context::new(None) } } #[cfg(test)] mod tests { use block::{describe, given, suite}; macro_rules! test_suite_alias { ($suite: ident) => { $suite("suite (or alias)", (), |_| {}); }; } #[test] fn it_has_root_functions() { test_suite_alias!(suite); test_suite_alias!(describe); test_suite_alias!(given); } macro_rules! test_context_alias { ($suite: ident, $context: ident) => { $suite("suite (or alias)", (), |ctx| { ctx.$context("context (or alias)", |_| {}) }); }; } #[test] fn it_has_contextual_function_context() { test_context_alias!(suite, context); test_context_alias!(describe, context); test_context_alias!(given, context); } #[test] fn it_has_contexual_function_specify() { test_context_alias!(suite, specify); test_context_alias!(describe, specify); test_context_alias!(given, specify); } #[test] fn it_has_contexual_function_when() { test_context_alias!(suite, when); test_context_alias!(describe, when); test_context_alias!(given, when); } macro_rules! test_example_alias { ($suite: ident, $context: ident, $example: ident) => { $suite("suite (or alias)", (), |ctx| { ctx.$context("context (or alias)", |ctx| { ctx.$example("example (or alias)", |_| {}); }); }); }; } #[test] fn it_has_check_function_example() { test_example_alias!(suite, context, example); test_example_alias!(suite, specify, example); test_example_alias!(suite, when, example); test_example_alias!(describe, context, example); test_example_alias!(describe, specify, example); test_example_alias!(describe, when, example); test_example_alias!(given, context, example); test_example_alias!(given, specify, example); test_example_alias!(given, when, example); } #[test] fn it_has_check_function_it() { test_example_alias!(suite, context, it); test_example_alias!(suite, specify, it); test_example_alias!(suite, when, it); test_example_alias!(describe, context, it); test_example_alias!(describe, specify, it); test_example_alias!(describe, when, it); test_example_alias!(given, context, it); test_example_alias!(given, specify, it); test_example_alias!(given, when, it); } #[test] fn it_has_check_function_then() { test_example_alias!(suite, context, then); test_example_alias!(suite, specify, then); test_example_alias!(suite, when, then); test_example_alias!(describe, context, then); test_example_alias!(describe, specify, then); test_example_alias!(describe, when, then); test_example_alias!(given, context, then); test_example_alias!(given, specify, then); test_example_alias!(given, when, then); } } rspec-1.0.0/src/block/example.rs010064400007650000024000000021771400752777600147130ustar 00000000000000use header::ExampleHeader; use report::ExampleResult; /// Test examples are the smallest unit of a testing framework, wrapping one or more assertions. pub struct Example { pub(crate) header: ExampleHeader, pub(crate) function: Box ExampleResult>, } impl Example { pub(crate) fn new(header: ExampleHeader, assertion: F) -> Self where F: 'static + Fn(&T) -> ExampleResult, { Example { header, function: Box::new(assertion), } } /// Used for testing purpose #[cfg(test)] pub fn fixture_success() -> Self { Example::new(ExampleHeader::default(), |_| ExampleResult::Success) } /// Used for testing purpose #[cfg(test)] pub fn fixture_ignored() -> Self { Example::new(ExampleHeader::default(), |_| ExampleResult::Ignored) } /// Used for testing purpose #[cfg(test)] pub fn fixture_failed() -> Self { Example::new(ExampleHeader::default(), |_| ExampleResult::Failure(None)) } } unsafe impl Send for Example where T: Send {} unsafe impl Sync for Example where T: Sync {} rspec-1.0.0/src/block/mod.rs010064400007650000024000000012241400752777600140270ustar 00000000000000//! Blocks are used to build a tree structure of named tests and contextes. pub mod context; pub mod example; pub mod suite; pub use block::context::*; pub use block::example::*; pub use block::suite::*; /// Blocks are used to build a tree structure of named tests and contextes. pub enum Block { Context(Context), Example(Example), } impl Block { pub fn num_examples(&self) -> usize { match self { Block::Context(ref context) => context.num_examples(), Block::Example(_) => 1, } } } unsafe impl Send for Block where T: Send {} unsafe impl Sync for Block where T: Sync {} rspec-1.0.0/src/block/suite.rs010064400007650000024000000101741400752777600144050ustar 00000000000000use block::Context; use header::{SuiteHeader, SuiteLabel}; /// Test suites bundle a set of closely related test examples into a logical execution group. #[derive(new)] pub struct Suite { pub(crate) header: SuiteHeader, pub(crate) environment: T, pub(crate) context: Context, } impl Suite { pub fn num_blocks(&self) -> usize { self.context.num_blocks() } pub fn num_examples(&self) -> usize { self.context.num_examples() } pub fn is_empty(&self) -> bool { self.context.is_empty() } } unsafe impl Send for Suite where T: Send {} unsafe impl Sync for Suite where T: Sync {} /// Creates a test suite from a given root context. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # use std::io; /// # use std::sync::Arc; /// # /// # pub fn main() { /// # let logger = Arc::new(rspec::Logger::new(io::stdout())); /// # let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); /// # let runner = rspec::Runner::new(configuration, vec![logger]); /// # /// runner.run(&rspec::suite("a test suite", (), |_ctx| { /// // … /// })); /// # } /// ``` /// /// Corresponding console output: /// /// ```text /// tests /// Suite "a test suite": /// … /// ``` /// /// Available aliases: /// /// - [`describe`](fn.describe.html). /// - [`given`](fn.given.html). pub fn suite(name: &'static str, environment: T, body: F) -> Suite where F: FnOnce(&mut Context), T: Clone + ::std::fmt::Debug, { let header = SuiteHeader { label: SuiteLabel::Suite, name, }; suite_internal(header, environment, body) } /// Alias for [`suite`](fn.suite.html), see for more info. /// /// Available further aliases: /// /// - [`given`](fn.describe.html). pub fn describe(name: &'static str, environment: T, body: F) -> Suite where F: FnOnce(&mut Context), T: Clone + ::std::fmt::Debug, { let header = SuiteHeader { label: SuiteLabel::Describe, name, }; suite_internal(header, environment, body) } /// Alias for [`suite`](fn.suite.html), see for more info. /// /// Available further aliases: /// /// - [`describe`](fn.describe.html). pub fn given(name: &'static str, environment: T, body: F) -> Suite where F: FnOnce(&mut Context), T: Clone + ::std::fmt::Debug, { let header = SuiteHeader { label: SuiteLabel::Given, name, }; suite_internal(header, environment, body) } fn suite_internal(header: SuiteHeader, environment: T, body: F) -> Suite where F: FnOnce(&mut Context), T: Clone + ::std::fmt::Debug, { let mut ctx = Context::new(None); body(&mut ctx); Suite::new(header, environment, ctx) } #[cfg(test)] mod tests { use super::*; #[test] fn empty_suite() { let suite = suite("name", (), |_| {}); assert_eq!(suite.header.label, SuiteLabel::Suite); assert_eq!(suite.header.name, "name"); assert_eq!(suite.environment, ()); assert_eq!(suite.is_empty(), true); assert_eq!(suite.num_examples(), 0); } #[test] fn empty_describe() { let describe = describe("name", (), |_| {}); assert_eq!(describe.header.label, SuiteLabel::Describe); assert_eq!(describe.header.name, "name"); assert_eq!(describe.environment, ()); assert_eq!(describe.is_empty(), true); assert_eq!(describe.num_examples(), 0); } #[test] fn empty_given() { let given = given("name", (), |_| {}); assert_eq!(given.header.label, SuiteLabel::Given); assert_eq!(given.header.name, "name"); assert_eq!(given.environment, ()); assert_eq!(given.is_empty(), true); assert_eq!(given.num_examples(), 0); } #[test] fn non_empty_suite() { let suite = suite("suite", (), |ctx| { ctx.context("context", |_| {}); }); assert_eq!(suite.header.label, SuiteLabel::Suite); assert_eq!(suite.header.name, "suite"); assert_eq!(suite.environment, ()); assert_eq!(suite.is_empty(), false); assert_eq!(suite.num_examples(), 0); } } rspec-1.0.0/src/header/context.rs010064400007650000024000000034171400752777600151000ustar 00000000000000use std::fmt; /// How the [`Context`](../block/struct.Context.html) will be printed by the [`Logger`](../logger/index.html). #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum ContextLabel { Context, Specify, When, } impl fmt::Display for ContextLabel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ContextLabel::Context => write!(f, "Context"), ContextLabel::Specify => write!(f, "Specify"), ContextLabel::When => write!(f, "When"), } } } /// A [`Header`](trait.Header.html) with label and name of a [`Context`](../block/struct.Context.html). #[derive(Clone, PartialEq, Eq, Debug, new)] pub struct ContextHeader { pub label: ContextLabel, pub name: &'static str, } impl fmt::Display for ContextHeader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {:?}", self.label, self.name) } } #[cfg(test)] mod tests { use super::*; #[test] fn label_fmt() { fn subject(label: ContextLabel) -> String { format!("{}", label) }; assert_eq!(subject(ContextLabel::Context), "Context".to_owned()); assert_eq!(subject(ContextLabel::Specify), "Specify".to_owned()); assert_eq!(subject(ContextLabel::When), "When".to_owned()); } #[test] fn header_fmt() { fn subject(label: ContextLabel) -> String { format!("{}", ContextHeader::new(label, "Test")) }; assert_eq!( subject(ContextLabel::Context), "Context \"Test\"".to_owned() ); assert_eq!( subject(ContextLabel::Specify), "Specify \"Test\"".to_owned() ); assert_eq!(subject(ContextLabel::When), "When \"Test\"".to_owned()); } } rspec-1.0.0/src/header/example.rs010064400007650000024000000035561400752777600150530ustar 00000000000000use std::fmt; /// How the [`Example`](../block/struct.Example.html) will be printed by the [`Logger`](../logger/index.html). #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum ExampleLabel { It, Example, Then, } impl fmt::Display for ExampleLabel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ExampleLabel::It => write!(f, "It"), ExampleLabel::Example => write!(f, "Example"), ExampleLabel::Then => write!(f, "Then"), } } } /// A [`Header`](trait.Header.html) with label and name of an [`Example`](../block/struct.Example.html). #[derive(Clone, PartialEq, Eq, Debug, new)] pub struct ExampleHeader { pub label: ExampleLabel, pub name: &'static str, } #[cfg(test)] impl Default for ExampleHeader { /// Used for testing fn default() -> Self { ExampleHeader::new(ExampleLabel::It, "example") } } impl fmt::Display for ExampleHeader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {:?}", self.label, self.name) } } #[cfg(test)] mod tests { use super::*; #[test] fn label_fmt() { fn subject(label: ExampleLabel) -> String { format!("{}", label) }; assert_eq!(subject(ExampleLabel::Example), "Example".to_owned()); assert_eq!(subject(ExampleLabel::It), "It".to_owned()); assert_eq!(subject(ExampleLabel::Then), "Then".to_owned()); } #[test] fn header_fmt() { fn subject(label: ExampleLabel) -> String { format!("{}", ExampleHeader::new(label, "Test")) }; assert_eq!( subject(ExampleLabel::Example), "Example \"Test\"".to_owned() ); assert_eq!(subject(ExampleLabel::It), "It \"Test\"".to_owned()); assert_eq!(subject(ExampleLabel::Then), "Then \"Test\"".to_owned()); } } rspec-1.0.0/src/header/mod.rs010064400007650000024000000003061400752777600141650ustar 00000000000000//! Headers store the label and name of a Suite/Context/Example. pub mod context; pub mod example; pub mod suite; pub use header::context::*; pub use header::example::*; pub use header::suite::*; rspec-1.0.0/src/header/suite.rs010064400007650000024000000033031400752777600145370ustar 00000000000000use std::fmt; /// How the [`Suite`](../block/struct.Suite.html) will be printed by the [`Logger`](../logger/index.html). #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SuiteLabel { Suite, Describe, Given, } impl fmt::Display for SuiteLabel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { SuiteLabel::Suite => write!(f, "Suite"), SuiteLabel::Describe => write!(f, "Describe"), SuiteLabel::Given => write!(f, "Given"), } } } /// A [`Header`](trait.Header.html) with label and name of a [`Suite`](../block/struct.Suite.html). #[derive(Clone, PartialEq, Eq, Debug, new)] pub struct SuiteHeader { pub label: SuiteLabel, pub name: &'static str, } impl fmt::Display for SuiteHeader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {:?}", self.label, self.name) } } #[cfg(test)] mod tests { use super::*; #[test] fn label_fmt() { fn subject(label: SuiteLabel) -> String { format!("{}", label) }; assert_eq!(subject(SuiteLabel::Suite), "Suite".to_owned()); assert_eq!(subject(SuiteLabel::Describe), "Describe".to_owned()); assert_eq!(subject(SuiteLabel::Given), "Given".to_owned()); } #[test] fn header_fmt() { fn subject(label: SuiteLabel) -> String { format!("{}", SuiteHeader::new(label, "Test")) }; assert_eq!(subject(SuiteLabel::Suite), "Suite \"Test\"".to_owned()); assert_eq!( subject(SuiteLabel::Describe), "Describe \"Test\"".to_owned() ); assert_eq!(subject(SuiteLabel::Given), "Given \"Test\"".to_owned()); } } rspec-1.0.0/src/lib.rs010064400007650000024000000046111400752777600127270ustar 00000000000000#![doc(html_root_url = "https://mackwic.github.io/rspec")] #![cfg_attr(feature = "clippy", feature(plugin))] #![cfg_attr(feature = "clippy", plugin(clippy))] #![allow(dead_code)] #[macro_use] extern crate derive_builder; #[macro_use] extern crate derive_new; extern crate colored; #[cfg(feature = "expectest_compat")] extern crate expectest; extern crate rayon; extern crate time; pub mod block; pub mod header; pub mod logger; pub mod report; pub mod runner; mod visitor; pub use block::{describe, given, suite}; pub use logger::Logger; pub use runner::{Configuration, ConfigurationBuilder, Runner}; use block::Suite; /// A wrapper for conveniently running a test suite with /// the default configuration with considerebly less glue-code. /// /// # Examples /// /// ``` /// # extern crate rspec; /// # /// # pub fn main() { /// rspec::run(&rspec::given("a scenario", (), |ctx| { /// ctx.when("...", |ctx| { /// // ... /// }); /// /// ctx.then("...", |env| { /* ... */ }); /// })); /// # } /// ``` pub fn run(suite: &Suite) where T: Clone + Send + Sync + ::std::fmt::Debug, { use std::io; use std::sync::Arc; let logger = Arc::new(Logger::new(io::stdout())); let configuration = ConfigurationBuilder::default().build().unwrap(); let runner = Runner::new(configuration, vec![logger]); runner.run(suite); } #[cfg(test)] mod tests { pub use super::*; pub use block::*; // Test list: // x check that tests can call `assert_eq!` // x check that tests can return Err or Ok // x runner can count the tests // x runner can count the success and failed // x check that we can use before in a describe // x check that we can use after in a describe // x check that after/before are run in all child contextes // x runner broadcasts run events // x progress logger is an event handler // x pluggable loggers via logger trait // - stats time events is an event handler // - detect slow tests via treshold // x time the total running time // - failure-only via a tmp file // - filter tests // - coloration // - seed for deterministic randomization // - fail-fast fail at the first failed test // x beforeAll // x afterAll // x beforeEach // x afterEach // - use Any to return anything that can be Ok-ed or () or None or panic-ed // - bench ? --> see what's the protocol // } rspec-1.0.0/src/logger/mod.rs010064400007650000024000000104631400752777600142210ustar 00000000000000//! Loggers can be attached to the Runner to log the progression and results of a test suite. //! //! # Examples //! //! ``` //! # extern crate rspec; //! # //! # use std::io; //! # use std::sync::Arc; //! # //! # pub fn main() { //! let logger = Arc::new(rspec::Logger::new(io::stdout())); //! let configuration = rspec::ConfigurationBuilder::default().build().unwrap(); //! let runner = rspec::Runner::new(configuration, vec![logger]); //! # } //! ``` mod serial; use std::io; use header::{ContextHeader, ExampleHeader, SuiteHeader}; use logger::serial::SerialLogger; use report::{BlockReport, ContextReport, ExampleReport, SuiteReport}; use runner::{Runner, RunnerObserver}; /// Preferred logger for test suite execution. pub struct Logger { serial: SerialLogger, } impl Logger where T: Send + Sync, { pub fn new(buffer: T) -> Logger { Logger { serial: SerialLogger::new(buffer), } } fn replay_suite(&self, runner: &Runner, suite: &SuiteHeader, report: &SuiteReport) { self.serial.enter_suite(runner, suite); self.replay_context(runner, None, report.get_context()); self.serial.exit_suite(runner, suite, report); } fn replay_block(&self, runner: &Runner, report: &BlockReport) { match report { BlockReport::Context(ref header, ref report) => { self.replay_context(runner, header.as_ref(), report); } BlockReport::Example(ref header, ref report) => { self.replay_example(runner, header, report); } } } fn replay_context( &self, runner: &Runner, context: Option<&ContextHeader>, report: &ContextReport, ) { if let Some(header) = context { self.serial.enter_context(runner, header); } for report in report.get_blocks() { self.replay_block(runner, report); } if let Some(header) = context { self.serial.exit_context(runner, header, report); } } fn replay_example(&self, runner: &Runner, example: &ExampleHeader, report: &ExampleReport) { self.serial.enter_example(runner, example); self.serial.exit_example(runner, example, report); } } impl RunnerObserver for Logger where T: Send + Sync, { fn enter_suite(&self, runner: &Runner, header: &SuiteHeader) { if runner.configuration.parallel { // If the suite is being evaluated in parallel we basically wait for `exit_suite`. } else { self.serial.enter_suite(runner, header); } } fn exit_suite(&self, runner: &Runner, header: &SuiteHeader, report: &SuiteReport) { if runner.configuration.parallel { // If the suite is being evaluated in parallel and we have reached the end of it, // then it is time to forward a replay of the events to the inner serial logger: self.replay_suite(runner, header, report); } else { self.serial.exit_suite(runner, header, report); } } fn enter_context(&self, runner: &Runner, header: &ContextHeader) { if runner.configuration.parallel { // If the suite is being evaluated in parallel we basically wait for `exit_suite`. } else { self.serial.enter_context(runner, header); } } fn exit_context(&self, runner: &Runner, header: &ContextHeader, report: &ContextReport) { if runner.configuration.parallel { // If the suite is being evaluated in parallel we basically wait for `exit_suite`. } else { self.serial.exit_context(runner, header, report); } } fn enter_example(&self, runner: &Runner, header: &ExampleHeader) { if runner.configuration.parallel { // If the suite is being evaluated in parallel we basically wait for `exit_suite`. } else { self.serial.enter_example(runner, header); } } fn exit_example(&self, runner: &Runner, header: &ExampleHeader, report: &ExampleReport) { if runner.configuration.parallel { // If the suite is being evaluated in parallel we basically wait for `exit_suite`. } else { self.serial.exit_example(runner, header, report); } } } rspec-1.0.0/src/logger/serial.rs010064400007650000024000000174041400752777600147230ustar 00000000000000use std::io; use std::ops::DerefMut; use std::sync::Mutex; use time::Duration; use colored::*; use header::{ContextHeader, ExampleHeader, SuiteHeader}; use report::{BlockReport, ContextReport, ExampleReport, ExampleResult, Report, SuiteReport}; use runner::{Runner, RunnerObserver}; #[derive(new)] struct SerialLoggerState { buffer: T, #[new(value = "0")] level: usize, } /// Preferred logger for serial test suite execution /// (see [`Configuration.parallel`](struct.Configuration.html#fields)). pub struct SerialLogger { state: Mutex>, } impl Default for SerialLogger { fn default() -> Self { SerialLogger::new(io::stdout()) } } impl SerialLogger { pub fn new(buffer: T) -> Self { let state = SerialLoggerState::new(buffer); SerialLogger { state: Mutex::new(state), } } fn padding(depth: usize) -> String { " ".repeat(depth) } fn access_state(&self, mut accessor: F) where F: FnMut(&mut SerialLoggerState) -> io::Result<()>, { if let Ok(ref mut mutex_guard) = self.state.lock() { let result = accessor(mutex_guard.deref_mut()); if let Err(error) = result { // TODO: better error handling eprintln!("\n{}: {:?}", "error".red().bold(), error); } } else { // TODO: better error handling eprintln!( "\n{}: failed to aquire lock on mutex.", "error".red().bold() ); } } fn write_suite_failures( &self, buffer: &mut T, indent: usize, report: &SuiteReport, ) -> io::Result<()> { if report.is_failure() { let _ = writeln!(buffer, "\nfailures:\n"); writeln!(buffer, "{}{}", Self::padding(indent), report.get_header())?; let context_report = report.get_context(); for block_report in context_report.get_blocks() { self.write_block_failures(buffer, indent + 1, block_report)?; } } Ok(()) } fn write_block_failures( &self, buffer: &mut T, indent: usize, report: &BlockReport, ) -> io::Result<()> { if report.is_failure() { match report { BlockReport::Context(ref header, ref report) => { if let Some(header) = header.as_ref() { write!(buffer, "{}{}", Self::padding(indent), header)?; } self.write_context_failures(buffer, indent + 1, report)?; } BlockReport::Example(ref header, ref report) => { writeln!(buffer, "{}{}", Self::padding(indent), header)?; self.write_example_failure(buffer, indent + 1, report)?; } } } Ok(()) } fn write_context_failures( &self, buffer: &mut T, indent: usize, report: &ContextReport, ) -> io::Result<()> { if report.is_failure() { writeln!(buffer)?; for block_report in report.get_blocks() { self.write_block_failures(buffer, indent + 1, block_report)?; } } Ok(()) } fn write_example_failure( &self, buffer: &mut T, indent: usize, report: &ExampleReport, ) -> io::Result<()> { if let ExampleResult::Failure(Some(ref reason)) = report.get_result() { let padding = Self::padding(indent); writeln!(buffer, "{}{}", padding, reason)?; } Ok(()) } fn write_suite_prefix(&self, buffer: &mut T) -> io::Result<()> { writeln!(buffer, "\ntests:\n")?; Ok(()) } fn write_suite_suffix(&self, buffer: &mut T, report: &SuiteReport) -> io::Result<()> { self.write_duration(buffer, report.get_duration())?; write!(buffer, "\ntest result: {}.", self.report_flag(report))?; writeln!( buffer, " {} passed; {} failed; {} ignored", report.get_passed(), report.get_failed(), report.get_ignored() )?; if report.is_failure() { writeln!(buffer, "\n{}: test failed", "error".red().bold())?; } Ok(()) } fn write_duration(&self, buffer: &mut T, duration: Duration) -> io::Result<()> { let millisecond = 1; let second = 1000 * millisecond; let minute = 60 * second; let hour = 60 * minute; let remainder = duration.whole_milliseconds(); let hours = remainder / hour; let remainder = remainder % hour; let minutes = remainder / minute; let remainder = remainder % minute; let seconds = remainder / second; let remainder = remainder % second; let milliseconds = remainder / millisecond; match (hours, minutes, seconds, milliseconds) { (0, 0, s, ms) => writeln!(buffer, "\nduration: {}.{:03}s.", s, ms), (0, m, s, ms) => writeln!(buffer, "\nduration: {}m {}.{:03}s.", m, s, ms), (h, m, s, ms) => writeln!(buffer, "\nduration: {}h {}m {}.{:03}s.", h, m, s, ms), } } fn report_flag(&self, report: &R) -> ColoredString where R: Report, { if report.is_success() { "ok".green() } else { "FAILED".red() } } } impl RunnerObserver for SerialLogger where T: Send + Sync, { fn enter_suite(&self, _runner: &Runner, header: &SuiteHeader) { self.access_state(|state| { state.level += 1; self.write_suite_prefix(&mut state.buffer)?; writeln!(state.buffer, "{}{}", Self::padding(state.level - 1), header)?; Ok(()) }); } fn exit_suite(&self, _runner: &Runner, _header: &SuiteHeader, report: &SuiteReport) { self.access_state(|state| { self.write_suite_failures(&mut state.buffer, 0, report)?; self.write_suite_suffix(&mut state.buffer, report)?; state.level -= 1; Ok(()) }); } fn enter_context(&self, _runner: &Runner, header: &ContextHeader) { self.access_state(|state| { state.level += 1; writeln!(state.buffer, "{}{}", Self::padding(state.level - 1), header)?; Ok(()) }); } fn exit_context(&self, _runner: &Runner, _header: &ContextHeader, _report: &ContextReport) { self.access_state(|state| { state.level -= 1; Ok(()) }); } fn enter_example(&self, _runner: &Runner, header: &ExampleHeader) { self.access_state(|state| { state.level += 1; write!( state.buffer, "{}{} ... ", Self::padding(state.level - 1), header )?; Ok(()) }); } fn exit_example(&self, _runner: &Runner, _header: &ExampleHeader, report: &ExampleReport) { self.access_state(|state| { writeln!(state.buffer, "{}", self.report_flag(report))?; state.level -= 1; Ok(()) }); } } #[cfg(test)] mod tests { use super::*; mod padding { use super::*; #[test] fn it_padds() { // arrange let expected = vec![("", 0), (" ", 1), (" ", 2), (" ", 3)]; for (expected_res, given_depth) in expected { // act let res = SerialLogger::>::padding(given_depth); // assert assert_eq!(String::from(expected_res), res) } } } } rspec-1.0.0/src/report/context.rs010064400007650000024000000024221400752777600151560ustar 00000000000000use report::{BlockReport, Report}; use time::Duration; /// `ContextReport` holds the results of a context's test execution. #[derive(PartialEq, Eq, Clone, Debug, new)] pub struct ContextReport { sub_reports: Vec, duration: Duration, } impl ContextReport { pub fn get_blocks(&self) -> &[BlockReport] { &self.sub_reports[..] } } impl Report for ContextReport { fn is_success(&self) -> bool { self.sub_reports .iter() .fold(true, |success, report| success & report.is_success()) } fn is_failure(&self) -> bool { self.sub_reports .iter() .fold(false, |failure, report| failure | report.is_failure()) } fn get_passed(&self) -> u32 { self.sub_reports .iter() .fold(0, |count, report| count + report.get_passed()) } fn get_failed(&self) -> u32 { self.sub_reports .iter() .fold(0, |count, report| count + report.get_failed()) } fn get_ignored(&self) -> u32 { self.sub_reports .iter() .fold(0, |count, report| count + report.get_ignored()) } fn get_duration(&self) -> Duration { self.duration } } #[cfg(test)] mod tests { // use super::*; } rspec-1.0.0/src/report/example.rs010064400007650000024000000100761400752777600151310ustar 00000000000000use std::convert::From; use time::Duration; use report::Report; #[cfg(feature = "expectest_compat")] use expectest::core::TestResult as ExpectestResult; #[derive(Clone, PartialEq, Eq, Debug)] pub enum ExampleResult { Success, Failure(Option), Ignored, } impl ExampleResult { fn is_success(&self) -> bool { &ExampleResult::Success == self } fn is_failure(&self) -> bool { matches!(self, &ExampleResult::Failure(_)) } fn get_passed(&self) -> u32 { if &ExampleResult::Success == self { 1 } else { 0 } } fn get_failed(&self) -> u32 { if let ExampleResult::Failure(_) = self { 1 } else { 0 } } fn get_ignored(&self) -> u32 { if &ExampleResult::Ignored == self { 1 } else { 0 } } } /// rspec considers examples returning `()` a success. impl From<()> for ExampleResult { fn from(_other: ()) -> ExampleResult { ExampleResult::Success } } /// rspec considers examples returning `true` a success, `false` a failure. impl From for ExampleResult { fn from(other: bool) -> ExampleResult { if other { ExampleResult::Success } else { ExampleResult::Failure(Some( "assertion failed: `expected condition to be true`".to_owned(), )) } } } /// rspec considers examples returning `Result::Ok(…)` a success, `Result::Err(…)` a failure. impl From> for ExampleResult where T2: ::std::fmt::Debug, { fn from(other: Result) -> ExampleResult { match other { Ok(_) => ExampleResult::Success, Err(error) => ExampleResult::Failure(Some(format!("{:?}", error))), } } } /// rspec considers examples returning `ExpectestResult::Ok(…)` a success, `ExpectestResult::Err(…)` a failure. #[cfg(feature = "expectest_compat")] impl From for ExampleResult { fn from(other: ExpectestResult) -> ExampleResult { match other { ExpectestResult::Success => ExampleResult::Success, ExpectestResult::Failure(failure) => { ExampleResult::Failure(Some(format!("{:?}", failure))) } } } } /// `ExampleReport` holds the results of a context example's test execution. #[derive(Clone, PartialEq, Eq, Debug, new)] pub struct ExampleReport { result: ExampleResult, duration: Duration, } impl ExampleReport { pub fn get_result(&self) -> &ExampleResult { &self.result } } impl Report for ExampleReport { fn is_success(&self) -> bool { self.result.is_success() } fn is_failure(&self) -> bool { self.result.is_failure() } fn get_passed(&self) -> u32 { self.result.get_passed() } fn get_failed(&self) -> u32 { self.result.get_failed() } fn get_ignored(&self) -> u32 { self.result.get_ignored() } fn get_duration(&self) -> Duration { self.duration } } #[cfg(test)] mod tests { use super::*; #[test] fn from_void() { assert!(ExampleResult::from(()).is_success()); } #[test] fn from_bool() { assert!(ExampleResult::from(true).is_success()); assert!(ExampleResult::from(false).is_failure()); } #[test] fn from_result() { let ok_result: Result<(), ()> = Ok(()); let err_result: Result<(), ()> = Err(()); assert!(ExampleResult::from(ok_result).is_success()); assert!(ExampleResult::from(err_result).is_failure()); } #[cfg(feature = "expectest_compat")] #[test] #[should_panic] fn from_expectest_result() { let ok_result = ExpectestResult::new_success(); // A failure ExpectestResult panics on drop, hence the `#[should_panic]`. let err_result = ExpectestResult::new_failure("dummy".to_owned(), None); assert!(ExampleResult::from(ok_result).is_success()); assert!(ExampleResult::from(err_result).is_failure()); } } rspec-1.0.0/src/report/mod.rs010064400007650000024000000045421400752777600142560ustar 00000000000000//! Reports provide information about an evaluated test unit. mod context; mod example; mod suite; pub use time::Duration; pub use report::context::*; pub use report::example::*; pub use report::suite::*; use header::ContextHeader; use header::ExampleHeader; /// `Report` holds the results of a structural group's test execution. pub trait Report { fn is_success(&self) -> bool; fn is_failure(&self) -> bool; fn get_passed(&self) -> u32; fn get_failed(&self) -> u32; fn get_ignored(&self) -> u32; fn get_duration(&self) -> Duration; } /// `BlockReport` holds the results of a context block's test execution. #[derive(PartialEq, Eq, Clone, Debug)] pub enum BlockReport { Context(Option, context::ContextReport), Example(ExampleHeader, example::ExampleReport), } impl BlockReport { pub fn get_blocks(&self) -> Option<&[BlockReport]> { match self { BlockReport::Context(_, ref report) => Some(report.get_blocks()), BlockReport::Example(_, _) => None, } } } impl Report for BlockReport { fn is_success(&self) -> bool { match self { BlockReport::Context(_, ref report) => report.is_success(), BlockReport::Example(_, ref report) => report.is_success(), } } fn is_failure(&self) -> bool { match self { BlockReport::Context(_, ref report) => report.is_failure(), BlockReport::Example(_, ref report) => report.is_failure(), } } fn get_passed(&self) -> u32 { match self { BlockReport::Context(_, ref report) => report.get_passed(), BlockReport::Example(_, ref report) => report.get_passed(), } } fn get_failed(&self) -> u32 { match self { BlockReport::Context(_, ref report) => report.get_failed(), BlockReport::Example(_, ref report) => report.get_failed(), } } fn get_ignored(&self) -> u32 { match self { BlockReport::Context(_, ref report) => report.get_ignored(), BlockReport::Example(_, ref report) => report.get_ignored(), } } fn get_duration(&self) -> Duration { match self { BlockReport::Context(_, ref report) => report.get_duration(), BlockReport::Example(_, ref report) => report.get_duration(), } } } rspec-1.0.0/src/report/suite.rs010064400007650000024000000017501400752777600146260ustar 00000000000000use time::Duration; use header::SuiteHeader; use report::{ContextReport, Report}; /// `SuiteReport` holds the results of a context suite's test execution. #[derive(PartialEq, Eq, Clone, Debug, new)] pub struct SuiteReport { header: SuiteHeader, context: ContextReport, } impl SuiteReport { pub fn get_header(&self) -> &SuiteHeader { &self.header } pub fn get_context(&self) -> &ContextReport { &self.context } } impl Report for SuiteReport { fn is_success(&self) -> bool { self.context.is_success() } fn is_failure(&self) -> bool { self.context.is_failure() } fn get_passed(&self) -> u32 { self.context.get_passed() } fn get_failed(&self) -> u32 { self.context.get_failed() } fn get_ignored(&self) -> u32 { self.context.get_ignored() } fn get_duration(&self) -> Duration { self.context.get_duration() } } #[cfg(test)] mod tests { // use super::*; } rspec-1.0.0/src/runner/configuration.rs010064400007650000024000000033461400751413100163220ustar 00000000000000// derive_builder emits warnings otherwise: #![allow(unused_mut)] /// A Runner's configuration. #[derive(Builder)] pub struct Configuration { /// Whether the runner executes tests in parallel #[builder(default = "true")] pub parallel: bool, /// Whether the runner exits the procees upon encountering failures #[builder(default = "true")] pub exit_on_failure: bool, } impl Default for Configuration { fn default() -> Self { ConfigurationBuilder::default().build().unwrap() } } #[cfg(test)] mod tests { use super::*; #[test] fn default_with_builder() { let config = ConfigurationBuilder::default().build().unwrap(); assert_eq!(config.parallel, true); assert_eq!(config.exit_on_failure, true); } #[test] fn default() { // arrange let expected = ConfigurationBuilder::default().build().unwrap(); // act let config = Configuration::default(); // assert assert_eq!(expected.parallel, config.parallel); assert_eq!(expected.exit_on_failure, config.exit_on_failure); } #[test] fn builder() { let config = ConfigurationBuilder::default().build().unwrap(); assert_eq!(config.parallel, true); assert_eq!(config.exit_on_failure, true); let config = ConfigurationBuilder::default() .parallel(false) .build() .unwrap(); assert_eq!(config.parallel, false); assert_eq!(config.exit_on_failure, true); let config = ConfigurationBuilder::default() .exit_on_failure(false) .build() .unwrap(); assert_eq!(config.parallel, true); assert_eq!(config.exit_on_failure, false); } } rspec-1.0.0/src/runner/mod.rs010064400007650000024000000612221400752777600142520ustar 00000000000000//! Runners are responsible for executing a test suite's examples. mod configuration; mod observer; pub use runner::configuration::*; pub use runner::observer::*; use std::borrow::Borrow; use std::cell::Cell; use std::ops::{Deref, DerefMut}; use std::panic; #[cfg(not(test))] use std::process; use std::sync::{Arc, Mutex}; use time::Instant; use rayon::prelude::*; use block::Block; use block::Context; use block::Example; use block::Suite; use report::ContextReport; use report::ExampleReport; use report::SuiteReport; use report::{BlockReport, Report}; use visitor::TestSuiteVisitor; /// Runner for executing a test suite's examples. pub struct Runner { pub configuration: configuration::Configuration, observers: Vec>, should_exit: Mutex>, } impl Runner { pub fn new(configuration: Configuration, observers: Vec>) -> Runner { Runner { configuration, observers, should_exit: Mutex::new(Cell::new(false)), } } } impl Runner { pub fn run(&self, suite: &Suite) -> SuiteReport where T: Clone + Send + Sync + ::std::fmt::Debug, { let mut environment = suite.environment.clone(); self.prepare_before_run(); let report = self.visit(suite, &mut environment); self.clean_after_run(); if let Ok(mut mutex_guard) = self.should_exit.lock() { *mutex_guard.deref_mut().get_mut() |= report.is_failure(); } report } fn broadcast(&self, mut handler: F) where F: FnMut(&dyn RunnerObserver), { for observer in &self.observers { handler(observer.borrow()); } } fn wrap_all(&self, context: &Context, environment: &mut T, wrapped_block: F) -> U where F: Fn(&mut T) -> U, { for before_function in context.before_all.iter() { before_function(environment); } let result = wrapped_block(environment); for after_function in context.after_all.iter() { after_function(environment); } result } fn wrap_each(&self, context: &Context, environment: &mut T, wrapped_block: F) -> U where F: Fn(&mut T) -> U, { for before_function in context.before_each.iter() { before_function(environment); } let result = wrapped_block(environment); for after_function in context.after_each.iter() { after_function(environment); } result } fn evaluate_blocks_parallel(&self, context: &Context, environment: &T) -> Vec where T: Clone + Send + Sync + ::std::fmt::Debug, { context .blocks .par_iter() .map(|block| self.evaluate_block(block, context, environment)) .collect() } fn evaluate_blocks_serial(&self, context: &Context, environment: &T) -> Vec where T: Clone + Send + Sync + ::std::fmt::Debug, { context .blocks .iter() .map(|block| self.evaluate_block(block, context, environment)) .collect() } fn evaluate_block( &self, block: &Block, context: &Context, environment: &T, ) -> BlockReport where T: Clone + Send + Sync + ::std::fmt::Debug, { let mut environment = environment.clone(); self.wrap_each(context, &mut environment, |environment| { self.visit(block, environment) }) } fn prepare_before_run(&self) { panic::set_hook(Box::new(|_panic_info| { // XXX panics already catched at the test call site, don't output the trace in stdout })); } fn clean_after_run(&self) { // XXX reset panic hook back to default hook: let _ = panic::take_hook(); } } #[cfg(test)] impl Default for Runner { /// Used for testing fn default() -> Self { Runner::new(Configuration::default(), vec![]) } } impl Drop for Runner { fn drop(&mut self) { let should_exit = if let Ok(mutex_guard) = self.should_exit.lock() { mutex_guard.deref().get() } else { false }; if self.configuration.exit_on_failure && should_exit { // XXX Cargo test failure returns 101. // // > "We use 101 as the standard failure exit code because it's something unique // > that the test runner can check for in run-fail tests (as opposed to something // > like 1, which everybody uses). I don't expect this behavior can ever change. // > This behavior probably dates to before 2013, // > all the way back to the creation of compiletest." – @brson #[cfg(not(test))] process::exit(101); #[cfg(test)] panic!("test suite failed !") } } } impl TestSuiteVisitor> for Runner where T: Clone + Send + Sync + ::std::fmt::Debug, { type Environment = T; type Output = SuiteReport; fn visit(&self, suite: &Suite, environment: &mut Self::Environment) -> Self::Output { self.broadcast(|handler| handler.enter_suite(self, &suite.header)); let report = SuiteReport::new( suite.header.clone(), self.visit(&suite.context, environment), ); self.broadcast(|handler| handler.exit_suite(self, &suite.header, &report)); report } } impl TestSuiteVisitor> for Runner where T: Clone + Send + Sync + ::std::fmt::Debug, { type Environment = T; type Output = BlockReport; fn visit(&self, member: &Block, environment: &mut Self::Environment) -> Self::Output { match member { Block::Example(ref example) => { let header = example.header.clone(); let report = self.visit(example, environment); BlockReport::Example(header, report) } Block::Context(ref context) => { let header = context.header.clone(); let report = self.visit(context, &mut environment.clone()); BlockReport::Context(header, report) } } } } impl TestSuiteVisitor> for Runner where T: Clone + Send + Sync + ::std::fmt::Debug, { type Environment = T; type Output = ContextReport; fn visit(&self, context: &Context, environment: &mut Self::Environment) -> Self::Output { if let Some(ref header) = context.header { self.broadcast(|handler| handler.enter_context(self, &header)); } let start_time = Instant::now(); let reports: Vec<_> = self.wrap_all(context, environment, |environment| { if self.configuration.parallel { self.evaluate_blocks_parallel(context, environment) } else { self.evaluate_blocks_serial(context, environment) } }); let end_time = Instant::now(); let elapsed_time = end_time - start_time; let report = ContextReport::new(reports, elapsed_time); if let Some(ref header) = context.header { self.broadcast(|handler| handler.exit_context(self, &header, &report)); } report } } impl TestSuiteVisitor> for Runner where T: Clone + Send + Sync + ::std::fmt::Debug, { type Environment = T; type Output = ExampleReport; fn visit(&self, example: &Example, environment: &mut Self::Environment) -> Self::Output { self.broadcast(|handler| handler.enter_example(self, &example.header)); let start_time = Instant::now(); let result = (example.function)(environment); let end_time = Instant::now(); let elapsed_time = end_time - start_time; let report = ExampleReport::new(result, elapsed_time); self.broadcast(|handler| handler.exit_example(self, &example.header, &report)); report } } #[cfg(test)] mod tests { use super::*; mod runner { use super::*; #[test] fn it_can_be_instanciated() { // arrange let _ = Runner::new(Configuration::default(), vec![]); // act // assert } mod broadcast { use super::*; use header::*; use std::sync::atomic::*; // XXX blank impl for stubbing impl RunnerObserver for () {} #[test] fn it_calls_the_closure() { // arrange let spy = Arc::new(()); let runner = Runner::new(Configuration::default(), vec![spy]); let has_been_called = AtomicBool::new(false); // act runner.broadcast(|_| has_been_called.store(true, Ordering::SeqCst)); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_it_once_per_observer() { // arrange let spy1 = Arc::new(()); let spy2 = Arc::new(()); let runner = Runner::new(Configuration::default(), vec![spy1, spy2]); let call_times = AtomicUsize::new(0); // act runner.broadcast(|_| { call_times.fetch_add(1, Ordering::SeqCst); }); // assert assert_eq!(2, call_times.load(Ordering::SeqCst)) } struct ObserverStub { events: Mutex>, } impl ObserverStub { fn new() -> Self { ObserverStub { events: Mutex::new(vec![]), } } } // XXX stub implem impl RunnerObserver for ObserverStub { fn enter_suite(&self, _runner: &Runner, header: &SuiteHeader) { let mut vec = self.events.lock().unwrap(); (*vec).push(("enter_suite", header.clone())); } } #[test] fn it_gives_the_observer_as_callback_argument() { // arrange let spy1 = Arc::new(ObserverStub::new()); let expected = SuiteHeader::new(SuiteLabel::Describe, "hello"); let runner = Runner::new(Configuration::default(), vec![spy1.clone()]); // act runner.broadcast(|observer| observer.enter_suite(&runner, &expected.clone())); // assert let lock = spy1.events.lock().expect("no dangling threads"); let res = (*lock).get(0).expect("to have been called once"); assert_eq!(&("enter_suite", expected), res); } } mod wrap_each { use super::*; use std::sync::atomic::*; #[test] fn it_can_be_called() { // arrange let runner = Runner::default(); // act runner.wrap_each(&Context::default(), &mut (), |_| {}) // assert } #[test] fn it_calls_the_closure() { // arrange let runner = Runner::default(); let has_been_called = AtomicBool::new(false); // act runner.wrap_each(&Context::default(), &mut (), |_| { has_been_called.store(true, Ordering::SeqCst) }); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_the_before_each_callbacks() { // arrange let runner = Runner::default(); let has_been_called = Arc::new(AtomicBool::new(false)); let closure_bool_handler = has_been_called.clone(); let mut context = Context::default(); // act context.before_each(move |_| closure_bool_handler.store(true, Ordering::SeqCst)); runner.wrap_each(&context, &mut (), |_| ()); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_the_after_each_callbacks() { // arrange let runner = Runner::default(); let has_been_called = Arc::new(AtomicBool::new(false)); let closure_bool_handler = has_been_called.clone(); let mut context = Context::default(); // act context.after_each(move |_| closure_bool_handler.store(true, Ordering::SeqCst)); runner.wrap_each(&context, &mut (), |_| ()); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_all_before_each_callbacks() { // arrange let runner = Runner::default(); let call_counter = Arc::new(AtomicUsize::new(0)); let closure_counter_handler1 = call_counter.clone(); let closure_counter_handler2 = call_counter.clone(); let mut context = Context::default(); // act context.before_each(move |_| { closure_counter_handler1.fetch_add(1, Ordering::SeqCst); }); context.before_each(move |_| { closure_counter_handler2.fetch_add(1, Ordering::SeqCst); }); runner.wrap_each(&context, &mut (), |_| ()); // assert assert_eq!(2, call_counter.load(Ordering::SeqCst)); } #[test] fn it_calls_all_after_each_callbacks() { // arrange let runner = Runner::default(); let call_counter = Arc::new(AtomicUsize::new(0)); let closure_counter_handler1 = call_counter.clone(); let closure_counter_handler2 = call_counter.clone(); let mut context = Context::default(); // act context.after_each(move |_| { closure_counter_handler1.fetch_add(1, Ordering::SeqCst); }); context.after_each(move |_| { closure_counter_handler2.fetch_add(1, Ordering::SeqCst); }); runner.wrap_each(&context, &mut (), |_| ()); // assert assert_eq!(2, call_counter.load(Ordering::SeqCst)); } #[test] fn it_calls_before_each_hook_before_the_main_closure() { // arrange let runner = Runner::default(); let last_caller_id = Arc::new(AtomicUsize::new(0)); let last_caller_handler1 = last_caller_id.clone(); let last_caller_handler2 = last_caller_id.clone(); let mut context = Context::default(); // act context.before_each(move |_| { last_caller_handler1.store(1, Ordering::SeqCst); }); runner.wrap_each(&context, &mut (), |_| { last_caller_handler2.store(2, Ordering::SeqCst); }); // assert assert_eq!(2, last_caller_id.load(Ordering::SeqCst)); } #[test] fn it_calls_after_each_hook_after_the_main_closure() { // arrange let runner = Runner::default(); let last_caller_id = Arc::new(AtomicUsize::new(0)); let last_caller_handler1 = last_caller_id.clone(); let last_caller_handler2 = last_caller_id.clone(); let mut context = Context::default(); // act context.after_each(move |_| { last_caller_handler1.store(1, Ordering::SeqCst); }); runner.wrap_each(&context, &mut (), |_| { last_caller_handler2.store(2, Ordering::SeqCst); }); // assert assert_eq!(1, last_caller_id.load(Ordering::SeqCst)); } } mod wrap_all { use super::*; use std::sync::atomic::*; #[test] fn it_can_be_called() { // arrange let runner = Runner::default(); // act runner.wrap_all(&Context::default(), &mut (), |_| {}) // assert } #[test] fn it_calls_the_closure() { // arrange let runner = Runner::default(); let has_been_called = AtomicBool::new(false); // act runner.wrap_all(&Context::default(), &mut (), |_| { has_been_called.store(true, Ordering::SeqCst) }); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_the_before_all_callbacks() { // arrange let runner = Runner::default(); let has_been_called = Arc::new(AtomicBool::new(false)); let closure_bool_handler = has_been_called.clone(); let mut context = Context::default(); // act context.before_all(move |_| closure_bool_handler.store(true, Ordering::SeqCst)); runner.wrap_all(&context, &mut (), |_| ()); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_the_after_all_callbacks() { // arrange let runner = Runner::default(); let has_been_called = Arc::new(AtomicBool::new(false)); let closure_bool_handler = has_been_called.clone(); let mut context = Context::default(); // act context.after_all(move |_| closure_bool_handler.store(true, Ordering::SeqCst)); runner.wrap_all(&context, &mut (), |_| ()); // assert assert_eq!(true, has_been_called.load(Ordering::SeqCst)); } #[test] fn it_calls_all_before_all_callbacks() { // arrange let runner = Runner::default(); let call_counter = Arc::new(AtomicUsize::new(0)); let closure_counter_handler1 = call_counter.clone(); let closure_counter_handler2 = call_counter.clone(); let mut context = Context::default(); // act context.before_all(move |_| { closure_counter_handler1.fetch_add(1, Ordering::SeqCst); }); context.before_all(move |_| { closure_counter_handler2.fetch_add(1, Ordering::SeqCst); }); runner.wrap_all(&context, &mut (), |_| ()); // assert assert_eq!(2, call_counter.load(Ordering::SeqCst)); } #[test] fn it_calls_all_after_all_callbacks() { // arrange let runner = Runner::default(); let call_counter = Arc::new(AtomicUsize::new(0)); let closure_counter_handler1 = call_counter.clone(); let closure_counter_handler2 = call_counter.clone(); let mut context = Context::default(); // act context.after_all(move |_| { closure_counter_handler1.fetch_add(1, Ordering::SeqCst); }); context.after_all(move |_| { closure_counter_handler2.fetch_add(1, Ordering::SeqCst); }); runner.wrap_all(&context, &mut (), |_| ()); // assert assert_eq!(2, call_counter.load(Ordering::SeqCst)); } #[test] fn it_calls_before_all_hook_before_the_main_closure() { // arrange let runner = Runner::default(); let last_caller_id = Arc::new(AtomicUsize::new(0)); let last_caller_handler1 = last_caller_id.clone(); let last_caller_handler2 = last_caller_id.clone(); let mut context = Context::default(); // act context.before_all(move |_| { last_caller_handler1.store(1, Ordering::SeqCst); }); runner.wrap_all(&context, &mut (), |_| { last_caller_handler2.store(2, Ordering::SeqCst); }); // assert assert_eq!(2, last_caller_id.load(Ordering::SeqCst)); } #[test] fn it_calls_after_all_hook_after_the_main_closure() { // arrange let runner = Runner::default(); let last_caller_id = Arc::new(AtomicUsize::new(0)); let last_caller_handler1 = last_caller_id.clone(); let last_caller_handler2 = last_caller_id.clone(); let mut context = Context::default(); // act context.after_all(move |_| { last_caller_handler1.store(1, Ordering::SeqCst); }); runner.wrap_all(&context, &mut (), |_| { last_caller_handler2.store(2, Ordering::SeqCst); }); // assert assert_eq!(1, last_caller_id.load(Ordering::SeqCst)); } } } mod impl_drop_for_runner { use super::*; #[test] #[should_panic] fn it_should_abort() { // arrange let config = ConfigurationBuilder::default() .exit_on_failure(true) .build() .unwrap(); // act { let runner = Runner::new(config, vec![]); (*runner.should_exit.lock().unwrap()).set(true); } // assert // test should panic } } mod impl_visitor_example_for_runner { use super::*; use header::*; use report::*; use std::sync::atomic::*; #[derive(Default, Debug, Clone)] struct SpyObserver { enter_example: Arc, exit_example: Arc, } impl RunnerObserver for SpyObserver { fn enter_example(&self, _runner: &Runner, _header: &ExampleHeader) { self.enter_example.store(true, Ordering::SeqCst) } fn exit_example( &self, _runner: &Runner, _header: &ExampleHeader, _report: &ExampleReport, ) { self.exit_example.store(true, Ordering::SeqCst) } } #[test] fn it_can_be_called() { // arrange let runner = Runner::default(); let example = Example::fixture_success(); // act // assert runner.visit(&example, &mut ()); } #[test] fn it_calls_observer_hooks() { // arrange let spy = Arc::new(SpyObserver::default()); let runner = Runner::new(Configuration::default(), vec![spy.clone()]); let example = Example::fixture_success(); // act runner.visit(&example, &mut ()); // assert assert_eq!(true, spy.enter_example.load(Ordering::SeqCst)); assert_eq!(true, spy.exit_example.load(Ordering::SeqCst)) } #[test] fn it_gives_an_env_to_the_example() { // arrange let runner = Runner::default(); let mut environment = Arc::new(AtomicBool::new(false)); // act let example = Example::new(ExampleHeader::default(), |env: &Arc| { env.store(true, Ordering::SeqCst); ExampleResult::Success }); runner.visit(&example, &mut environment); // assert assert_eq!(true, environment.load(Ordering::SeqCst)); } } mod impl_visitor_block_for_runner { use super::*; #[test] fn it_can_be_called() { // arrange let runner = Runner::default(); let block = Block::Example(Example::fixture_success()); // act // assert runner.visit(&block, &mut ()); } } } rspec-1.0.0/src/runner/observer.rs010064400007650000024000000016671400752777600153310ustar 00000000000000//! Events are sent by the Runner to signal the progression in the test suite, with the results use header::{ContextHeader, ExampleHeader, SuiteHeader}; use report::{ContextReport, ExampleReport, SuiteReport}; use runner::Runner; /// `RunnerObserver`s can be attached to a [`Runner`](../runner/struct.Runner.html) to observe a #[allow(unused_variables)] pub trait RunnerObserver: Send + Sync { fn enter_suite(&self, runner: &Runner, header: &SuiteHeader) {} fn exit_suite(&self, runner: &Runner, header: &SuiteHeader, report: &SuiteReport) {} fn enter_context(&self, runner: &Runner, header: &ContextHeader) {} fn exit_context(&self, runner: &Runner, header: &ContextHeader, report: &ContextReport) {} fn enter_example(&self, runner: &Runner, header: &ExampleHeader) {} fn exit_example(&self, runner: &Runner, header: &ExampleHeader, report: &ExampleReport) {} } #[cfg(test)] mod tests { // Nothing to test here, yet. } rspec-1.0.0/src/visitor.rs010064400007650000024000000002521400752777600136550ustar 00000000000000pub(crate) trait TestSuiteVisitor { type Environment; type Output; fn visit(&self, visitable: &T, environment: &mut Self::Environment) -> Self::Output; }