diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 923834a..b3f342f 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - rust: [nightly, beta, stable, 1.51.0] + rust: [nightly, beta, stable, 1.56.0] steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index f20a489..1dfa3f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["circus-simulation", "circus-test"] +members = ["circus-simulation", "circus-test", "circus-buggify"] diff --git a/README.md b/README.md index 1b76238..141acc8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A toolkit to develop distributed systems [![Coverage Status](https://coveralls.io/repos/github/PierreZ/circus/badge.svg?branch=main)](https://coveralls.io/github/PierreZ/circus?branch=main) [![Dependency Status](https://deps.rs/repo/github/PierreZ/circus/status.svg)](https://deps.rs/repo/github/PierreZ/circus) ![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51.0+-lightgray.svg)](#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56.0+-lightgray.svg)](#rust-version-requirements) ## Overview @@ -19,12 +19,13 @@ It will be compatible with both `async-std` and `Tokio`, allowing you to use Cir ## Available crates -* [circus-simulation](/circus-simulation) [![Crates.io Version](https://img.shields.io/crates/v/circus_simulation.svg)](https://crates.io/crates/circus_simulation) [![Docs.rs](https://img.shields.io/docsrs/circus_simulation)](https://docs.rs/circus_simulation) * [circus-test](/circus-test) [![Crates.io Version](https://img.shields.io/crates/v/circus_test.svg)](https://crates.io/crates/circus_test) [![Docs.rs](https://img.shields.io/docsrs/circus_test)](https://docs.rs/circus_test) +* [circus-buggify](/circus-buggify) [![Crates.io Version](https://img.shields.io/crates/v/circus_buggify.svg)](https://crates.io/crates/circus_buggify) [![Docs.rs](https://img.shields.io/docsrs/circus_buggify)](https://docs.rs/circus_buggify) +* [circus-simulation](/circus-simulation) [![Crates.io Version](https://img.shields.io/crates/v/circus_simulation.svg)](https://crates.io/crates/circus_simulation) [![Docs.rs](https://img.shields.io/docsrs/circus_simulation)](https://docs.rs/circus_simulation) ## Rust version requirements -The MSRV is Rust 1.51.0. +The MSRV is Rust 1.56.0. ## Examples diff --git a/circus-buggify/Cargo.toml b/circus-buggify/Cargo.toml new file mode 100644 index 0000000..39ce231 --- /dev/null +++ b/circus-buggify/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "circus_buggify" +version = "0.1.0" +authors = ["Pierre Zemb "] +edition = "2021" +rust-version = "1.56" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/PierreZ/circus" +homepage = "https://github.com/PierreZ/circus" +documentation = "https://docs.rs/circus_buggify" + +include = [ + "../LICENSE-APACHE", + "../LICENSE-MIT", + "README.md", + ".gitignore", + "Cargo.toml", + "src/*.rs", + "src/*/*.rs", + "tests/*.rs", +] + +description = "A Rust port of the Buggify macro from FoundationDB" +categories = ["simulation"] + +[dependencies] +rand = { version = "0.8.5", features = ["small_rng"] } +parking_lot = "0.12.0" +once_cell = "1.10.0" + +[dev-dependencies] +tracing = "0.1.34" +tracing-subscriber = "0.3.11" + +[badges] +coveralls = { repository = "PierreZ/circus", branch = "main", service = "github" } +maintenance = { status = "experimental" } diff --git a/circus-buggify/README.md b/circus-buggify/README.md new file mode 100644 index 0000000..43d3f98 --- /dev/null +++ b/circus-buggify/README.md @@ -0,0 +1,33 @@ +# circus-simulation :circus_tent: + +A simulation framework, inspired by [FoundationDB](https://foundationdb.org) + +![status](https://img.shields.io/badge/status-experimental-red) +[![Crates.io Version](https://img.shields.io/crates/v/circus_simulation.svg)](https://crates.io/crates/circus_simulation) +[![Docs.rs](https://img.shields.io/docsrs/circus_simulation)](https://docs.rs/circus_simulation) +[![Build status](https://github.com/PierreZ/circus/workflows/Build%20and%20test/badge.svg)](https://github.com/PierreZ/circus/actions) +![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56.0+-lightgray.svg)](#rust-version-requirements) + +## Rust version requirements + +The MSRV is Rust 1.56.0. + +## Examples + +Examples can be found in the [examples folder](/circus-simulation/examples). + +## License + +Licensed under either of + +* Apache License, Version 2.0 ([LICENSE-APACHE](/LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](/LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/circus-simulation/examples/buggify.rs b/circus-buggify/examples/buggify.rs similarity index 62% rename from circus-simulation/examples/buggify.rs rename to circus-buggify/examples/buggify.rs index 83d1e58..9819122 100644 --- a/circus-simulation/examples/buggify.rs +++ b/circus-buggify/examples/buggify.rs @@ -1,7 +1,8 @@ -extern crate circus_simulation; +extern crate circus_buggify; -use circus_simulation::buggify::{buggifier, Buggifier}; -use circus_simulation::deterministic::random::DeterministicRandom; +use circus_buggify::{buggify_with_prob, enable_buggify, Buggifier}; +use rand::rngs::SmallRng; +use rand::SeedableRng; use tracing::Level; fn main() { @@ -12,11 +13,8 @@ fn main() { // let's create a buggifier let b = Buggifier::default(); - // init random with a seed - let random = DeterministicRandom::new_with_seed(42); - - // enables buggify - b.enable_buggify(random); + // enables buggify with a seed + b.enable_buggify(SmallRng::seed_from_u64(42)); for i in 0..10 { // this block has a 0.05% chance to be run @@ -32,8 +30,8 @@ fn main() { } // you can also get a static buggifier that needs to be enabled - buggifier().enable_buggify(DeterministicRandom::new_with_seed(42)); - if buggifier().buggify_with_prob(1.00) { + enable_buggify(SmallRng::seed_from_u64(42)); + if buggify_with_prob(1.00) { tracing::info!("buggified with a 100% probability!"); } } diff --git a/circus-simulation/src/buggify.rs b/circus-buggify/src/lib.rs similarity index 61% rename from circus-simulation/src/buggify.rs rename to circus-buggify/src/lib.rs index 2c39fae..0ade5d2 100644 --- a/circus-simulation/src/buggify.rs +++ b/circus-buggify/src/lib.rs @@ -1,30 +1,63 @@ //! Inject failure with buggify -use crate::deterministic::random::DeterministicRandom; +//! `buggify` allow you to cooperate with the simulator to inject failures. +//! It has the following rules: +//! 1. it only ever evaluates to true when run in simulation. +//! 1. The first time each `buggify` use is evaluated, it is either enabled or disabled for the entire simulation run. +//! 1. Enabled uses of `buggify` have a 5% chance of evaluating to true +//! +//! A good blogpost about buggify can be found [here](https://transactional.blog/simulation/buggify.html). +//! ```rust +//! use circus_buggify::{buggify_with_prob, enable_buggify, Buggifier}; +//! use rand::rngs::SmallRng; +//! use rand::SeedableRng; +//! +//! // let's create a buggifier +//! let b = Buggifier::default(); +//! +//! // enables buggify with a seed +//! b.enable_buggify(SmallRng::seed_from_u64(42)); +//! +//! for i in 0..10 { +//! // this block has a 0.05% chance to be run +//! // which is iteration 8 for seed 42 +//! if b.buggify() { +//! println!("buggified at iteration {}", i); +//! } +//! } +//! +//! // buggify can also accept a probability +//! if b.buggify_with_prob(1.0) { +//! println!("buggified with a 100% probability!"); +//! } +//! +//! // you can also get a static buggifier that needs to be enabled +//! enable_buggify(SmallRng::seed_from_u64(42)); +//! if buggify_with_prob(1.00) { +//! println!("buggified with a 100% probability!"); +//! } +//!``` + +#![warn(missing_docs)] +#![warn(rust_2018_idioms)] use parking_lot::Mutex; use std::collections::HashMap; use once_cell::sync::Lazy; +use rand::rngs::SmallRng; +use rand::Rng; use std::ops::Deref; use std::panic::Location; -/// Buggifier is providing the buggify methods. -/// `buggify` allow you to cooperate with the simulator to inject failures. -/// It has the following rules: -/// 1. it only ever evaluates to true when run in simulation. -/// 1. The first time each `buggify` use is evaluated, it is either enabled or disabled for the entire simulation run. -/// 1. Enabled uses of `buggify` have a 5% chance of evaluating to true -/// -/// A good blogpost about buggify can be found [here](https://transactional.blog/simulation/buggify.html). -/// +/// Buggifier's definition #[derive(Debug)] pub struct Buggifier { buggified_lines: Mutex>, - random: Mutex>, + random: Mutex>, } impl Buggifier { /// create a new Buggifier - pub fn new(r: DeterministicRandom) -> Self { + pub fn new(r: SmallRng) -> Self { Buggifier { buggified_lines: Mutex::new(HashMap::new()), random: Mutex::new(Some(r)), @@ -55,9 +88,8 @@ impl Buggifier { Some(deterministic_random) => { let mut already_buggified = self.buggified_lines.lock(); if !already_buggified.contains_key(&line) - && deterministic_random.random_boolean(probability) + && deterministic_random.gen_bool(probability) { - tracing::info!("buggifying line {}", line); already_buggified.insert(line, true); return true; } @@ -72,15 +104,13 @@ impl Buggifier { } /// enables buggify by giving a random source - pub fn enable_buggify(&self, r: DeterministicRandom) { - tracing::info!("enabling buggify"); + pub fn enable_buggify(&self, r: SmallRng) { let mut data = self.random.lock(); *data = Some(r); } /// disable buggify pub fn disable_buggify(&self) { - tracing::info!("disabling buggify"); let mut data = self.random.lock(); *data = None; let mut map = self.buggified_lines.lock(); @@ -107,14 +137,50 @@ pub fn buggifier() -> &'static Buggifier { BUGGIFIER_INSTANCE.deref() } +#[track_caller] +/// `buggify` will returns true only once per execution with a probability of 0.05. +pub fn buggify() -> bool { + let location = Location::caller(); + buggifier().handle_buggify(format!("{}:{}", location.file(), location.line()), 0.05) +} + +#[track_caller] +/// `buggify` version where you can choose the probability. +pub fn buggify_with_prob(probability: f64) -> bool { + let location = Location::caller(); + buggifier().handle_buggify( + format!("{}:{}", location.file(), location.line()), + probability, + ) +} + +/// checks if buggify is enabled +pub fn is_buggify_enabled() -> bool { + buggifier().is_buggify_enabled() +} + +/// enables buggify by giving a random source +pub fn enable_buggify(r: SmallRng) { + buggifier().enable_buggify(r) +} + +/// disable buggify +pub fn disable_buggify() { + buggifier().disable_buggify() +} + #[cfg(test)] mod tests { - use crate::buggify::{buggifier, Buggifier}; - use crate::deterministic::random::DeterministicRandom; + use crate::{ + buggifier, buggify, buggify_with_prob, disable_buggify, enable_buggify, is_buggify_enabled, + Buggifier, + }; + use rand::rngs::SmallRng; + use rand::SeedableRng; use tracing::Level; #[test] - fn buggify() { + fn test_buggifier() { let _ = tracing_subscriber::fmt() .with_max_level(Level::TRACE) .with_test_writer() @@ -132,7 +198,7 @@ mod tests { assert!((*map).is_empty()); } - let random = DeterministicRandom::new_with_seed(42); + let random = SmallRng::seed_from_u64(42); b.enable_buggify(random); assert!(b.is_buggify_enabled(), "should be activated"); @@ -166,23 +232,25 @@ mod tests { } #[test] - fn static_buggify() { + fn test_static_buggify() { let _ = tracing_subscriber::fmt() .with_max_level(Level::TRACE) .with_test_writer() .try_init(); - assert!(!buggifier().is_buggify_enabled()); - assert!(!buggifier().buggify_with_prob(1.0), "should not buggified"); + // reset any previous test + disable_buggify(); + + assert!(!is_buggify_enabled()); + assert!(!buggify_with_prob(1.0), "should not buggified"); - let random = DeterministicRandom::new_with_seed(42); - buggifier().enable_buggify(random); - assert!(buggifier().is_buggify_enabled(), "should be activated"); + enable_buggify(SmallRng::seed_from_u64(42)); + assert!(is_buggify_enabled(), "should be activated"); for i in 0..100 { let result = i == 8; assert_eq!( - buggifier().buggify(), + buggify(), result, "iteration {} should have been {}", i, diff --git a/circus-simulation/Cargo.toml b/circus-simulation/Cargo.toml index cb92fc2..6059190 100644 --- a/circus-simulation/Cargo.toml +++ b/circus-simulation/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "circus_simulation" -version = "0.0.1" +version = "0.1.0" authors = ["Pierre Zemb "] -edition = "2018" +edition = "2021" +rust-version = "1.56" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/PierreZ/circus" @@ -24,6 +25,7 @@ description = "Simulation framework inspired by FoundationDB" categories = ["simulation"] [dependencies] +circus_buggify = { version = "0.1.0", path = "../circus-buggify"} rand = { version = "0.8.5", features = ["small_rng"] } parking_lot = "0.12.0" once_cell = "1.10.0" diff --git a/circus-simulation/README.md b/circus-simulation/README.md index 99074e7..699a787 100644 --- a/circus-simulation/README.md +++ b/circus-simulation/README.md @@ -1,21 +1,21 @@ -# circus-simulation :circus_tent: +# circus-buggify :circus_tent: -A simulation framework, inspired by [FoundationDB](https://foundationdb.org) +A buggify-like feature, inspired by [FoundationDB](https://foundationdb.org) ![status](https://img.shields.io/badge/status-experimental-red) -[![Crates.io Version](https://img.shields.io/crates/v/circus_simulation.svg)](https://crates.io/crates/circus_simulation) -[![Docs.rs](https://img.shields.io/docsrs/circus_simulation)](https://docs.rs/circus_simulation) +[![Crates.io Version](https://img.shields.io/crates/v/circus_buggify.svg)](https://crates.io/crates/circus_simulation) +[![Docs.rs](https://img.shields.io/docsrs/circus_buggify)](https://docs.rs/circus_simulation) [![Build status](https://github.com/PierreZ/circus/workflows/Build%20and%20test/badge.svg)](https://github.com/PierreZ/circus/actions) ![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51.0+-lightgray.svg)](#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56.0+-lightgray.svg)](#rust-version-requirements) ## Rust version requirements -The MSRV is Rust 1.51.0. +The MSRV is Rust 1.56.0. ## Examples -Examples can be found in the [examples folder](/circus-simulation/examples). +Examples can be found in the [examples folder](/circus-buggify/examples). ## License diff --git a/circus-simulation/examples/simulation.rs b/circus-simulation/examples/simulation.rs index 5ba3e82..546c01f 100644 --- a/circus-simulation/examples/simulation.rs +++ b/circus-simulation/examples/simulation.rs @@ -44,8 +44,8 @@ async fn run_platform(mut platform: PlatformProvider) { .duration_since(start_time) .eq(&Duration::from_millis(817))); } - if i == 4 { - // using the seed 42, the fourth opening will trigger an error. + if i == 8 { + // using the seed 42, the 8th opening will trigger an error. assert!(file_result.is_err()); } else { assert!(file_result.is_ok()); diff --git a/circus-simulation/src/deterministic/platform.rs b/circus-simulation/src/deterministic/platform.rs index a06f289..1d65c0d 100644 --- a/circus-simulation/src/deterministic/platform.rs +++ b/circus-simulation/src/deterministic/platform.rs @@ -1,5 +1,4 @@ //! Deterministic platform module -use crate::buggify::Buggifier; use crate::deterministic::fs::file::SimulatedFile; use crate::deterministic::random::DeterministicRandom; use crate::deterministic::runtime::reactor::DeterministicReactor; @@ -14,6 +13,9 @@ use std::io::ErrorKind; use std::path::Path; use std::sync::Arc; +use circus_buggify::Buggifier; +use rand::rngs::SmallRng; +use rand::SeedableRng; use std::time::{Duration, Instant}; /// Simulated version of the plateform. Every API exposed is subject to an deterministic output, @@ -38,9 +40,9 @@ impl SimulationPlatform { SimulationPlatform { time: reactor.get_deterministic_time(), - random: random.clone(), + random, reactor, - buggifier: Arc::new(Buggifier::new(random)), + buggifier: Arc::new(Buggifier::new(SmallRng::seed_from_u64(seed))), } } } @@ -145,7 +147,7 @@ mod tests { let mut platform = SimulationPlatform::new(42, reactor); for i in 0..10 { let file_result = platform.open(Path::new("/etc/hosts")).await; - if i == 4 { + if i == 8 { assert!(file_result.is_err()); } else { assert!(file_result.is_ok()); diff --git a/circus-simulation/src/lib.rs b/circus-simulation/src/lib.rs index d1de091..93dcc39 100644 --- a/circus-simulation/src/lib.rs +++ b/circus-simulation/src/lib.rs @@ -9,7 +9,6 @@ //! //! Examples can be found in the [examples folder](https://github.com/PierreZ/circus/tree/main/simulation/examples). -pub mod buggify; pub mod deterministic; pub mod file; pub mod platform; diff --git a/circus-test/Cargo.toml b/circus-test/Cargo.toml index d69533d..61b0f74 100644 --- a/circus-test/Cargo.toml +++ b/circus-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "circus_test" -version = "0.0.1" +version = "0.1.0" edition = "2018" authors = ["Pierre Zemb "] license = "MIT OR Apache-2.0" diff --git a/circus-test/README.md b/circus-test/README.md index b1d1495..4f997b2 100644 --- a/circus-test/README.md +++ b/circus-test/README.md @@ -5,7 +5,7 @@ [![Docs.rs](https://img.shields.io/docsrs/circus_test)](https://docs.rs/circus_test) [![Build status](https://github.com/PierreZ/circus/workflows/Build%20and%20test/badge.svg)](https://github.com/PierreZ/circus/actions) ![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51.0+-lightgray.svg)](#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56.0+-lightgray.svg)](#rust-version-requirements) Allow injection of a random seed upon a test. Can be overloaded with environment var `DETERMINISTIC_SEED`. @@ -21,7 +21,7 @@ fn random_seed(seed: u64) { ## Rust version requirements -The MSRV is Rust 1.51.0. +The MSRV is Rust 1.56.0. ## License