├── .github └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── GUIDE.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples_readme ├── Cargo.toml └── src │ └── lib.rs ├── justfile └── src ├── adapters.rs ├── adapters ├── arc_die.rs ├── boxed_die.rs ├── boxed_die_once.rs ├── flat_map_die.rs ├── flatten_die.rs ├── map_die.rs └── rc_die.rs ├── asserts.rs ├── codice.rs ├── codie.rs ├── dice.rs ├── dice ├── array.rs ├── b_tree_map.rs ├── b_tree_set.rs ├── binary_heap.rs ├── bool.rs ├── char.rs ├── collection.rs ├── float.rs ├── fn_builder.rs ├── from.rs ├── hash_map.rs ├── hash_set.rs ├── index_of.rs ├── integer.rs ├── just.rs ├── length.rs ├── linked_list.rs ├── one_of.rs ├── option.rs ├── rand.rs ├── result.rs ├── shuffle.rs ├── split_integer.rs ├── split_vec.rs ├── string.rs ├── todo.rs ├── vec.rs ├── vec_deque.rs └── zip.rs ├── die.rs ├── die_once.rs ├── fate.rs ├── frontend.rs ├── frontend ├── dicetest.rs ├── env.rs ├── formatter.rs ├── mode.rs └── run_code.rs ├── hints.rs ├── lib.rs ├── limit.rs ├── macros.rs ├── prelude.rs ├── prng.rs ├── runner.rs ├── runner ├── error.rs ├── limit_series.rs ├── once.rs ├── repeatedly.rs └── util.rs ├── seed.rs ├── stats.rs ├── util.rs └── util ├── base64.rs ├── conversion.rs ├── events.rs └── finalizer.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/cache@v4 21 | with: 22 | path: | 23 | # See https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci 24 | ~/.cargo/.crates.toml 25 | ~/.cargo/.crates2.json 26 | ~/.cargo/bin/ 27 | ~/.cargo/registry/index/ 28 | ~/.cargo/registry/cache/ 29 | ~/.cargo/git/db/ 30 | # See https://doc.rust-lang.org/cargo/guide/build-cache.html 31 | target/ 32 | key: ${{ runner.os }}|${{ github.job }}|${{ github.run_attempt }} 33 | restore-keys: | 34 | ${{ runner.os }}|${{ github.job }} 35 | ${{ runner.os }} 36 | 37 | - name: Install Rust 38 | run: | 39 | rustup install 1.79 --profile minimal --no-self-update 40 | rustup default 1.79 41 | rustup component add rustfmt 42 | rustup component add clippy 43 | cargo install --locked cargo-semver-checks 44 | 45 | - name: Check fmt 46 | run: cargo fmt --all --check 47 | 48 | - name: Check clippy 49 | run: cargo clippy --workspace --all-targets 50 | 51 | - name: Build 52 | run: | 53 | cargo build --workspace --all-targets --all-features 54 | cargo build --workspace --all-targets --no-default-features 55 | cargo build --workspace --all-targets --no-default-features --features hints 56 | cargo build --workspace --all-targets --no-default-features --features stats 57 | cargo build --workspace --all-targets --no-default-features --features rand 58 | cargo build --workspace --all-targets 59 | 60 | - name: Run tests 61 | run: cargo test -- --format=terse 62 | env: 63 | RUST_BACKTRACE: 1 64 | 65 | - name: SemVer check 66 | run: cargo semver-checks check-release --all-features 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - Add function `dicetest::dice::todo`. This generator is for prototyping and always panics when created. 10 | - Add functions `dicetest::dice::split_{u8,u16,u32,u64,u128,usize}`. These generators are similar to `dicetest::dice::split_{u8,u16,u32,u64,u128,usize}_n`, but use a const parameter for the number of parts. 11 | - Add function `dicetest::dice::split_vec_n`. This generators is similar to `dicetest::dice::split_vec`, but uses a non-const parameter for the number of parts. 12 | - Add function `dicetest::dice::split_limit`. This generator splits a `dicetest::Limit` into random parts. 13 | - Add function `dicetest::dice::split_limit_n`. This generators is similar to `dicetest::dice::split_limit`, but uses a non-const parameter for the number of parts. 14 | - Add support for regression tests 15 | - Add function `dicetest::Dicetest::regression` for adding a regression test. 16 | - Add function `dicetest::Dicetest::regressions_enabled` for enabling/disabling regression tests. 17 | - Add environment variable `DICETEST_REGRESSIONS_ENABLED` for enabling/disabling regression tests. 18 | - Add struct `dicetest::runner::repeatedly::Regression` and field `dicetest::runner::repeatedly::Config::regressions`. 19 | 20 | ### Fixed 21 | - Fix unintentional panic in `dicetest::dice::weighted_*` if sum of weights is zero. 22 | 23 | ### Changed 24 | - Rename functions `dicetest::dice::terms_of_{u8,u16,u32,u64,u128,usize}` to `dicetest::dice::split_{u8,u16,u32,u64,u128,usize}_n`. 25 | - Change signature of `dicetest::dice::split_vec`. Instead of returning a pair with two parts, it now has a type parameter `const N: usize` and returns an array with `N` parts. 26 | - Rename feature flag `rand_full` to `rand`. 27 | - Upgrade dependency rand_core to 0.6 and rand to 0.8. 28 | 29 | ### Removed 30 | - Remove feature flag `quickcheck_full` and the integration of `quickcheck::Gen` and `quickcheck::Arbitrary` due to missing functionality in quickcheck 1.0. 31 | 32 | ## [0.3.1] - 2022-02-27 33 | ### Fixed 34 | - Fix unintentional panic in `dicetest::dice::one_of_die_once` and `dicetest::dice::one_of_die`. 35 | 36 | ## [0.3.0] - 2021-09-10 37 | ### Added 38 | - Add function `dicetest::dice::shuffle_slice`. This allows to shuffle a slice in-place. 39 | - Add function `dicetest::dice::array`. This allows to generate arrays using const generics. 40 | 41 | ### Removed 42 | - Remove function `dicetest::dice::array_1`, ..., `dicetest::dice::array_32`. Use `dicetest::dice::array` instead. 43 | 44 | ### Changed 45 | - Rename `dicetest::dice::size` to `dicetest::dice::length` and `dicetest::dice::SizeRange` to `dicetest::dice::LengthRange`. This expresses better their intention and prevents confusion with `dicetest::dice::usize` and `dicetest::dice::isize`. 46 | - Reorganize functions `dicetest::dice::zip*`, `dicetest::dice::one_of*` and `dicetest::dice::weighted_one_of*`. For each group of functions with same functionality but different arity a struct is added that bundles these functions as methods. E.g. instead of `dice::one_of_2(1, 2)` you can write now `dice::one_of().two(1, 2)`. 47 | 48 | ## [0.2.1] - 2020-12-05 49 | ### Added 50 | - Add function `dicetest::Fate::roll_distribution`. This allows to generate values directly from a `rand::distributions::Distribution`. 51 | - Add optional feature flag `quickcheck_full`. This enables the integration of `quickcheck::Arbitrary`. 52 | - Implement `rand_core::RngCore` for `dicetest::Fate`. 53 | - Implement `quickcheck::Gen` for `dicetest::Fate`. 54 | - Add function `dicetest::Fate::roll_arbitrary`. This allows to generate values for a type that implements `quickcheck::Arbitrary`. 55 | - Add function `dicetest::dice::arbitrary`. This allows to create a `dicetest::Die` for a type that implements `quickcheck::Arbitrary`. 56 | - Add struct `dicetest::hints::Section`. 57 | - Add marco `dicetest::hint_section`. This makes it easier to work with hint indents. 58 | 59 | 60 | [Unreleased]: https://github.com/jakoschiko/dicetest/compare/v0.3.1...HEAD 61 | [0.3.1]: https://github.com/jakoschiko/dicetest/compare/v0.3.0...v0.3.1 62 | [0.3.0]: https://github.com/jakoschiko/dicetest/compare/v0.2.1...v0.3.0 63 | [0.2.1]: https://github.com/jakoschiko/dicetest/compare/v0.2.0...v0.2.1 64 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dicetest" 3 | version = "0.4.0" 4 | authors = ["Jakob Schikowski"] 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | description = "Framework for writing tests with randomly generated test data" 8 | keywords = ["testing", "fuzz", "property", "quickcheck"] 9 | categories = ["development-tools::testing"] 10 | repository = "https://github.com/jakoschiko/dicetest" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | rand_core = { version = "0.6", optional = true } 15 | rand = { version = "0.8", optional = true } 16 | 17 | [features] 18 | default = ["hints", "stats"] 19 | hints = [] 20 | stats = [] 21 | rand_core = ["dep:rand_core"] 22 | rand = ["dep:rand", "rand_core"] 23 | 24 | [package.metadata.docs.rs] 25 | all-features = true 26 | rustdoc-args = ["--cfg", "docsrs"] 27 | 28 | [workspace] 29 | members = [ 30 | "examples_readme", 31 | ] 32 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | 3 | This document will guide you through the most important concepts and features of dicetest. 4 | 5 | ## Pseudorandomness 6 | 7 | The type `Seed` allows to determine the [pseudorandomness]. You can either use a fixed 8 | `Seed` or a random `Seed`: 9 | 10 | ```rust 11 | use dicetest::Seed; 12 | 13 | println!("{:?}", Seed(42)); 14 | // Output: Seed(42) 15 | 16 | println!("{:?}", Seed::random()); 17 | // Output: Seed(8019292413750407764) 18 | ``` 19 | 20 | The `Seed` can be used to initialize the [pseudorandom number generator] `Prng`. For each 21 | `Seed` the `Prng` provides a different infinite pseudorandom sequence of `u64`s. 22 | 23 | ```rust 24 | use dicetest::{Prng, Seed}; 25 | 26 | fn print_random_values(mut prng: Prng) { 27 | for _ in 0..3 { 28 | print!("{:?}, ", prng.next_number()); 29 | } 30 | println!("..."); 31 | } 32 | 33 | print_random_values(Prng::from_seed(Seed(42))); 34 | // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ... 35 | print_random_values(Prng::from_seed(Seed(42))); 36 | // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ... 37 | print_random_values(Prng::from_seed(Seed::random())); 38 | // Output: 4221507577048064061, 15374206214556255352, 4977687432463843847, ... 39 | print_random_values(Prng::from_seed(Seed::random())); 40 | // Output: 11086225885938422405, 9312304973013875005, 1036200222843160301, ... 41 | ``` 42 | 43 | [pseudorandomness]: https://en.wikipedia.org/wiki/Pseudorandomness 44 | [pseudorandom number generator]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator 45 | 46 | ## Dice 47 | 48 | Although `Prng` can only generate pseudorandom `u64`s, the `u64`s can be used for constructing 49 | more complex values. The traits `DieOnce` and `Die` represents `Prng`-based generators for 50 | values of any type. 51 | 52 | An implementor of `DieOnce` is a generator that can be used a single time 53 | (similar to [`FnOnce`]). 54 | 55 | ```rust 56 | use dicetest::prelude::*; 57 | 58 | let xx = "xx".to_string(); 59 | let yy = "yy".to_string(); 60 | 61 | // This generator implements `DieOnce`. 62 | // It chooses one of the `String`s without cloning them. 63 | let xx_or_yy_die = dice::one_of_once().two(xx, yy); 64 | ``` 65 | 66 | An implementor of `Die` is a generator that can be used infinite times (similar to [`Fn`]). 67 | 68 | ```rust 69 | use dicetest::prelude::*; 70 | 71 | let xx = "xx".to_string(); 72 | let yy = "yy".to_string(); 73 | 74 | // This generator implements `Die`. 75 | // It chooses one of the `String`s by cloning them. 76 | let xx_or_yy_die = dice::one_of().two(xx, yy); 77 | 78 | // This generator uses `xx_or_yy_die` to generate three `String`s at once. 79 | let three_xx_or_yy_die = dice::array::<_, _, 3>(xx_or_yy_die); 80 | ``` 81 | 82 | Generators can be easily implemented and composed: 83 | ```rust 84 | use dicetest::prelude::*; 85 | 86 | // A classic die that generates a number between 1 and 6 with uniform distribution. 87 | let classic_die = dice::one_of().six::(1, 2, 3, 4, 5, 6); 88 | 89 | // A loaded die that generates the number 6 more frequently. 90 | let loaded_die = 91 | dice::weighted_one_of().six::((1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 6)); 92 | 93 | // This die generates the result of the function. 94 | let die_from_fn = dice::from_fn(|_| 42); 95 | 96 | // This die generates always the same `String` by cloning the original one. 97 | let foo_die = dice::just("foo".to_string()); 98 | 99 | // This die generates an arbitrary byte. 100 | let byte_die = dice::u8(..); 101 | 102 | // This die generates a non-zero byte. 103 | let non_zero_byte_die = dice::u8(1..); 104 | 105 | // This die generates a `Vec` that contains an arbitrary number of arbitrary bytes. 106 | let bytes_die = dice::vec(dice::u8(..), ..); 107 | 108 | // This die generates a `Vec` that contains up to 10 arbitrary bytes. 109 | let up_to_ten_bytes_die = dice::vec(dice::u8(..), ..=10); 110 | 111 | // This die generates an arbitrary wrapped byte. 112 | struct WrappedByte(u8); 113 | let wrapped_byte_die = dice::u8(..).map(WrappedByte); 114 | 115 | // This die generates a permutation of `(0..=n)` for an arbitrary `n`. 116 | let permutation_die = dice::length(0..).flat_map(|n| { 117 | let vec = (0..=n).collect::>(); 118 | dice::shuffled_vec(vec) 119 | }); 120 | ``` 121 | 122 | The struct `Fate` is necessary for using `DieOnce` or `Die`. It contains two parameters: 123 | 124 | - `Prng`: Provides the pseudorandom `u64`s that the implementor of `DieOnce` or `Die` can use 125 | for constructing more complex values. The implementor should only use this as its source of 126 | randomness. 127 | - `Limit`: The upper limit for the length of dynamic data structures generated by the 128 | implementor of `DieOnce` or `Die`. The implementor is allowed to freely interpret or even 129 | ignore this value. 130 | 131 | ```rust 132 | use dicetest::prelude::*; 133 | use dicetest::{Limit, Prng}; 134 | 135 | // Provides the randomness for the generator and will be mutated when used. 136 | let mut prng = Prng::from_seed(0x5EED.into()); 137 | // Limits the length of dynamic data structures. The generator has only read access. 138 | let limit = Limit(5); 139 | 140 | // Contains all parameters necessary for using `DieOnce` or `Die`. 141 | let mut fate = Fate::new(&mut prng, limit); 142 | 143 | // Generator for a `Vec` with an arbitrary length. 144 | let vec_die = dice::vec(dice::u8(..), ..); 145 | 146 | // Generates a `Vec`. Although `vec_die` can generate a `Vec` with an arbitrary length, 147 | // the length of the actual `Vec` is limited by `limit`. 148 | let vec = fate.roll(vec_die); 149 | assert!(vec.len() <= 5); 150 | 151 | println!("{:?}", vec); 152 | // Output: [252, 231, 153, 0] 153 | ``` 154 | 155 | [`FnOnce`]: https://doc.rust-lang.org/std/ops/trait.FnOnce.html 156 | [`Fn`]: https://doc.rust-lang.org/std/ops/trait.Fn.html 157 | 158 | ## Tests 159 | 160 | If you want to write a test with randomly generated test data you can use the test 161 | builder `Dicetest`: 162 | - It can be configured via source code or environment variables. 163 | - It runs your test repeatedly with different seeds. 164 | - It logs useful information that helps you to debug your test. 165 | 166 | ```rust 167 | use dicetest::prelude::*; 168 | 169 | #[test] 170 | fn test_foo() { 171 | // Runs your test with default configuration. 172 | Dicetest::repeatedly().run(|fate| { 173 | // Write your test here. 174 | }); 175 | } 176 | 177 | #[test] 178 | fn test_bar() { 179 | // Runs your test with custom configuration. 180 | Dicetest::repeatedly().passes(10000).run(|fate| { 181 | // Write your test here. 182 | }); 183 | } 184 | ``` 185 | 186 | The closure contains your test. With the passed `fate` you can generate test data and make 187 | assertions. If the closure panics, `Dicetest` catches the panic, logs the test result to 188 | stdout and resumes the panic. 189 | 190 | ## Hints 191 | 192 | Hints can be used to analyze a single test run. In most cases you want to analyze the 193 | counterexample. Use it to reveal what test data were generated or which branches were taken: 194 | 195 | ```rust 196 | use dicetest::prelude::*; 197 | 198 | #[test] 199 | fn test_foo() { 200 | Dicetest::repeatedly().run(|mut fate| { 201 | let x = fate.roll(dice::u8(1..=5)); 202 | hint_debug!(x); 203 | 204 | let y = fate.roll(dice::u8(1..=3)); 205 | if y != x { 206 | hint!("took branch if with y = {}", y); 207 | 208 | assert_eq!(3, y); 209 | } else { 210 | hint!("took branch else"); 211 | } 212 | }) 213 | } 214 | ``` 215 | 216 | Running the test produces the following output: 217 | 218 | ```text 219 | The test failed after 0 passes. 220 | 221 | # Config 222 | - seed: 10929669535587280453 223 | - start limit: 0 224 | - end limit: 100 225 | - passes: 200 226 | 227 | # Counterexample 228 | - run code: "JfXG0LRXjKUMu+YmdrF38/GstRdeLAeMRTKskCQcgNoAAAAAAAAAAA==" 229 | - limit: 0 230 | - hints: 231 | - x = 5 232 | - took branch if with y = 1 233 | - error: assertion failed: `(left == right)` 234 | left: `3`, 235 | right: `1` 236 | ``` 237 | 238 | ## Stats 239 | 240 | Stats can be used to analyze multiple test runs. Use it to reveal the distribution of 241 | generated test data or the probability of branches: 242 | 243 | ```rust 244 | use dicetest::prelude::*; 245 | 246 | #[test] 247 | fn test_foo() { 248 | Dicetest::repeatedly().run(|mut fate| { 249 | let x = fate.roll(dice::u8(1..=5)); 250 | stat_debug!(x); 251 | 252 | let y = fate.roll(dice::u8(1..=3)); 253 | if y != x { 254 | stat!("branch", "if with y = {}", y) 255 | } else { 256 | stat!("branch", "else"); 257 | } 258 | }) 259 | } 260 | ``` 261 | 262 | Running the test with the environment variable `DICETEST_STATS_ENABLED=true` produces 263 | the following output: 264 | 265 | ```text 266 | The test withstood 200 passes. 267 | 268 | # Config 269 | - seed: 5043079553183914912 270 | - start limit: 0 271 | - end limit: 100 272 | - passes: 200 273 | 274 | # Stats 275 | - branch: 276 | - 29.50% (59): if with y = 1 277 | - 27.50% (55): if with y = 3 278 | - 22.50% (45): if with y = 2 279 | - 20.50% (41): else 280 | - x: 281 | - 31.50% (63): 1 282 | - 22.00% (44): 5 283 | - 17.00% (34): 2 284 | - 15.50% (31): 4 285 | - 14.00% (28): 3 286 | ``` 287 | 288 | ## Environment variables 289 | 290 | You can use environment variables to configure your tests without changing the source code. 291 | See the documentation of [`Dicetest`] for a full list of supported environment variables. 292 | Here are some examples: 293 | 294 | - You want to debug the counterexample of `mytest` with its run code (copied from the test result): 295 | ```text 296 | DICETEST_DEBUG=ABIDje/+CYVkmmCVTwKJ2go6VrzZWMjO2Bqc9m3b3h0DAAAAAAAAAA== cargo test mytest 297 | ``` 298 | - You want to reproduce the result of `mytest` with its seed (copied from the test result): 299 | ```text 300 | DICETEST_SEED=795359663177100823 cargo test mytest 301 | ``` 302 | - You want to see the stats of `mytest`: 303 | ```text 304 | DICETEST_STATS_ENABLED=true cargo test -- --show-output mytest 305 | ``` 306 | - You want to run `mytest` with more passes and bigger test data: 307 | ```text 308 | DICETEST_PASSES_MULTIPLIER=10 DICETEST_LIMIT_MULTIPLIER=2 cargo test mytest 309 | ``` 310 | - You want to run `mytest` with a single test run and see the test result: 311 | ```text 312 | DICETEST_MODE=once cargo test -- --show-output mytest 313 | ``` 314 | 315 | [`Dicetest`]: https://docs.rs/dicetest/latest/dicetest/struct.Dicetest.html 316 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jakob Schikowski 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [github](https://github.com/jakoschiko/dicetest) 2 | [![crates.io](https://img.shields.io/crates/v/dicetest.svg)](https://crates.io/crates/dicetest) 3 | [![Documentation](https://docs.rs/dicetest/badge.svg)](https://docs.rs/dicetest) 4 | [![Build & Test](https://github.com/jakoschiko/dicetest/actions/workflows/main.yml/badge.svg)](https://github.com/jakoschiko/dicetest/actions/workflows/main.yml) 5 | [![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/jakoschiko/dicetest#License) 6 | 7 | # dicetest 8 | 9 | Framework for writing tests with randomly generated test data. 10 | 11 | ## Status of this crate 12 | 13 | The author does not consider this crate as stable yet. Changes will be documented in the 14 | [changelog](https://github.com/jakoschiko/dicetest/blob/main/CHANGELOG.md). 15 | 16 | ## Example 17 | 18 | Here's an example of an incorrect sort function tested with dicetest: 19 | 20 | ```rust,no_run 21 | fn bubble_sort(slice: &mut [T]) { 22 | let len = slice.len(); 23 | 24 | for _ in 0..len { 25 | for j in 1..len - 1 { 26 | let jpp = j + 1; 27 | if slice[j] > slice[jpp] { 28 | slice.swap(j, jpp); 29 | } 30 | } 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use dicetest::prelude::*; 38 | 39 | #[test] 40 | fn result_of_bubble_sort_is_sorted() { 41 | Dicetest::repeatedly().run(|mut fate| { 42 | let mut v = fate.roll(dice::vec(dice::u8(..), ..)); 43 | hint!("unsorted: {:?}", v); 44 | 45 | bubble_sort(&mut v); 46 | hint!(" sorted: {:?}", v); 47 | 48 | let is_sorted = v.windows(2).all(|w| w[0] <= w[1]); 49 | assert!(is_sorted); 50 | }) 51 | } 52 | } 53 | ``` 54 | 55 | Running `cargo test` produces the following output: 56 | 57 | ```text 58 | The test failed after 31 passes. 59 | 60 | # Config 61 | - seed: 3713861809241954222 62 | - start limit: 0 63 | - end limit: 100 64 | - passes: 200 65 | 66 | # Counterexample 67 | - run code: "/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA==" 68 | - limit: 3 69 | - hints: 70 | - unsorted: [201, 209, 2] 71 | - sorted: [201, 2, 209] 72 | - error: assertion failed: is_sorted 73 | ``` 74 | 75 | You can rerun the counterexample by setting an environment variable: 76 | 77 | ```text 78 | DICETEST_DEBUG=/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA== cargo test 79 | ``` 80 | 81 | Or you can modify the test: 82 | 83 | ```rust,ignore 84 | Dicetest::debug("/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA==").run(|mut fate| { 85 | // ... 86 | }) 87 | ``` 88 | 89 | After fixing the bug you can keep the counterexample as a regression test: 90 | 91 | ```rust,ignore 92 | Dicetest::repeatedly() 93 | .regression("/yiA1sab3S4UnCf4ozyMpxMxzg1NtFybCuYLHy0/oscDAAAAAAAAAA==") 94 | .run(|mut fate| { 95 | // ... 96 | }) 97 | ``` 98 | 99 | For a more comprehensive explanation of dicetest, see [the guide](GUIDE.md). 100 | 101 | ## Features 102 | 103 | These features are **available**: 104 | 105 | - Generators for many libstd types (`u8`, `String`, `Vec`, etc.). 106 | - Generators for functions (`FnMut`, `FnOnce`, `Fn`). 107 | - Generator combinators (`map`, `flat_map`, `zip`, etc.). 108 | - Integration of `rand::distributions::Distribution`. 109 | - Configurable test runner. 110 | - Utilities for debugging tests (`hints` and `stats`). 111 | 112 | These features are **missing**: 113 | 114 | - Derivable trait for arbitrary types. 115 | - Shrinking of counterexamples. 116 | - Custom pseudorandom number generators. 117 | 118 | ## Alternatives 119 | 120 | - Write down your test data and use a loop. 121 | - Use the crate [arbitrary] and [arbtest]. 122 | - Use the crate [quickcheck]. 123 | - Use the crate [proptest]. 124 | 125 | [arbitrary]: https://crates.io/crates/arbitrary 126 | [arbtest]: https://crates.io/crates/arbtest 127 | [quickcheck]: https://crates.io/crates/quickcheck 128 | [proptest]: https://crates.io/crates/proptest 129 | 130 | ## License 131 | 132 | Licensed under either of 133 | 134 | * Apache License, Version 2.0 135 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 136 | * MIT license 137 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 138 | 139 | at your option. 140 | 141 | ## Contribution 142 | 143 | Unless you explicitly state otherwise, any contribution intentionally submitted 144 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 145 | dual licensed as above, without any additional terms or conditions. 146 | -------------------------------------------------------------------------------- /examples_readme/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples_readme" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | dicetest = { path = "..", features = ["hints", "stats", "rand"] } 9 | rand = "0.8" 10 | -------------------------------------------------------------------------------- /examples_readme/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | mod section_example { 5 | fn bubble_sort(slice: &mut [T]) { 6 | let len = slice.len(); 7 | 8 | for _ in 0..len { 9 | for j in 1..len - 1 { 10 | let jpp = j + 1; 11 | if slice[j] > slice[jpp] { 12 | slice.swap(j, jpp); 13 | } 14 | } 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::*; 21 | use dicetest::prelude::*; 22 | 23 | #[test] 24 | fn result_of_bubble_sort_is_sorted() { 25 | Dicetest::repeatedly().run(|mut fate| { 26 | let mut v = fate.roll(dice::vec(dice::u8(..), ..)); 27 | hint!("unsorted: {:?}", v); 28 | 29 | bubble_sort(&mut v); 30 | hint!(" sorted: {:?}", v); 31 | 32 | let is_sorted = v.windows(2).all(|w| w[0] <= w[1]); 33 | assert!(is_sorted); 34 | }) 35 | } 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod section_pseudorandomness { 41 | #[test] 42 | fn seed() { 43 | use dicetest::Seed; 44 | 45 | println!("{:?}", Seed(42)); 46 | // Output: Seed(42) 47 | 48 | println!("{:?}", Seed::random()); 49 | // Output: Seed(8019292413750407764) 50 | } 51 | 52 | #[test] 53 | fn prng() { 54 | use dicetest::{Prng, Seed}; 55 | 56 | fn print_random_values(mut prng: Prng) { 57 | for _ in 0..3 { 58 | print!("{:?}, ", prng.next_number()); 59 | } 60 | println!("..."); 61 | } 62 | 63 | print_random_values(Prng::from_seed(Seed(42))); 64 | // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ... 65 | print_random_values(Prng::from_seed(Seed(42))); 66 | // Output: 16628028624323922065, 3476588890713931039, 59688652182557721, ... 67 | print_random_values(Prng::from_seed(Seed::random())); 68 | // Output: 4221507577048064061, 15374206214556255352, 4977687432463843847, ... 69 | print_random_values(Prng::from_seed(Seed::random())); 70 | // Output: 11086225885938422405, 9312304973013875005, 1036200222843160301, ... 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod section_dice { 76 | #[test] 77 | fn die_once() { 78 | use dicetest::prelude::*; 79 | 80 | let xx = "xx".to_string(); 81 | let yy = "yy".to_string(); 82 | 83 | // This generator implements `DieOnce`. 84 | // It chooses one of the `String`s without cloning them. 85 | let xx_or_yy_die = dice::one_of_once().two(xx, yy); 86 | } 87 | 88 | #[test] 89 | fn die() { 90 | use dicetest::prelude::*; 91 | 92 | let xx = "xx".to_string(); 93 | let yy = "yy".to_string(); 94 | 95 | // This generator implements `Die`. 96 | // It chooses one of the `String`s by cloning them. 97 | let xx_or_yy_die = dice::one_of().two(xx, yy); 98 | 99 | // This generator uses `xx_or_yy_die` to generate three `String`s at once. 100 | let three_xx_or_yy_die = dice::array::<_, _, 3>(xx_or_yy_die); 101 | } 102 | 103 | #[test] 104 | fn implement_and_compose() { 105 | use dicetest::prelude::*; 106 | 107 | // A classic die that generates a number between 1 and 6 with uniform distribution. 108 | let classic_die = dice::one_of().six::(1, 2, 3, 4, 5, 6); 109 | 110 | // A loaded die that generates the number 6 more frequently. 111 | let loaded_die = 112 | dice::weighted_one_of().six::((1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 6)); 113 | 114 | // This die generates the result of the function. 115 | let die_from_fn = dice::from_fn(|_| 42); 116 | 117 | // This die generates always the same `String` by cloning it. 118 | let foo_die = dice::just("foo".to_string()); 119 | 120 | // This die generates an arbitrary byte. 121 | let byte_die = dice::u8(..); 122 | 123 | // This die generates a non-zero byte. 124 | let non_zero_byte_die = dice::u8(1..); 125 | 126 | // This die generates a `Vec` that contains an arbitrary number of arbitrary bytes. 127 | let bytes_die = dice::vec(dice::u8(..), ..); 128 | 129 | // This die generates a `Vec` that contains up to 10 arbitrary bytes. 130 | let up_to_ten_bytes_die = dice::vec(dice::u8(..), ..=10); 131 | 132 | // This die generates an arbitrary wrapped byte. 133 | struct WrappedByte(u8); 134 | let wrapped_byte_die = dice::u8(..).map(WrappedByte); 135 | 136 | // This die generates a permutation of `(0..=n)` for an arbitrary `n`. 137 | let permutation_die = dice::length(0..).flat_map(|n| { 138 | let vec = (0..=n).collect::>(); 139 | dice::shuffled_vec(vec) 140 | }); 141 | } 142 | 143 | #[test] 144 | fn fate() { 145 | use dicetest::prelude::*; 146 | use dicetest::{Limit, Prng}; 147 | 148 | // Provides the randomness for the generator and will be mutated when used. 149 | let mut prng = Prng::from_seed(0x5EED.into()); 150 | // Limits the length of dynamic data structures. The generator has only read access. 151 | let limit = Limit(5); 152 | 153 | // Contains all parameters necessary for using `DieOnce` or `Die`. 154 | let mut fate = Fate::new(&mut prng, limit); 155 | 156 | // Generator for a `Vec` with an arbitrary length. 157 | let vec_die = dice::vec(dice::u8(..), ..); 158 | 159 | // Generates a `Vec`. Although `vec_die` can generate a `Vec` with an arbitrary length, 160 | // the length of the actual `Vec` is limited by `limit`. 161 | let vec = fate.roll(vec_die); 162 | assert!(vec.len() <= 5); 163 | 164 | println!("{:?}", vec); 165 | // Output: [252, 231, 153, 0] 166 | } 167 | } 168 | 169 | #[cfg(test)] 170 | mod section_tests { 171 | use dicetest::prelude::*; 172 | 173 | #[test] 174 | fn test_foo() { 175 | // Runs your test with default configuration. 176 | Dicetest::repeatedly().run(|fate| { 177 | // Write your test here. 178 | }); 179 | } 180 | 181 | #[test] 182 | fn test_bar() { 183 | // Runs your test with custom configuration. 184 | Dicetest::repeatedly().passes(10000).run(|fate| { 185 | // Write your test here. 186 | }); 187 | } 188 | } 189 | 190 | #[cfg(test)] 191 | mod section_hints { 192 | use dicetest::prelude::*; 193 | 194 | #[test] 195 | fn test_foo() { 196 | Dicetest::repeatedly().run(|mut fate| { 197 | let x = fate.roll(dice::u8(1..=5)); 198 | hint_debug!(x); 199 | 200 | let y = fate.roll(dice::u8(1..=3)); 201 | if y != x { 202 | hint!("took branch if with y = {}", y); 203 | 204 | assert_eq!(3, y); 205 | } else { 206 | hint!("took branch else"); 207 | } 208 | }) 209 | } 210 | } 211 | 212 | #[cfg(test)] 213 | mod section_stats { 214 | use dicetest::prelude::*; 215 | 216 | #[test] 217 | fn test_foo() { 218 | Dicetest::repeatedly().run(|mut fate| { 219 | let x = fate.roll(dice::u8(1..=5)); 220 | stat_debug!(x); 221 | 222 | let y = fate.roll(dice::u8(1..=3)); 223 | if y != x { 224 | stat!("branch", "if with y = {}", y) 225 | } else { 226 | stat!("branch", "else"); 227 | } 228 | }) 229 | } 230 | } 231 | 232 | #[cfg(test)] 233 | mod section_features { 234 | #[test] 235 | fn rand() { 236 | use dicetest::prelude::*; 237 | use dicetest::{Limit, Prng}; 238 | 239 | let mut prng = Prng::from_seed(0x5EED.into()); 240 | let limit = Limit(5); 241 | let mut fate = Fate::new(&mut prng, limit); 242 | 243 | // Generate a value from a `rand::distributions::Distribution` 244 | let byte: u8 = fate.roll_distribution(rand::distributions::Standard); 245 | println!("{:?}", byte); 246 | // Output: 28 247 | 248 | // Create a `Die` from a `rand::distributions::Distribution` 249 | let byte_die = dice::from_distribution(rand::distributions::Standard); 250 | let bytes_die = dice::vec(byte_die, 1..); 251 | let bytes: Vec = fate.roll(bytes_die); 252 | println!("{:?}", bytes); 253 | // Output: [236, 205, 151, 229] 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: fmt build test clippy doc 2 | 3 | fmt: 4 | cargo fmt 5 | cargo fmt --package examples_readme 6 | 7 | build: 8 | cargo build --all-targets 9 | cargo build --all-targets --no-default-features 10 | cargo build --all-targets --no-default-features --features hints 11 | cargo build --all-targets --no-default-features --features stats 12 | cargo build --all-targets --no-default-features --features rand 13 | cargo build --all-targets --all-features 14 | cargo build --all-targets --package examples_readme 15 | 16 | test: 17 | cargo test -- --format=terse 18 | 19 | clippy: 20 | cargo clippy 21 | cargo clippy --package examples_readme 22 | 23 | doc: 24 | RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 25 | 26 | doc-open: 27 | RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --open 28 | -------------------------------------------------------------------------------- /src/adapters.rs: -------------------------------------------------------------------------------- 1 | //! A collection of [`DieOnce`] and [`Die`] adapters. 2 | //! 3 | //! [`DieOnce`]: crate::DieOnce 4 | //! [`Die`]: crate::Die 5 | 6 | mod map_die; 7 | pub use map_die::MapDie; 8 | 9 | mod flatten_die; 10 | pub use flatten_die::FlattenDie; 11 | 12 | mod flat_map_die; 13 | pub use flat_map_die::FlatMapDie; 14 | 15 | mod boxed_die_once; 16 | pub use boxed_die_once::BoxedDieOnce; 17 | 18 | mod boxed_die; 19 | pub use boxed_die::BoxedDie; 20 | 21 | mod rc_die; 22 | pub use rc_die::RcDie; 23 | 24 | mod arc_die; 25 | pub use arc_die::ArcDie; 26 | -------------------------------------------------------------------------------- /src/adapters/arc_die.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{Die, DieOnce, Fate}; 4 | 5 | /// Adapter for [`Die::arc`]. 6 | #[derive(Clone)] 7 | pub struct ArcDie { 8 | die: Arc>, 9 | } 10 | 11 | impl ArcDie { 12 | pub fn new(die: D) -> Self 13 | where 14 | D: Die + 'static, 15 | { 16 | let die = Arc::new(die); 17 | ArcDie { die } 18 | } 19 | } 20 | 21 | impl Die for ArcDie { 22 | fn roll(&self, fate: Fate) -> T { 23 | self.die.roll(fate) 24 | } 25 | } 26 | 27 | impl DieOnce for ArcDie { 28 | fn roll_once(self, fate: Fate) -> T { 29 | self.roll(fate) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/adapters/boxed_die.rs: -------------------------------------------------------------------------------- 1 | use crate::{Die, DieOnce, Fate}; 2 | 3 | /// Adapter for [`Die::boxed`]. 4 | pub struct BoxedDie<'a, T> { 5 | die: Box + 'a>, 6 | } 7 | 8 | impl<'a, T> BoxedDie<'a, T> { 9 | pub fn new(die: D) -> Self 10 | where 11 | D: Die + 'a, 12 | { 13 | let die = Box::new(die); 14 | BoxedDie { die } 15 | } 16 | } 17 | 18 | impl<'a, T> Die for BoxedDie<'a, T> { 19 | fn roll(&self, fate: Fate) -> T { 20 | self.die.roll(fate) 21 | } 22 | } 23 | 24 | impl<'a, T> DieOnce for BoxedDie<'a, T> { 25 | fn roll_once(self, fate: Fate) -> T { 26 | self.roll(fate) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adapters/boxed_die_once.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{DieOnce, Fate}; 4 | 5 | /// Adapter for [`DieOnce::boxed_once`]. 6 | pub struct BoxedDieOnce<'a, T> 7 | where 8 | T: 'a, 9 | { 10 | die: Box + 'a>, 11 | } 12 | 13 | impl<'a, T> BoxedDieOnce<'a, T> 14 | where 15 | T: 'a, 16 | { 17 | pub fn new(die: D) -> Self 18 | where 19 | D: DieOnce + 'a, 20 | { 21 | let wrapper = DieOnceWrapper { 22 | die: Some(die), 23 | _t: PhantomData, 24 | }; 25 | let die = Box::new(wrapper); 26 | BoxedDieOnce { die } 27 | } 28 | } 29 | 30 | impl<'a, T> DieOnce for BoxedDieOnce<'a, T> 31 | where 32 | T: 'a, 33 | { 34 | fn roll_once(mut self, fate: Fate) -> T { 35 | self.die.roll_once(fate) 36 | } 37 | } 38 | 39 | trait Wrapper { 40 | fn roll_once(&mut self, fate: Fate) -> T; 41 | } 42 | 43 | struct DieOnceWrapper 44 | where 45 | D: DieOnce, 46 | { 47 | die: Option, 48 | _t: PhantomData, 49 | } 50 | 51 | impl Wrapper for DieOnceWrapper 52 | where 53 | D: DieOnce, 54 | { 55 | fn roll_once(&mut self, fate: Fate) -> T { 56 | let die = self.die.take().unwrap(); 57 | die.roll_once(fate) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/adapters/flat_map_die.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Die, DieOnce, Fate}; 4 | 5 | /// Adapter for [`DieOnce::flat_map_once`] and [`Die::flat_map`]. 6 | pub struct FlatMapDie { 7 | t_die: TD, 8 | f: F, 9 | _t: PhantomData, 10 | _u: PhantomData, 11 | _u_die: PhantomData, 12 | } 13 | 14 | impl FlatMapDie { 15 | pub fn new(t_die: TD, f: F) -> Self { 16 | FlatMapDie { 17 | t_die, 18 | f, 19 | _t: PhantomData, 20 | _u: PhantomData, 21 | _u_die: PhantomData, 22 | } 23 | } 24 | } 25 | 26 | impl DieOnce for FlatMapDie 27 | where 28 | TD: DieOnce, 29 | UD: DieOnce, 30 | F: FnOnce(T) -> UD, 31 | { 32 | fn roll_once(self, mut fate: Fate) -> U { 33 | let t_die = self.t_die; 34 | let f = self.f; 35 | 36 | let t = t_die.roll_once(fate.copy()); 37 | let gu = f(t); 38 | gu.roll_once(fate) 39 | } 40 | } 41 | 42 | impl Die for FlatMapDie 43 | where 44 | TD: Die, 45 | UD: DieOnce, 46 | F: Fn(T) -> UD, 47 | { 48 | fn roll(&self, mut fate: Fate) -> U { 49 | let t_die = &self.t_die; 50 | let f = &self.f; 51 | 52 | let t = t_die.roll(fate.copy()); 53 | let gu = f(t); 54 | gu.roll_once(fate) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/adapters/flatten_die.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Die, DieOnce, Fate}; 4 | 5 | /// Adapter for [`DieOnce::flatten_once`] and [`Die::flatten`]. 6 | pub struct FlattenDie { 7 | td_die: TDD, 8 | _t: PhantomData, 9 | _t_die: PhantomData, 10 | } 11 | 12 | impl FlattenDie { 13 | pub fn new(td_die: TDD) -> Self { 14 | FlattenDie { 15 | td_die, 16 | _t: PhantomData, 17 | _t_die: PhantomData, 18 | } 19 | } 20 | } 21 | 22 | impl DieOnce for FlattenDie 23 | where 24 | TD: DieOnce, 25 | TDD: DieOnce, 26 | { 27 | fn roll_once(self, mut fate: Fate) -> T { 28 | let td_die = self.td_die; 29 | 30 | let t_die = td_die.roll_once(fate.copy()); 31 | t_die.roll_once(fate) 32 | } 33 | } 34 | 35 | impl Die for FlattenDie 36 | where 37 | TD: DieOnce, 38 | TDD: Die, 39 | { 40 | fn roll(&self, mut fate: Fate) -> T { 41 | let td_die = &self.td_die; 42 | 43 | let t_die = td_die.roll(fate.copy()); 44 | t_die.roll_once(fate) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/adapters/map_die.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Die, DieOnce, Fate}; 4 | 5 | /// Adapter for [`DieOnce::map_once`] and [`Die::map`]. 6 | pub struct MapDie { 7 | d: D, 8 | f: F, 9 | _t: PhantomData, 10 | _u: PhantomData, 11 | } 12 | 13 | impl MapDie { 14 | pub fn new(d: D, f: F) -> Self { 15 | MapDie { 16 | d, 17 | f, 18 | _t: PhantomData, 19 | _u: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl DieOnce for MapDie 25 | where 26 | D: DieOnce, 27 | F: FnOnce(T) -> U, 28 | { 29 | fn roll_once(self, fate: Fate) -> U { 30 | let d = self.d; 31 | let f = self.f; 32 | 33 | let t = d.roll_once(fate); 34 | f(t) 35 | } 36 | } 37 | 38 | impl Die for MapDie 39 | where 40 | D: Die, 41 | F: Fn(T) -> U, 42 | { 43 | fn roll(&self, fate: Fate) -> U { 44 | let d = &self.d; 45 | let f = &self.f; 46 | 47 | let t = d.roll(fate); 48 | f(t) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/adapters/rc_die.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::{Die, DieOnce, Fate}; 4 | 5 | /// Adapter for [`Die::rc`]. 6 | #[derive(Clone)] 7 | pub struct RcDie<'a, T> { 8 | die: Rc + 'a>, 9 | } 10 | 11 | impl<'a, T> RcDie<'a, T> { 12 | pub fn new(die: D) -> Self 13 | where 14 | D: Die + 'a, 15 | { 16 | let die = Rc::new(die); 17 | RcDie { die } 18 | } 19 | } 20 | 21 | impl<'a, T> Die for RcDie<'a, T> { 22 | fn roll(&self, fate: Fate) -> T { 23 | self.die.roll(fate) 24 | } 25 | } 26 | 27 | impl<'a, T> DieOnce for RcDie<'a, T> { 28 | fn roll_once(self, fate: Fate) -> T { 29 | self.roll(fate) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/asserts.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::prelude::*; 4 | 5 | /// Asserts that `g` is a left inverse for `f`. 6 | pub fn left_inverse( 7 | mut fate: Fate, 8 | x_die: impl DieOnce, 9 | f: impl FnOnce(X) -> Y, 10 | g: impl FnOnce(Y) -> X, 11 | ) where 12 | X: Debug + Clone + PartialEq, 13 | { 14 | let x = fate.roll(x_die); 15 | let y = f(x.clone()); 16 | let other_x = g(y); 17 | 18 | assert_eq!(x, other_x) 19 | } 20 | 21 | /// Asserts that `h` is a right inverse for `f`. 22 | pub fn right_inverse( 23 | mut fate: Fate, 24 | y_die: impl DieOnce, 25 | f: impl FnOnce(X) -> Y, 26 | h: impl FnOnce(Y) -> X, 27 | ) where 28 | Y: Debug + Clone + PartialEq, 29 | { 30 | let y = fate.roll(y_die); 31 | let x = h(y.clone()); 32 | let other_y = f(x); 33 | 34 | assert_eq!(y, other_y) 35 | } 36 | -------------------------------------------------------------------------------- /src/codice.rs: -------------------------------------------------------------------------------- 1 | //! The standard collection of [`Codie`] implementations. 2 | 3 | use std::collections::hash_map::DefaultHasher; 4 | use std::hash::{Hash, Hasher}; 5 | 6 | use crate::prelude::*; 7 | use crate::Seed; 8 | 9 | struct Fun(F); 10 | 11 | impl Codie for Fun 12 | where 13 | F: Fn(T) -> Seed, 14 | { 15 | fn coroll(&self, value: T) -> Seed { 16 | self.0(value) 17 | } 18 | } 19 | 20 | /// Helper for implementing a [`Codie`] from a [`Fn`] that returns a seed. 21 | pub fn from_fn(f: F) -> impl Codie 22 | where 23 | F: Fn(T) -> Seed, 24 | { 25 | Fun(f) 26 | } 27 | 28 | /// Uses libstd's [`DefaultHasher`] to create a seed from a hashable value. 29 | pub fn from_default_hasher() -> impl Codie { 30 | from_fn(|value: T| { 31 | let mut hasher = DefaultHasher::new(); 32 | value.hash(&mut hasher); 33 | hasher.finish().into() 34 | }) 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use crate::codice; 40 | use crate::codice::Codie; 41 | 42 | #[test] 43 | fn from_default_hasher_is_deterministic() { 44 | let codie_0 = codice::from_default_hasher::(); 45 | let codie_1 = codice::from_default_hasher::(); 46 | 47 | assert_eq!(codie_0.coroll(42), codie_1.coroll(42)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/codie.rs: -------------------------------------------------------------------------------- 1 | use crate::Seed; 2 | 3 | /// Trait for converting values of type `T` into a seed. 4 | /// 5 | /// This trait can be seen as the counterpart of [`DieOnce`] and [`Die`]. 6 | /// Instead of generating a value of type `T` from a seed, this trait allows to convert a value 7 | /// of type `T` into a seed. 8 | /// 9 | /// The most important use case is implementing pseudorandom functions. A pseudorandom function 10 | /// can be implemented by converting its arguments into a seed and then using the seed for 11 | /// generating a result. 12 | /// 13 | /// [`DieOnce`]: crate::DieOnce 14 | /// [`Die`]: crate::Die 15 | pub trait Codie { 16 | /// Converts the given value into a seed. The implementation must be deterministic. 17 | fn coroll(&self, value: T) -> Seed; 18 | } 19 | -------------------------------------------------------------------------------- /src/dice.rs: -------------------------------------------------------------------------------- 1 | //! The standard collection of [`DieOnce`] and [`Die`] implementations. 2 | //! 3 | //! [`DieOnce`]: crate::DieOnce 4 | //! [`Die`]: crate::Die 5 | 6 | mod from; 7 | pub use from::*; 8 | 9 | mod todo; 10 | pub use todo::*; 11 | 12 | mod just; 13 | pub use just::*; 14 | 15 | mod zip; 16 | pub use zip::*; 17 | 18 | mod one_of; 19 | pub use one_of::*; 20 | 21 | mod bool; 22 | pub use self::bool::*; 23 | 24 | mod integer; 25 | pub use integer::*; 26 | 27 | mod float; 28 | pub use float::*; 29 | 30 | mod char; 31 | pub use self::char::*; 32 | 33 | mod array; 34 | pub use array::*; 35 | 36 | mod option; 37 | pub use option::*; 38 | 39 | mod result; 40 | pub use result::*; 41 | 42 | mod length; 43 | pub use length::*; 44 | 45 | mod split_integer; 46 | pub use split_integer::*; 47 | 48 | mod collection; 49 | pub use collection::*; 50 | 51 | mod vec; 52 | pub use vec::*; 53 | 54 | mod vec_deque; 55 | pub use vec_deque::*; 56 | 57 | mod linked_list; 58 | pub use linked_list::*; 59 | 60 | mod hash_map; 61 | pub use hash_map::*; 62 | 63 | mod b_tree_map; 64 | pub use b_tree_map::*; 65 | 66 | mod hash_set; 67 | pub use hash_set::*; 68 | 69 | mod b_tree_set; 70 | pub use b_tree_set::*; 71 | 72 | mod binary_heap; 73 | pub use binary_heap::*; 74 | 75 | mod string; 76 | pub use string::*; 77 | 78 | mod shuffle; 79 | pub use shuffle::*; 80 | 81 | mod split_vec; 82 | pub use split_vec::*; 83 | 84 | mod fn_builder; 85 | pub use fn_builder::*; 86 | 87 | mod index_of; 88 | pub use index_of::*; 89 | 90 | #[cfg(feature = "rand")] 91 | mod rand; 92 | #[cfg(feature = "rand")] 93 | pub use self::rand::*; 94 | -------------------------------------------------------------------------------- /src/dice/array.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates an array with random elements. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use dicetest::prelude::*; 9 | /// use dicetest::{Prng, Limit}; 10 | /// 11 | /// let mut prng = Prng::from_seed(0x5EED.into()); 12 | /// let limit = Limit::default(); 13 | /// let mut fate = Fate::new(&mut prng, limit); 14 | /// 15 | /// let byte_die = dice::u8(..); 16 | /// let bytes_die = dice::array(byte_die); 17 | /// let [a, b, c, d] = fate.roll(bytes_die); 18 | /// ``` 19 | pub fn array, const N: usize>(elem_die: D) -> impl Die<[T; N]> { 20 | dice::from_fn(move |mut fate| [(); N].map(|_| fate.roll(&elem_die))) 21 | } 22 | -------------------------------------------------------------------------------- /src/dice/b_tree_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use crate::dice::{CollectionBuilder, LengthRange}; 4 | use crate::prelude::*; 5 | 6 | /// [`BTreeMap`] builder for [`dice::collection`]. 7 | /// 8 | /// [`dice::collection`]: dice::collection() 9 | pub struct BTreeMapBuilder; 10 | 11 | impl BTreeMapBuilder { 12 | fn die() -> impl Die { 13 | dice::from_fn(|_fate| Self) 14 | } 15 | } 16 | 17 | impl CollectionBuilder<(K, V), BTreeMap> for BTreeMapBuilder 18 | where 19 | K: Ord, 20 | { 21 | fn build(self, elems: impl ExactSizeIterator) -> BTreeMap { 22 | elems.collect() 23 | } 24 | } 25 | 26 | /// Generates a [`BTreeMap`] that contains keys of type `K` with values of type `V`. 27 | /// 28 | /// The range specifies the number of tries to generate key-value entries with distinct keys. 29 | /// 30 | /// # Panics 31 | /// 32 | /// Panics if the range is empty. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use dicetest::prelude::*; 38 | /// use dicetest::{Prng, Limit}; 39 | /// 40 | /// let mut prng = Prng::from_seed(0x5EED.into()); 41 | /// let limit = Limit::default(); 42 | /// let mut fate = Fate::new(&mut prng, limit); 43 | /// 44 | /// let elem_die = dice::zip().two(dice::u8(..), dice::char()); 45 | /// 46 | /// let map = fate.with_limit(100.into()).roll(dice::b_tree_map(&elem_die, ..)); 47 | /// assert!(map.len() <= 100); 48 | /// 49 | /// let map = fate.roll(dice::b_tree_map(&elem_die, ..=73)); 50 | /// assert!(map.len() <= 73); 51 | /// 52 | /// let map = fate.roll(dice::b_tree_map(&elem_die, 17..)); 53 | /// assert!(map.len() >= 17); 54 | /// 55 | /// let map = fate.roll(dice::b_tree_map(&elem_die, 42)); 56 | /// assert!(map.len() <= 42); 57 | /// ``` 58 | pub fn b_tree_map( 59 | elem_die: impl Die<(K, V)>, 60 | tries_range: impl LengthRange, 61 | ) -> impl Die> 62 | where 63 | K: Ord, 64 | { 65 | dice::collection(BTreeMapBuilder::die(), elem_die, tries_range) 66 | } 67 | 68 | /// Similar to [`dice::b_tree_map`] but each element is generated using only a random part of 69 | /// [`Limit`]. 70 | /// 71 | /// If you want to generate a [`BTreeMap`] that contains other collections, then you should 72 | /// consider using this generator for the outer [`BTreeMap`]. That way the overall length is 73 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 74 | /// 75 | /// [`Limit`]: crate::Limit 76 | /// [`dice::b_tree_map`]: dice::b_tree_map() 77 | /// 78 | /// # Panics 79 | /// 80 | /// Panics if the range is empty. 81 | /// 82 | /// # Examples 83 | /// 84 | /// ``` 85 | /// use dicetest::prelude::*; 86 | /// use dicetest::{Prng, Limit}; 87 | /// 88 | /// let mut prng = Prng::from_seed(0x5EED.into()); 89 | /// let limit = Limit::default(); 90 | /// let mut fate = Fate::new(&mut prng, limit); 91 | /// 92 | /// let elem_die = dice::char(); 93 | /// let vec_die = dice::zip().two(dice::u8(..), dice::vec(elem_die, ..)); 94 | /// let map_of_vecs_die = dice::outer_b_tree_map(vec_die, ..); 95 | /// 96 | /// let map_of_vecs = fate.roll(map_of_vecs_die); 97 | /// assert!(map_of_vecs.values().flatten().count() <= 100); 98 | /// ``` 99 | pub fn outer_b_tree_map( 100 | elem_die: impl Die<(K, V)>, 101 | tries_range: impl LengthRange, 102 | ) -> impl Die> 103 | where 104 | K: Ord, 105 | { 106 | dice::outer_collection(BTreeMapBuilder::die(), elem_die, tries_range) 107 | } 108 | -------------------------------------------------------------------------------- /src/dice/b_tree_set.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use crate::dice::{CollectionBuilder, LengthRange}; 4 | use crate::prelude::*; 5 | 6 | /// [`BTreeSet`] builder for [`dice::collection`]. 7 | /// 8 | /// [`dice::collection`]: dice::collection() 9 | pub struct BTreeSetBuilder; 10 | 11 | impl BTreeSetBuilder { 12 | fn die() -> impl Die { 13 | dice::from_fn(|_fate| Self) 14 | } 15 | } 16 | 17 | impl CollectionBuilder> for BTreeSetBuilder 18 | where 19 | T: Ord, 20 | { 21 | fn build(self, elems: impl ExactSizeIterator) -> BTreeSet { 22 | elems.collect() 23 | } 24 | } 25 | 26 | /// Generates a [`BTreeSet`] that contains elements of type `T`. 27 | /// 28 | /// The range specifies the number of tries to generate distinct elements. 29 | /// 30 | /// # Panics 31 | /// 32 | /// Panics if the range is empty. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use dicetest::prelude::*; 38 | /// use dicetest::{Prng, Limit}; 39 | /// 40 | /// let mut prng = Prng::from_seed(0x5EED.into()); 41 | /// let limit = Limit::default(); 42 | /// let mut fate = Fate::new(&mut prng, limit); 43 | /// 44 | /// let elem_die = dice::u8(..); 45 | /// 46 | /// let set = fate.with_limit(100.into()).roll(dice::b_tree_set(&elem_die, ..)); 47 | /// assert!(set.len() <= 100); 48 | /// 49 | /// let set = fate.roll(dice::b_tree_set(&elem_die, ..=73)); 50 | /// assert!(set.len() <= 73); 51 | /// 52 | /// let set = fate.roll(dice::b_tree_set(&elem_die, 17..)); 53 | /// assert!(set.len() >= 17); 54 | /// 55 | /// let set = fate.roll(dice::b_tree_set(&elem_die, 42)); 56 | /// assert!(set.len() <= 42); 57 | /// ``` 58 | pub fn b_tree_set(elem_die: impl Die, tries_range: impl LengthRange) -> impl Die> 59 | where 60 | T: Ord, 61 | { 62 | dice::collection(BTreeSetBuilder::die(), elem_die, tries_range) 63 | } 64 | 65 | /// Similar to [`dice::b_tree_set`] but each element is generated using only a random part of 66 | /// [`Limit`]. 67 | /// 68 | /// If you want to generate a [`BTreeSet`] that contains other collections, then you should 69 | /// consider using this generator for the outer [`BTreeSet`]. That way the overall length is 70 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 71 | /// 72 | /// [`Limit`]: crate::Limit 73 | /// [`dice::b_tree_set`]: dice::b_tree_set() 74 | /// 75 | /// # Panics 76 | /// 77 | /// Panics if the range is empty. 78 | /// 79 | /// # Examples 80 | /// 81 | /// ``` 82 | /// use dicetest::prelude::*; 83 | /// use dicetest::{Prng, Limit}; 84 | /// 85 | /// let mut prng = Prng::from_seed(0x5EED.into()); 86 | /// let limit = Limit::default(); 87 | /// let mut fate = Fate::new(&mut prng, limit); 88 | /// 89 | /// let elem_die = dice::u8(..); 90 | /// let vec_die = dice::vec(elem_die, ..); 91 | /// let set_of_vecs_die = dice::outer_b_tree_set(vec_die, ..); 92 | /// 93 | /// let set_of_vecs = fate.roll(set_of_vecs_die); 94 | /// assert!(set_of_vecs.iter().flatten().count() <= 100); 95 | /// ``` 96 | pub fn outer_b_tree_set( 97 | elem_die: impl Die, 98 | tries_range: impl LengthRange, 99 | ) -> impl Die> 100 | where 101 | T: Ord, 102 | { 103 | dice::outer_collection(BTreeSetBuilder::die(), elem_die, tries_range) 104 | } 105 | -------------------------------------------------------------------------------- /src/dice/binary_heap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | 3 | use crate::dice::{CollectionBuilder, LengthRange}; 4 | use crate::prelude::*; 5 | 6 | /// [`BinaryHeap`] builder for [`dice::collection`]. 7 | /// 8 | /// [`dice::collection`]: dice::collection() 9 | pub struct BinaryHeapBuilder; 10 | 11 | impl BinaryHeapBuilder { 12 | fn die() -> impl Die { 13 | dice::from_fn(|_fate| Self) 14 | } 15 | } 16 | 17 | impl CollectionBuilder> for BinaryHeapBuilder 18 | where 19 | T: Ord, 20 | { 21 | fn build(self, elems: impl ExactSizeIterator) -> BinaryHeap { 22 | let mut heap = BinaryHeap::with_capacity(elems.len()); 23 | heap.extend(elems); 24 | heap 25 | } 26 | } 27 | 28 | /// Generates a [`BinaryHeap`] that contains elements of type `T`. 29 | /// 30 | /// The range specifies the length of the [`BinaryHeap`]. 31 | /// 32 | /// # Panics 33 | /// 34 | /// Panics if the range is empty. 35 | /// 36 | /// # Examples 37 | /// 38 | /// ``` 39 | /// use dicetest::prelude::*; 40 | /// use dicetest::{Prng, Limit}; 41 | /// 42 | /// let mut prng = Prng::from_seed(0x5EED.into()); 43 | /// let limit = Limit::default(); 44 | /// let mut fate = Fate::new(&mut prng, limit); 45 | /// 46 | /// let elem_die = dice::u8(..); 47 | /// 48 | /// let heap = fate.with_limit(100.into()).roll(dice::binary_heap(&elem_die, ..)); 49 | /// assert!(heap.len() <= 100); 50 | /// 51 | /// let heap = fate.roll(dice::binary_heap(&elem_die, ..=73)); 52 | /// assert!(heap.len() <= 73); 53 | /// 54 | /// let heap = fate.roll(dice::binary_heap(&elem_die, 17..)); 55 | /// assert!(heap.len() >= 17); 56 | /// 57 | /// let heap = fate.roll(dice::binary_heap(&elem_die, 42)); 58 | /// assert!(heap.len() == 42); 59 | /// ``` 60 | pub fn binary_heap( 61 | elem_die: impl Die, 62 | length_range: impl LengthRange, 63 | ) -> impl Die> 64 | where 65 | T: Ord, 66 | { 67 | dice::collection(BinaryHeapBuilder::die(), elem_die, length_range) 68 | } 69 | 70 | /// Similar to [`dice::binary_heap`] but each element is generated using only a random part of 71 | /// [`Limit`]. 72 | /// 73 | /// If you want to generate a [`BinaryHeap`] that contains other collections, then you should 74 | /// consider using this generator for the outer [`BinaryHeap`]. That way the overall length is 75 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 76 | /// 77 | /// [`Limit`]: crate::Limit 78 | /// [`dice::binary_heap`]: dice::binary_heap() 79 | /// 80 | /// # Panics 81 | /// 82 | /// Panics if the range is empty. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ``` 87 | /// use dicetest::prelude::*; 88 | /// use dicetest::{Prng, Limit}; 89 | /// 90 | /// let mut prng = Prng::from_seed(0x5EED.into()); 91 | /// let limit = Limit::default(); 92 | /// let mut fate = Fate::new(&mut prng, limit); 93 | /// 94 | /// let elem_die = dice::u8(..); 95 | /// let vec_die = dice::vec(elem_die, ..); 96 | /// let heap_of_vecs_die = dice::outer_binary_heap(vec_die, ..); 97 | /// 98 | /// let heap_of_vecs = fate.roll(heap_of_vecs_die); 99 | /// assert!(heap_of_vecs.iter().flatten().count() <= 100); 100 | /// ``` 101 | pub fn outer_binary_heap( 102 | elem_die: impl Die, 103 | tries_range: impl LengthRange, 104 | ) -> impl Die> 105 | where 106 | T: Ord, 107 | { 108 | dice::outer_collection(BinaryHeapBuilder::die(), elem_die, tries_range) 109 | } 110 | -------------------------------------------------------------------------------- /src/dice/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates `true` or `false` with the same probability. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use dicetest::prelude::*; 9 | /// use dicetest::{Prng, Limit}; 10 | /// 11 | /// let mut prng = Prng::from_seed(0x5EED.into()); 12 | /// let limit = Limit::default(); 13 | /// let mut fate = Fate::new(&mut prng, limit); 14 | /// 15 | /// let true_or_false = fate.roll(dice::bool()); 16 | /// assert!(true_or_false == true || true_or_false == false); 17 | /// ``` 18 | pub fn bool() -> impl Die { 19 | dice::one_of().two(false, true) 20 | } 21 | 22 | /// Generates `true` or `false` with probabilities based on the given weights. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use dicetest::prelude::*; 28 | /// use dicetest::{Prng, Limit}; 29 | /// 30 | /// let mut prng = Prng::from_seed(0x5EED.into()); 31 | /// let limit = Limit::default(); 32 | /// let mut fate = Fate::new(&mut prng, limit); 33 | /// 34 | /// let more_often_true_than_false = fate.roll(dice::weighted_bool(10, 1)); 35 | /// assert!(more_often_true_than_false == true || more_often_true_than_false == false); 36 | /// ``` 37 | pub fn weighted_bool(false_weight: u32, true_weight: u32) -> impl Die { 38 | dice::weighted_one_of().two((false_weight, false), (true_weight, true)) 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use crate::prelude::*; 44 | 45 | #[test] 46 | fn bool_calc_stats() { 47 | Dicetest::repeatedly() 48 | .passes(0) 49 | .stats_enabled(true) 50 | .run(|mut fate| { 51 | stat!("bool()", "{}", fate.roll(dice::bool())); 52 | }) 53 | } 54 | 55 | #[test] 56 | fn weighted_bool_calc_stats() { 57 | Dicetest::repeatedly() 58 | .passes(0) 59 | .stats_enabled(true) 60 | .run(|mut fate| { 61 | stat!( 62 | "weighted_bool(1, 2)", 63 | "{}", 64 | fate.roll(dice::weighted_bool(1, 2)), 65 | ); 66 | stat!( 67 | "weighted_bool(10, 1)", 68 | "{}", 69 | fate.roll(dice::weighted_bool(9, 1)), 70 | ); 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/dice/char.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | fn char_in_range(lower: u32, upper: u32) -> impl Die { 4 | dice::uni_u32(lower..=upper) 5 | .map(std::char::from_u32) 6 | .map(Option::unwrap) 7 | } 8 | 9 | /// Generator for lowercase letters from the ASCII alphabet ('a' to 'z'). 10 | pub fn char_ascii_alphabetic_lowercase() -> impl Die { 11 | char_in_range('a' as u32, 'z' as u32) 12 | } 13 | 14 | /// Generator for uppercase letters from the ASCII alphabet ('A' to 'Z'). 15 | pub fn char_ascii_alphabetic_uppercase() -> impl Die { 16 | char_in_range('A' as u32, 'Z' as u32) 17 | } 18 | 19 | /// Generator for letters from the ASCII alphabet ('a' to 'z' and 'A' to 'Z'). 20 | pub fn char_ascii_alphabetic() -> impl Die { 21 | dice::one_of_die().two( 22 | char_ascii_alphabetic_lowercase(), 23 | char_ascii_alphabetic_uppercase(), 24 | ) 25 | } 26 | 27 | /// Generator for ASCII digits ('0' to '9'). 28 | pub fn char_ascii_digit() -> impl Die { 29 | char_in_range('0' as u32, '9' as u32) 30 | } 31 | 32 | /// Generator for lowercase and uppercase letters from 33 | /// the ASCII alphabet ('a' to 'z' and 'A' to 'Z') and 34 | /// ASCII digits ('0' to '9'). 35 | pub fn char_ascii_alphanumeric() -> impl Die { 36 | dice::one_of_die().three( 37 | char_ascii_alphabetic_lowercase(), 38 | char_ascii_alphabetic_uppercase(), 39 | char_ascii_digit(), 40 | ) 41 | } 42 | 43 | /// Generator for [printable ASCII] characters. 44 | /// 45 | /// [printable ASCII]: https://en.wikipedia.org/wiki/ASCII#Printable_characters 46 | pub fn char_ascii_printable() -> impl Die { 47 | char_in_range(0x20, 0x7E) 48 | } 49 | 50 | /// Generator for [ASCII] characters. 51 | /// 52 | /// Note that not all characters are printable. 53 | /// 54 | /// [ASCII]: https://en.wikipedia.org/wiki/ASCII 55 | pub fn char_ascii() -> impl Die { 56 | char_in_range(0x0, 0x7F) 57 | } 58 | 59 | /// Generator for valid [`char`]s. 60 | /// 61 | /// A [`char`] represents an [unicode scalar value]. 62 | /// 63 | /// [`char`]: prim@char 64 | /// [unicode scalar value]: http://www.unicode.org/glossary/#unicode_scalar_value 65 | pub fn char() -> impl Die { 66 | dice::one_of_die().two( 67 | char_in_range(0x0, 0xD7FF), 68 | char_in_range(0xE000, 0x0010_FFFF), 69 | ) 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use crate::prelude::*; 75 | 76 | #[test] 77 | fn char_ascii_alphabetic_lowercase_generates_only_valid_values() { 78 | Dicetest::repeatedly().run(|mut fate| { 79 | let char = fate.roll(dice::char_ascii_alphabetic_lowercase()); 80 | 81 | hint_debug!(char); 82 | 83 | assert!(char.is_ascii_alphabetic()); 84 | assert!(char.is_ascii_lowercase()); 85 | }) 86 | } 87 | 88 | #[test] 89 | fn char_ascii_alphabetic_uppercase_generates_only_valid_values() { 90 | Dicetest::repeatedly().run(|mut fate| { 91 | let char = fate.roll(dice::char_ascii_alphabetic_uppercase()); 92 | 93 | hint_debug!(char); 94 | 95 | assert!(char.is_ascii_alphabetic()); 96 | assert!(char.is_ascii_uppercase()); 97 | }) 98 | } 99 | 100 | #[test] 101 | fn char_ascii_alphabetic_generates_only_valid_values() { 102 | Dicetest::repeatedly().run(|mut fate| { 103 | let char = fate.roll(dice::char_ascii_alphabetic()); 104 | 105 | hint_debug!(char); 106 | 107 | assert!(char.is_ascii_alphabetic()); 108 | }) 109 | } 110 | 111 | #[test] 112 | fn char_ascii_digit_generates_only_valid_values() { 113 | Dicetest::repeatedly().run(|mut fate| { 114 | let char = fate.roll(dice::char_ascii_digit()); 115 | 116 | hint_debug!(char); 117 | 118 | assert!(char.is_ascii_digit()); 119 | }) 120 | } 121 | 122 | #[test] 123 | fn char_ascii_alphanumeric_generates_only_valid_values() { 124 | Dicetest::repeatedly().run(|mut fate| { 125 | let char = fate.roll(dice::char_ascii_alphanumeric()); 126 | 127 | hint_debug!(char); 128 | 129 | assert!(char.is_ascii_alphanumeric()); 130 | }) 131 | } 132 | 133 | #[test] 134 | fn char_ascii_printable_generates_only_valid_values() { 135 | Dicetest::repeatedly().run(|mut fate| { 136 | let char = fate.roll(dice::char_ascii_printable()); 137 | 138 | hint_debug!(char); 139 | 140 | assert!(char.is_ascii()); 141 | assert!(!char.is_ascii_control()); 142 | }) 143 | } 144 | 145 | #[test] 146 | fn char_ascii_generates_only_valid_values() { 147 | Dicetest::repeatedly().run(|mut fate| { 148 | let char = fate.roll(dice::char_ascii()); 149 | 150 | hint_debug!(char); 151 | 152 | assert!(char.is_ascii()); 153 | }) 154 | } 155 | 156 | #[test] 157 | fn char_generates_only_valid_values() { 158 | Dicetest::repeatedly().run(|mut fate| { 159 | let char = fate.roll(dice::char()); 160 | 161 | hint_debug!(char); 162 | 163 | assert!(std::char::from_u32(char as u32).is_some()); 164 | }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/dice/collection.rs: -------------------------------------------------------------------------------- 1 | use crate::dice::LengthRange; 2 | use crate::prelude::*; 3 | 4 | /// A collection builder for [`dice::collection`]. 5 | /// 6 | /// The collection has the type `C` and contains elements of type `T`. 7 | /// 8 | /// [`dice::collection`]: dice::collection() 9 | pub trait CollectionBuilder { 10 | /// Build a collection from the given elements. 11 | fn build(self, elems: impl ExactSizeIterator) -> C; 12 | } 13 | 14 | /// Generates a collection of type `C` that contains elements of type `T`. 15 | /// 16 | /// The collection is created as follows: 17 | /// * The [`CollectionBuilder`] is generated. 18 | /// * The element count is generated using the given range. 19 | /// * The elements are generated. 20 | /// * The generated elements are passed to [`CollectionBuilder::build`]. 21 | /// 22 | /// # Panics 23 | /// 24 | /// Panics if the range is empty. 25 | pub fn collection( 26 | builder_die: impl Die, 27 | elem_die: impl Die, 28 | length_range: impl LengthRange, 29 | ) -> impl Die 30 | where 31 | B: CollectionBuilder, 32 | { 33 | let length_die = dice::length(length_range); 34 | dice::from_fn(move |mut fate| { 35 | let builder = fate.roll(&builder_die); 36 | let length = fate.roll(&length_die); 37 | let elems = (0..length).map(|_| fate.roll(&elem_die)); 38 | builder.build(elems) 39 | }) 40 | } 41 | 42 | /// Similar to [`dice::collection`] but each element is generated using only a random part of 43 | /// [`Limit`]. 44 | /// 45 | /// If you want to generate a collection that contains other collections, then you should 46 | /// consider using this generator for the outer collection. That way the overall length is 47 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 48 | /// 49 | /// [`Limit`]: crate::Limit 50 | /// [`dice::collection`]: dice::collection() 51 | /// 52 | /// # Panics 53 | /// 54 | /// Panics if the range is empty. 55 | pub fn outer_collection( 56 | builder_die: impl Die, 57 | elem_die: impl Die, 58 | length_range: impl LengthRange, 59 | ) -> impl Die 60 | where 61 | B: CollectionBuilder, 62 | { 63 | let length_die = dice::length(length_range); 64 | dice::from_fn(move |mut fate| { 65 | let builder = fate.roll(&builder_die); 66 | let length = fate.roll(&length_die); 67 | let elem_limits = if length == 0 { 68 | Vec::new() 69 | } else { 70 | fate.roll(dice::split_limit_n(fate.limit(), length)) 71 | }; 72 | let elems = elem_limits 73 | .into_iter() 74 | .map(|limit| fate.with_limit(limit).roll(&elem_die)); 75 | builder.build(elems) 76 | }) 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use crate::prelude::*; 82 | use crate::Limit; 83 | 84 | #[test] 85 | fn outer_collection_overall_length_is_bounded_by_limit() { 86 | Dicetest::repeatedly().run(|mut fate| { 87 | let length = fate.roll(dice::length(..)); 88 | 89 | let limit = Limit::saturating_from_usize(length); 90 | 91 | pub struct TestBuilder; 92 | 93 | impl dice::CollectionBuilder> for TestBuilder { 94 | fn build(self, elems: impl ExactSizeIterator) -> Vec { 95 | elems.collect() 96 | } 97 | } 98 | 99 | let builder_die = dice::from_fn(|_| TestBuilder); 100 | let elem_die = dice::u8(..); 101 | let vec_die = dice::collection(&builder_die, elem_die, ..); 102 | let vec_of_vecs_die = dice::outer_collection(&builder_die, vec_die, ..); 103 | let vec_of_vecs = fate.with_limit(limit).roll(vec_of_vecs_die); 104 | 105 | let overall_length = vec_of_vecs.iter().flatten().count(); 106 | assert!(overall_length <= length); 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/dice/fn_builder.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::prelude::*; 4 | use crate::{Limit, Prng}; 5 | 6 | /// The value generated by [`dice::fn_builder`]. 7 | /// 8 | /// [`dice::fn_builder`]: dice::fn_builder() 9 | pub struct FnBuilder { 10 | input_codie: IC, 11 | output_die: OD, 12 | prng: Prng, 13 | limit: Limit, 14 | _i: PhantomData, 15 | _o: PhantomData, 16 | } 17 | 18 | impl FnBuilder 19 | where 20 | IC: Codie, 21 | OD: DieOnce, 22 | { 23 | pub fn build_fn_once(mut self) -> impl FnOnce(I) -> O { 24 | move |input| { 25 | let output_die = self.output_die; 26 | let randomness = self.input_codie.coroll(input); 27 | let prng = &mut self.prng; 28 | prng.reseed(randomness); 29 | let limit = self.limit; 30 | let fate = Fate::new(prng, limit); 31 | output_die.roll_once(fate) 32 | } 33 | } 34 | } 35 | 36 | impl FnBuilder 37 | where 38 | IC: Codie, 39 | OD: Die, 40 | { 41 | pub fn build_fn(self) -> impl Fn(I) -> O { 42 | move |input| { 43 | let output_die = &self.output_die; 44 | let randomness = self.input_codie.coroll(input); 45 | let prng = &mut self.prng.clone(); 46 | prng.reseed(randomness); 47 | let limit = self.limit; 48 | let fate = Fate::new(prng, limit); 49 | output_die.roll(fate) 50 | } 51 | } 52 | 53 | pub fn build_fn_mut(mut self) -> impl FnMut(I) -> O { 54 | move |input| { 55 | let output_die = &self.output_die; 56 | let randomness = self.input_codie.coroll(input); 57 | let prng = &mut self.prng; 58 | prng.reseed(randomness); 59 | let limit = self.limit; 60 | let fate = Fate::new(prng, limit); 61 | output_die.roll(fate) 62 | } 63 | } 64 | } 65 | 66 | /// Generates a function builder. 67 | /// 68 | /// The builder can be converted into an implementation of [`FnOnce`], [`FnMut`] or [`Fn`]. 69 | /// 70 | /// # Examples 71 | /// 72 | /// This example generates a [`FnOnce`]: 73 | /// ``` 74 | /// use dicetest::prelude::*; 75 | /// use dicetest::{Prng, Limit}; 76 | /// 77 | /// let mut prng = Prng::from_seed(0x5EED.into()); 78 | /// let limit = Limit::default(); 79 | /// let mut fate = Fate::new(&mut prng, limit); 80 | /// 81 | /// let f = fate.roll(dice::fn_builder( 82 | /// codice::from_default_hasher(), 83 | /// dice::u8(..), 84 | /// )).build_fn_once(); 85 | /// 86 | /// let x = f(42); 87 | /// ``` 88 | /// 89 | /// This example generates a [`FnMut`]: 90 | /// ``` 91 | /// use dicetest::prelude::*; 92 | /// use dicetest::{Prng, Limit}; 93 | /// 94 | /// let mut prng = Prng::from_seed(0x5EED.into()); 95 | /// let limit = Limit::default(); 96 | /// let mut fate = Fate::new(&mut prng, limit); 97 | /// 98 | /// let mut f = fate.roll(dice::fn_builder( 99 | /// codice::from_default_hasher(), 100 | /// dice::u8(..), 101 | /// )).build_fn_mut(); 102 | /// 103 | /// let x = f(42); 104 | /// let y = f(42); 105 | /// ``` 106 | /// 107 | /// This example generates a [`Fn`]: 108 | /// ``` 109 | /// use dicetest::prelude::*; 110 | /// use dicetest::{Prng, Limit}; 111 | /// 112 | /// let mut prng = Prng::from_seed(0x5EED.into()); 113 | /// let limit = Limit::default(); 114 | /// let mut fate = Fate::new(&mut prng, limit); 115 | /// 116 | /// let f = fate.roll(dice::fn_builder( 117 | /// codice::from_default_hasher(), 118 | /// dice::u8(..), 119 | /// )).build_fn(); 120 | /// 121 | /// let x = f(42); 122 | /// let y = f(42); 123 | /// assert_eq!(x, y); 124 | /// ``` 125 | pub fn fn_builder( 126 | input_codie: IC, 127 | output_die: OD, 128 | ) -> impl DieOnce> 129 | where 130 | IC: Codie, 131 | OD: DieOnce, 132 | { 133 | dice::from_fn_once(|mut fate| FnBuilder { 134 | input_codie, 135 | output_die, 136 | prng: fate.fork_prng(), 137 | limit: fate.limit(), 138 | _i: PhantomData, 139 | _o: PhantomData, 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /src/dice/from.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | struct Fun(F); 4 | 5 | impl DieOnce for Fun 6 | where 7 | F: FnOnce(Fate) -> T, 8 | { 9 | fn roll_once(self, fate: Fate) -> T { 10 | (self.0)(fate) 11 | } 12 | } 13 | 14 | impl Die for Fun 15 | where 16 | F: Fn(Fate) -> T, 17 | { 18 | fn roll(&self, fate: Fate) -> T { 19 | (self.0)(fate) 20 | } 21 | } 22 | 23 | /// Helper for implementing a [`DieOnce`] from a [`FnOnce`] that takes a [`Fate`]. 24 | /// 25 | /// # Examples 26 | /// 27 | /// ``` 28 | /// use dicetest::prelude::*; 29 | /// use dicetest::{Prng, Limit}; 30 | /// 31 | /// let mut prng = Prng::from_seed(0x5EED.into()); 32 | /// let limit = Default::default(); 33 | /// let mut fate = Fate::new(&mut prng, limit); 34 | /// 35 | /// let zero_or_one = fate.roll(dice::from_fn_once(|mut fate| fate.next_number() % 2)); 36 | /// assert!(zero_or_one == 0 || zero_or_one == 1); 37 | /// 38 | /// #[derive(Debug, PartialEq, Eq)] 39 | /// struct CannotBeCloned; 40 | /// let not_a_clone = fate.roll(dice::from_fn_once(|_| CannotBeCloned)); 41 | /// assert_eq!(not_a_clone, CannotBeCloned); 42 | /// ``` 43 | pub fn from_fn_once(f: F) -> impl DieOnce 44 | where 45 | F: FnOnce(Fate) -> T, 46 | { 47 | Fun(f) 48 | } 49 | 50 | /// Helper for implementing a [`Die`] from a [`Fn`] that takes a [`Fate`]. 51 | /// 52 | /// # Examples 53 | /// 54 | /// ``` 55 | /// use dicetest::prelude::*; 56 | /// use dicetest::{Prng, Limit}; 57 | /// 58 | /// let mut prng = Prng::from_seed(0x5EED.into()); 59 | /// let limit = Limit::default(); 60 | /// let mut fate = Fate::new(&mut prng, limit); 61 | /// 62 | /// let zero_or_one = fate.roll(dice::from_fn(|mut fate| fate.next_number() % 2)); 63 | /// assert!(zero_or_one == 0 || zero_or_one == 1); 64 | /// 65 | /// let vec = vec![0, 1, 2]; 66 | /// let cloning_die = dice::from_fn(move |_| vec.clone()); 67 | /// for _ in 0..10 { 68 | /// assert_eq!(fate.roll(&cloning_die), vec![0, 1, 2]); 69 | /// } 70 | /// ``` 71 | pub fn from_fn(f: F) -> impl Die 72 | where 73 | F: Fn(Fate) -> T, 74 | { 75 | Fun(f) 76 | } 77 | -------------------------------------------------------------------------------- /src/dice/hash_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::{BuildHasher, Hash}; 3 | 4 | use crate::dice::{CollectionBuilder, LengthRange}; 5 | use crate::prelude::*; 6 | use crate::Prng; 7 | 8 | /// [`HashMap`] builder for [`dice::collection`]. 9 | /// 10 | /// [`dice::collection`]: dice::collection() 11 | pub struct HashMapBuilder 12 | where 13 | S: BuildHasher, 14 | { 15 | build_hasher: S, 16 | } 17 | 18 | impl HashMapBuilder 19 | where 20 | S: BuildHasher, 21 | { 22 | /// Creates a builder that uses the given [`BuildHasher`] for constructing a [`HashMap`]. 23 | pub fn with_hasher(build_hasher: S) -> Self { 24 | HashMapBuilder { build_hasher } 25 | } 26 | } 27 | 28 | impl HashMapBuilder { 29 | fn die() -> impl Die { 30 | dice::from_fn(|mut fate| Self::with_hasher(fate.fork_prng())) 31 | } 32 | } 33 | 34 | impl CollectionBuilder<(K, V), HashMap> for HashMapBuilder 35 | where 36 | K: Eq + Hash, 37 | S: BuildHasher, 38 | { 39 | fn build(self, elems: impl ExactSizeIterator) -> HashMap { 40 | let build_hasher = self.build_hasher; 41 | let mut map = HashMap::with_capacity_and_hasher(elems.len(), build_hasher); 42 | map.extend(elems); 43 | map 44 | } 45 | } 46 | 47 | /// Generates a [`HashMap`] that uses a default pseudorandom [`BuildHasher`] and contains keys of 48 | /// type `K` with values of type `V`. 49 | /// 50 | /// The range specifies the number of tries to generate key-value entries with distinct keys. 51 | /// 52 | /// # Panics 53 | /// 54 | /// Panics if the range is empty. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ``` 59 | /// use dicetest::prelude::*; 60 | /// use dicetest::{Prng, Limit}; 61 | /// 62 | /// let mut prng = Prng::from_seed(0x5EED.into()); 63 | /// let limit = Limit::default(); 64 | /// let mut fate = Fate::new(&mut prng, limit); 65 | /// 66 | /// let elem_die = dice::zip().two(dice::u8(..), dice::char()); 67 | /// 68 | /// let map = fate.with_limit(100.into()).roll(dice::hash_map(&elem_die, ..)); 69 | /// assert!(map.len() <= 100); 70 | /// 71 | /// let map = fate.roll(dice::hash_map(&elem_die, ..=73)); 72 | /// assert!(map.len() <= 73); 73 | /// 74 | /// let map = fate.roll(dice::hash_map(&elem_die, 17..)); 75 | /// assert!(map.len() >= 17); 76 | /// 77 | /// let map = fate.roll(dice::hash_map(&elem_die, 42)); 78 | /// assert!(map.len() <= 42); 79 | /// ``` 80 | pub fn hash_map( 81 | elem_die: impl Die<(K, V)>, 82 | tries_range: impl LengthRange, 83 | ) -> impl Die> 84 | where 85 | K: Eq + Hash, 86 | { 87 | dice::collection(HashMapBuilder::die(), elem_die, tries_range) 88 | } 89 | 90 | /// Similar to [`dice::hash_map`] but each element is generated using only a random part of 91 | /// [`Limit`]. 92 | /// 93 | /// If you want to generate a [`HashMap`] that contains other collections, then you should 94 | /// consider using this generator for the outer [`HashMap`]. That way the overall length is 95 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 96 | /// 97 | /// [`Limit`]: crate::Limit 98 | /// [`dice::hash_map`]: dice::hash_map() 99 | /// 100 | /// # Panics 101 | /// 102 | /// Panics if the range is empty. 103 | /// 104 | /// # Examples 105 | /// 106 | /// ``` 107 | /// use dicetest::prelude::*; 108 | /// use dicetest::{Prng, Limit}; 109 | /// 110 | /// let mut prng = Prng::from_seed(0x5EED.into()); 111 | /// let limit = Limit::default(); 112 | /// let mut fate = Fate::new(&mut prng, limit); 113 | /// 114 | /// let elem_die = dice::char(); 115 | /// let vec_die = dice::zip().two(dice::u8(..), dice::vec(elem_die, ..)); 116 | /// let map_of_vecs_die = dice::outer_hash_map(vec_die, ..); 117 | /// 118 | /// let map_of_vecs = fate.roll(map_of_vecs_die); 119 | /// assert!(map_of_vecs.values().flatten().count() <= 100); 120 | /// ``` 121 | pub fn outer_hash_map( 122 | elem_die: impl Die<(K, V)>, 123 | tries_range: impl LengthRange, 124 | ) -> impl Die> 125 | where 126 | K: Eq + Hash, 127 | { 128 | dice::outer_collection(HashMapBuilder::die(), elem_die, tries_range) 129 | } 130 | -------------------------------------------------------------------------------- /src/dice/hash_set.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::hash::{BuildHasher, Hash}; 3 | 4 | use crate::dice::{CollectionBuilder, LengthRange}; 5 | use crate::prelude::*; 6 | use crate::Prng; 7 | 8 | /// [`HashSet`] builder for [`dice::collection`]. 9 | /// 10 | /// [`dice::collection`]: dice::collection() 11 | pub struct HashSetBuilder 12 | where 13 | S: BuildHasher, 14 | { 15 | build_hasher: S, 16 | } 17 | 18 | impl HashSetBuilder 19 | where 20 | S: BuildHasher, 21 | { 22 | /// Creates a builder that uses the given [`BuildHasher`] for constructing a [`HashSet`]. 23 | pub fn with_hasher(build_hasher: S) -> Self { 24 | Self { build_hasher } 25 | } 26 | } 27 | 28 | impl HashSetBuilder { 29 | fn die() -> impl Die { 30 | dice::from_fn(|mut fate| Self::with_hasher(fate.fork_prng())) 31 | } 32 | } 33 | 34 | impl CollectionBuilder> for HashSetBuilder 35 | where 36 | T: Eq + Hash, 37 | S: BuildHasher, 38 | { 39 | fn build(self, elems: impl ExactSizeIterator) -> HashSet { 40 | let build_hasher = self.build_hasher; 41 | let mut set = HashSet::with_capacity_and_hasher(elems.len(), build_hasher); 42 | set.extend(elems); 43 | set 44 | } 45 | } 46 | 47 | /// Generates a [`HashSet`] that uses a default pseudorandom [`BuildHasher`] and contains elements 48 | /// of type `T`. 49 | /// 50 | /// The range specifies the number of tries to generate distinct elements. 51 | /// 52 | /// # Panics 53 | /// 54 | /// Panics if the range is empty. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ``` 59 | /// use dicetest::prelude::*; 60 | /// use dicetest::{Prng, Limit}; 61 | /// 62 | /// let mut prng = Prng::from_seed(0x5EED.into()); 63 | /// let limit = Limit::default(); 64 | /// let mut fate = Fate::new(&mut prng, limit); 65 | /// 66 | /// let elem_die = dice::u8(..); 67 | /// 68 | /// let set = fate.with_limit(100.into()).roll(dice::hash_set(&elem_die, ..)); 69 | /// assert!(set.len() <= 100); 70 | /// 71 | /// let set = fate.roll(dice::hash_set(&elem_die, ..=73)); 72 | /// assert!(set.len() <= 73); 73 | /// 74 | /// let set = fate.roll(dice::hash_set(&elem_die, 17..)); 75 | /// assert!(set.len() >= 17); 76 | /// 77 | /// let set = fate.roll(dice::hash_set(&elem_die, 42)); 78 | /// assert!(set.len() <= 42); 79 | /// ``` 80 | pub fn hash_set( 81 | elem_die: impl Die, 82 | tries_range: impl LengthRange, 83 | ) -> impl Die> 84 | where 85 | T: Eq + Hash, 86 | { 87 | dice::collection(HashSetBuilder::die(), elem_die, tries_range) 88 | } 89 | 90 | /// Similar to [`dice::hash_set`] but each element is generated using only a random part of 91 | /// [`Limit`]. 92 | /// 93 | /// If you want to generate a [`HashSet`] that contains other collections, then you should 94 | /// consider using this generator for the outer [`HashSet`]. That way the overall length is 95 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 96 | /// 97 | /// [`Limit`]: crate::Limit 98 | /// [`dice::hash_set`]: dice::hash_set() 99 | /// 100 | /// # Panics 101 | /// 102 | /// Panics if the range is empty. 103 | /// 104 | /// # Examples 105 | /// 106 | /// ``` 107 | /// use dicetest::prelude::*; 108 | /// use dicetest::{Prng, Limit}; 109 | /// 110 | /// let mut prng = Prng::from_seed(0x5EED.into()); 111 | /// let limit = Limit::default(); 112 | /// let mut fate = Fate::new(&mut prng, limit); 113 | /// 114 | /// let elem_die = dice::u8(..); 115 | /// let vec_die = dice::vec(elem_die, ..); 116 | /// let set_of_vecs_die = dice::outer_hash_set(vec_die, ..); 117 | /// 118 | /// let set_of_vecs = fate.roll(set_of_vecs_die); 119 | /// assert!(set_of_vecs.iter().flatten().count() <= 100); 120 | /// ``` 121 | pub fn outer_hash_set( 122 | elem_die: impl Die, 123 | tries_range: impl LengthRange, 124 | ) -> impl Die> 125 | where 126 | T: Eq + Hash, 127 | { 128 | dice::outer_collection(HashSetBuilder::die(), elem_die, tries_range) 129 | } 130 | -------------------------------------------------------------------------------- /src/dice/index_of.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates an valid index for the given slice. 4 | /// 5 | /// # Panics 6 | /// 7 | /// Panics if the slice empty. 8 | /// 9 | /// # Examples 10 | /// 11 | /// This example generates an index without panicking: 12 | /// 13 | /// ``` 14 | /// use dicetest::prelude::*; 15 | /// use dicetest::{Prng, Limit}; 16 | /// 17 | /// let mut prng = Prng::from_seed(0x5EED.into()); 18 | /// let limit = Limit::default(); 19 | /// let mut fate = Fate::new(&mut prng, limit); 20 | /// 21 | /// let array = ['a', 'b', 'c']; 22 | /// let index = fate.roll(dice::index_of(&array)); 23 | /// assert!(0 <= index && index < array.len()); 24 | /// ``` 25 | /// 26 | /// This example panics: 27 | /// 28 | /// ```should_panic 29 | /// use dicetest::prelude::*; 30 | /// 31 | /// // Oh no, panic! 32 | /// let _index_die = dice::index_of::(&[]); 33 | /// ``` 34 | pub fn index_of<'a, T>(slice: &[T]) -> impl Die + 'a { 35 | let len = slice.len(); 36 | dice::uni_usize(0..len) 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use crate::prelude::*; 42 | 43 | #[test] 44 | fn index_of_generates_valid_index() { 45 | Dicetest::repeatedly().run(|mut fate| { 46 | let vec = fate.roll(dice::vec(dice::u8(..), 1..)); 47 | let index = fate.roll(dice::index_of(&vec)); 48 | 49 | assert!(index < vec.len()); 50 | }) 51 | } 52 | 53 | #[test] 54 | fn index_of_calc_stats() { 55 | Dicetest::repeatedly() 56 | .passes(0) 57 | .stats_enabled(true) 58 | .run(|mut fate| { 59 | stat!( 60 | "index_of(&[1, 2, 3, 4, 5])", 61 | "{}", 62 | fate.roll(dice::index_of(&[1, 2, 3, 4, 5])), 63 | ); 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/dice/just.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates the given value. 4 | /// 5 | /// # Examples 6 | /// 7 | /// ``` 8 | /// use dicetest::prelude::*; 9 | /// use dicetest::{Prng, Limit}; 10 | /// 11 | /// let mut prng = Prng::from_seed(0x5EED.into()); 12 | /// let limit = Limit::default(); 13 | /// let mut fate = Fate::new(&mut prng, limit); 14 | /// 15 | /// assert_eq!(fate.roll(dice::just_once(42)), 42); 16 | /// 17 | /// #[derive(Debug, PartialEq, Eq)] 18 | /// struct CannotBeCloned; 19 | /// assert_eq!(fate.roll(dice::just_once(CannotBeCloned)), CannotBeCloned); 20 | /// ``` 21 | pub fn just_once(value: T) -> impl DieOnce { 22 | dice::from_fn_once(|_| value) 23 | } 24 | 25 | /// Generates a clone of the given value. 26 | /// 27 | /// ``` 28 | /// use dicetest::prelude::*; 29 | /// use dicetest::{Prng, Limit}; 30 | /// 31 | /// let mut prng = Prng::from_seed(0x5EED.into()); 32 | /// let limit = Limit::default(); 33 | /// let mut fate = Fate::new(&mut prng, limit); 34 | /// 35 | /// assert_eq!(fate.roll(dice::just(42)), 42); 36 | /// 37 | /// let cloning_die = dice::just(vec![0, 1, 2]); 38 | /// for _ in 0..10 { 39 | /// assert_eq!(fate.roll(&cloning_die), vec![0, 1, 2]); 40 | /// } 41 | /// ``` 42 | pub fn just(value: T) -> impl Die 43 | where 44 | T: Clone, 45 | { 46 | dice::from_fn(move |_| value.clone()) 47 | } 48 | -------------------------------------------------------------------------------- /src/dice/length.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; 3 | 4 | use crate::prelude::*; 5 | 6 | /// Non-empty range for [`dice::length`]. 7 | /// 8 | /// [`dice::length`]: dice::length() 9 | pub trait LengthRange { 10 | /// Returns the inclusive lower bound and the optional inclusive upper bound that represent 11 | /// the range. 12 | /// 13 | /// # Panics 14 | /// 15 | /// Panics if the range is empty. 16 | fn bounds(self) -> (usize, Option); 17 | } 18 | 19 | fn empty_length_range(range: &(impl LengthRange + Debug)) -> ! { 20 | panic!( 21 | "LengthRange is invalid because it contains no values: {:?}", 22 | range 23 | ) 24 | } 25 | 26 | impl LengthRange for usize { 27 | fn bounds(self) -> (usize, Option) { 28 | (self, Some(self)) 29 | } 30 | } 31 | 32 | impl LengthRange for Range { 33 | fn bounds(self) -> (usize, Option) { 34 | if self.start < self.end { 35 | let lower = self.start; 36 | let upper = self.end - 1; 37 | (lower, Some(upper)) 38 | } else { 39 | empty_length_range(&self); 40 | } 41 | } 42 | } 43 | 44 | impl LengthRange for RangeFrom { 45 | fn bounds(self) -> (usize, Option) { 46 | (self.start, None) 47 | } 48 | } 49 | 50 | impl LengthRange for RangeFull { 51 | fn bounds(self) -> (usize, Option) { 52 | (0, None) 53 | } 54 | } 55 | 56 | impl LengthRange for RangeInclusive { 57 | fn bounds(self) -> (usize, Option) { 58 | if self.start() <= self.end() { 59 | let (lower, upper) = self.into_inner(); 60 | (lower, Some(upper)) 61 | } else { 62 | empty_length_range(&self); 63 | } 64 | } 65 | } 66 | 67 | impl LengthRange for RangeTo { 68 | fn bounds(self) -> (usize, Option) { 69 | if self.end > 0 { 70 | let lower = 0; 71 | let upper = self.end - 1; 72 | (lower, Some(upper)) 73 | } else { 74 | empty_length_range(&self); 75 | } 76 | } 77 | } 78 | 79 | impl LengthRange for RangeToInclusive { 80 | fn bounds(self) -> (usize, Option) { 81 | (0, Some(self.end)) 82 | } 83 | } 84 | 85 | /// Generates a random length that can be used for collections, etc. The length is bounded by the 86 | /// given range and the [`Limit`] parameter passed to [`Die::roll`]. 87 | /// 88 | /// [`Limit`]: crate::Limit 89 | /// 90 | /// # Panics 91 | /// 92 | /// Panics if the range is empty. 93 | /// 94 | /// # Examples 95 | /// 96 | /// This example generates lengths without panicking: 97 | /// 98 | /// ``` 99 | /// use dicetest::prelude::*; 100 | /// use dicetest::{Prng, Limit}; 101 | /// 102 | /// let mut prng = Prng::from_seed(0x5EED.into()); 103 | /// let limit = Limit::default(); 104 | /// let mut fate = Fate::new(&mut prng, limit); 105 | /// 106 | /// assert!(fate.roll(dice::length(42)) == 42); 107 | /// 108 | /// let length = fate.with_limit(100.into()).roll(dice::length(42..)); 109 | /// assert!(length >= 42 && length <= 142); 110 | /// 111 | /// assert!(fate.roll(dice::length(..=71)) <= 71); 112 | /// 113 | /// assert!(fate.roll(dice::length(..71)) < 71); 114 | /// 115 | /// let length = fate.roll(dice::length(42..=71)); 116 | /// assert!(length >= 42 && length <= 71); 117 | /// 118 | /// let length = fate.roll(dice::length(42..71)); 119 | /// assert!(length >= 42 && length < 71); 120 | /// 121 | /// let length = fate.with_limit(100.into()).roll(dice::length(..)); 122 | /// assert!(length >= 0 && length <= 100); 123 | /// ``` 124 | /// 125 | /// This example panics: 126 | /// 127 | /// ```should_panic 128 | /// use dicetest::prelude::*; 129 | /// 130 | /// // Oh no, panic! 131 | /// let _length_die = dice::length(71..42); 132 | /// ``` 133 | pub fn length(range: impl LengthRange) -> impl Die { 134 | let (lower, upper_opt) = range.bounds(); 135 | 136 | dice::from_fn(move |mut fate| { 137 | let upper = 138 | upper_opt.unwrap_or_else(|| lower.saturating_add(fate.limit().saturating_to_usize())); 139 | 140 | fate.roll(dice::uni_usize(lower..=upper)) 141 | }) 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use std::fmt::Debug; 147 | 148 | use crate::prelude::*; 149 | 150 | fn range_contains_length( 151 | mut fate: Fate, 152 | range_data_die: impl DieOnce, 153 | create_range: impl FnOnce(B) -> R, 154 | is_in_range: impl FnOnce(B, usize) -> bool, 155 | ) where 156 | B: Copy + Debug, 157 | R: dice::LengthRange + Debug, 158 | { 159 | let prng = &mut fate.fork_prng(); 160 | let limit = fate.roll(dice::u64(..)).into(); 161 | let range_data = fate.roll(range_data_die); 162 | 163 | hint_debug!(prng); 164 | hint_debug!(limit); 165 | hint_debug!(range_data); 166 | 167 | let range = create_range(range_data); 168 | hint_debug!(range); 169 | 170 | let length = dice::length(range).roll(Fate::new(prng, limit)); 171 | hint_debug!(length); 172 | 173 | assert!(is_in_range(range_data, length)); 174 | } 175 | 176 | #[test] 177 | fn length_is_equal_to_target() { 178 | Dicetest::repeatedly().run(|fate| { 179 | range_contains_length( 180 | fate, 181 | dice::usize(..), 182 | |target| target, 183 | |target, length| length == target, 184 | ); 185 | }) 186 | } 187 | 188 | #[test] 189 | fn length_is_in_range() { 190 | Dicetest::repeatedly().run(|fate| { 191 | range_contains_length( 192 | fate, 193 | dice::array(dice::usize(..usize::MAX - 1)).map(|[a, b]| (a.min(b), a.max(b) + 1)), 194 | |(lower, upper)| lower..upper, 195 | |(lower, upper), length| lower <= length && length < upper, 196 | ); 197 | }) 198 | } 199 | 200 | #[test] 201 | fn length_is_in_range_from() { 202 | Dicetest::repeatedly().run(|fate| { 203 | range_contains_length( 204 | fate, 205 | dice::usize(..), 206 | |lower| lower.., 207 | |lower, length| lower <= length, 208 | ); 209 | }) 210 | } 211 | 212 | #[test] 213 | fn length_is_in_range_inclusive() { 214 | Dicetest::repeatedly().run(|fate| { 215 | range_contains_length( 216 | fate, 217 | dice::array(dice::usize(..)).map(|[a, b]| (a.min(b), a.max(b))), 218 | |(lower, upper)| lower..=upper, 219 | |(lower, upper), length| lower <= length && length <= upper, 220 | ); 221 | }) 222 | } 223 | 224 | #[test] 225 | fn length_is_in_range_to() { 226 | Dicetest::repeatedly().run(|fate| { 227 | range_contains_length( 228 | fate, 229 | dice::usize(1..), 230 | |upper| ..upper, 231 | |upper, length| length < upper, 232 | ); 233 | }) 234 | } 235 | 236 | #[test] 237 | fn length_is_in_range_to_inclusive() { 238 | Dicetest::repeatedly().run(|fate| { 239 | range_contains_length( 240 | fate, 241 | dice::usize(..), 242 | |upper| ..=upper, 243 | |upper, length| length <= upper, 244 | ); 245 | }) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/dice/linked_list.rs: -------------------------------------------------------------------------------- 1 | use std::collections::LinkedList; 2 | 3 | use crate::dice::{CollectionBuilder, LengthRange}; 4 | use crate::prelude::*; 5 | 6 | /// [`LinkedList`] builder for [`dice::collection`]. 7 | /// 8 | /// [`dice::collection`]: dice::collection() 9 | pub struct LinkedListBuilder; 10 | 11 | impl LinkedListBuilder { 12 | fn die() -> impl Die { 13 | dice::from_fn(|_fate| Self) 14 | } 15 | } 16 | 17 | impl CollectionBuilder> for LinkedListBuilder { 18 | fn build(self, elems: impl ExactSizeIterator) -> LinkedList { 19 | elems.collect() 20 | } 21 | } 22 | 23 | /// Generates a [`LinkedList`] that contains elements of type `T`. 24 | /// 25 | /// The range specifies the length of the [`LinkedList`]. 26 | /// 27 | /// # Panics 28 | /// 29 | /// Panics if the range is empty. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// use dicetest::prelude::*; 35 | /// use dicetest::{Prng, Limit}; 36 | /// 37 | /// let mut prng = Prng::from_seed(0x5EED.into()); 38 | /// let limit = Limit::default(); 39 | /// let mut fate = Fate::new(&mut prng, limit); 40 | /// 41 | /// let elem_die = dice::u8(..); 42 | /// 43 | /// let list = fate.with_limit(100.into()).roll(dice::linked_list(&elem_die, ..)); 44 | /// assert!(list.len() <= 100); 45 | /// 46 | /// let list = fate.roll(dice::linked_list(&elem_die, ..=73)); 47 | /// assert!(list.len() <= 73); 48 | /// 49 | /// let list = fate.roll(dice::linked_list(&elem_die, 17..)); 50 | /// assert!(list.len() >= 17); 51 | /// 52 | /// let list = fate.roll(dice::linked_list(&elem_die, 42)); 53 | /// assert!(list.len() == 42); 54 | /// ``` 55 | pub fn linked_list( 56 | elem_die: impl Die, 57 | length_range: impl LengthRange, 58 | ) -> impl Die> { 59 | dice::collection(LinkedListBuilder::die(), elem_die, length_range) 60 | } 61 | 62 | /// Similar to [`dice::linked_list`] but each element is generated using only a random part of 63 | /// [`Limit`.] 64 | /// 65 | /// If you want to generate a [`LinkedList`] that contains other collections, then you should 66 | /// consider using this generator for the outer [`LinkedList`.] That way the overall length is 67 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 68 | /// 69 | /// [`Limit`]: crate::Limit 70 | /// [`dice::linked_list`]: dice::linked_list() 71 | /// 72 | /// # Panics 73 | /// 74 | /// Panics if the range is empty. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// use dicetest::prelude::*; 80 | /// use dicetest::{Prng, Limit}; 81 | /// 82 | /// let mut prng = Prng::from_seed(0x5EED.into()); 83 | /// let limit = Limit::default(); 84 | /// let mut fate = Fate::new(&mut prng, limit); 85 | /// 86 | /// let elem_die = dice::u8(..); 87 | /// let list_die = dice::linked_list(elem_die, ..); 88 | /// let list_of_lists_die = dice::outer_linked_list(list_die, ..); 89 | /// 90 | /// let list_of_lists = fate.roll(list_of_lists_die); 91 | /// assert!(list_of_lists.iter().flatten().count() <= 100); 92 | /// ``` 93 | pub fn outer_linked_list( 94 | elem_die: impl Die, 95 | length_range: impl LengthRange, 96 | ) -> impl Die> { 97 | dice::outer_collection(LinkedListBuilder::die(), elem_die, length_range) 98 | } 99 | -------------------------------------------------------------------------------- /src/dice/option.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates a [`None`] or a [`Some`] that contains a value from the given generator. 4 | /// [`None`] and [`Some`] have the same probability. 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// use dicetest::prelude::*; 10 | /// use dicetest::{Prng, Limit}; 11 | /// 12 | /// let mut prng = Prng::from_seed(0x5EED.into()); 13 | /// let limit = Limit::default(); 14 | /// let mut fate = Fate::new(&mut prng, limit); 15 | /// 16 | /// let some_die = dice::just_once(42); 17 | /// let option_die = dice::option_once(some_die); 18 | /// 19 | /// let some_or_none = fate.roll(option_die); 20 | /// ``` 21 | pub fn option_once(some_die: impl DieOnce) -> impl DieOnce> { 22 | dice::one_of_die_once().two(dice::just_once(None), some_die.map_once(Some)) 23 | } 24 | 25 | /// Generates a [`None`] or a [`Some`] that contains a value from the given generator. 26 | /// [`None`] and [`Some`] have the same probability. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// use dicetest::prelude::*; 32 | /// use dicetest::{Prng, Limit}; 33 | /// 34 | /// let mut prng = Prng::from_seed(0x5EED.into()); 35 | /// let limit = Limit::default(); 36 | /// let mut fate = Fate::new(&mut prng, limit); 37 | /// 38 | /// let some_die = dice::just(42); 39 | /// let option_die = dice::option(some_die); 40 | /// 41 | /// let some_or_none = fate.roll(option_die); 42 | /// ``` 43 | pub fn option(some_die: impl Die) -> impl Die> { 44 | dice::one_of_die().two(dice::from_fn(|_| None), some_die.map(Some)) 45 | } 46 | 47 | /// Generates a [`None`] or a [`Some`] that contains a value from the given generator. 48 | /// The probabilities of [`None`] and [`Some`] depend on the given weights. 49 | /// 50 | /// # Examples 51 | /// 52 | /// ``` 53 | /// use dicetest::prelude::*; 54 | /// use dicetest::{Prng, Limit}; 55 | /// 56 | /// let mut prng = Prng::from_seed(0x5EED.into()); 57 | /// let limit = Limit::default(); 58 | /// let mut fate = Fate::new(&mut prng, limit); 59 | /// 60 | /// let some_die = dice::just_once(42); 61 | /// let option_die = dice::weighted_option_once(10, (1, some_die)); 62 | /// 63 | /// let probably_none = fate.roll(option_die); 64 | /// ``` 65 | pub fn weighted_option_once( 66 | none_weight: u32, 67 | (some_weight, some_die): (u32, impl DieOnce), 68 | ) -> impl DieOnce> { 69 | dice::weighted_one_of_die_once().two( 70 | (none_weight, dice::just_once(None)), 71 | (some_weight, some_die.map_once(Some)), 72 | ) 73 | } 74 | 75 | /// Generates a [`None`] or a [`Some`] that contains a value from the given generator. 76 | /// The probabilities of [`None`] and [`Some`] depend on the given weights. 77 | /// 78 | /// # Examples 79 | /// 80 | /// ``` 81 | /// use dicetest::prelude::*; 82 | /// use dicetest::{Prng, Limit}; 83 | /// 84 | /// let mut prng = Prng::from_seed(0x5EED.into()); 85 | /// let limit = Limit::default(); 86 | /// let mut fate = Fate::new(&mut prng, limit); 87 | /// 88 | /// let some_die = dice::just(42); 89 | /// let option_die = dice::weighted_option(10, (1, some_die)); 90 | /// 91 | /// let probably_none = fate.roll(option_die); 92 | /// ``` 93 | pub fn weighted_option( 94 | none_weight: u32, 95 | (some_weight, some_die): (u32, impl Die), 96 | ) -> impl Die> { 97 | dice::weighted_one_of_die().two( 98 | (none_weight, dice::from_fn(|_| None)), 99 | (some_weight, some_die.map(Some)), 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /src/dice/rand.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Distribution; 2 | 3 | use crate::prelude::*; 4 | 5 | /// Generates a value using the given [`Distribution`]. 6 | /// 7 | /// Only available if the feature `rand` is enabled. 8 | #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] 9 | pub fn from_distribution(distribution: D) -> impl Die 10 | where 11 | D: Distribution, 12 | { 13 | dice::from_fn(move |mut fate| { 14 | let mut prng = fate.fork_prng(); 15 | distribution.sample(&mut prng) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/dice/result.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates a [`Ok`] or a [`Err`] that contain a value from one of the given generators. 4 | /// [`Ok`] and [`Err`] have the same probability. 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// use dicetest::prelude::*; 10 | /// use dicetest::{Prng, Limit}; 11 | /// 12 | /// let mut prng = Prng::from_seed(0x5EED.into()); 13 | /// let limit = Limit::default(); 14 | /// let mut fate = Fate::new(&mut prng, limit); 15 | /// 16 | /// let ok_die = dice::just_once(42); 17 | /// let err_die = dice::just_once("error"); 18 | /// let result_die = dice::result_once(ok_die, err_die); 19 | /// 20 | /// let ok_or_err = fate.roll(result_die); 21 | /// ``` 22 | pub fn result_once( 23 | ok_die: impl DieOnce, 24 | err_die: impl DieOnce, 25 | ) -> impl DieOnce> { 26 | dice::one_of_die_once().two(ok_die.map_once(Ok), err_die.map_once(Err)) 27 | } 28 | 29 | /// Generates a [`Ok`] or a [`Err`] that contain a value from one of the given generators. 30 | /// [`Ok`] and [`Err`] have the same probability. 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// use dicetest::prelude::*; 36 | /// use dicetest::{Prng, Limit}; 37 | /// 38 | /// let mut prng = Prng::from_seed(0x5EED.into()); 39 | /// let limit = Limit::default(); 40 | /// let mut fate = Fate::new(&mut prng, limit); 41 | /// 42 | /// let ok_die = dice::just(42); 43 | /// let err_die = dice::just("error"); 44 | /// let result_die = dice::result(ok_die, err_die); 45 | /// 46 | /// let ok_or_err = fate.roll(result_die); 47 | /// ``` 48 | pub fn result(ok_die: impl Die, err_die: impl Die) -> impl Die> { 49 | dice::one_of_die().two(ok_die.map(Ok), err_die.map(Err)) 50 | } 51 | 52 | /// Generates a [`Ok`] or a [`Err`] that contain a value from one of the given generators. 53 | /// The probabilities of [`Ok`] and [`Err`] depend on the given weights. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ``` 58 | /// use dicetest::prelude::*; 59 | /// use dicetest::{Prng, Limit}; 60 | /// 61 | /// let mut prng = Prng::from_seed(0x5EED.into()); 62 | /// let limit = Limit::default(); 63 | /// let mut fate = Fate::new(&mut prng, limit); 64 | /// 65 | /// let ok_die = dice::just_once(42); 66 | /// let err_die = dice::just_once("error"); 67 | /// let result_die = dice::weighted_result_once((1, ok_die), (10, err_die)); 68 | /// 69 | /// let probably_err = fate.roll(result_die); 70 | /// ``` 71 | pub fn weighted_result_once( 72 | (ok_weight, ok_die): (u32, impl DieOnce), 73 | (err_weight, err_die): (u32, impl DieOnce), 74 | ) -> impl DieOnce> { 75 | dice::weighted_one_of_die_once().two( 76 | (ok_weight, ok_die.map_once(Ok)), 77 | (err_weight, err_die.map_once(Err)), 78 | ) 79 | } 80 | 81 | /// Generates a [`Ok`] or a [`Err`] that contain a value from one of the given generators. 82 | /// The probabilities of [`Ok`] and [`Err`] depend on the given weights. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ``` 87 | /// use dicetest::prelude::*; 88 | /// use dicetest::{Prng, Limit}; 89 | /// 90 | /// let mut prng = Prng::from_seed(0x5EED.into()); 91 | /// let limit = Limit::default(); 92 | /// let mut fate = Fate::new(&mut prng, limit); 93 | /// 94 | /// let ok_die = dice::just(42); 95 | /// let err_die = dice::just("error"); 96 | /// let result_die = dice::weighted_result((1, ok_die), (10, err_die)); 97 | /// 98 | /// let probably_err = fate.roll(result_die); 99 | /// ``` 100 | pub fn weighted_result( 101 | (ok_weight, ok_die): (u32, impl Die), 102 | (err_weight, err_die): (u32, impl Die), 103 | ) -> impl Die> { 104 | dice::weighted_one_of_die().two((ok_weight, ok_die.map(Ok)), (err_weight, err_die.map(Err))) 105 | } 106 | -------------------------------------------------------------------------------- /src/dice/shuffle.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Shuffles the given slice randomly in-place using the [Fisher-Yates shuffle]. 4 | /// 5 | /// [Fisher-Yates shuffle]: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 6 | /// 7 | /// # Examples 8 | /// 9 | /// ``` 10 | /// use dicetest::prelude::*; 11 | /// use dicetest::{Prng, Limit}; 12 | /// 13 | /// let mut prng = Prng::from_seed(0x5EED.into()); 14 | /// let limit = Limit::default(); 15 | /// let mut fate = Fate::new(&mut prng, limit); 16 | /// 17 | /// let mut elems = vec![1, 2, 3, 4]; 18 | /// 19 | /// fate.roll(dice::shuffle_slice(&mut elems)); 20 | /// ``` 21 | pub fn shuffle_slice(elems: &'_ mut [T]) -> impl DieOnce<()> + '_ { 22 | dice::from_fn_once(move |mut fate| { 23 | let n = elems.len(); 24 | if n > 0 { 25 | for i in 0..(n - 1) { 26 | let j = fate.roll(dice::uni_usize(i..n)); 27 | elems.swap(i, j); 28 | } 29 | } 30 | }) 31 | } 32 | 33 | /// Shuffles the given [`Vec`] randomly using the [Fisher-Yates shuffle]. 34 | /// 35 | /// [Fisher-Yates shuffle]: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 36 | /// 37 | /// # Examples 38 | /// 39 | /// ``` 40 | /// use dicetest::prelude::*; 41 | /// use dicetest::{Prng, Limit}; 42 | /// 43 | /// let mut prng = Prng::from_seed(0x5EED.into()); 44 | /// let limit = Limit::default(); 45 | /// let mut fate = Fate::new(&mut prng, limit); 46 | /// 47 | /// let sorted = vec![1, 2, 3, 4]; 48 | /// 49 | /// let probably_unsorted = fate.roll(dice::shuffled_vec(sorted)); 50 | /// ``` 51 | pub fn shuffled_vec(mut vec: Vec) -> impl DieOnce> { 52 | dice::from_fn_once(move |mut fate| { 53 | fate.roll(shuffle_slice(&mut vec)); 54 | vec 55 | }) 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use crate::prelude::*; 61 | use std::collections::HashMap; 62 | 63 | fn count_vec_elems(vec: &[u8]) -> HashMap { 64 | let mut elems = HashMap::new(); 65 | for &elem in vec.iter() { 66 | let count = elems.entry(elem).or_insert(0); 67 | *count += 1; 68 | } 69 | elems 70 | } 71 | 72 | #[test] 73 | fn shuffled_vec_contains_same_elems() { 74 | Dicetest::repeatedly().run(|mut fate| { 75 | let orig_vec = fate.roll(dice::vec(dice::u8(..), ..)); 76 | let orig_vec_elems = count_vec_elems(&orig_vec); 77 | hint_debug!(orig_vec); 78 | 79 | let shuffled_vec = fate.roll(dice::shuffled_vec(orig_vec)); 80 | let shuffled_vec_elems = count_vec_elems(&shuffled_vec); 81 | hint_debug!(shuffled_vec); 82 | 83 | assert_eq!(orig_vec_elems, shuffled_vec_elems); 84 | }) 85 | } 86 | 87 | #[test] 88 | fn shuffled_vec_calc_stats() { 89 | Dicetest::repeatedly() 90 | .passes(0) 91 | .stats_enabled(true) 92 | .run(|mut fate| { 93 | stat!( 94 | "shuffled_vec(vec![1, 2, 3])", 95 | "{:?}", 96 | fate.roll(dice::shuffled_vec(vec![1, 2, 3])), 97 | ); 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/dice/split_vec.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generates `N` [`Vec`]s by splitting the given [`Vec`] at random indices. 4 | /// 5 | /// # Panics 6 | /// 7 | /// Panics if `!vec.is_empty() && N == 0` is true. 8 | /// 9 | /// # Examples 10 | /// 11 | /// This example splits [`Vec`]s without panicking: 12 | /// 13 | /// ``` 14 | /// use dicetest::prelude::*; 15 | /// use dicetest::{Prng, Limit}; 16 | /// 17 | /// let mut prng = Prng::from_seed(0x5EED.into()); 18 | /// let limit = Limit::default(); 19 | /// let mut fate = Fate::new(&mut prng, limit); 20 | /// 21 | /// let vec = vec![1, 2, 3, 4]; 22 | /// let [prefix, suffix] = fate.roll(dice::split_vec(vec.clone())); 23 | /// assert!(vec.starts_with(&prefix)); 24 | /// assert!(vec.ends_with(&suffix)); 25 | /// 26 | /// let empty_vec: Vec = vec![]; 27 | /// let [empty_prefix, empty_suffix] = fate.roll(dice::split_vec(empty_vec)); 28 | /// assert!(empty_prefix.is_empty()); 29 | /// assert!(empty_suffix.is_empty()); 30 | /// ``` 31 | /// 32 | /// This example panics: 33 | /// 34 | /// ```should_panic 35 | /// use dicetest::prelude::*; 36 | /// 37 | /// let vec = vec![1, 2, 3, 4]; 38 | /// // Oh no, panic! 39 | /// let _parts_die = dice::split_vec::<_, 0>(vec); 40 | /// ``` 41 | #[track_caller] 42 | pub fn split_vec(mut vec: Vec) -> impl DieOnce<[Vec; N]> { 43 | let sizes_die = dice::split_usize(vec.len()); 44 | dice::from_fn_once(move |mut fate| { 45 | let sizes = fate.roll(sizes_die); 46 | let mut parts = sizes.map(|size| vec.split_off(vec.len() - size)); 47 | parts.reverse(); 48 | parts 49 | }) 50 | } 51 | 52 | /// Generates `n` [`Vec`]s by splitting the given [`Vec`] at random indices. 53 | /// 54 | /// # Panics 55 | /// 56 | /// Panics if `!vec.is_empty() && n == 0` is true. 57 | /// 58 | /// # Examples 59 | /// 60 | /// This example splits [`Vec`]s without panicking: 61 | /// 62 | /// ``` 63 | /// use dicetest::prelude::*; 64 | /// use dicetest::{Prng, Limit}; 65 | /// 66 | /// let mut prng = Prng::from_seed(0x5EED.into()); 67 | /// let limit = Limit::default(); 68 | /// let mut fate = Fate::new(&mut prng, limit); 69 | /// 70 | /// let vec = vec![1, 2, 3, 4]; 71 | /// let parts = fate.roll(dice::split_vec_n(vec.clone(), 2)); 72 | /// assert!(vec.starts_with(&parts[0])); 73 | /// assert!(vec.ends_with(&parts[1])); 74 | /// 75 | /// let empty_vec: Vec = vec![]; 76 | /// let parts = fate.roll(dice::split_vec_n(empty_vec, 2)); 77 | /// assert!(parts[0].is_empty()); 78 | /// assert!(parts[1].is_empty()); 79 | /// ``` 80 | /// 81 | /// This example panics: 82 | /// 83 | /// ```should_panic 84 | /// use dicetest::prelude::*; 85 | /// 86 | /// let vec = vec![1, 2, 3, 4]; 87 | /// // Oh no, panic! 88 | /// let _parts_die = dice::split_vec_n(vec, 0); 89 | /// ``` 90 | #[track_caller] 91 | pub fn split_vec_n(mut vec: Vec, n: usize) -> impl DieOnce>> { 92 | let sizes_die = dice::split_usize_n(vec.len(), n); 93 | dice::from_fn_once(move |mut fate| { 94 | let sizes = fate.roll(sizes_die); 95 | let mut parts = sizes 96 | .into_iter() 97 | .map(|size| vec.split_off(vec.len() - size)) 98 | .collect::>(); 99 | parts.reverse(); 100 | parts 101 | }) 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use crate::prelude::*; 107 | 108 | fn merge_parts(parts: &[Vec]) -> Vec { 109 | let mut merged = Vec::new(); 110 | for part in parts { 111 | merged.extend(part); 112 | } 113 | merged 114 | } 115 | 116 | #[test] 117 | fn split_vec_result_can_be_merged_to_orig_vec() { 118 | Dicetest::repeatedly().run(|mut fate| { 119 | let orig_vec = fate.roll(dice::vec(dice::u8(..), ..)); 120 | 121 | let parts = fate.roll(dice::split_vec::<_, 1>(orig_vec.clone())); 122 | let merged_vec = merge_parts(&parts); 123 | assert_eq!(merged_vec, orig_vec); 124 | 125 | let parts = fate.roll(dice::split_vec::<_, 2>(orig_vec.clone())); 126 | let merged_vec = merge_parts(&parts); 127 | assert_eq!(merged_vec, orig_vec); 128 | 129 | let parts = fate.roll(dice::split_vec::<_, 3>(orig_vec.clone())); 130 | let merged_vec = merge_parts(&parts); 131 | assert_eq!(merged_vec, orig_vec); 132 | 133 | let parts = fate.roll(dice::split_vec::<_, 4>(orig_vec.clone())); 134 | let merged_vec = merge_parts(&parts); 135 | assert_eq!(merged_vec, orig_vec); 136 | }) 137 | } 138 | 139 | #[test] 140 | fn split_vec_n_result_can_be_merged_to_orig_vec() { 141 | Dicetest::repeatedly().run(|mut fate| { 142 | let expected_count = fate.roll(dice::length(..)); 143 | let orig_vec = if expected_count == 0 { 144 | Vec::new() 145 | } else { 146 | fate.roll(dice::vec(dice::u8(..), ..)) 147 | }; 148 | let parts = fate.roll(dice::split_vec_n(orig_vec.clone(), expected_count)); 149 | 150 | let actual_count = parts.len(); 151 | let merged_vec = merge_parts(&parts); 152 | 153 | assert_eq!(actual_count, expected_count); 154 | assert_eq!(merged_vec, orig_vec); 155 | }) 156 | } 157 | 158 | #[test] 159 | fn split_vec_with_zero() { 160 | Dicetest::repeatedly().run(|mut fate| { 161 | let parts = fate.roll(dice::split_vec::(Vec::new())); 162 | assert_eq!(parts, [] as [Vec; 0]); 163 | }) 164 | } 165 | 166 | #[test] 167 | fn split_vec_n_calc_stats() { 168 | Dicetest::repeatedly() 169 | .passes(0) 170 | .stats_enabled(true) 171 | .run(|mut fate| { 172 | stat!( 173 | "split_vec_n(vec![1, 2, 3, 4, 5], 2)", 174 | "{:?}", 175 | fate.roll(dice::split_vec_n(vec![1, 2, 3, 4, 5], 2)), 176 | ); 177 | 178 | stat!( 179 | "split_vec_n(vec![1, 2, 3, 4, 5], 3)", 180 | "{:?}", 181 | fate.roll(dice::split_vec_n(vec![1, 2, 3, 4, 5], 3)), 182 | ); 183 | }) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/dice/string.rs: -------------------------------------------------------------------------------- 1 | use crate::dice::{CollectionBuilder, LengthRange}; 2 | use crate::prelude::*; 3 | 4 | /// [`String`] builder for [`dice::collection`]. 5 | /// 6 | /// [`dice::collection`]: dice::collection() 7 | pub struct StringBuilder; 8 | 9 | impl StringBuilder { 10 | fn die() -> impl Die { 11 | dice::from_fn(|_fate| Self) 12 | } 13 | } 14 | 15 | impl CollectionBuilder for StringBuilder { 16 | fn build(self, elems: impl ExactSizeIterator) -> String { 17 | let mut string = String::with_capacity(elems.len()); 18 | string.extend(elems); 19 | string 20 | } 21 | } 22 | 23 | /// Generates a [`String`] that contains the specified [`char`]s. 24 | /// 25 | /// The range specifies the number of [`char`]s in the [`String`]. 26 | /// 27 | /// # Panics 28 | /// 29 | /// Panics if the range is empty. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// use dicetest::prelude::*; 35 | /// use dicetest::{Prng, Limit}; 36 | /// 37 | /// let mut prng = Prng::from_seed(0x5EED.into()); 38 | /// let limit = Limit::default(); 39 | /// let mut fate = Fate::new(&mut prng, limit); 40 | /// 41 | /// let char_die = dice::char(); 42 | /// 43 | /// let string = fate.with_limit(100.into()).roll(dice::string(&char_die, ..)); 44 | /// assert!(string.chars().count() <= 100); 45 | /// 46 | /// let string = fate.roll(dice::string(&char_die, ..=73)); 47 | /// assert!(string.chars().count() <= 73); 48 | /// 49 | /// let string = fate.roll(dice::string(&char_die, 17..)); 50 | /// assert!(string.chars().count() >= 17); 51 | /// 52 | /// let string = fate.roll(dice::string(&char_die, 42)); 53 | /// assert!(string.chars().count() == 42); 54 | /// ``` 55 | pub fn string(char_die: impl Die, length_range: impl LengthRange) -> impl Die { 56 | dice::collection(StringBuilder::die(), char_die, length_range) 57 | } 58 | -------------------------------------------------------------------------------- /src/dice/todo.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Generator for prototyping that panics when created. 4 | /// 5 | /// The `todo!()` macro doesn't work in places where a `impl Die` or `impl DieOnce` is expected 6 | /// because the type `!` is not stabilized yet, hence it's not possible to implement `Die` or 7 | /// `DieOnce` for `!`. This function can be used as an alternative. 8 | /// 9 | /// # Examples 10 | /// 11 | /// This example panics: 12 | /// 13 | /// ```should_panic 14 | /// use dicetest::prelude::*; 15 | /// use dicetest::{Prng, Limit}; 16 | /// 17 | /// let mut prng = Prng::from_seed(0x5EED.into()); 18 | /// let limit = Limit::default(); 19 | /// let mut fate = Fate::new(&mut prng, limit); 20 | /// 21 | /// let _number_die = dice::todo::(); 22 | /// ``` 23 | pub fn todo() -> impl Die { 24 | panic!( 25 | "implementation for Die<{}> is missing", 26 | std::any::type_name::(), 27 | ); 28 | #[allow(unreachable_code)] 29 | dice::from_fn(|_| unreachable!()) 30 | } 31 | -------------------------------------------------------------------------------- /src/dice/vec.rs: -------------------------------------------------------------------------------- 1 | use crate::dice::{CollectionBuilder, LengthRange}; 2 | use crate::prelude::*; 3 | 4 | /// [`Vec`] builder for [`dice::collection`]. 5 | /// 6 | /// [`dice::collection`]: dice::collection() 7 | pub struct VecBuilder; 8 | 9 | impl VecBuilder { 10 | fn die() -> impl Die { 11 | dice::from_fn(|_fate| Self) 12 | } 13 | } 14 | 15 | impl CollectionBuilder> for VecBuilder { 16 | fn build(self, elems: impl ExactSizeIterator) -> Vec { 17 | let mut vec = Vec::with_capacity(elems.len()); 18 | vec.extend(elems); 19 | vec 20 | } 21 | } 22 | 23 | /// Generates a [`Vec`] that contains elements of type `T`. 24 | /// 25 | /// The range specifies the length of the [`Vec`]. 26 | /// 27 | /// # Panics 28 | /// 29 | /// Panics if the range is empty. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// use dicetest::prelude::*; 35 | /// use dicetest::{Prng, Limit}; 36 | /// 37 | /// let mut prng = Prng::from_seed(0x5EED.into()); 38 | /// let limit = Limit::default(); 39 | /// let mut fate = Fate::new(&mut prng, limit); 40 | /// 41 | /// let elem_die = dice::u8(..); 42 | /// 43 | /// let vec = fate.with_limit(100.into()).roll(dice::vec(&elem_die, ..)); 44 | /// assert!(vec.len() <= 100); 45 | /// 46 | /// let vec = fate.roll(dice::vec(&elem_die, ..=73)); 47 | /// assert!(vec.len() <= 73); 48 | /// 49 | /// let vec = fate.roll(dice::vec(&elem_die, 17..)); 50 | /// assert!(vec.len() >= 17); 51 | /// 52 | /// let vec = fate.roll(dice::vec(&elem_die, 42)); 53 | /// assert!(vec.len() == 42); 54 | /// ``` 55 | pub fn vec(elem_die: impl Die, length_range: impl LengthRange) -> impl Die> { 56 | dice::collection(VecBuilder::die(), elem_die, length_range) 57 | } 58 | 59 | /// Similar to [`dice::vec`] but each element is generated using only a random part of 60 | /// [`Limit`]. 61 | /// 62 | /// If you want to generate a [`Vec`] that contains other collections, then you should 63 | /// consider using this generator for the outer [`Vec`]. That way the overall length is 64 | /// bounded by [`Limit]` (and not the square of [`Limit`)]. 65 | /// 66 | /// [`Limit`]: crate::Limit 67 | /// [`dice::vec`]: dice::vec() 68 | /// 69 | /// # Panics 70 | /// 71 | /// Panics if the range is empty. 72 | /// 73 | /// # Examples 74 | /// 75 | /// ``` 76 | /// use dicetest::prelude::*; 77 | /// use dicetest::{Prng, Limit}; 78 | /// 79 | /// let mut prng = Prng::from_seed(0x5EED.into()); 80 | /// let limit = Limit::default(); 81 | /// let mut fate = Fate::new(&mut prng, limit); 82 | /// 83 | /// let elem_die = dice::u8(..); 84 | /// let vec_die = dice::vec(elem_die, ..); 85 | /// let vec_of_vecs_die = dice::outer_vec(vec_die, ..); 86 | /// 87 | /// let vec_of_vecs = fate.roll(vec_of_vecs_die); 88 | /// assert!(vec_of_vecs.iter().flatten().count() <= 100); 89 | /// ``` 90 | pub fn outer_vec(elem_die: impl Die, length_range: impl LengthRange) -> impl Die> { 91 | dice::outer_collection(VecBuilder::die(), elem_die, length_range) 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use crate::prelude::*; 97 | 98 | #[test] 99 | fn vec_calc_stats() { 100 | Dicetest::repeatedly() 101 | .passes(0) 102 | .stats_enabled(true) 103 | .run(|mut fate| { 104 | stat!( 105 | "vec(dice::bool(), ..=3)", 106 | "{:?}", 107 | fate.roll(dice::vec(dice::bool(), ..=3)), 108 | ); 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/dice/vec_deque.rs: -------------------------------------------------------------------------------- 1 | use crate::dice::{CollectionBuilder, LengthRange}; 2 | use crate::prelude::*; 3 | use std::collections::VecDeque; 4 | 5 | /// [`VecDeque`] builder for [`dice::collection`]. 6 | /// 7 | /// [`dice::collection`]: dice::collection() 8 | pub struct VecDequeBuilder; 9 | 10 | impl VecDequeBuilder { 11 | fn die() -> impl Die { 12 | dice::from_fn(|_fate| Self) 13 | } 14 | } 15 | 16 | impl CollectionBuilder> for VecDequeBuilder { 17 | fn build(self, elems: impl ExactSizeIterator) -> VecDeque { 18 | let mut vec = VecDeque::with_capacity(elems.len()); 19 | vec.extend(elems); 20 | vec 21 | } 22 | } 23 | 24 | /// Generates a [`VecDeque`] that contains elements of type `T`. 25 | /// 26 | /// The range specifies the length of the [`VecDeque`]. 27 | /// 28 | /// # Panics 29 | /// 30 | /// Panics if the range is empty. 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// use dicetest::prelude::*; 36 | /// use dicetest::{Prng, Limit}; 37 | /// 38 | /// let mut prng = Prng::from_seed(0x5EED.into()); 39 | /// let limit = Limit::default(); 40 | /// let mut fate = Fate::new(&mut prng, limit); 41 | /// 42 | /// let elem_die = dice::u8(..); 43 | /// 44 | /// let vec = fate.with_limit(100.into()).roll(dice::vec_deque(&elem_die, ..)); 45 | /// assert!(vec.len() <= 100); 46 | /// 47 | /// let vec = fate.roll(dice::vec_deque(&elem_die, ..=73)); 48 | /// assert!(vec.len() <= 73); 49 | /// 50 | /// let vec = fate.roll(dice::vec_deque(&elem_die, 17..)); 51 | /// assert!(vec.len() >= 17); 52 | /// 53 | /// let vec = fate.roll(dice::vec_deque(&elem_die, 42)); 54 | /// assert!(vec.len() == 42); 55 | /// ``` 56 | pub fn vec_deque( 57 | elem_die: impl Die, 58 | length_range: impl LengthRange, 59 | ) -> impl Die> { 60 | dice::collection(VecDequeBuilder::die(), elem_die, length_range) 61 | } 62 | 63 | /// Similar to [`dice::vec_deque`] but each element is generated using only a random part of 64 | /// [`Limit`]. 65 | /// 66 | /// If you want to generate a [`VecDeque]` that contains other collections, then you should 67 | /// consider using this generator for the outer [`VecDeque`]. That way the overall length is 68 | /// bounded by [`Limit`] (and not the square of [`Limit`]). 69 | /// 70 | /// [`Limit`]: crate::Limit 71 | /// [`dice::vec_deque`]: dice::vec_deque() 72 | /// 73 | /// # Panics 74 | /// 75 | /// Panics if the range is empty. 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// use dicetest::prelude::*; 81 | /// use dicetest::{Prng, Limit}; 82 | /// 83 | /// let mut prng = Prng::from_seed(0x5EED.into()); 84 | /// let limit = Limit::default(); 85 | /// let mut fate = Fate::new(&mut prng, limit); 86 | /// 87 | /// let elem_die = dice::u8(..); 88 | /// let vec_die = dice::vec_deque(elem_die, ..); 89 | /// let vec_of_vecs_die = dice::outer_vec_deque(vec_die, ..); 90 | /// 91 | /// let vec_of_vecs = fate.roll(vec_of_vecs_die); 92 | /// assert!(vec_of_vecs.iter().flatten().count() <= 100); 93 | /// ``` 94 | pub fn outer_vec_deque( 95 | elem_die: impl Die, 96 | length_range: impl LengthRange, 97 | ) -> impl Die> { 98 | dice::outer_collection(VecDequeBuilder::die(), elem_die, length_range) 99 | } 100 | -------------------------------------------------------------------------------- /src/dice/zip.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Intermediate result of [`dice::zip_once`]. 4 | /// 5 | /// [`dice::zip_once`]: dice::zip_once() 6 | #[non_exhaustive] 7 | pub struct ZipOnceArities; 8 | 9 | macro_rules! zip_once_with_arity { 10 | ($arity:ident: $($Ti:ident, $die_i:ident)+) => ( 11 | #[allow(clippy::too_many_arguments)] 12 | pub fn $arity<$($Ti,)*>( 13 | self, 14 | $($die_i: impl DieOnce<$Ti>,)* 15 | ) -> impl DieOnce<($($Ti,)*)> { 16 | dice::from_fn_once(move |mut fate| { 17 | ($(fate.roll($die_i),)*) 18 | }) 19 | } 20 | ) 21 | } 22 | 23 | impl ZipOnceArities { 24 | zip_once_with_arity! { two: 25 | T1, die_1 26 | T2, die_2 27 | } 28 | 29 | zip_once_with_arity! { three: 30 | T1, die_1 31 | T2, die_2 32 | T3, die_3 33 | } 34 | 35 | zip_once_with_arity! { four: 36 | T1, die_1 37 | T2, die_2 38 | T3, die_3 39 | T4, die_4 40 | } 41 | 42 | zip_once_with_arity! { five: 43 | T1, die_1 44 | T2, die_2 45 | T3, die_3 46 | T4, die_4 47 | T5, die_5 48 | } 49 | 50 | zip_once_with_arity! { six: 51 | T1, die_1 52 | T2, die_2 53 | T3, die_3 54 | T4, die_4 55 | T5, die_5 56 | T6, die_6 57 | } 58 | 59 | zip_once_with_arity! { seven: 60 | T1, die_1 61 | T2, die_2 62 | T3, die_3 63 | T4, die_4 64 | T5, die_5 65 | T6, die_6 66 | T7, die_7 67 | } 68 | 69 | zip_once_with_arity! { eight: 70 | T1, die_1 71 | T2, die_2 72 | T3, die_3 73 | T4, die_4 74 | T5, die_5 75 | T6, die_6 76 | T7, die_7 77 | T8, die_8 78 | } 79 | 80 | zip_once_with_arity! { nine: 81 | T1, die_1 82 | T2, die_2 83 | T3, die_3 84 | T4, die_4 85 | T5, die_5 86 | T6, die_6 87 | T7, die_7 88 | T8, die_8 89 | T9, die_9 90 | } 91 | } 92 | 93 | /// Generates a tuple containing the generated values of several generators. 94 | /// 95 | /// # Examples 96 | /// 97 | /// ``` 98 | /// use dicetest::prelude::*; 99 | /// use dicetest::{Prng, Limit}; 100 | /// 101 | /// let mut prng = Prng::from_seed(0x5EED.into()); 102 | /// let limit = Limit::default(); 103 | /// let mut fate = Fate::new(&mut prng, limit); 104 | /// 105 | /// let zero_die = dice::just_once(0); 106 | /// let one_die = dice::just_once(1.0); 107 | /// let (zero, one) = fate.roll(dice::zip_once().two(zero_die, one_die)); 108 | /// assert_eq!(zero, 0); 109 | /// assert_eq!(one, 1.0); 110 | /// ``` 111 | pub fn zip_once() -> ZipOnceArities { 112 | ZipOnceArities {} 113 | } 114 | 115 | /// Intermediate result of [`dice::zip`]. 116 | /// 117 | /// [`dice::zip`]: dice::zip() 118 | #[non_exhaustive] 119 | pub struct ZipArities; 120 | 121 | macro_rules! zip_with_arity { 122 | ($arity:ident: $($Ti:ident, $die_i:ident)+) => ( 123 | #[allow(clippy::too_many_arguments)] 124 | pub fn $arity<$($Ti,)*>( 125 | self, 126 | $($die_i: impl Die<$Ti>,)* 127 | ) -> impl Die<($($Ti,)*)> { 128 | dice::from_fn(move |mut fate| { 129 | ($(fate.roll(&$die_i),)*) 130 | }) 131 | } 132 | ) 133 | } 134 | 135 | impl ZipArities { 136 | zip_with_arity! { two: 137 | T1, die_1 138 | T2, die_2 139 | } 140 | 141 | zip_with_arity! { three: 142 | T1, die_1 143 | T2, die_2 144 | T3, die_3 145 | } 146 | 147 | zip_with_arity! { four: 148 | T1, die_1 149 | T2, die_2 150 | T3, die_3 151 | T4, die_4 152 | } 153 | 154 | zip_with_arity! { five: 155 | T1, die_1 156 | T2, die_2 157 | T3, die_3 158 | T4, die_4 159 | T5, die_5 160 | } 161 | 162 | zip_with_arity! { six: 163 | T1, die_1 164 | T2, die_2 165 | T3, die_3 166 | T4, die_4 167 | T5, die_5 168 | T6, die_6 169 | } 170 | 171 | zip_with_arity! { seven: 172 | T1, die_1 173 | T2, die_2 174 | T3, die_3 175 | T4, die_4 176 | T5, die_5 177 | T6, die_6 178 | T7, die_7 179 | } 180 | 181 | zip_with_arity! { eight: 182 | T1, die_1 183 | T2, die_2 184 | T3, die_3 185 | T4, die_4 186 | T5, die_5 187 | T6, die_6 188 | T7, die_7 189 | T8, die_8 190 | } 191 | 192 | zip_with_arity! { nine: 193 | T1, die_1 194 | T2, die_2 195 | T3, die_3 196 | T4, die_4 197 | T5, die_5 198 | T6, die_6 199 | T7, die_7 200 | T8, die_8 201 | T9, die_9 202 | } 203 | } 204 | 205 | /// Generates a tuple containing the generated values of several generators. 206 | /// 207 | /// # Examples 208 | /// 209 | /// ``` 210 | /// use dicetest::prelude::*; 211 | /// use dicetest::{Prng, Limit}; 212 | /// 213 | /// let mut prng = Prng::from_seed(0x5EED.into()); 214 | /// let limit = Limit::default(); 215 | /// let mut fate = Fate::new(&mut prng, limit); 216 | /// 217 | /// let zero_die = dice::just(0); 218 | /// let one_die = dice::just(1.0); 219 | /// let (zero, one) = fate.roll(dice::zip().two(zero_die, one_die)); 220 | /// assert_eq!(zero, 0); 221 | /// assert_eq!(one, 1.0); 222 | /// ``` 223 | pub fn zip() -> ZipArities { 224 | ZipArities {} 225 | } 226 | -------------------------------------------------------------------------------- /src/die.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::{ArcDie, BoxedDie, FlatMapDie, FlattenDie, MapDie, RcDie}; 2 | use crate::{DieOnce, Fate}; 3 | 4 | /// Trait for generating pseudorandom values of type `T`. 5 | /// 6 | /// The [`Die`] trait represents a subset of [`DieOnce`]. It mirrors all methods of 7 | /// [`DieOnce`] without the suffix `_once`. These methods must behave in the same way. 8 | /// For example an implementation of [`Die`] must produce the same value with its methods 9 | /// [`roll`] and [`roll_once`] if they are called with the same [`Fate`]. 10 | /// 11 | /// [`roll_once`]: DieOnce::roll_once 12 | /// [`roll`]: Die::roll 13 | pub trait Die: DieOnce { 14 | /// Generates a pseudorandom value. 15 | /// 16 | /// The [`Fate`] is the only source of the randomness. Besides that, the generation is 17 | /// deterministic. 18 | fn roll(&self, fate: Fate) -> T; 19 | 20 | /// Creates a new [`Die`] by mapping the generated values of `self`. 21 | /// 22 | /// The function `f` will be applied to the generated values of `self`. The results of the 23 | /// function are the generated values of the new [`Die`]. 24 | fn map(self, f: F) -> MapDie 25 | where 26 | Self: Sized, 27 | F: Fn(T) -> U, 28 | { 29 | MapDie::new(self, f) 30 | } 31 | 32 | /// Creates a new [`Die`] whose values are generated by the generated [`Die`]s of `self`. 33 | fn flatten(self) -> FlattenDie 34 | where 35 | Self: Sized, 36 | T: DieOnce, 37 | { 38 | FlattenDie::new(self) 39 | } 40 | 41 | /// Creates a new [`Die`] similar to [`map`], except that the mapping produces [`DieOnce`]s. 42 | /// 43 | /// The function `f` will be applied to the generated values of `self`. The results of the 44 | /// function are [`DieOnce`]s that generates the values for the new [`Die`]. 45 | /// 46 | /// It is semantically equivalent to `self.map(f).flatten()`. 47 | /// 48 | /// [`map`]: Die::map 49 | fn flat_map(self, f: F) -> FlatMapDie 50 | where 51 | Self: Sized, 52 | UD: DieOnce, 53 | F: Fn(T) -> UD, 54 | { 55 | FlatMapDie::new(self, f) 56 | } 57 | 58 | /// Puts `self` behind a [`Box`] pointer. 59 | fn boxed<'a>(self) -> BoxedDie<'a, T> 60 | where 61 | Self: Sized + 'a, 62 | { 63 | BoxedDie::new(self) 64 | } 65 | 66 | /// Puts `self` behind an [`Rc`](std::rc::Rc) pointer. 67 | fn rc<'a>(self) -> RcDie<'a, T> 68 | where 69 | Self: Sized + 'a, 70 | { 71 | RcDie::new(self) 72 | } 73 | 74 | /// Puts `self` behind an [`Arc`](std::sync::Arc) pointer. 75 | fn arc(self) -> ArcDie 76 | where 77 | Self: Sized + 'static, 78 | { 79 | ArcDie::new(self) 80 | } 81 | } 82 | 83 | impl> DieOnce for &TD { 84 | fn roll_once(self, fate: Fate) -> T { 85 | (*self).roll(fate) 86 | } 87 | } 88 | 89 | impl> Die for &TD { 90 | fn roll(&self, fate: Fate) -> T { 91 | (**self).roll(fate) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/die_once.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::{BoxedDieOnce, FlatMapDie, FlattenDie, MapDie}; 2 | use crate::Fate; 3 | 4 | /// Trait for generating a single pseudorandom value of type `T`. 5 | pub trait DieOnce { 6 | /// Consumes the generator and generates a pseudorandom value. 7 | /// 8 | /// The [`Fate`] is the only source of the randomness. Besides that, the generation is 9 | /// deterministic. 10 | fn roll_once(self, fate: Fate) -> T; 11 | 12 | /// Creates a new [`DieOnce`] by mapping the generated values of `self`. 13 | /// 14 | /// The function `f` will be applied to the generated value of `self`. The result of the 15 | /// function is the generated value of the new [`DieOnce`]. 16 | fn map_once(self, f: F) -> MapDie 17 | where 18 | Self: Sized, 19 | F: FnOnce(T) -> U, 20 | { 21 | MapDie::new(self, f) 22 | } 23 | 24 | /// Creates a new [`DieOnce`] whose value is generated by the generated [`DieOnce`] of `self`. 25 | fn flatten_once(self) -> FlattenDie 26 | where 27 | Self: Sized, 28 | T: DieOnce, 29 | { 30 | FlattenDie::new(self) 31 | } 32 | 33 | /// Creates a new [`DieOnce`] similar to [`map_once`], except that the mapping 34 | /// produces a [`DieOnce`]. 35 | /// 36 | /// The function `f` will be applied to the generated value of `self`. The result of the 37 | /// function is a [`DieOnce`] that generate the value for the new [`DieOnce`]. 38 | /// 39 | /// It is semantically equivalent to `self.map_once(f).flatten_once()`. 40 | /// 41 | /// [`map_once`]: DieOnce::map_once 42 | fn flat_map_once(self, f: F) -> FlatMapDie 43 | where 44 | Self: Sized, 45 | DU: DieOnce, 46 | F: FnOnce(T) -> DU, 47 | { 48 | FlatMapDie::new(self, f) 49 | } 50 | 51 | /// Puts `self` behind a [`Box`] pointer. 52 | fn boxed_once<'a>(self) -> BoxedDieOnce<'a, T> 53 | where 54 | Self: Sized + 'a, 55 | { 56 | BoxedDieOnce::new(self) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/fate.rs: -------------------------------------------------------------------------------- 1 | use crate::{DieOnce, Limit, Prng}; 2 | 3 | /// Contains parameters for controlling the value generation with [`DieOnce`] and [`Die`]. 4 | /// 5 | /// The first parameter is a [`Prng`]. It is the only source of randomness that a implementor of 6 | /// [`DieOnce`] or [`Die`] is allowed to use. Using the [`Prng`] will mutate its state, but for 7 | /// the cause of preventing misuse there is no direct write access to it. 8 | /// 9 | /// The second parameter is a [`Limit`]. It's the upper limit for the length of dynamic data 10 | /// structures generated by the implementor of `DieOnce` or [`Die`]. The implementor has only read 11 | /// access to the [`Limit`]. 12 | /// 13 | /// [`Die`]: crate::Die 14 | pub struct Fate<'a> { 15 | prng: &'a mut Prng, 16 | limit: Limit, 17 | } 18 | 19 | impl<'a> Fate<'a> { 20 | /// Creates a new instance that uses the given parameters for value generation. 21 | pub fn new(prng: &'a mut Prng, limit: Limit) -> Self { 22 | Self { prng, limit } 23 | } 24 | 25 | /// Returns the next pseudorandom number generated with the underlying [`Prng`]. 26 | pub fn next_number(&mut self) -> u64 { 27 | self.prng.next_number() 28 | } 29 | 30 | /// Returns a [`Prng`] split off from the underlying [`Prng`]. 31 | pub fn fork_prng(&mut self) -> Prng { 32 | self.prng.fork() 33 | } 34 | 35 | /// Returns the underlying [`Limit`]. 36 | pub fn limit(&self) -> Limit { 37 | self.limit 38 | } 39 | 40 | /// Creates a borrowed copy. 41 | /// 42 | /// [`Fate`] cannot implement the [`Copy`] trait because it contains a mutable 43 | /// reference. When it's necessary to move [`Fate`] multiple times this functions provides a 44 | /// convenient workaround. 45 | /// 46 | /// # Example 47 | /// 48 | /// ``` 49 | /// use dicetest::{Limit, Fate, Prng}; 50 | /// 51 | /// let mut prng = Prng::from_seed(42.into()); 52 | /// let limit = Limit::default(); 53 | /// let mut fate = Fate::new(&mut prng, limit); 54 | /// 55 | /// pub fn take_fate(_fate: Fate) {} 56 | /// 57 | /// take_fate(fate.copy()); 58 | /// take_fate(fate); 59 | /// ``` 60 | pub fn copy(&mut self) -> Fate { 61 | Fate { 62 | prng: self.prng, 63 | limit: self.limit, 64 | } 65 | } 66 | 67 | /// Creates a copy with the given limit. 68 | pub fn with_limit(&mut self, limit: Limit) -> Fate { 69 | let mut fate = self.copy(); 70 | fate.limit = limit; 71 | fate 72 | } 73 | 74 | /// Generates a value with the given [`DieOnce`] using `self` as parameter. 75 | /// 76 | /// This function is more convenient than calling [`DieOnce::roll_once`] directly because 77 | /// it borrows the [`Fate`] instead of moving it. 78 | /// 79 | /// ``` 80 | /// use dicetest::prelude::*; 81 | /// use dicetest::{Limit, Prng}; 82 | /// 83 | /// let mut prng = Prng::from_seed(42.into()); 84 | /// let limit = Limit::default(); 85 | /// let mut fate = Fate::new(&mut prng, limit); 86 | /// 87 | /// let die = dice::bool(); 88 | /// 89 | /// let val1 = fate.roll(&die); // Borrows `fate` 90 | /// let val2 = die.roll(fate); // Moves `fate` 91 | /// ``` 92 | pub fn roll>(&mut self, die: D) -> T { 93 | die.roll_once(self.copy()) 94 | } 95 | 96 | /// Generates a value using the given [`Distribution`]. 97 | /// 98 | /// Only available if the feature `rand` is enabled. 99 | /// 100 | /// [`Distribution`]: rand::distributions::Distribution 101 | #[cfg(feature = "rand")] 102 | #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] 103 | pub fn roll_distribution(&mut self, distribution: D) -> T 104 | where 105 | D: rand::distributions::Distribution, 106 | { 107 | let die = crate::dice::from_distribution(distribution); 108 | self.roll(die) 109 | } 110 | } 111 | 112 | #[cfg(feature = "rand_core")] 113 | #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] 114 | impl<'a> rand_core::RngCore for Fate<'a> { 115 | fn next_u32(&mut self) -> u32 { 116 | self.next_number() as u32 117 | } 118 | 119 | fn next_u64(&mut self) -> u64 { 120 | self.next_number() 121 | } 122 | 123 | fn fill_bytes(&mut self, dest: &mut [u8]) { 124 | rand_core::impls::fill_bytes_via_next(self, dest) 125 | } 126 | 127 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { 128 | self.fill_bytes(dest); 129 | Ok(()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/frontend.rs: -------------------------------------------------------------------------------- 1 | mod run_code; 2 | use run_code::RunCode; 3 | 4 | mod mode; 5 | use mode::Mode; 6 | 7 | mod formatter; 8 | 9 | mod env; 10 | 11 | mod dicetest; 12 | pub use self::dicetest::Dicetest; 13 | -------------------------------------------------------------------------------- /src/frontend/env.rs: -------------------------------------------------------------------------------- 1 | use std::env::{self, VarError}; 2 | use std::str::FromStr; 3 | 4 | use crate::frontend::{Mode, RunCode}; 5 | use crate::{Limit, Seed}; 6 | 7 | const KEY_MODE: &str = "DICETEST_MODE"; 8 | const KEY_DEBUG: &str = "DICETEST_DEBUG"; 9 | const KEY_REGRESSIONS_ENABLED: &str = "DICETEST_REGRESSIONS_ENABLED"; 10 | const KEY_SEED: &str = "DICETEST_SEED"; 11 | const KEY_ONCE_LIMIT: &str = "DICETEST_ONCE_LIMIT"; 12 | const KEY_START_LIMIT: &str = "DICETEST_START_LIMIT"; 13 | const KEY_END_LIMIT: &str = "DICETEST_END_LIMIT"; 14 | const KEY_LIMIT_MULTIPLIER: &str = "DICETEST_LIMIT_MULTIPLIER"; 15 | const KEY_PASSES: &str = "DICETEST_PASSES"; 16 | const KEY_PASSES_MULTIPLIER: &str = "DICETEST_PASSES_MULTIPLIER"; 17 | const KEY_HINTS_ENABLED: &str = "DICETEST_HINTS_ENABLED"; 18 | const KEY_STATS_ENABLED: &str = "DICETEST_STATS_ENABLED"; 19 | const KEY_STATS_MAX_VALUE_COUNT: &str = "DICETEST_STATS_MAX_VALUE_COUNT"; 20 | const KEY_STATS_PERCENT_PRECISION: &str = "DICETEST_STATS_PERCENT_PRECISION"; 21 | 22 | const VALUE_NONE: &str = "none"; 23 | const VALUE_REPEATEDLY: &str = "repeatedly"; 24 | const VALUE_ONCE: &str = "once"; 25 | 26 | pub enum EnvValue { 27 | NotPresent, 28 | Present(T), 29 | } 30 | 31 | pub fn read_mode() -> Result, String> { 32 | let key = KEY_DEBUG; 33 | match env::var(key) { 34 | Err(VarError::NotPresent) => read_non_debug_mode(), 35 | Err(err) => handle_var_error(key, err), 36 | Ok(s) => match RunCode::from_base64(&s) { 37 | Ok(run_code) => Ok(EnvValue::Present(Mode::Debug(run_code))), 38 | Err(err) => Err(format!("Value for '{}' is not valid: {}", key, err)), 39 | }, 40 | } 41 | } 42 | 43 | fn read_non_debug_mode() -> Result, String> { 44 | match env::var(KEY_MODE) { 45 | Err(err) => handle_var_error(KEY_MODE, err), 46 | Ok(var) => { 47 | let str = var.as_str(); 48 | if str == VALUE_REPEATEDLY { 49 | Ok(EnvValue::Present(Mode::Repeatedly)) 50 | } else if str == VALUE_ONCE { 51 | Ok(EnvValue::Present(Mode::Once)) 52 | } else { 53 | let error = format!( 54 | "Value for '{}' must be either '{}', or '{}'", 55 | KEY_MODE, VALUE_REPEATEDLY, VALUE_ONCE 56 | ); 57 | Err(error) 58 | } 59 | } 60 | } 61 | } 62 | pub fn read_regressions_enabled() -> Result, String> { 63 | read_value(KEY_REGRESSIONS_ENABLED, "a bool", bool::from_str) 64 | } 65 | 66 | pub fn read_seed() -> Result>, String> { 67 | read_option_value(KEY_SEED, "an u64", |s| u64::from_str(s).map(Seed)) 68 | } 69 | 70 | pub fn read_once_limit() -> Result, String> { 71 | read_value(KEY_ONCE_LIMIT, "an u64", |s| u64::from_str(s).map(Limit)) 72 | } 73 | 74 | pub fn read_start_limit() -> Result, String> { 75 | read_value(KEY_START_LIMIT, "an u64", |s| u64::from_str(s).map(Limit)) 76 | } 77 | 78 | pub fn read_end_limit() -> Result, String> { 79 | read_value(KEY_END_LIMIT, "an u64", |s| u64::from_str(s).map(Limit)) 80 | } 81 | 82 | pub fn read_limit_multiplier() -> Result>, String> { 83 | read_option_value(KEY_LIMIT_MULTIPLIER, "a f64", f64::from_str) 84 | } 85 | 86 | pub fn read_passes() -> Result, String> { 87 | read_value(KEY_PASSES, "an u64", u64::from_str) 88 | } 89 | 90 | pub fn read_passes_multiplier() -> Result>, String> { 91 | read_option_value(KEY_PASSES_MULTIPLIER, "a f64", f64::from_str) 92 | } 93 | 94 | pub fn read_hints_enabled() -> Result, String> { 95 | read_value(KEY_HINTS_ENABLED, "a bool", bool::from_str) 96 | } 97 | 98 | pub fn read_stats_enabled() -> Result, String> { 99 | read_value(KEY_STATS_ENABLED, "a bool", bool::from_str) 100 | } 101 | 102 | pub fn read_stats_max_value_count() -> Result>, String> { 103 | read_option_value(KEY_STATS_MAX_VALUE_COUNT, "an usize", usize::from_str) 104 | } 105 | 106 | pub fn read_stats_percent_precision() -> Result, String> { 107 | read_value(KEY_STATS_PERCENT_PRECISION, "an usize", usize::from_str) 108 | } 109 | 110 | fn read_value( 111 | key: &str, 112 | typ: &str, 113 | parse: impl FnOnce(&str) -> Result, 114 | ) -> Result, String> { 115 | match env::var(key) { 116 | Err(err) => handle_var_error(key, err), 117 | Ok(s) => match parse(&s) { 118 | Ok(value) => Ok(EnvValue::Present(value)), 119 | Err(_) => Err(format!("Value for '{}' must be {}", key, typ)), 120 | }, 121 | } 122 | } 123 | 124 | fn read_option_value( 125 | key: &str, 126 | typ: &str, 127 | parse: impl FnOnce(&str) -> Result, 128 | ) -> Result>, String> { 129 | match env::var(key) { 130 | Err(err) => handle_var_error(key, err), 131 | Ok(s) if s == VALUE_NONE => Ok(EnvValue::Present(None)), 132 | Ok(s) => match parse(&s) { 133 | Ok(value) => Ok(EnvValue::Present(Some(value))), 134 | Err(_) => Err(format!( 135 | "Value for '{}' must be either '{}' or {}", 136 | key, VALUE_NONE, typ 137 | )), 138 | }, 139 | } 140 | } 141 | 142 | fn handle_var_error(key: &str, err: VarError) -> Result, String> { 143 | match err { 144 | VarError::NotPresent => Ok(EnvValue::NotPresent), 145 | VarError::NotUnicode(_) => Err(format!("Value for '{}' is not valid unicode", key)), 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/frontend/mode.rs: -------------------------------------------------------------------------------- 1 | use crate::frontend::RunCode; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq)] 4 | pub enum Mode { 5 | Debug(RunCode), 6 | Once, 7 | Repeatedly, 8 | } 9 | -------------------------------------------------------------------------------- /src/frontend/run_code.rs: -------------------------------------------------------------------------------- 1 | use crate::util::{base64, conversion}; 2 | use crate::{Limit, Prng}; 3 | 4 | #[derive(Debug, Clone, PartialEq, Eq)] 5 | pub struct RunCode { 6 | pub prng: Prng, 7 | pub limit: Limit, 8 | } 9 | 10 | impl RunCode { 11 | pub fn from_base64(string: &str) -> Result { 12 | let bytes = base64::decode(string)?; 13 | 14 | if bytes.len() != 40 { 15 | return Err("Run code has invalid length".to_string()); 16 | } 17 | 18 | let prng = { 19 | let mut seed_bytes = [0; 32]; 20 | seed_bytes.copy_from_slice(&bytes[0..32]); 21 | Prng::from_bytes(seed_bytes) 22 | }; 23 | 24 | let limit = { 25 | let mut limit_bytes = [0; 8]; 26 | limit_bytes.copy_from_slice(&bytes[32..40]); 27 | Limit(conversion::bytes_to_u64(limit_bytes)) 28 | }; 29 | 30 | let run_code = RunCode { prng, limit }; 31 | 32 | Ok(run_code) 33 | } 34 | 35 | pub fn to_base64(&self) -> String { 36 | let mut bytes = Vec::new(); 37 | 38 | bytes.extend_from_slice(&self.prng.to_bytes()); 39 | bytes.extend_from_slice(&conversion::u64_to_bytes(self.limit.0)); 40 | 41 | base64::encode(&bytes) 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use crate::frontend::RunCode; 48 | use crate::prelude::*; 49 | use crate::{asserts, Limit}; 50 | 51 | #[test] 52 | fn display_is_right_inverse_for_parse() { 53 | Dicetest::repeatedly().run(|fate| { 54 | let prng_die = dice::from_fn(|mut fate| fate.fork_prng()); 55 | let limit_die = dice::u64(..).map(Limit); 56 | let run_code_die = dice::zip() 57 | .two(prng_die, limit_die) 58 | .map(|(prng, limit)| RunCode { prng, limit }); 59 | 60 | asserts::right_inverse( 61 | fate, 62 | run_code_die, 63 | |base64: String| RunCode::from_base64(&base64).unwrap(), 64 | |run_code| run_code.to_base64(), 65 | ); 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/hints.rs: -------------------------------------------------------------------------------- 1 | //! Hints help to analyze a single test run, mostly the counterexample. 2 | //! 3 | //! You can put context information like local variables 4 | //! inside of hints. Use it to reveal what test data were generated or 5 | //! which branches were taken. Hints must be enabled with the feature 6 | //! `hints`. 7 | 8 | #[cfg(feature = "hints")] 9 | use crate::util::events; 10 | 11 | /// A single hint that contains context information. 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct Hint { 14 | /// The indent level of the text. 15 | pub indent: usize, 16 | /// Contains the context information. 17 | pub text: String, 18 | } 19 | 20 | /// A collection of hints. 21 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 22 | pub struct Hints(pub Vec); 23 | 24 | impl Hints { 25 | /// Returns an instance without any hints. 26 | pub fn new() -> Self { 27 | Hints(Vec::new()) 28 | } 29 | } 30 | 31 | #[cfg(feature = "hints")] 32 | struct InterimHints { 33 | current_indent: usize, 34 | hints: Hints, 35 | } 36 | 37 | #[cfg(feature = "hints")] 38 | impl events::Events for InterimHints { 39 | fn new() -> Self { 40 | InterimHints { 41 | current_indent: 0, 42 | hints: Hints::new(), 43 | } 44 | } 45 | 46 | fn take(&mut self) -> Self { 47 | InterimHints { 48 | current_indent: self.current_indent, 49 | hints: Hints(self.hints.0.drain(..).collect()), 50 | } 51 | } 52 | } 53 | 54 | #[cfg(feature = "hints")] 55 | thread_local! { 56 | static LOCAL: events::Stack = events::new_stack(); 57 | } 58 | 59 | /// Returns all hints that were added during the evaluation of the given function. 60 | pub fn collect(f: impl FnOnce() -> R) -> (R, Hints) { 61 | #[cfg(feature = "hints")] 62 | { 63 | let (result, interim) = events::collect(&LOCAL, f); 64 | (result, interim.hints) 65 | } 66 | #[cfg(not(feature = "hints"))] 67 | { 68 | (f(), Hints::new()) 69 | } 70 | } 71 | 72 | /// Returns if hints are currently enabled. 73 | /// 74 | /// Hints are enabled if and only if this function is executed inside of [`collect`] and 75 | /// the feature `hints` is present. 76 | pub fn enabled() -> bool { 77 | #[cfg(feature = "hints")] 78 | { 79 | events::enabled(&LOCAL) 80 | } 81 | #[cfg(not(feature = "hints"))] 82 | { 83 | false 84 | } 85 | } 86 | 87 | /// If hints are enabled, this function evaluates and adds the given hint. Otherwise this function 88 | /// is a noop. 89 | pub fn add(message_text: impl FnOnce() -> String) { 90 | #[cfg(feature = "hints")] 91 | { 92 | events::modify(&LOCAL, move |stack| { 93 | let text = message_text(); 94 | let len = stack.len(); 95 | 96 | fn add_message(interim: &mut InterimHints, text: String) { 97 | let indent = interim.current_indent; 98 | let message = Hint { indent, text }; 99 | interim.hints.0.push(message); 100 | } 101 | 102 | stack[0..len - 1] 103 | .iter_mut() 104 | .for_each(|collection| add_message(collection, text.clone())); 105 | add_message(&mut stack[len - 1], text); 106 | }); 107 | } 108 | #[cfg(not(feature = "hints"))] 109 | { 110 | let _ = message_text; 111 | } 112 | } 113 | 114 | /// Increases the indent of all following added hints. 115 | pub fn indent() { 116 | #[cfg(feature = "hints")] 117 | { 118 | events::modify(&LOCAL, |stack| { 119 | stack.iter_mut().for_each(|interim| { 120 | let current_indent = interim.current_indent; 121 | interim.current_indent = current_indent.saturating_add(1); 122 | }); 123 | }); 124 | } 125 | } 126 | 127 | /// Decreases the indent of all following added hints. 128 | pub fn unindent() { 129 | #[cfg(feature = "hints")] 130 | { 131 | events::modify(&LOCAL, |stack| { 132 | stack.iter_mut().for_each(|interim| { 133 | let current_indent = interim.current_indent; 134 | interim.current_indent = current_indent.saturating_sub(1); 135 | }); 136 | }); 137 | } 138 | } 139 | 140 | /// Represents a currently active indent. 141 | /// 142 | /// It increases the indent (see [`indent`]) when created and decreases the indent 143 | /// (see [`unindent`]) when dropped. 144 | pub struct Section { 145 | _dummy: (), 146 | } 147 | 148 | impl Section { 149 | /// Increases the indent and returns a [`Section`]. 150 | pub fn start() -> Section { 151 | indent(); 152 | Section { _dummy: () } 153 | } 154 | } 155 | 156 | impl Drop for Section { 157 | fn drop(&mut self) { 158 | unindent(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Framework for writing tests with randomly generated test data. 2 | //! 3 | //! See [the readme] and the [the guide] for more information. 4 | //! 5 | //! [the readme]: https://github.com/jakoschiko/dicetest/blob/main/README.md 6 | //! [the guide]: https://github.com/jakoschiko/dicetest/blob/main/GUIDE.md 7 | //! 8 | //! # Example 9 | //! 10 | //! ```no_run 11 | //! use dicetest::prelude::*; 12 | //! 13 | //! #[test] 14 | //! fn result_of_bubble_sort_is_sorted() { 15 | //! Dicetest::repeatedly().run(|mut fate| { 16 | //! let mut v = fate.roll(dice::vec(dice::u8(..), ..)); 17 | //! hint!("unsorted: {:?}", v); 18 | //! 19 | //! v.sort(); 20 | //! hint!(" sorted: {:?}", v); 21 | //! 22 | //! let is_sorted = v.windows(2).all(|w| w[0] <= w[1]); 23 | //! assert!(is_sorted); 24 | //! }) 25 | //! } 26 | //! ``` 27 | //! 28 | //! # Environment variables 29 | //! 30 | //! See the documentation of [`Dicetest`] for a full list of supported environment variables. 31 | //! 32 | //! # Feature flags 33 | //! 34 | //! There are several feature flags for disabling runtime overhead or enabling additional 35 | //! features at compile time. 36 | //! 37 | //! #### `hints` (enabled by default) 38 | //! 39 | //! Enables or disables the hints feature at compile time. If disabled, 40 | //! all hints operations are no-ops. 41 | //! 42 | //! #### `stats` (enabled by default) 43 | //! 44 | //! Enables or disables the stats feature at compile time. If disabled, 45 | //! all stats operations are no-ops. 46 | //! 47 | //! #### `rand_core` (disabled by default) 48 | //! 49 | //! If enabled, [`Prng`] and [`Fate`] implements the [`rand_core::RngCore`] 50 | //! trait. 51 | //! 52 | //! #### `rand` (disabled by default) 53 | //! 54 | //! If enabled, [`Fate::roll_distribution`] and [`dice::from_distribution`] are available. 55 | //! This allows to generate values and create [`Die`]s from implementations 56 | //! of [`rand::distributions::Distribution`]. 57 | //! 58 | //! ``` 59 | //! # #[cfg(feature = "rand")] { 60 | //! use dicetest::prelude::*; 61 | //! use dicetest::{Limit, Prng}; 62 | //! 63 | //! let mut prng = Prng::from_seed(0x5EED.into()); 64 | //! let limit = Limit(5); 65 | //! let mut fate = Fate::new(&mut prng, limit); 66 | //! 67 | //! // Generate a value from a `rand::distributions::Distribution` 68 | //! let byte: u8 = fate.roll_distribution(rand::distributions::Standard); 69 | //! println!("{:?}", byte); 70 | //! // Output: 28 71 | //! 72 | //! // Create a `Die` from a `rand::distributions::Distribution` 73 | //! let byte_die = dice::from_distribution(rand::distributions::Standard); 74 | //! let bytes_die = dice::vec(byte_die, 1..); 75 | //! let bytes: Vec = fate.roll(bytes_die); 76 | //! println!("{:?}", bytes); 77 | //! // Output: [236, 205, 151, 229] 78 | //! # } 79 | //! ``` 80 | 81 | // This allows us to add annotations to feature-gated items. 82 | #![cfg_attr(docsrs, feature(doc_cfg))] 83 | 84 | // This crate makes assumptions regarding the pointer width. The following conditional error 85 | // prevents the compilation for unsupported pointer widths. 86 | // 87 | // See https://github.com/rust-lang/rfcs/issues/1748 88 | #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] 89 | compile_error!("Only targets with pointer width 32 and 64 are currently supported"); 90 | 91 | mod macros; 92 | 93 | mod util; 94 | 95 | mod seed; 96 | pub use seed::Seed; 97 | 98 | mod prng; 99 | pub use prng::Prng; 100 | 101 | mod codie; 102 | pub use codie::Codie; 103 | 104 | mod limit; 105 | pub use limit::Limit; 106 | 107 | mod fate; 108 | pub use fate::Fate; 109 | 110 | mod die_once; 111 | pub use die_once::DieOnce; 112 | 113 | mod die; 114 | pub use die::Die; 115 | 116 | pub mod adapters; 117 | 118 | pub mod codice; 119 | 120 | pub mod dice; 121 | 122 | pub mod hints; 123 | 124 | pub mod stats; 125 | 126 | pub mod runner; 127 | 128 | mod frontend; 129 | pub use frontend::Dicetest; 130 | 131 | pub mod prelude; 132 | 133 | #[cfg(test)] 134 | mod asserts; 135 | 136 | // Test examples from the README. 137 | #[doc = include_str!("../README.md")] 138 | #[cfg(doctest)] 139 | pub struct ReadmeDoctests; 140 | 141 | // Test examples from the GUIDE. 142 | #[doc = include_str!("../GUIDE.md")] 143 | #[cfg(doctest)] 144 | pub struct GuideDoctests; 145 | -------------------------------------------------------------------------------- /src/limit.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | /// The upper limit for the length of dynamic data structures generated with [`DieOnce`] and 4 | /// [`Die`]. 5 | /// 6 | /// The implementor of [`DieOnce`] or [`Die`] is allowed to freely interpret or even ignore this 7 | /// value, but it's recommended that the complexity of the value generation is in `O(limit)`. 8 | /// 9 | /// This parameter exists because the hardware of the testing machine is limited. For example 10 | /// a very big list could not fit in the memory or its generation could take too much time. 11 | /// With this parameter you can implement a generator for lists of arbitrary length and its 12 | /// user can choose an upper limit depending on his hardware. 13 | /// 14 | /// [`DieOnce`]: crate::DieOnce 15 | /// [`Die`]: crate::Die 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub struct Limit(pub u64); 18 | 19 | impl Limit { 20 | /// Uses the given [`usize`] as limit. 21 | /// 22 | /// If the [`usize`] is greater than the largest [`u64`] value, 23 | /// the function returns the largest [`u64`] value as limit. 24 | pub fn saturating_from_usize(usize: usize) -> Self { 25 | Self(u64::try_from(usize).unwrap_or(u64::MAX)) 26 | } 27 | 28 | /// Returns the limit as `usize`. 29 | /// 30 | /// If the limit is greater than the largest [`usize`] value, 31 | /// the function returns the largest [`usize`] value. 32 | pub fn saturating_to_usize(self) -> usize { 33 | usize::try_from(self.0).unwrap_or(usize::MAX) 34 | } 35 | } 36 | 37 | impl From for Limit { 38 | fn from(limit: u64) -> Self { 39 | Limit(limit) 40 | } 41 | } 42 | 43 | impl Default for Limit { 44 | fn default() -> Self { 45 | Limit(100) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Adds a hint that contains the arguments applied to the [`format`] macro. 2 | /// 3 | /// # Examples 4 | /// 5 | /// ``` 6 | /// use dicetest::hint; 7 | /// 8 | /// let unknown_value = 42; 9 | /// hint!("Revealing the unknown value: {}", unknown_value); 10 | /// ``` 11 | #[macro_export] 12 | macro_rules! hint { 13 | ($($arg:tt)*) => { 14 | $crate::hints::add(|| format!($($arg)*)); 15 | } 16 | } 17 | 18 | /// Adds a hint that contains the stringified argument and the argument value converted with 19 | /// [`Debug`]. 20 | /// 21 | /// [`Debug`]: std::fmt::Debug 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// use dicetest::hint_debug; 27 | /// 28 | /// let unknown_value = 42; 29 | /// hint_debug!(unknown_value); 30 | /// ``` 31 | #[macro_export] 32 | macro_rules! hint_debug { 33 | ($arg:tt) => { 34 | $crate::hints::add(|| format!(concat!("{} = {:?}"), stringify!($arg), $arg)); 35 | }; 36 | } 37 | 38 | /// Indents all hints in the caller's code block after this macro is called. 39 | /// 40 | /// If arguments are specified, a (not indented) hint will be added with the arguments applied 41 | /// to the [`format`] macro. This hint is meant as the title of the section. 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// use dicetest::{hint, hint_section}; 47 | /// 48 | /// hint!("Start test"); // This hint is not indented 49 | /// 50 | /// { 51 | /// hint_section!("Test foo"); // This hint is not indented 52 | /// hint!("foo"); // This hint is indented 53 | /// } 54 | /// 55 | /// { 56 | /// hint_section!("Test bar"); // This hint is not indented 57 | /// hint!("bar"); // This hint is indented 58 | /// 59 | /// hint_section!(); // No hint 60 | /// hint!("bar"); // This hint is indented twice 61 | /// } 62 | /// 63 | /// hint!("Test finished"); // This hint is not indented 64 | /// ``` 65 | #[macro_export] 66 | macro_rules! hint_section { 67 | () => { 68 | let _block_ident = $crate::hints::Section::start(); 69 | }; 70 | ($($arg:tt)*) => { 71 | $crate::hints::add(|| format!($($arg)*)); 72 | let _block_ident = $crate::hints::Section::start(); 73 | } 74 | } 75 | 76 | /// Creates a stat with the first argument as stat key and the remaining arguments applied to the 77 | /// [`format`] macro as stat value. 78 | /// 79 | /// # Examples 80 | /// 81 | /// ``` 82 | /// use dicetest::stat; 83 | /// 84 | /// let random_number = 4; 85 | /// stat!("Is random number even?", "{}", random_number % 2 == 0); 86 | /// ``` 87 | #[macro_export] 88 | macro_rules! stat { 89 | ($key:tt, $($arg:tt)*) => { 90 | $crate::stats::inc($key, || format!($($arg)*)) 91 | } 92 | } 93 | 94 | /// Creates a stat with the stringified argument as stat key and the argument value converted with 95 | /// [`Debug`] as stat value. 96 | /// 97 | /// [`Debug`]: std::fmt::Debug 98 | /// 99 | /// # Examples 100 | /// 101 | /// ``` 102 | /// use dicetest::stat_debug; 103 | /// 104 | /// let random_number = 4; 105 | /// stat_debug!({ random_number % 2 == 0 }); 106 | /// ``` 107 | #[macro_export] 108 | macro_rules! stat_debug { 109 | ($arg:tt) => { 110 | $crate::stats::inc(stringify!($arg), || format!("{:?}", $arg)) 111 | }; 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | #[test] 117 | fn macro_hint_produces_valid_code() { 118 | if false { 119 | hint!("foo"); 120 | hint!("bar {}", 42); 121 | } 122 | } 123 | 124 | #[test] 125 | fn macro_hint_debug_produces_valid_code() { 126 | if false { 127 | hint_debug!(42); 128 | hint_debug!((0 < 20)); 129 | hint_debug!((if true { 1 } else { 2 })); 130 | } 131 | } 132 | 133 | #[test] 134 | fn macro_hint_section_produces_valid_code() { 135 | if false { 136 | hint_section!(); 137 | hint_section!("foo"); 138 | hint_section!("bar {}", 42); 139 | } 140 | } 141 | 142 | #[test] 143 | #[cfg(feature = "hints")] 144 | fn macro_hint_section_produces_correct_indent() { 145 | let (_, actual_hints) = crate::hints::collect(|| { 146 | { 147 | hint!("foo1"); 148 | hint_section!(); 149 | hint!("foo2"); 150 | hint_section!("bar"); 151 | hint!("foo3"); 152 | }; 153 | hint!("foo4"); 154 | }); 155 | let expected_hints = crate::hints::Hints(vec![ 156 | crate::hints::Hint { 157 | indent: 0, 158 | text: "foo1".to_owned(), 159 | }, 160 | crate::hints::Hint { 161 | indent: 1, 162 | text: "foo2".to_owned(), 163 | }, 164 | crate::hints::Hint { 165 | indent: 1, 166 | text: "bar".to_owned(), 167 | }, 168 | crate::hints::Hint { 169 | indent: 2, 170 | text: "foo3".to_owned(), 171 | }, 172 | crate::hints::Hint { 173 | indent: 0, 174 | text: "foo4".to_owned(), 175 | }, 176 | ]); 177 | assert_eq!(expected_hints, actual_hints) 178 | } 179 | 180 | #[test] 181 | fn macro_stat_produces_valid_code() { 182 | if false { 183 | stat!("A", "foo"); 184 | stat!("B", "bar {}", 42); 185 | } 186 | } 187 | 188 | #[test] 189 | fn macro_stat_debug_produces_valid_code() { 190 | if false { 191 | stat_debug!(42); 192 | stat_debug!((0 < 20)); 193 | stat_debug!((if true { 1 } else { 2 })); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Contains the most useful imports for writing tests and value generators. 2 | 3 | pub use crate::{ 4 | codice, dice, hint, hint_debug, hints, stat, stat_debug, stats, Codie, Dicetest, Die, DieOnce, 5 | Fate, 6 | }; 7 | -------------------------------------------------------------------------------- /src/prng.rs: -------------------------------------------------------------------------------- 1 | use std::hash::BuildHasher; 2 | #[allow(deprecated)] 3 | use std::hash::SipHasher; 4 | use std::num::Wrapping; 5 | 6 | use crate::util::conversion; 7 | use crate::Seed; 8 | 9 | /// A pseudorandom number generator. 10 | /// 11 | /// The algorithms are based on [this article] by Bob Jenkins. 12 | /// 13 | /// [this article]: http://burtleburtle.net/bob/rand/smallprng.html 14 | #[derive(Debug, Clone, Eq, PartialEq)] 15 | pub struct Prng { 16 | state: (u64, u64, u64, u64), 17 | } 18 | 19 | impl Prng { 20 | /// Creates a [`Prng`] whose internal state is initialized with the given seed. 21 | /// 22 | /// The result has a satisfying cycle length. 23 | pub fn from_seed(seed: Seed) -> Prng { 24 | let state = (0xf1ea_5eed, seed.0, seed.0, seed.0); 25 | let mut prng = Prng { state }; 26 | for _ in 0..20 { 27 | prng.next_number(); 28 | } 29 | prng 30 | } 31 | 32 | /// Creates a [`Prng`] using the given byte array as internal state. 33 | /// 34 | /// This function is a left and right inverse for [`to_bytes`]. 35 | /// 36 | /// A satisfying cycle length is only guaranteed for bytes from [`to_bytes`] called 37 | /// with an [`Prng`] that has a satisfying cycle length. Other bytes should not be passed to this 38 | /// function. For initializing an [`Prng`] with an arbitrary seed, use [`from_seed`] instead. 39 | /// 40 | /// [`to_bytes`]: Prng::to_bytes 41 | /// [`from_seed`]: Prng::from_seed 42 | pub fn from_bytes(state_bytes: [u8; 32]) -> Prng { 43 | #[rustfmt::skip] 44 | let [ 45 | a0, a1, a2, a3, a4, a5, a6, a7, 46 | b0, b1, b2, b3, b4, b5, b6, b7, 47 | c0, c1, c2, c3, c4, c5, c6, c7, 48 | d0, d1, d2, d3, d4, d5, d6, d7, 49 | ] = state_bytes; 50 | 51 | let a = conversion::bytes_to_u64([a0, a1, a2, a3, a4, a5, a6, a7]); 52 | let b = conversion::bytes_to_u64([b0, b1, b2, b3, b4, b5, b6, b7]); 53 | let c = conversion::bytes_to_u64([c0, c1, c2, c3, c4, c5, c6, c7]); 54 | let d = conversion::bytes_to_u64([d0, d1, d2, d3, d4, d5, d6, d7]); 55 | 56 | let state = (a, b, c, d); 57 | Prng { state } 58 | } 59 | 60 | /// Returns the internal state as a byte array. 61 | /// 62 | /// This function is a left and right inverse for `from_bytes`. 63 | /// 64 | /// [`from_bytes`]: Prng::from_bytes 65 | pub fn to_bytes(&self) -> [u8; 32] { 66 | let (a, b, c, d) = self.state; 67 | 68 | let [a0, a1, a2, a3, a4, a5, a6, a7] = conversion::u64_to_bytes(a); 69 | let [b0, b1, b2, b3, b4, b5, b6, b7] = conversion::u64_to_bytes(b); 70 | let [c0, c1, c2, c3, c4, c5, c6, c7] = conversion::u64_to_bytes(c); 71 | let [d0, d1, d2, d3, d4, d5, d6, d7] = conversion::u64_to_bytes(d); 72 | 73 | #[rustfmt::skip] 74 | let state_bytes = [ 75 | a0, a1, a2, a3, a4, a5, a6, a7, 76 | b0, b1, b2, b3, b4, b5, b6, b7, 77 | c0, c1, c2, c3, c4, c5, c6, c7, 78 | d0, d1, d2, d3, d4, d5, d6, d7, 79 | ]; 80 | 81 | state_bytes 82 | } 83 | 84 | #[allow(clippy::many_single_char_names)] 85 | /// Returns the next pseudorandom number. 86 | pub fn next_number(&mut self) -> u64 { 87 | let (a, b, c, d) = self.state; 88 | 89 | // We use `Wrapping` because overflow and underflow is intended 90 | let Wrapping(e) = Wrapping(a) - Wrapping(b.rotate_left(7)); 91 | let Wrapping(f) = Wrapping(b) ^ Wrapping(c.rotate_left(13)); 92 | let Wrapping(g) = Wrapping(c) + Wrapping(d.rotate_left(37)); 93 | let Wrapping(h) = Wrapping(d) + Wrapping(e); 94 | let Wrapping(i) = Wrapping(e) + Wrapping(a); 95 | 96 | self.state = (f, g, h, i); 97 | i 98 | } 99 | 100 | #[allow(clippy::many_single_char_names)] 101 | /// Reinitialize the internal state of `self` using the current internal state and the given 102 | /// seed. 103 | pub fn reseed(&mut self, seed: Seed) { 104 | let (a, b, c, d) = self.state; 105 | 106 | // The implementation is inspired by ScalaCheck. 107 | let n0 = (seed.0 >> 32) & 0xffff_ffff; 108 | let n1 = seed.0 & 0xffff_ffff; 109 | 110 | self.state = (a ^ n0, b ^ n1, c, d); 111 | 112 | for _ in 0..16 { 113 | self.next_number(); 114 | } 115 | } 116 | 117 | /// Splits off a new [`Prng`] from `self`. The internal state of the new [`Prng`] is generated 118 | /// with `self`. 119 | pub fn fork(&mut self) -> Prng { 120 | let random_number = self.next_number(); 121 | let mut reseeded_prng = self.clone(); 122 | reseeded_prng.reseed(random_number.into()); 123 | reseeded_prng 124 | } 125 | } 126 | 127 | impl BuildHasher for Prng { 128 | #[allow(deprecated)] 129 | type Hasher = SipHasher; 130 | 131 | fn build_hasher(&self) -> Self::Hasher { 132 | let mut prng = self.clone(); 133 | let (key0, key1) = (prng.next_number(), prng.next_number()); 134 | #[allow(deprecated)] 135 | SipHasher::new_with_keys(key0, key1) 136 | } 137 | } 138 | 139 | #[cfg(feature = "rand_core")] 140 | #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] 141 | impl rand_core::RngCore for Prng { 142 | fn next_u32(&mut self) -> u32 { 143 | self.next_number() as u32 144 | } 145 | 146 | fn next_u64(&mut self) -> u64 { 147 | self.next_number() 148 | } 149 | 150 | fn fill_bytes(&mut self, dest: &mut [u8]) { 151 | rand_core::impls::fill_bytes_via_next(self, dest) 152 | } 153 | 154 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { 155 | self.fill_bytes(dest); 156 | Ok(()) 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use crate::prelude::*; 163 | use crate::{asserts, Prng}; 164 | 165 | #[test] 166 | fn from_seed_must_not_have_cycle_length_zero() { 167 | Dicetest::repeatedly().run(|mut fate| { 168 | let seed = fate.roll(dice::u64(..)); 169 | 170 | let prng_init = Prng::from_seed(seed.into()); 171 | let mut prng_next = prng_init.clone(); 172 | let _ = prng_next.next_number(); 173 | let cycle_length_is_zero = prng_init == prng_next; 174 | 175 | hint_debug!(seed); 176 | hint_debug!(prng_init); 177 | hint_debug!(prng_next); 178 | 179 | assert!(!cycle_length_is_zero); 180 | }) 181 | } 182 | 183 | #[test] 184 | fn from_bytes_is_left_inverse() { 185 | Dicetest::repeatedly().run(|fate| { 186 | asserts::left_inverse( 187 | fate, 188 | dice::from_fn(|mut fate| fate.fork_prng()), 189 | |prng| prng.to_bytes(), 190 | Prng::from_bytes, 191 | ); 192 | }) 193 | } 194 | 195 | #[test] 196 | fn to_bytes_is_left_inverse() { 197 | Dicetest::repeatedly().run(|fate| { 198 | asserts::left_inverse(fate, dice::array(dice::u8(..)), Prng::from_bytes, |prng| { 199 | prng.to_bytes() 200 | }); 201 | }) 202 | } 203 | 204 | #[test] 205 | fn reseed_changes_prng() { 206 | Dicetest::repeatedly().run(|mut fate| { 207 | let prng = fate.fork_prng(); 208 | let seed = fate.roll(dice::u64(..)).into(); 209 | 210 | let mut prng_reseeded = prng.clone(); 211 | prng_reseeded.reseed(seed); 212 | 213 | hint_debug!(prng); 214 | hint_debug!(seed); 215 | hint_debug!(prng_reseeded); 216 | 217 | assert_ne!(prng, prng_reseeded); 218 | }) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/runner.rs: -------------------------------------------------------------------------------- 1 | //! A runner for tests with pseudorandomly generated test data. 2 | //! 3 | //! The runner mainly exists for implementing [`Dicetest`]. You probably want to use [`Dicetest`] 4 | //! instead of using the runner directly. 5 | //! 6 | //! The modules [`once`] and [`repeatedly`] contains runner functions with different strategies. 7 | //! 8 | //! [`Dicetest`]: crate::Dicetest 9 | 10 | mod util; 11 | 12 | mod limit_series; 13 | use limit_series::LimitSeries; 14 | 15 | mod error; 16 | pub use error::Error; 17 | 18 | pub mod once; 19 | 20 | pub mod repeatedly; 21 | -------------------------------------------------------------------------------- /src/runner/error.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | /// Contains an error that occurred during a test run. 4 | #[derive(Debug)] 5 | pub struct Error(pub Box); 6 | -------------------------------------------------------------------------------- /src/runner/limit_series.rs: -------------------------------------------------------------------------------- 1 | use crate::Limit; 2 | 3 | /// Generates a series of linearly interpolated [`Limit`]s. 4 | #[derive(Debug, Clone)] 5 | pub struct LimitSeries { 6 | start: u64, 7 | end: u64, 8 | diff: u64, 9 | len: u64, 10 | } 11 | 12 | impl LimitSeries { 13 | /// Creates a new instance that produces `len` linearly interpolated [`Limit`]s between `start` 14 | /// and `end`. 15 | pub fn new(start: Limit, end: Limit, len: u64) -> Self { 16 | let start = start.0; 17 | let end = end.0; 18 | 19 | let diff = if start <= end { 20 | end - start 21 | } else { 22 | start - end 23 | }; 24 | 25 | LimitSeries { 26 | start, 27 | end, 28 | diff, 29 | len, 30 | } 31 | } 32 | 33 | /// Returns the n-th interpolated [`Limit`] or `None` if `n` is out of bounds. 34 | pub fn nth(&self, n: u64) -> Option { 35 | if n >= self.len { 36 | None 37 | } else if self.len == 1 { 38 | Some(Limit(self.start)) 39 | } else if self.start <= self.end { 40 | Some(self.interpolate(n, self.start)) 41 | } else { 42 | Some(self.interpolate(self.len - n - 1, self.end)) 43 | } 44 | } 45 | 46 | fn interpolate(&self, x: u64, offset_y: u64) -> Limit { 47 | let delta_x = u128::from(self.len) - 1; 48 | let delta_y = u128::from(self.diff); 49 | 50 | let ipol_y = ((u128::from(x) * delta_y) / delta_x) as u64; 51 | 52 | Limit(offset_y + ipol_y) 53 | } 54 | 55 | /// Returns an iterator that emits all [`Limit`]s. 56 | pub fn into_iter(self) -> impl Iterator { 57 | LimitSeriesIntoIter { 58 | series: self, 59 | idx: 0, 60 | } 61 | } 62 | } 63 | 64 | struct LimitSeriesIntoIter { 65 | series: LimitSeries, 66 | idx: u64, 67 | } 68 | 69 | impl Iterator for LimitSeriesIntoIter { 70 | type Item = Limit; 71 | 72 | fn next(&mut self) -> Option { 73 | let limit = self.series.nth(self.idx); 74 | if limit.is_some() { 75 | self.idx += 1; 76 | } 77 | limit 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use crate::prelude::*; 84 | use crate::runner::LimitSeries; 85 | 86 | fn assert_example(start: u64, end: u64, len: u64, expected_limits: Vec) { 87 | let series = LimitSeries::new(start.into(), end.into(), len); 88 | let actual_limits = series.into_iter().map(|limit| limit.0).collect::>(); 89 | 90 | assert_eq!(actual_limits, expected_limits); 91 | } 92 | 93 | #[test] 94 | fn examples() { 95 | assert_example(0, 2, 2, vec![0, 2]); 96 | assert_example(2, 0, 2, vec![2, 0]); 97 | assert_example(0, 2, 3, vec![0, 1, 2]); 98 | assert_example(2, 0, 3, vec![2, 1, 0]); 99 | } 100 | 101 | #[test] 102 | fn iterator_produces_exact_len_limits() { 103 | Dicetest::repeatedly().run(|mut fate| { 104 | let start = fate.roll(dice::u64(..)); 105 | let end = fate.roll(dice::u64(..)); 106 | let len = fate.roll(dice::u64(..=fate.limit().0)); 107 | 108 | let series = LimitSeries::new(start.into(), end.into(), len); 109 | let iter = series.into_iter(); 110 | let iter_len: u64 = iter.map(|_| 1).sum(); 111 | 112 | assert_eq!(iter_len, len); 113 | }) 114 | } 115 | 116 | #[test] 117 | fn if_len_gt_1_then_start_is_first_limit() { 118 | Dicetest::repeatedly().run(|mut fate| { 119 | let start = fate.roll(dice::u64(..)); 120 | let end = fate.roll(dice::u64(..)); 121 | let len = fate.roll(dice::u64(1..)); 122 | 123 | hint_debug!(start); 124 | hint_debug!(end); 125 | hint_debug!(len); 126 | 127 | let series = LimitSeries::new(start.into(), end.into(), len); 128 | let first_limit = series.nth(0).unwrap().0; 129 | 130 | assert_eq!(first_limit, start); 131 | }) 132 | } 133 | 134 | #[test] 135 | fn if_len_is_gt_2_then_end_is_last_limit() { 136 | Dicetest::repeatedly().run(|mut fate| { 137 | let start = fate.roll(dice::u64(..)); 138 | let end = fate.roll(dice::u64(..)); 139 | let len = fate.roll(dice::u64(2..)); 140 | 141 | hint_debug!(start); 142 | hint_debug!(end); 143 | hint_debug!(len); 144 | 145 | let series = LimitSeries::new(start.into(), end.into(), len); 146 | let last_limit = series.nth(len - 1).unwrap().0; 147 | 148 | assert_eq!(last_limit, end); 149 | }) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/runner/once.rs: -------------------------------------------------------------------------------- 1 | //! Provides a runner function that runs a test once. 2 | //! 3 | //! This runner function can be used for debugging a counterexample 4 | //! that was found with [`runner::repeatedly::run`]. 5 | 6 | use std::panic::{catch_unwind, UnwindSafe}; 7 | 8 | use crate::hints::Hints; 9 | use crate::runner; 10 | use crate::runner::Error; 11 | use crate::stats::Stats; 12 | use crate::{Fate, Limit, Prng}; 13 | 14 | /// The configuration for a single test run. 15 | #[derive(Debug, Clone)] 16 | pub struct Config { 17 | /// The upper limit for the length of generated dynamic data structures used for the test run. 18 | pub limit: Limit, 19 | /// Defines whether the test will be run with enabled hints. The hints will be 20 | /// added to the report. 21 | /// 22 | /// This parameter does only work if the feature `hints` is present. 23 | pub hints_enabled: bool, 24 | /// Defines whether the stats will be enabled during the test run. The stats will be added 25 | /// to the report. 26 | /// 27 | /// This parameter does only work if the feature `stats` is present. 28 | pub stats_enabled: bool, 29 | } 30 | 31 | /// The result of a single test run. 32 | #[derive(Debug)] 33 | pub struct Report { 34 | /// The hints collected during the test run. It's defined if and only if hints are enabled. 35 | pub hints: Option, 36 | /// The stats collected during the test run. It's defined if and only if stats are enabled. 37 | pub stats: Option, 38 | /// The error occurred during the test run. It's defined if and only the test has panicked. 39 | pub error: Option, 40 | } 41 | 42 | /// Runs the test once with the given configuration. 43 | /// 44 | /// If the test panics the error will be caught and added to the report. 45 | pub fn run(mut prng: Prng, config: &Config, test: T) -> Report 46 | where 47 | T: FnOnce(Fate) + UnwindSafe, 48 | { 49 | let ((test_result, hints), stats) = { 50 | let limit = config.limit; 51 | runner::util::collect_stats(config.stats_enabled, || { 52 | runner::util::collect_hints(config.hints_enabled, || { 53 | catch_unwind(move || { 54 | let fate = Fate::new(&mut prng, limit); 55 | test(fate) 56 | }) 57 | }) 58 | }) 59 | }; 60 | 61 | let error = test_result.err().map(Error); 62 | 63 | Report { 64 | hints, 65 | stats, 66 | error, 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use crate::runner::once::{run, Config}; 73 | use crate::{Prng, Seed}; 74 | 75 | fn default_prng() -> Prng { 76 | Prng::from_seed(Seed::from(42)) 77 | } 78 | 79 | fn default_config() -> Config { 80 | Config { 81 | limit: 100.into(), 82 | hints_enabled: true, 83 | stats_enabled: false, 84 | } 85 | } 86 | 87 | #[test] 88 | fn has_error_if_test_fails() { 89 | let config = default_config(); 90 | let report = run(default_prng(), &config, |_| panic!()); 91 | assert!(report.error.is_some()); 92 | } 93 | 94 | #[test] 95 | fn no_error_if_test_succeeds() { 96 | let config = default_config(); 97 | let report = run(default_prng(), &config, |_| ()); 98 | assert!(report.error.is_none()); 99 | } 100 | 101 | #[test] 102 | fn no_hints_if_disabled_and_test_succeeds() { 103 | let config = Config { 104 | hints_enabled: false, 105 | ..default_config() 106 | }; 107 | let report = run(default_prng(), &config, |_| ()); 108 | assert!(report.hints.is_none()); 109 | } 110 | 111 | #[test] 112 | fn no_hints_if_disabled_and_test_fails() { 113 | let config = Config { 114 | hints_enabled: false, 115 | ..default_config() 116 | }; 117 | let report = run(default_prng(), &config, |_| panic!()); 118 | assert!(report.hints.is_none()); 119 | } 120 | 121 | #[test] 122 | fn has_hints_if_enabled_and_test_succeeds() { 123 | let config = Config { 124 | hints_enabled: true, 125 | ..default_config() 126 | }; 127 | let report = run(default_prng(), &config, |_| ()); 128 | assert!(report.hints.is_some()); 129 | } 130 | 131 | #[test] 132 | fn has_hints_if_enabled_and_test_fails() { 133 | let config = Config { 134 | hints_enabled: true, 135 | ..default_config() 136 | }; 137 | let report = run(default_prng(), &config, |_| panic!()); 138 | assert!(report.hints.is_some()); 139 | } 140 | 141 | #[test] 142 | fn no_stats_if_disabled_and_test_succeeds() { 143 | let config = Config { 144 | stats_enabled: false, 145 | ..default_config() 146 | }; 147 | let report = run(default_prng(), &config, |_| ()); 148 | let stats = report.stats; 149 | assert!(stats.is_none()); 150 | } 151 | 152 | #[test] 153 | fn no_stats_if_disabled_and_test_fails() { 154 | let config = Config { 155 | stats_enabled: false, 156 | ..default_config() 157 | }; 158 | let report = run(default_prng(), &config, |_| panic!()); 159 | let stats = report.stats; 160 | assert!(stats.is_none()); 161 | } 162 | 163 | #[test] 164 | fn has_stats_if_enabled_and_test_succeeds() { 165 | let config = Config { 166 | stats_enabled: true, 167 | ..default_config() 168 | }; 169 | let report = run(default_prng(), &config, |_| ()); 170 | let stats = report.stats; 171 | assert!(stats.is_some()); 172 | } 173 | 174 | #[test] 175 | fn has_stats_if_enabled_and_test_fails() { 176 | let config = Config { 177 | stats_enabled: true, 178 | ..default_config() 179 | }; 180 | let report = run(default_prng(), &config, |_| panic!()); 181 | let stats = report.stats; 182 | assert!(stats.is_some()); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/runner/util.rs: -------------------------------------------------------------------------------- 1 | use crate::hints::{self, Hints}; 2 | use crate::stats::{self, Stats}; 3 | 4 | pub fn collect_hints(enabled: bool, f: impl FnOnce() -> R) -> (R, Option) { 5 | if enabled { 6 | let (result, hints) = hints::collect(f); 7 | (result, Some(hints)) 8 | } else { 9 | let result = f(); 10 | (result, None) 11 | } 12 | } 13 | 14 | pub fn collect_stats(enabled: bool, f: impl FnOnce() -> R) -> (R, Option) { 15 | if enabled { 16 | let (result, stats) = stats::collect(f); 17 | (result, Some(stats)) 18 | } else { 19 | let result = f(); 20 | (result, None) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/seed.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{BuildHasher, Hasher, RandomState}; 2 | 3 | /// A seed for pseudorandomness. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 5 | pub struct Seed(pub u64); 6 | 7 | impl Seed { 8 | /// Creates a random seed using the random number generator of the operating system. 9 | /// 10 | /// # Panics 11 | /// 12 | /// If the operation system fails to generate the needed amount of random bytes this function 13 | /// will panic. See the documentation of [getrandom] for more details. 14 | /// 15 | /// [getrandom]: https://docs.rs/getrandom 16 | pub fn random() -> Self { 17 | // Hack for obtaining randomness from stdlib. 18 | // The idea was taken from the crate arbtest. 19 | let seed = RandomState::new().build_hasher().finish(); 20 | Seed(seed) 21 | } 22 | } 23 | 24 | impl From for Seed { 25 | fn from(seed: u64) -> Self { 26 | Seed(seed) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | //! Stats help to analyze repeated test runs. 2 | //! 3 | //! For any key you can count the occurrences of its values. Use it to reveal the 4 | //! distribution of generated test data or the probability of branches. 5 | //! Stats must enabled with the feature `stats`. 6 | 7 | use std::collections::{btree_map::Entry, BTreeMap}; 8 | 9 | #[cfg(feature = "stats")] 10 | use crate::util::events; 11 | 12 | /// A counter for occurrences of a value. 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub enum Counter { 15 | /// Contains the current count. 16 | Value(u64), 17 | /// The counter has overflowed. 18 | Overflow, 19 | } 20 | 21 | impl Counter { 22 | /// Returns an initial counter. 23 | pub fn new() -> Self { 24 | Counter::Value(0) 25 | } 26 | 27 | /// Increments the counter by one. 28 | pub fn inc(self) -> Self { 29 | match self { 30 | Counter::Overflow => Counter::Overflow, 31 | Counter::Value(n) => { 32 | let incremented = n.checked_add(1); 33 | incremented.map_or(Counter::Overflow, Counter::Value) 34 | } 35 | } 36 | } 37 | 38 | /// Sums up both counters. 39 | pub fn merge(self, other: Self) -> Self { 40 | match (self, other) { 41 | (Counter::Value(left), Counter::Value(right)) => { 42 | let sum = left.checked_add(right); 43 | sum.map_or(Counter::Overflow, Counter::Value) 44 | } 45 | _ => Counter::Overflow, 46 | } 47 | } 48 | 49 | /// Returns the count as option. 50 | pub fn value(self) -> Option { 51 | match self { 52 | Counter::Overflow => None, 53 | Counter::Value(n) => Some(n), 54 | } 55 | } 56 | } 57 | 58 | impl Default for Counter { 59 | fn default() -> Self { 60 | Self::new() 61 | } 62 | } 63 | 64 | /// Contains the counters of different values with the same key. 65 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 66 | pub struct Stat(pub BTreeMap); 67 | 68 | impl Stat { 69 | /// Returns an instance without any counters. 70 | pub fn new() -> Self { 71 | let counters = BTreeMap::new(); 72 | Stat(counters) 73 | } 74 | 75 | /// Increases the counter for the given value by one. 76 | pub fn inc(&mut self, value: String) { 77 | let counter_entry = self.0.entry(value).or_default(); 78 | *counter_entry = counter_entry.inc(); 79 | } 80 | 81 | /// Merges both instances by merging the counters that belong to same value. 82 | pub fn merge(mut self, other: Self) -> Self { 83 | if self.0.len() < other.0.len() { 84 | other.merge(self) 85 | } else { 86 | for (value, right_counter) in other.0.into_iter() { 87 | match self.0.entry(value) { 88 | Entry::Vacant(entry) => { 89 | entry.insert(right_counter); 90 | } 91 | Entry::Occupied(entry) => { 92 | let left_counter = *entry.get(); 93 | *entry.into_mut() = left_counter.merge(right_counter); 94 | } 95 | } 96 | } 97 | 98 | self 99 | } 100 | } 101 | 102 | /// Returns the total occurrence count for all values. 103 | pub fn total_counter(&self) -> Counter { 104 | self.0 105 | .values() 106 | .fold(Counter::new(), |left, &right| left.merge(right)) 107 | } 108 | } 109 | 110 | /// Contains the stats for different keys. 111 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 112 | pub struct Stats(pub BTreeMap<&'static str, Stat>); 113 | 114 | impl Stats { 115 | /// Returns an instance without any stats. 116 | pub fn new() -> Self { 117 | Stats(BTreeMap::new()) 118 | } 119 | 120 | /// Increases the counter for the given key and value by one. 121 | pub fn inc(&mut self, key: &'static str, value: String) { 122 | let stat_entry = self.0.entry(key).or_default(); 123 | stat_entry.inc(value); 124 | } 125 | 126 | /// Merges both instances by merging the stats that belong to the same key. 127 | pub fn merge(mut self, other: Self) -> Self { 128 | if self.0.len() < other.0.len() { 129 | other.merge(self) 130 | } else { 131 | for (key, right_stat) in other.0.into_iter() { 132 | let left_stat = self.0.remove(key); 133 | let stat = match left_stat { 134 | None => right_stat, 135 | Some(left_stat) => left_stat.merge(right_stat), 136 | }; 137 | self.0.insert(key, stat); 138 | } 139 | 140 | self 141 | } 142 | } 143 | } 144 | 145 | #[cfg(feature = "stats")] 146 | impl events::Events for Stats { 147 | fn new() -> Self { 148 | Stats::new() 149 | } 150 | 151 | fn take(&mut self) -> Self { 152 | let first_key = self.0.keys().next().cloned(); 153 | match first_key { 154 | None => Stats::new(), 155 | Some(first_key) => { 156 | let stats = self.0.split_off(first_key); 157 | Stats(stats) 158 | } 159 | } 160 | } 161 | } 162 | 163 | #[cfg(feature = "stats")] 164 | thread_local! { 165 | static LOCAL: events::Stack = events::new_stack(); 166 | } 167 | 168 | /// Returns the stats for the evaluation of the given function. 169 | pub fn collect(f: impl FnOnce() -> R) -> (R, Stats) { 170 | #[cfg(feature = "stats")] 171 | { 172 | events::collect(&LOCAL, f) 173 | } 174 | #[cfg(not(feature = "stats"))] 175 | { 176 | (f(), Stats::new()) 177 | } 178 | } 179 | 180 | /// Returns if stats are currently enabled. 181 | /// 182 | /// Stats are enabled if and only if this function is executed inside of [`collect`] and 183 | /// the feature `stats` is present. 184 | pub fn enabled() -> bool { 185 | #[cfg(feature = "stats")] 186 | { 187 | events::enabled(&LOCAL) 188 | } 189 | #[cfg(not(feature = "stats"))] 190 | { 191 | false 192 | } 193 | } 194 | 195 | /// If stats are enabled, this function evaluates the given value and increments its counter for 196 | /// the given key. Otherwise this function is a noop. 197 | pub fn inc(key: &'static str, value: impl FnOnce() -> String) { 198 | #[cfg(feature = "stats")] 199 | { 200 | events::modify(&LOCAL, move |stack| { 201 | let value = value(); 202 | let len = stack.len(); 203 | 204 | stack[0..len - 1] 205 | .iter_mut() 206 | .for_each(|stats| stats.inc(key, value.clone())); 207 | stack[len - 1].inc(key, value); 208 | }); 209 | } 210 | #[cfg(not(feature = "stats"))] 211 | { 212 | let _ = key; 213 | let _ = value; 214 | } 215 | } 216 | 217 | #[cfg(test)] 218 | mod tests { 219 | use crate::stats::Counter::{self, Overflow, Value}; 220 | 221 | #[test] 222 | fn counter_inc_examples() { 223 | assert_eq!(Counter::new().inc(), Value(1)); 224 | assert_eq!(Value(u64::MAX).inc(), Overflow); 225 | } 226 | 227 | #[test] 228 | fn counter_merge_examples() { 229 | assert_eq!(Overflow.merge(Overflow), Overflow); 230 | assert_eq!(Overflow.merge(Counter::new()), Overflow); 231 | assert_eq!(Counter::new().merge(Overflow), Overflow); 232 | assert_eq!(Value(1).merge(Value(1)), Value(2)); 233 | } 234 | 235 | #[cfg(feature = "stats")] 236 | #[test] 237 | fn stats_take_takes_all_elements() { 238 | use crate::stats::{Stat, Stats}; 239 | use crate::util::events::Events; 240 | 241 | let mut stat1 = Stat::new(); 242 | let mut stat2 = Stat::new(); 243 | let mut stats = Stats::new(); 244 | 245 | stat1.0.insert("foofoo".to_string(), Value(1)); 246 | stat2.0.insert("barbar".to_string(), Overflow); 247 | stats.0.insert("foo", stat1); 248 | stats.0.insert("bar", stat2); 249 | 250 | assert_eq!(stats, stats.clone().take()); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "hints", feature = "stats"))] 2 | pub mod finalizer; 3 | 4 | #[cfg(any(feature = "hints", feature = "stats"))] 5 | pub mod events; 6 | 7 | pub mod conversion; 8 | 9 | pub mod base64; 10 | -------------------------------------------------------------------------------- /src/util/base64.rs: -------------------------------------------------------------------------------- 1 | //! Provides the traditional (MIME) base64 encoding and decoding. 2 | //! 3 | //! The algorithms are based on the examples from [Wikipedia]. 4 | //! 5 | //! [Wikipedia]: https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64 6 | 7 | use std::iter; 8 | 9 | #[rustfmt::skip] 10 | const BYTE_TO_CHAR: [char; 64] = [ 11 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 12 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 13 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 14 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 15 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 16 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 17 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 18 | '4', '5', '6', '7', '8', '9', '+', '/', 19 | ]; 20 | 21 | #[rustfmt::skip] 22 | #[allow(clippy::zero_prefixed_literal)] 23 | const CHAR_TO_BYTE: [u8; 256] = [ 24 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 25 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 26 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 27 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 28 | 64, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 29 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 30 | 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 31 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, 32 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 33 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 34 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 35 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 36 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 37 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 38 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 39 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 40 | ]; 41 | 42 | const PAD_CHAR: char = '='; 43 | 44 | fn is_invalid_char(c: char) -> bool { 45 | (c as u32) >= 256 || CHAR_TO_BYTE[c as usize] >= 64 46 | } 47 | 48 | /// Converts the given bytes to a base64 string. 49 | /// 50 | /// This function is a right inverse for [`decode`]. 51 | pub fn encode(bytes: &[u8]) -> String { 52 | let pad_count = { 53 | let outrange = bytes.len() % 3; 54 | (3 - outrange) % 3 55 | }; 56 | 57 | let bytes_padding = iter::repeat_with(|| &0).take(pad_count); 58 | 59 | let mut i = bytes.iter().chain(bytes_padding); 60 | 61 | let mut result = String::new(); 62 | 63 | while let (Some(b0), Some(b1), Some(b2)) = (i.next(), i.next(), i.next()) { 64 | let n = ((u32::from(*b0)) << 16) | ((u32::from(*b1)) << 8) | u32::from(*b2); 65 | 66 | let n0 = (n >> 18) & 63; 67 | let n1 = (n >> 12) & 63; 68 | let n2 = (n >> 6) & 63; 69 | let n3 = n & 63; 70 | 71 | result.push(BYTE_TO_CHAR[n0 as usize]); 72 | result.push(BYTE_TO_CHAR[n1 as usize]); 73 | result.push(BYTE_TO_CHAR[n2 as usize]); 74 | result.push(BYTE_TO_CHAR[n3 as usize]); 75 | } 76 | 77 | let result_len = result.len(); 78 | result.truncate(result_len - pad_count); 79 | 80 | let result_padding = PAD_CHAR.to_string().repeat(pad_count); 81 | result.push_str(&result_padding); 82 | 83 | result 84 | } 85 | 86 | /// Tries to convert the given base64 string to bytes. 87 | /// Fails if the string has no valid base64 encoding. 88 | /// 89 | /// This function is a left inverse for [`encode`]. 90 | pub fn decode(base64: &str) -> Result, String> { 91 | let suffix = if base64.ends_with("==") { 92 | "AA" 93 | } else if base64.ends_with('=') { 94 | "A" 95 | } else { 96 | "" 97 | }; 98 | 99 | let prefix = &base64[0..base64.len() - suffix.len()]; 100 | 101 | let invalid_char = prefix.chars().find(|&c| is_invalid_char(c)); 102 | 103 | if let Some(invalid_char) = invalid_char { 104 | return Err(format!( 105 | "Base64 string contains invalid character: {:?}", 106 | invalid_char 107 | )); 108 | } 109 | 110 | let has_invalid_length = (prefix.len() + suffix.len()) % 4 != 0; 111 | 112 | if has_invalid_length { 113 | return Err("Base64 string has invalid length".to_string()); 114 | } 115 | 116 | let all = prefix.chars().chain(suffix.chars()); 117 | let mut i = all.map(|c| CHAR_TO_BYTE[c as usize]); 118 | 119 | let mut result = Vec::new(); 120 | 121 | while let (Some(n0), Some(n1), Some(n2), Some(n3)) = (i.next(), i.next(), i.next(), i.next()) { 122 | let n = 123 | (u32::from(n0) << 18) | (u32::from(n1) << 12) | (u32::from(n2) << 6) | u32::from(n3); 124 | 125 | let b1 = ((n >> 16) & 0xFF) as u8; 126 | let b2 = ((n >> 8) & 0xFF) as u8; 127 | let b3 = (n & 0xFF) as u8; 128 | 129 | result.extend_from_slice(&[b1, b2, b3]); 130 | } 131 | 132 | for _ in 0..suffix.len() { 133 | result.pop(); 134 | } 135 | 136 | Ok(result) 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use crate::prelude::*; 142 | use crate::util::base64; 143 | 144 | #[test] 145 | fn encode_produces_empty_string_if_bytes_is_empty() { 146 | let bytes = Vec::new(); 147 | let base64 = base64::encode(&bytes); 148 | assert_eq!(base64, ""); 149 | } 150 | 151 | #[test] 152 | fn encode_produces_non_empty_string_if_bytes_is_non_empty() { 153 | Dicetest::repeatedly().run(|mut fate| { 154 | let bytes = fate.roll(dice::vec(dice::u8(..), 1..)); 155 | let base64 = base64::encode(&bytes); 156 | 157 | hint_debug!(bytes); 158 | hint_debug!(base64); 159 | 160 | assert!(!base64.is_empty()) 161 | }) 162 | } 163 | 164 | #[test] 165 | fn decode_is_left_inverse() { 166 | Dicetest::repeatedly().run(|mut fate| { 167 | let bytes = fate.roll(dice::vec(dice::u8(..), ..)); 168 | let base64 = base64::encode(&bytes); 169 | 170 | hint_debug!(bytes); 171 | hint_debug!(base64); 172 | 173 | let decoded_bytes = base64::decode(&base64).unwrap(); 174 | 175 | assert_eq!(bytes, decoded_bytes); 176 | }) 177 | } 178 | 179 | #[test] 180 | fn decode_fails_if_string_contains_invalid_char() { 181 | Dicetest::repeatedly().run(|mut fate| { 182 | let valid_length_die = dice::length(4..).map(|length| length - (length % 4)); 183 | 184 | let length = fate.roll(valid_length_die); 185 | let base64 = fate.roll(dice::string(dice::char(), length)); 186 | 187 | let is_invalid = base64.chars().any(base64::is_invalid_char); 188 | 189 | if !is_invalid { 190 | return; 191 | } 192 | 193 | let bytes = base64::decode(&base64); 194 | 195 | hint_debug!(base64); 196 | hint_debug!(bytes); 197 | 198 | assert!(bytes.is_err()); 199 | }) 200 | } 201 | 202 | #[test] 203 | fn decode_fails_if_string_has_invalid_length() { 204 | Dicetest::repeatedly().run(|mut fate| { 205 | let base_64_char_die = dice::one_of_slice(&base64::BYTE_TO_CHAR); 206 | let invalid_length_die = 207 | dice::length(1..).map(|length| if length % 4 == 0 { length + 1 } else { length }); 208 | 209 | let length = fate.roll(invalid_length_die); 210 | let invalid_base64 = fate.roll(dice::string(base_64_char_die, length)); 211 | 212 | let bytes = base64::decode(&invalid_base64); 213 | 214 | hint_debug!(invalid_base64); 215 | hint_debug!(bytes); 216 | 217 | assert!(bytes.is_err()); 218 | }) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/util/conversion.rs: -------------------------------------------------------------------------------- 1 | /// Converts the [`u32`] to bytes using little endian. 2 | /// 3 | /// This function is a left and right inverse for [`bytes_to_u64`]. 4 | pub fn u64_to_bytes(u64: u64) -> [u8; 8] { 5 | u64.to_le_bytes() 6 | } 7 | 8 | /// Converts the bytes to an [`u64`] using little endian. 9 | /// 10 | /// This function is a left and right inverse for [`u64_to_bytes`]. 11 | pub fn bytes_to_u64(bytes: [u8; 8]) -> u64 { 12 | u64::from_le_bytes(bytes) 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use crate::asserts; 18 | use crate::prelude::*; 19 | use crate::util::conversion; 20 | 21 | #[test] 22 | fn bytes_to_u64_is_left_inverse() { 23 | Dicetest::repeatedly().run(|fate| { 24 | asserts::left_inverse( 25 | fate, 26 | dice::array(dice::u8(..)), 27 | conversion::bytes_to_u64, 28 | conversion::u64_to_bytes, 29 | ) 30 | }) 31 | } 32 | 33 | #[test] 34 | fn u64_to_bytes_is_left_inverse() { 35 | Dicetest::repeatedly().run(|fate| { 36 | asserts::left_inverse( 37 | fate, 38 | dice::u64(..), 39 | conversion::u64_to_bytes, 40 | conversion::bytes_to_u64, 41 | ) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/util/events.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::thread::LocalKey; 3 | 4 | use crate::util::finalizer::Finalizer; 5 | 6 | pub trait Events { 7 | fn new() -> Self; 8 | fn take(&mut self) -> Self; 9 | } 10 | 11 | pub type Stack = RefCell>; 12 | 13 | pub fn new_stack() -> Stack { 14 | RefCell::new(Vec::new()) 15 | } 16 | 17 | pub type Local = LocalKey>; 18 | 19 | pub fn collect(local: &'static Local, f: impl FnOnce() -> R) -> (R, E) { 20 | local.with(move |cell| { 21 | { 22 | let events = E::new(); 23 | let mut events_stack = cell.borrow_mut(); 24 | events_stack.push(events); 25 | } 26 | 27 | // Removes the events even in case of panic 28 | let finalizer = Finalizer::new(|| { 29 | local.with(move |cell| { 30 | let mut events_stack = cell.borrow_mut(); 31 | events_stack.pop(); 32 | }) 33 | }); 34 | 35 | // This function may panic 36 | let result = f(); 37 | 38 | let events = { 39 | let mut events_stack = cell.borrow_mut(); 40 | let events = events_stack.last_mut().unwrap(); 41 | events.take() 42 | }; 43 | 44 | drop(finalizer); 45 | 46 | (result, events) 47 | }) 48 | } 49 | 50 | fn enabled_with_cell(cell: &RefCell>) -> bool { 51 | !cell.borrow().is_empty() 52 | } 53 | 54 | pub fn enabled(local: &'static Local) -> bool { 55 | local.with(move |cell| enabled_with_cell(cell)) 56 | } 57 | 58 | pub fn modify(local: &'static Local, f: impl FnOnce(&mut Vec)) { 59 | local.with(move |cell| { 60 | if enabled_with_cell(cell) { 61 | let mut events_stack = cell.borrow_mut(); 62 | f(&mut events_stack); 63 | } 64 | }); 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use std::panic::catch_unwind; 70 | 71 | use crate::util::events::{self, Events, Stack}; 72 | 73 | struct Counter(u32); 74 | 75 | impl Events for Counter { 76 | fn new() -> Self { 77 | Counter(0) 78 | } 79 | 80 | fn take(&mut self) -> Self { 81 | Counter(self.0) 82 | } 83 | } 84 | 85 | thread_local! { 86 | static LOCAL: Stack = events::new_stack(); 87 | } 88 | 89 | fn enabled() -> bool { 90 | events::enabled(&LOCAL) 91 | } 92 | 93 | fn collect(f: impl FnOnce()) -> u32 { 94 | let ((), Counter(n)) = events::collect(&LOCAL, f); 95 | n 96 | } 97 | 98 | fn inc() { 99 | events::modify(&LOCAL, |stack| stack.iter_mut().for_each(|c| c.0 += 1)); 100 | } 101 | 102 | #[test] 103 | fn collect_and_modify() { 104 | let n = collect(|| { 105 | inc(); 106 | inc(); 107 | 108 | let m = collect(inc); 109 | 110 | assert_eq!(1, m); 111 | }); 112 | 113 | assert_eq!(3, n); 114 | } 115 | 116 | #[test] 117 | fn is_only_enabled_during_collection() { 118 | assert!(!enabled()); 119 | collect(|| { 120 | assert!(enabled()); 121 | }); 122 | assert!(!enabled()); 123 | } 124 | 125 | #[test] 126 | fn logger_is_not_enabled_after_panic() { 127 | let _ = catch_unwind(|| { 128 | collect(|| { 129 | panic!(); 130 | }) 131 | }); 132 | assert!(!enabled()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/util/finalizer.rs: -------------------------------------------------------------------------------- 1 | /// Stores a function that will be executed once the finalizer has been dropped. 2 | /// 3 | /// The default use case is to guarantee the execution of cleanup code. 4 | pub struct Finalizer 5 | where 6 | F: FnOnce(), 7 | { 8 | f: Option, 9 | } 10 | 11 | impl Finalizer 12 | where 13 | F: FnOnce(), 14 | { 15 | pub fn new(f: F) -> Self { 16 | let f = Some(f); 17 | Finalizer { f } 18 | } 19 | } 20 | 21 | impl Drop for Finalizer 22 | where 23 | F: FnOnce(), 24 | { 25 | fn drop(&mut self) { 26 | if let Some(f) = self.f.take() { 27 | f(); 28 | } 29 | } 30 | } 31 | --------------------------------------------------------------------------------