From a6afbe090cabfccd22215bf7e5b08bc46f4ac8a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 22 Nov 2023 14:00:03 +0100 Subject: [PATCH 01/20] SM: update proptest dep to latest --- proptest-state-machine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proptest-state-machine/Cargo.toml b/proptest-state-machine/Cargo.toml index 965cfb20..38f78b1e 100644 --- a/proptest-state-machine/Cargo.toml +++ b/proptest-state-machine/Cargo.toml @@ -20,7 +20,7 @@ default = ["std"] std = ["proptest/std"] [dependencies] -proptest = { version = "1.2.0", path = "../proptest", default-features = true, features = [ +proptest = { version = "1.4.0", path = "../proptest", default-features = true, features = [ "fork", "timeout", "bit-set", From f896843137c26d469ad0648a430b2cc5aaec0fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 22 Nov 2023 14:05:26 +0100 Subject: [PATCH 02/20] SM/test: update number of expected simplifications --- proptest-state-machine/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proptest-state-machine/src/strategy.rs b/proptest-state-machine/src/strategy.rs index fca723a7..3754301a 100644 --- a/proptest-state-machine/src/strategy.rs +++ b/proptest-state-machine/src/strategy.rs @@ -567,7 +567,7 @@ mod test { /// /// This constant can be determined from the test /// `number_of_sequential_value_tree_simplifications`. - const SIMPLIFICATIONS: usize = 699; + const SIMPLIFICATIONS: usize = 32; /// Number of transitions in the [`deterministic_sequential_value_tree`]. const TRANSITIONS: usize = 32; From 3b44d44f4d5fb6e48b35f17b0fbc9e8091521387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 22 Nov 2023 14:05:58 +0100 Subject: [PATCH 03/20] SM: bump minor version --- proptest-state-machine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proptest-state-machine/Cargo.toml b/proptest-state-machine/Cargo.toml index 38f78b1e..ab14e9dc 100644 --- a/proptest-state-machine/Cargo.toml +++ b/proptest-state-machine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proptest-state-machine" -version = "0.1.0" +version = "0.2.0" authors = ["Tomáš Zemanovič"] license = "MIT OR Apache-2.0" edition = "2018" From 01d92e2d33234eba44bcd01006d483d3ff4262b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 22 Nov 2023 14:08:40 +0100 Subject: [PATCH 04/20] SM: update changelog --- proptest-state-machine/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proptest-state-machine/CHANGELOG.md b/proptest-state-machine/CHANGELOG.md index 02756482..d698a3d6 100644 --- a/proptest-state-machine/CHANGELOG.md +++ b/proptest-state-machine/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +## 0.2.0 + ### Other Notes - `message-io` updated from 0.17 to 0.18 From 8f1b35ead94ad569c200f9ece5bc9fd2cc9aabd0 Mon Sep 17 00:00:00 2001 From: Tim Cummings <47903610+timstobal@users.noreply.github.com> Date: Sat, 13 Jan 2024 17:32:08 -0500 Subject: [PATCH 05/20] Fix re-complication of initial state --- proptest-state-machine/CHANGELOG.md | 1 + .../proptest-regressions/strategy.txt | 8 ++ proptest-state-machine/src/strategy.rs | 112 +++++++++++++++++- 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 proptest-state-machine/proptest-regressions/strategy.txt diff --git a/proptest-state-machine/CHANGELOG.md b/proptest-state-machine/CHANGELOG.md index 02756482..949c323f 100644 --- a/proptest-state-machine/CHANGELOG.md +++ b/proptest-state-machine/CHANGELOG.md @@ -9,3 +9,4 @@ - Removed the limit of number of transitions that can be deleted in shrinking that depended on the number the of transitions given to `prop_state_machine!` or `ReferenceStateMachine::sequential_strategy`. - Fixed state-machine macro's inability to handle missing config - Fixed logging of state machine transitions to be enabled when verbose config is >= 1. The "std" feature is added to proptest-state-machine as a default feature that allows to switch the logging off in non-std env. +- Fixed an issue where after simplification of the initial state causes the test to succeed, the initial state would not be re-complicated - causing the test to report a succeeding input as the simplest failing input. diff --git a/proptest-state-machine/proptest-regressions/strategy.txt b/proptest-state-machine/proptest-regressions/strategy.txt new file mode 100644 index 00000000..d65b92e5 --- /dev/null +++ b/proptest-state-machine/proptest-regressions/strategy.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 2e5fb0b2e70b08bd8a567bf4d9ae66ee2478d832d6bdfb9670f5b98203e78178 # shrinks to seed = [120, 233, 214, 148, 193, 171, 178, 64, 157, 62, 78, 165, 215, 79, 177, 175, 171, 202, 51, 93, 79, 238, 39, 104, 174, 79, 152, 255, 45, 174, 27, 168] +cc 39927f23e5f67ac32c5219c226c49d87e3e3c995a73cd969d72fbcdf52ac895b # shrinks to seed = [0, 10, 244, 19, 249, 125, 161, 150, 61, 56, 77, 245, 12, 228, 187, 180, 148, 61, 17, 32, 189, 118, 70, 47, 147, 210, 94, 127, 210, 23, 128, 75] diff --git a/proptest-state-machine/src/strategy.rs b/proptest-state-machine/src/strategy.rs index fca723a7..926dd93c 100644 --- a/proptest-state-machine/src/strategy.rs +++ b/proptest-state-machine/src/strategy.rs @@ -357,6 +357,7 @@ impl< // Store the valid initial state self.last_valid_initial_state = self.initial_state.current(); + self.last_shrink = Some(self.shrink); return true; } else { // If the shrink is not acceptable, clear it out @@ -533,7 +534,6 @@ impl< false } Some(InitialState) => { - self.last_shrink = None; if self.initial_state.complicate() && self.check_acceptable(None) { @@ -752,4 +752,114 @@ mod test { } } } + + /// A tests that verifies that the strategy finds a simplest failing case, and + /// that this simplest failing case is ultimately reported by the test runner, + /// as opposed to reporting input that actually passes the test. + /// + /// This module defines a state machine test that is designed to fail. + /// The reference state machine consists of a lower bound the acceptable value + /// of a transition. And the test fails if an unacceptably low transition + /// value is observed, given the reference state's limit. + /// + /// This intentionally-failing state machine test is then run inside a proptest + /// to verify that it reports a simplest failing input when it fails. + mod find_simplest_failure { + use proptest::prelude::*; + use proptest::strategy::BoxedStrategy; + use proptest::test_runner::TestRng; + use proptest::{ + collection, + strategy::Strategy, + test_runner::{Config, TestError, TestRunner}, + }; + + use crate::{ReferenceStateMachine, StateMachineTest}; + + const MIN_TRANSITION: u32 = 10; + const MAX_TRANSITION: u32 = 20; + + const MIN_LIMIT: u32 = 2; + const MAX_LIMIT: u32 = 50; + + #[derive(Debug, Default, Clone)] + struct FailIfLessThan(u32); + impl ReferenceStateMachine for FailIfLessThan { + type State = Self; + type Transition = u32; + + fn init_state() -> BoxedStrategy { + (MIN_LIMIT..MAX_LIMIT).prop_map(FailIfLessThan).boxed() + } + + fn transitions(_: &Self::State) -> BoxedStrategy { + (MIN_TRANSITION..MAX_TRANSITION).boxed() + } + + fn apply(state: Self::State, _: &Self::Transition) -> Self::State { + state + } + } + + /// Defines a test that is intended to fail, so that we can inspect the + /// failing input. + struct FailIfLessThanTest; + impl StateMachineTest for FailIfLessThanTest { + type SystemUnderTest = (); + type Reference = FailIfLessThan; + + fn init_test(ref_state: &FailIfLessThan) { + println!(); + println!("starting {ref_state:?}"); + } + + fn apply( + (): Self::SystemUnderTest, + ref_state: &FailIfLessThan, + transition: u32, + ) -> Self::SystemUnderTest { + // Fail on any transition that is less than the ref state's limit. + let FailIfLessThan(limit) = ref_state; + println!("{transition} < {}?", limit); + if transition < ref_state.0 { + panic!("{transition} < {}", limit); + } + } + } + + proptest! { + #[test] + fn test_returns_simplest_failure( + seed in collection::vec(any::(), 32).no_shrink()) { + + // We need to explicitly run create a runner so that we can + // inspect the output, and determine if it does return an input that + // should fail, and is minimal. + let mut runner = TestRunner::new_with_rng( + Config::default(), TestRng::from_seed(Default::default(), &seed)); + let result = runner.run( + &FailIfLessThan::sequential_strategy(10..50_usize), + |(ref_state, transitions)| { + Ok(FailIfLessThanTest::test_sequential( + Default::default(), + ref_state, + transitions, + )) + }, + ); + if let Err(TestError::Fail( + _, + (FailIfLessThan(limit), transitions), + )) = result + { + assert_eq!(transitions.len(), 1, "The minimal failing case should be "); + assert_eq!(limit, MIN_TRANSITION + 1); + assert!(transitions[0] < limit); + } else { + prop_assume!(false, + "If the state machine doesn't fail as intended, we need a case that fails."); + } + } + } + } } From f0d55f76949b19b4c898e36a29c92936e4d7cad5 Mon Sep 17 00:00:00 2001 From: Tim Cummings <47903610+timstobal@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:48:50 -0400 Subject: [PATCH 06/20] Fix hygiene issues with prop_state_machine macro Eliminates unqualified type names from the prop_state_machine macro, allowing it to be used without importing all of the required types. --- proptest-state-machine/src/test_runner.rs | 85 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/proptest-state-machine/src/test_runner.rs b/proptest-state-machine/src/test_runner.rs index 924cde8a..56fb8c68 100644 --- a/proptest-state-machine/src/test_runner.rs +++ b/proptest-state-machine/src/test_runner.rs @@ -166,14 +166,15 @@ macro_rules! prop_state_machine { fn $test_name:ident(sequential $size:expr => $test:ident $(< $( $ty_param:tt ),+ >)?); )*) => { $( - proptest! { + ::proptest::proptest! { #![proptest_config($config)] $(#[$meta])* fn $test_name( - (initial_state, transitions) in <$test $(< $( $ty_param ),+ >)? as StateMachineTest>::Reference::sequential_strategy($size) + (initial_state, transitions) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) ) { + let config = $config.__sugar_to_owned(); - $test $(::< $( $ty_param ),+ >)? ::test_sequential(config, initial_state, transitions) + <$test $(::< $( $ty_param ),+ >)? as $crate::StateMachineTest>::test_sequential(config, initial_state, transitions) } } )* @@ -185,14 +186,86 @@ macro_rules! prop_state_machine { fn $test_name:ident(sequential $size:expr => $test:ident $(< $( $ty_param:tt ),+ >)?); )*) => { $( - proptest! { + ::proptest::proptest! { $(#[$meta])* fn $test_name( - (initial_state, transitions) in <$test $(< $( $ty_param ),+ >)? as StateMachineTest>::Reference::sequential_strategy($size) + (initial_state, transitions) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) ) { - $test $(::< $( $ty_param ),+ >)? ::test_sequential(Default::default(), initial_state, transitions) + <$test $(::< $( $ty_param ),+ >)? as $crate::StateMachineTest>::test_sequential( + ::proptest::test_runner::Config::default(), initial_state, transitions) } } )* }; } + +#[cfg(test)] +mod tests { + + mod macro_test { + //! tests to verify that invocations of all forms of the + //! `prop_state_machine!` macro compile cleanly, and hygenically, + //! as intended. + + /// Note: no imports here, so as to guarantee hygienic macros + + /// A no-op test. Exists strictly as something to reference + /// in the macro invocation. + struct Test; + impl crate::ReferenceStateMachine for Test { + type State = (); + type Transition = (); + + fn init_state() -> proptest::strategy::BoxedStrategy { + use proptest::prelude::*; + Just(()).boxed() + } + + fn transitions( + _: &Self::State, + ) -> proptest::strategy::BoxedStrategy + { + use proptest::prelude::*; + Just(()).boxed() + } + + fn apply(_: Self::State, _: &Self::Transition) -> Self::State { + () + } + } + + impl crate::StateMachineTest for Test { + type SystemUnderTest = (); + + type Reference = Self; + + fn init_test( + _: &::State, + ) -> Self::SystemUnderTest { + } + + fn apply( + _: Self::SystemUnderTest, + _: &::State, + _: ::Transition, + ) -> Self::SystemUnderTest { + } + } + + // Invocation of the `prop_state_machine` macro without + // a `![proptest_config]` annotation + prop_state_machine! { + #[test] + fn no_config_annotation(sequential 1..2 => Test); + } + + // Invocation of the `prop_state_machine` macro with a + // `![proptest_config]` annotation + prop_state_machine! { + #![proptest_config(::proptest::test_runner::Config::default())] + + #[test] + fn with_config_annotation(sequential 1..2 => Test); + } + } +} From b71253807372932f72a71b1af7975371a41e7c88 Mon Sep 17 00:00:00 2001 From: Rex Date: Sun, 11 Feb 2024 18:30:19 -0800 Subject: [PATCH 07/20] cleanup some compiler warnings/errors (#422) Co-authored-by: rexmas --- proptest/src/arbitrary/_std/sync.rs | 4 ++-- proptest/src/strategy/mod.rs | 1 - proptest/src/test_runner/failure_persistence/mod.rs | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/proptest/src/arbitrary/_std/sync.rs b/proptest/src/arbitrary/_std/sync.rs index e372b254..8dd22039 100644 --- a/proptest/src/arbitrary/_std/sync.rs +++ b/proptest/src/arbitrary/_std/sync.rs @@ -78,7 +78,7 @@ fn wtr_false() -> WaitTimeoutResult { }); let lock = Mutex::new(()); let wt = cvar.wait_timeout(lock.lock().unwrap(), Duration::from_millis(1)); - let (_, wtr) = wt.unwrap(); + let (_unused, wtr) = wt.unwrap(); wtr } @@ -86,7 +86,7 @@ fn wtr_true() -> WaitTimeoutResult { let cvar = Condvar::new(); let lock = Mutex::new(()); let wt = cvar.wait_timeout(lock.lock().unwrap(), Duration::from_millis(0)); - let (_, wtr) = wt.unwrap(); + let (_unused, wtr) = wt.unwrap(); wtr } diff --git a/proptest/src/strategy/mod.rs b/proptest/src/strategy/mod.rs index 6427fc10..8af3d21c 100644 --- a/proptest/src/strategy/mod.rs +++ b/proptest/src/strategy/mod.rs @@ -27,7 +27,6 @@ pub use self::flatten::*; pub use self::fuse::*; pub use self::just::*; pub use self::lazy::*; -pub use self::lazy::*; pub use self::map::*; pub use self::recursive::*; pub use self::shuffle::*; diff --git a/proptest/src/test_runner/failure_persistence/mod.rs b/proptest/src/test_runner/failure_persistence/mod.rs index d88bec98..f48d571b 100644 --- a/proptest/src/test_runner/failure_persistence/mod.rs +++ b/proptest/src/test_runner/failure_persistence/mod.rs @@ -22,7 +22,6 @@ mod noop; #[cfg(feature = "std")] pub use self::file::*; pub use self::map::*; -pub use self::noop::*; use crate::test_runner::Seed; From 1bec3083e26c0a01844f2bfd6a47861cf0480db7 Mon Sep 17 00:00:00 2001 From: Matthew Russo Date: Sun, 18 Feb 2024 18:55:56 -0800 Subject: [PATCH 08/20] [Fix] Derive : fix compiletest (#426) --- .../compile-fail/E0025-overspec-strat.rs | 1 + .../E0026-strategy-malformed-expr.rs | 1 + .../E0027-filter-malformed-expr.rs | 3 ++- .../compile-fail/E0027-filter-malformed.rs | 3 ++- .../E0028-skipped-variant-has-filter.rs | 3 ++- .../E0028-skipped-variant-has-param.rs | 3 ++- .../E0028-skipped-variant-has-strat.rs | 3 ++- .../E0028-skipped-variant-has-weight.rs | 3 ++- .../E0029-filter-on-unit-variant.rs | 1 + .../E0029-params-on-unit-variant.rs | 1 + .../E0029-strategy-on-unit-variant.rs | 1 + .../E0030-params-on-unit-struct.rs | 3 ++- .../compile-fail/E0031-no-bound-non-tyvar.rs | 1 + .../compile-fail/E0032-no-bound-malformed.rs | 1 + .../compile-fail/E0033-weight-overflow.rs | 1 + .../compile-fail/E0034-regex-malformed.rs | 1 + .../E0035-cant_set_param_and_regex.rs | 1 + .../compile-fail/E0658-no-bare-modifiers.rs | 1 + .../tests/compile-fail/must-be-debug.rs | 7 +++++- .../tests/compile-fail/no-arbitrary.rs | 7 +++++- .../tests/compile-fail/regex_wrong_type.rs | 25 ++++++++++--------- proptest-derive/tests/compiletest.rs | 1 + 22 files changed, 51 insertions(+), 21 deletions(-) diff --git a/proptest-derive/tests/compile-fail/E0025-overspec-strat.rs b/proptest-derive/tests/compile-fail/E0025-overspec-strat.rs index ff1a864b..77b813e3 100644 --- a/proptest-derive/tests/compile-fail/E0025-overspec-strat.rs +++ b/proptest-derive/tests/compile-fail/E0025-overspec-strat.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0026-strategy-malformed-expr.rs b/proptest-derive/tests/compile-fail/E0026-strategy-malformed-expr.rs index 85f55f01..bc17f60c 100644 --- a/proptest-derive/tests/compile-fail/E0026-strategy-malformed-expr.rs +++ b/proptest-derive/tests/compile-fail/E0026-strategy-malformed-expr.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0027-filter-malformed-expr.rs b/proptest-derive/tests/compile-fail/E0027-filter-malformed-expr.rs index 4a422304..c31bc9f9 100644 --- a/proptest-derive/tests/compile-fail/E0027-filter-malformed-expr.rs +++ b/proptest-derive/tests/compile-fail/E0027-filter-malformed-expr.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} @@ -153,4 +154,4 @@ enum T19 { V1( u8, ), -} \ No newline at end of file +} diff --git a/proptest-derive/tests/compile-fail/E0027-filter-malformed.rs b/proptest-derive/tests/compile-fail/E0027-filter-malformed.rs index bfa2732c..482f84fa 100644 --- a/proptest-derive/tests/compile-fail/E0027-filter-malformed.rs +++ b/proptest-derive/tests/compile-fail/E0027-filter-malformed.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} @@ -81,4 +82,4 @@ struct T8(u8); #[derive(Debug, Arbitrary)] //~ ERROR: [proptest_derive, E0027] #[proptest(filter(1))] -struct T9(u8); \ No newline at end of file +struct T9(u8); diff --git a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-filter.rs b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-filter.rs index a398ffca..d529ef1a 100644 --- a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-filter.rs +++ b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-filter.rs @@ -8,10 +8,11 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} -#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors +#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors //~| [proptest_derive, E0028] //~| [proptest_derive, E0006] enum NonFatal { diff --git a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-param.rs b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-param.rs index e369abc2..1892e195 100644 --- a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-param.rs +++ b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-param.rs @@ -8,10 +8,11 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} -#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors +#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors //~| [proptest_derive, E0028] //~| [proptest_derive, E0006] enum NonFatal { diff --git a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-strat.rs b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-strat.rs index df263dce..127ab566 100644 --- a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-strat.rs +++ b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-strat.rs @@ -8,10 +8,11 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} -#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors +#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors //~| [proptest_derive, E0028] //~| [proptest_derive, E0006] enum NonFatal { diff --git a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-weight.rs b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-weight.rs index 7ca58bf5..5c12e8bf 100644 --- a/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-weight.rs +++ b/proptest-derive/tests/compile-fail/E0028-skipped-variant-has-weight.rs @@ -8,10 +8,11 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} -#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors +#[derive(Debug, Arbitrary)] //~ ERROR: 2 errors //~| [proptest_derive, E0028] //~| [proptest_derive, E0006] enum NonFatal { diff --git a/proptest-derive/tests/compile-fail/E0029-filter-on-unit-variant.rs b/proptest-derive/tests/compile-fail/E0029-filter-on-unit-variant.rs index 36303850..77feaf92 100644 --- a/proptest-derive/tests/compile-fail/E0029-filter-on-unit-variant.rs +++ b/proptest-derive/tests/compile-fail/E0029-filter-on-unit-variant.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0029-params-on-unit-variant.rs b/proptest-derive/tests/compile-fail/E0029-params-on-unit-variant.rs index 04a5c3a4..f3dd715d 100644 --- a/proptest-derive/tests/compile-fail/E0029-params-on-unit-variant.rs +++ b/proptest-derive/tests/compile-fail/E0029-params-on-unit-variant.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0029-strategy-on-unit-variant.rs b/proptest-derive/tests/compile-fail/E0029-strategy-on-unit-variant.rs index fa2ea2a6..1eae63cf 100644 --- a/proptest-derive/tests/compile-fail/E0029-strategy-on-unit-variant.rs +++ b/proptest-derive/tests/compile-fail/E0029-strategy-on-unit-variant.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0030-params-on-unit-struct.rs b/proptest-derive/tests/compile-fail/E0030-params-on-unit-struct.rs index 527700e5..86f0b993 100644 --- a/proptest-derive/tests/compile-fail/E0030-params-on-unit-struct.rs +++ b/proptest-derive/tests/compile-fail/E0030-params-on-unit-struct.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} @@ -51,4 +52,4 @@ struct T7(); #[derive(Debug, Arbitrary)] //~ ERROR: [proptest_derive, E0030] #[proptest(filter(foo))] -struct T8 {} \ No newline at end of file +struct T8 {} diff --git a/proptest-derive/tests/compile-fail/E0031-no-bound-non-tyvar.rs b/proptest-derive/tests/compile-fail/E0031-no-bound-non-tyvar.rs index 554caf7b..876d41ed 100644 --- a/proptest-derive/tests/compile-fail/E0031-no-bound-non-tyvar.rs +++ b/proptest-derive/tests/compile-fail/E0031-no-bound-non-tyvar.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0032-no-bound-malformed.rs b/proptest-derive/tests/compile-fail/E0032-no-bound-malformed.rs index dc5c285f..5f0508ab 100644 --- a/proptest-derive/tests/compile-fail/E0032-no-bound-malformed.rs +++ b/proptest-derive/tests/compile-fail/E0032-no-bound-malformed.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0033-weight-overflow.rs b/proptest-derive/tests/compile-fail/E0033-weight-overflow.rs index 662cc7de..9c518766 100644 --- a/proptest-derive/tests/compile-fail/E0033-weight-overflow.rs +++ b/proptest-derive/tests/compile-fail/E0033-weight-overflow.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0034-regex-malformed.rs b/proptest-derive/tests/compile-fail/E0034-regex-malformed.rs index 40eb225c..b4585504 100644 --- a/proptest-derive/tests/compile-fail/E0034-regex-malformed.rs +++ b/proptest-derive/tests/compile-fail/E0034-regex-malformed.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0035-cant_set_param_and_regex.rs b/proptest-derive/tests/compile-fail/E0035-cant_set_param_and_regex.rs index f474ae98..8818c03b 100644 --- a/proptest-derive/tests/compile-fail/E0035-cant_set_param_and_regex.rs +++ b/proptest-derive/tests/compile-fail/E0035-cant_set_param_and_regex.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/E0658-no-bare-modifiers.rs b/proptest-derive/tests/compile-fail/E0658-no-bare-modifiers.rs index 253421c4..e6e75b92 100644 --- a/proptest-derive/tests/compile-fail/E0658-no-bare-modifiers.rs +++ b/proptest-derive/tests/compile-fail/E0658-no-bare-modifiers.rs @@ -13,6 +13,7 @@ extern crate proptest_derive; extern crate proptest; use proptest::prelude::*; +use proptest_derive::Arbitrary; fn main() {} diff --git a/proptest-derive/tests/compile-fail/must-be-debug.rs b/proptest-derive/tests/compile-fail/must-be-debug.rs index 5c1f6db0..6b39431c 100644 --- a/proptest-derive/tests/compile-fail/must-be-debug.rs +++ b/proptest-derive/tests/compile-fail/must-be-debug.rs @@ -8,8 +8,13 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} + #[derive(Arbitrary)] //~ `Foo` doesn't implement `Debug` [E0277] -struct Foo { x: usize } //~ `Foo` doesn't implement `Debug` [E0277] +struct Foo { //~^ `Foo` doesn't implement `Debug` [E0277] + //~^ `Foo` doesn't implement `Debug` [E0277] + x: usize +} diff --git a/proptest-derive/tests/compile-fail/no-arbitrary.rs b/proptest-derive/tests/compile-fail/no-arbitrary.rs index 86123780..0365ccd6 100644 --- a/proptest-derive/tests/compile-fail/no-arbitrary.rs +++ b/proptest-derive/tests/compile-fail/no-arbitrary.rs @@ -8,10 +8,15 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} +#[derive(Debug)] struct T0; #[derive(Debug, Arbitrary)] //~ the trait bound `T0: Arbitrary` is not satisfied [E0277] -struct T1 { f0: T0, } //~ the trait bound `T0: Arbitrary` is not satisfied [E0277] +struct T1 { + f0: T0, //~ the trait bound `T0: Arbitrary` is not satisfied [E0277] + //~^ the trait bound `T0: Arbitrary` is not satisfied [E0277] +} diff --git a/proptest-derive/tests/compile-fail/regex_wrong_type.rs b/proptest-derive/tests/compile-fail/regex_wrong_type.rs index 8d62095f..88feb625 100644 --- a/proptest-derive/tests/compile-fail/regex_wrong_type.rs +++ b/proptest-derive/tests/compile-fail/regex_wrong_type.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate proptest_derive; +use proptest_derive::Arbitrary; fn main() {} @@ -20,37 +21,37 @@ fn make_regex() -> &'static str { #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T0 { #[proptest(regex = "a+")] - f0: (), + f0: (), //~ StrategyFromRegex` is not satisfied [E0277] } #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T1 { #[proptest(regex("a*"))] - f0: u8, + f0: u8, //~ StrategyFromRegex` is not satisfied [E0277] } #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T2 { #[proptest(regex(make_regex))] - f0: Vec, + f0: Vec, //~ StrategyFromRegex` is not satisfied [E0277] } #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T3( #[proptest(regex = "a+")] - (), + (), //~ StrategyFromRegex` is not satisfied [E0277] ); #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T4( #[proptest(regex("a*"))] - u8, + u8, //~ StrategyFromRegex` is not satisfied [E0277] ); #[derive(Debug, Arbitrary)] //~ StrategyFromRegex` is not satisfied [E0277] struct T5( #[proptest(regex(make_regex))] - Vec, + Vec, //~ StrategyFromRegex` is not satisfied [E0277] ); // enum: @@ -59,7 +60,7 @@ struct T5( enum T6 { V0 { #[proptest(regex = "a+")] - f0: (), + f0: (), //~ StrategyFromRegex` is not satisfied [E0277] } } @@ -67,7 +68,7 @@ enum T6 { enum T7 { V0 { #[proptest(regex("a*"))] - f0: u8, + f0: u8, //~ StrategyFromRegex` is not satisfied [E0277] } } @@ -75,7 +76,7 @@ enum T7 { enum T8 { V0 { #[proptest(regex(make_regex))] - f0: Vec, + f0: Vec, //~ StrategyFromRegex` is not satisfied [E0277] } } @@ -83,7 +84,7 @@ enum T8 { enum T9 { V0( #[proptest(regex = "a+")] - (), + (), //~ StrategyFromRegex` is not satisfied [E0277] ) } @@ -91,7 +92,7 @@ enum T9 { enum T10 { V0( #[proptest(regex("a*"))] - u8, + u8, //~ StrategyFromRegex` is not satisfied [E0277] ) } @@ -99,6 +100,6 @@ enum T10 { enum T11 { V0( #[proptest(regex(make_regex))] - Vec, + Vec, //~ StrategyFromRegex` is not satisfied [E0277] ) } diff --git a/proptest-derive/tests/compiletest.rs b/proptest-derive/tests/compiletest.rs index 1198128a..11357ee0 100644 --- a/proptest-derive/tests/compiletest.rs +++ b/proptest-derive/tests/compiletest.rs @@ -20,6 +20,7 @@ fn run_mode(src: &'static str, mode: &'static str) { config.filters = vec![name]; } config.src_base = format!("tests/{}", src).into(); + config.clean_rmeta(); ct::run_tests(&config); } From 91549b728b36ee3d47ec39959505e22b06e58e84 Mon Sep 17 00:00:00 2001 From: Matthew Russo Date: Sun, 18 Feb 2024 19:09:09 -0800 Subject: [PATCH 09/20] [Fix] fix clippies (#425) --- proptest-derive/src/error.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/proptest-derive/src/error.rs b/proptest-derive/src/error.rs index 03d1315a..1cb6b81f 100644 --- a/proptest-derive/src/error.rs +++ b/proptest-derive/src/error.rs @@ -505,10 +505,6 @@ error!( in Rust. An example: `#[proptest(params = \"ComplexType\")]`." ); -// Happens when syn can't interpret in `#[proptest ]`. -error!(no_interp_meta, E0024, - "The tokens `` in #[proptest ] do not make for a valid attribute."); - // Happens when more than one of `#[proptest(strategy..)]`, // `#[proptest(value..)]`, or `#[proptest(regex..)]` were specified. // They are mutually exclusive choices. From d1c9b846f20a4d98a432845cb98e8a1c0f407ff0 Mon Sep 17 00:00:00 2001 From: Matthew Russo Date: Sun, 18 Feb 2024 19:48:25 -0800 Subject: [PATCH 10/20] [Fix] Config : don't clobber existing failure-persistence config during contextualization (#424) --- proptest/CHANGELOG.md | 3 +++ proptest/src/test_runner/config.rs | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/proptest/CHANGELOG.md b/proptest/CHANGELOG.md index 9dffab19..caa7b811 100644 --- a/proptest/CHANGELOG.md +++ b/proptest/CHANGELOG.md @@ -5,6 +5,9 @@ - Setting `PROPTEST_MAX_DEFAULT_SIZE_RANGE` now customizes the default `SizeRange` used by the default strategies for collections (like `Vec`). The default remains 100. +### Bug Fixes +- Fixed issue where config contextualization would clobber existing failure persistence config + ## 1.4.0 ### Breaking Changes diff --git a/proptest/src/test_runner/config.rs b/proptest/src/test_runner/config.rs index 90c1f4e3..0a5dc1c8 100644 --- a/proptest/src/test_runner/config.rs +++ b/proptest/src/test_runner/config.rs @@ -80,8 +80,6 @@ pub fn contextualize_config(mut result: Config) -> Config { } } - result.failure_persistence = - Some(Box::new(FileFailurePersistence::default())); for (var, value) in env::vars_os().filter_map(|(k, v)| k.into_string().ok().map(|k| (k, v))) { @@ -186,8 +184,11 @@ fn default_default_config() -> Config { // defaults. #[cfg(feature = "std")] lazy_static! { - static ref DEFAULT_CONFIG: Config = - contextualize_config(default_default_config()); + static ref DEFAULT_CONFIG: Config = { + let mut default_config = default_default_config(); + default_config.failure_persistence = Some(Box::new(FileFailurePersistence::default())); + contextualize_config(default_config) + }; } /// Configuration for how a proptest test should be run. From b1be99d2c3225325ef08072ef5f23f231e75a5fa Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 4 Mar 2024 02:48:34 +0000 Subject: [PATCH 11/20] fix: use anon-const for wrapping expanded impls (#427) * fix: use anon-const for wrapping expanded impls * fix derive tests --- proptest-derive/src/ast.rs | 4 +--- proptest-derive/src/tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/proptest-derive/src/ast.rs b/proptest-derive/src/ast.rs index 178a09e3..ff43b8b9 100644 --- a/proptest-derive/src/ast.rs +++ b/proptest-derive/src/ast.rs @@ -106,8 +106,6 @@ impl Impl { let _top = call_site_ident(TOP_PARAM_NAME); - let _const = call_site_ident(&format!("_IMPL_ARBITRARY_FOR_{}", typ)); - // Linearise everything. We're done after this. // // NOTE: The clippy::arc_with_non_send_sync lint is disabled here because the strategies @@ -118,7 +116,7 @@ impl Impl { let q = quote! { #[allow(non_upper_case_globals)] #[allow(clippy::arc_with_non_send_sync)] - const #_const: () = { + const _: () = { extern crate proptest as _proptest; impl #impl_generics _proptest::arbitrary::Arbitrary diff --git a/proptest-derive/src/tests.rs b/proptest-derive/src/tests.rs index 788a6e7c..10d80614 100644 --- a/proptest-derive/src/tests.rs +++ b/proptest-derive/src/tests.rs @@ -80,7 +80,7 @@ test! { } expands to { #[allow(non_upper_case_globals)] #[allow(clippy::arc_with_non_send_sync)] - const _IMPL_ARBITRARY_FOR_MyUnitStruct : () = { + const _: () = { extern crate proptest as _proptest; impl _proptest::arbitrary::Arbitrary for MyUnitStruct { type Parameters = (); @@ -101,7 +101,7 @@ test! { } expands to { #[allow(non_upper_case_globals)] #[allow(clippy::arc_with_non_send_sync)] - const _IMPL_ARBITRARY_FOR_MyTupleUnitStruct : () = { + const _: () = { extern crate proptest as _proptest; impl _proptest::arbitrary::Arbitrary for MyTupleUnitStruct { type Parameters = (); @@ -122,7 +122,7 @@ test! { } expands to { #[allow(non_upper_case_globals)] #[allow(clippy::arc_with_non_send_sync)] - const _IMPL_ARBITRARY_FOR_MyNamedUnitStruct : () = { + const _: () = { extern crate proptest as _proptest; impl _proptest::arbitrary::Arbitrary for MyNamedUnitStruct { type Parameters = (); From 1b7c426df5d2b3f40a71279cb88a2a056aff3b09 Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Mon, 4 Mar 2024 04:49:00 +0200 Subject: [PATCH 12/20] Fix feature = "cargo-clippy" deprecation (#429) --- proptest/src/arbitrary/_alloc/collections.rs | 2 +- proptest/src/arbitrary/_core/cell.rs | 4 ++-- proptest/src/bits.rs | 2 +- proptest/src/lib.rs | 2 +- proptest/src/option.rs | 2 +- proptest/src/result.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/proptest/src/arbitrary/_alloc/collections.rs b/proptest/src/arbitrary/_alloc/collections.rs index 20f3a929..3d0c50bc 100644 --- a/proptest/src/arbitrary/_alloc/collections.rs +++ b/proptest/src/arbitrary/_alloc/collections.rs @@ -9,7 +9,7 @@ //! Arbitrary implementations for `std::collections`. -//#![cfg_attr(feature="cargo-clippy", allow(implicit_hasher))] +//#![cfg_attr(clippy, allow(implicit_hasher))] //============================================================================== // Imports: diff --git a/proptest/src/arbitrary/_core/cell.rs b/proptest/src/arbitrary/_core/cell.rs index c10148fb..df48a54c 100644 --- a/proptest/src/arbitrary/_core/cell.rs +++ b/proptest/src/arbitrary/_core/cell.rs @@ -17,7 +17,7 @@ wrap_from!(UnsafeCell); lazy_just!(BorrowError, || { // False positive: - #[cfg_attr(feature = "cargo-clippy", allow(let_and_return))] + #[cfg_attr(clippy, allow(let_and_return))] { let _rc = RefCell::new(()); let _bm = _rc.borrow_mut(); @@ -28,7 +28,7 @@ lazy_just!(BorrowError, || { }); lazy_just!(BorrowMutError, || { // False positive: - #[cfg_attr(feature = "cargo-clippy", allow(let_and_return))] + #[cfg_attr(clippy, allow(let_and_return))] { let _rc = RefCell::new(()); let _bm = _rc.borrow_mut(); diff --git a/proptest/src/bits.rs b/proptest/src/bits.rs index 89b183ed..699362ac 100644 --- a/proptest/src/bits.rs +++ b/proptest/src/bits.rs @@ -32,7 +32,7 @@ use crate::strategy::*; use crate::test_runner::*; /// Trait for types which can be handled with `BitSetStrategy`. -#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))] +#[cfg_attr(clippy, allow(len_without_is_empty))] pub trait BitSetLike: Clone + fmt::Debug { /// Create a new value of `Self` with space for up to `max` bits, all /// initialised to zero. diff --git a/proptest/src/lib.rs b/proptest/src/lib.rs index aa5c9e8c..da37075d 100644 --- a/proptest/src/lib.rs +++ b/proptest/src/lib.rs @@ -17,7 +17,7 @@ #![forbid(future_incompatible)] #![deny(missing_docs, bare_trait_objects)] #![no_std] -#![cfg_attr(feature = "cargo-clippy", allow( +#![cfg_attr(clippy, allow( doc_markdown, // We have a lot of these lints for associated types... And we don't care. type_complexity diff --git a/proptest/src/option.rs b/proptest/src/option.rs index 466ada68..53b8d270 100644 --- a/proptest/src/option.rs +++ b/proptest/src/option.rs @@ -9,7 +9,7 @@ //! Strategies for generating `std::Option` values. -#![cfg_attr(feature = "cargo-clippy", allow(expl_impl_clone_on_copy))] +#![cfg_attr(clippy, allow(expl_impl_clone_on_copy))] use core::fmt; use core::marker::PhantomData; diff --git a/proptest/src/result.rs b/proptest/src/result.rs index bdb163b3..1da107e5 100644 --- a/proptest/src/result.rs +++ b/proptest/src/result.rs @@ -26,7 +26,7 @@ //! "maybe err" since the success case results in an easier to understand code //! path. -#![cfg_attr(feature = "cargo-clippy", allow(expl_impl_clone_on_copy))] +#![cfg_attr(clippy, allow(expl_impl_clone_on_copy))] use core::fmt; use core::marker::PhantomData; From 72ce2f1d79031c3d748e236b33e01915040c15b6 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:19:35 +0100 Subject: [PATCH 13/20] Use `array::from_fn` instead of `unarray::build_array` (#432) `core::array::from_fn` is stable since 1.63, and the current MSRV is 1.64. Ideally `unarray` would get entirely replaced but `core::array::try_from_fn` is still unstable. --- proptest/src/array.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proptest/src/array.rs b/proptest/src/array.rs index 2edce460..82ac0344 100644 --- a/proptest/src/array.rs +++ b/proptest/src/array.rs @@ -149,7 +149,7 @@ impl ValueTree for ArrayValueTree<[T; N]> { type Value = [T::Value; N]; fn current(&self) -> [T::Value; N] { - unarray::build_array(|i| self.tree[i].current()) + core::array::from_fn(|i| self.tree[i].current()) } fn simplify(&mut self) -> bool { From 923d50fd14a3ecbcd84c921feba7cbcc7ca5c610 Mon Sep 17 00:00:00 2001 From: tgolang Date: Mon, 11 Mar 2024 16:11:43 +0800 Subject: [PATCH 14/20] chore: fix some typos Signed-off-by: tgolang --- proptest/src/test_runner/errors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proptest/src/test_runner/errors.rs b/proptest/src/test_runner/errors.rs index a75e3a5c..bf17f201 100644 --- a/proptest/src/test_runner/errors.rs +++ b/proptest/src/test_runner/errors.rs @@ -39,7 +39,7 @@ pub enum TestCaseError { /// configured `PROPTEST_CASES`; only `NewCases` shall be counted. /// /// TODO-v2: Ideally `TestCaseResult = Result` -/// however this breaks source compability in version 1.x.x because +/// however this breaks source compatibility in version 1.x.x because /// `TestCaseResult` is public. #[derive(Debug, Clone)] pub(crate) enum TestCaseOk { @@ -56,7 +56,7 @@ pub type TestCaseResult = Result<(), TestCaseError>; /// Intended to replace `TestCaseResult` in v2. /// /// TODO-v2: Ideally `TestCaseResult = Result` -/// however this breaks source compability in version 1.x.x because +/// however this breaks source compatibility in version 1.x.x because /// `TestCaseResult` is public. pub(crate) type TestCaseResultV2 = Result; From 2f1b673e88d7f4bd77789c54d342f3a07681e008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6=C3=B6h?= Date: Thu, 19 Oct 2023 10:16:50 +0200 Subject: [PATCH 15/20] delete unseen transitions in state machine test --- proptest-state-machine/src/strategy.rs | 168 +++++++++++++++++++--- proptest-state-machine/src/test_runner.rs | 22 ++- 2 files changed, 163 insertions(+), 27 deletions(-) diff --git a/proptest-state-machine/src/strategy.rs b/proptest-state-machine/src/strategy.rs index 97ae39c0..596ec4ce 100644 --- a/proptest-state-machine/src/strategy.rs +++ b/proptest-state-machine/src/strategy.rs @@ -9,6 +9,9 @@ //! Strategies used for abstract state machine testing. +use std::sync::atomic::{self, AtomicUsize}; +use std::sync::Arc; + use proptest::bits::{BitSetLike, VarBitSet}; use proptest::collection::SizeRange; use proptest::num::sample_uniform_incl; @@ -118,14 +121,19 @@ pub trait ReferenceStateMachine { /// The shrinking strategy is to iteratively apply `Shrink::InitialState`, /// `Shrink::DeleteTransition` and `Shrink::Transition`. /// -/// 1. We start by trying to delete transitions from the back of the list, until -/// we can do so no further (reached the beginning of the list). -/// We start from the back, because it's less likely to affect the state -/// machine's pre-conditions, if any. -/// 2. Then, we again iteratively attempt to shrink the individual transitions, +/// 1. We start by trying to delete transitions from the back of the list that +/// were never seen by the test, if any. Note that because proptest expects +/// deterministic results in for reproducible issues, unlike the following +/// steps this step will not be undone on `complicate`. If there were any +/// unseen transitions, then the next step will start at trying to delete +/// the transition before the last one seen as we know that the last +/// transition cannot be deleted as it's the one that has failed. +/// 2. Then, we keep trying to delete transitions from the back of the list, until +/// we can do so no further (reached the beginning of the list).. +/// 3. Then, we again iteratively attempt to shrink the individual transitions, /// but this time starting from the front of the list - i.e. from the first /// transition to be applied. -/// 3. Finally, we try to shrink the initial state until it's not possible to +/// 4. Finally, we try to shrink the initial state until it's not possible to /// shrink it any further. /// /// For `complicate`, we attempt to undo the last shrink operation, if there was @@ -162,7 +170,7 @@ impl< StateStrategy::Tree, TransitionStrategy::Tree, >; - type Value = (State, Vec); + type Value = (State, Vec, Option>); fn new_tree(&self, runner: &mut TestRunner) -> NewTree { // Generate the initial state value tree @@ -214,6 +222,7 @@ impl< // which is less likely to invalidate pre-conditions shrink: Shrink::DeleteTransition(max_ix), last_shrink: None, + seen_transitions_counter: Some(Default::default()), }) } } @@ -276,6 +285,13 @@ pub struct SequentialValueTree< shrink: Shrink, /// The last applied shrink operation, if any last_shrink: Option, + /// The number of transitions that were seen by the test runner. + /// On a test run this is shared with `StateMachineTest::test_sequential` + /// which increments the inner counter value on every transition. If the + /// test fails, the counter is used to remove any unseen transitions before + /// shrinking and this field is set to `None` as it's no longer needed for + /// shrinking. + seen_transitions_counter: Option>, } impl< @@ -289,6 +305,48 @@ impl< /// Try to apply the next `self.shrink`. Returns `true` if a shrink has been /// applied. fn try_simplify(&mut self) -> bool { + if let Some(seen_transitions_counter) = + self.seen_transitions_counter.as_ref() + { + let seen_count = + seen_transitions_counter.load(atomic::Ordering::SeqCst); + + let included_count = self.included_transitions.count(); + + if seen_count >= included_count { + // the test runner saw all the transitions and failed on the + // last one, so we cant delete any transitions from the end. + } else { + // the test runner did not see all the transitions so we can + // delete the transitions that were not seen and thus not executed. + + let mut kept_count = 0; + for ix in 0..self.transitions.len() { + if self.included_transitions.test(ix) { + // transition at ix was part of test + + if kept_count < seen_count { + // transition at xi was seen by the test or we are + // still below minimum size for the test + kept_count += 1; + } else { + // transition at ix was never seen + self.included_transitions.clear(ix); + self.shrinkable_transitions.clear(ix); + } + } + } + // Set the next shrink to the transition before the last seen + // transition (subtract 2 from `kept_count`) + self.shrink = DeleteTransition( + kept_count.checked_sub(2).unwrap_or_default(), + ); + } + + // Remove the seen transitions counter for shrinking runs + self.seen_transitions_counter = None; + } + if let DeleteTransition(ix) = self.shrink { // Delete the index from the included transitions self.included_transitions.clear(ix); @@ -442,7 +500,7 @@ impl< /// yet been rejected. fn can_simplify(&self) -> bool { self.is_initial_state_shrinkable || - // If there are some transitions whose shrinking has not yet been + // If there are some transitions whose shrinking has not yet been // rejected, we can try to shrink them further !self .acceptable_transitions @@ -483,39 +541,48 @@ impl< TransitionValueTree, > { - type Value = (State, Vec); + type Value = (State, Vec, Option>); fn current(&self) -> Self::Value { ( self.last_valid_initial_state.clone(), // The current included acceptable transitions self.get_included_acceptable_transitions(None), + self.seen_transitions_counter.clone(), ) } fn simplify(&mut self) -> bool { - if self.can_simplify() { + let was_simplified = if self.can_simplify() { self.try_simplify() + } else if let Some(Transition(ix)) = self.last_shrink { + self.try_to_find_acceptable_transition(ix) } else { - if let Some(Transition(ix)) = self.last_shrink { - return self.try_to_find_acceptable_transition(ix); - } false - } + }; + + // reset seen transactions counter for next run + self.seen_transitions_counter = Default::default(); + + was_simplified } fn complicate(&mut self) -> bool { - match self.last_shrink { + // reset seen transactions counter for next run + self.seen_transitions_counter = Default::default(); + + match &self.last_shrink { None => false, Some(DeleteTransition(ix)) => { // Undo the last item we deleted. Can't complicate any further, // so unset prev_shrink. - self.included_transitions.set(ix); - self.shrinkable_transitions.set(ix); + self.included_transitions.set(*ix); + self.shrinkable_transitions.set(*ix); self.last_shrink = None; true } Some(Transition(ix)) => { + let ix = *ix; if self.transitions[ix].complicate() { if self.check_acceptable(Some(ix)) { self.acceptable_transitions[ix] = @@ -574,6 +641,11 @@ mod test { #[test] fn number_of_sequential_value_tree_simplifications() { let mut value_tree = deterministic_sequential_value_tree(); + value_tree + .seen_transitions_counter + .as_mut() + .unwrap() + .store(TRANSITIONS, atomic::Ordering::SeqCst); let mut i = 0; loop { @@ -614,7 +686,7 @@ mod test { let mut value_tree = deterministic_sequential_value_tree(); let check_preconditions = |value_tree: &TestValueTree| { - let (mut state, transitions) = value_tree.current(); + let (mut state, transitions, _seen_counter) = value_tree.current(); let len = transitions.len(); println!("Transitions {}", len); for (ix, transition) in transitions.into_iter().enumerate() { @@ -660,6 +732,57 @@ mod test { } } + proptest! { + /// Test the initial simplifications of the `SequentialValueTree` produced + /// by `deterministic_sequential_value_tree`. + /// + /// We want to make sure that we initially remove the transitions that + /// where not seen. + #[test] + fn test_value_tree_initial_simplification( + len in 10usize..100, + ) { + test_value_tree_initial_simplification_aux(len) + } + } + + fn test_value_tree_initial_simplification_aux(len: usize) { + let sequential = + ::sequential_strategy( + ..len, + ); + + let mut runner = TestRunner::deterministic(); + let mut value_tree = sequential.new_tree(&mut runner).unwrap(); + + let (_, transitions, mut seen_counter) = value_tree.current(); + + let num_seen = transitions.len() / 2; + let seen_counter = seen_counter.as_mut().unwrap(); + seen_counter.store(num_seen, atomic::Ordering::SeqCst); + + let mut seen_before_complication = + transitions.into_iter().take(num_seen).collect::>(); + + assert!(value_tree.simplify()); + + let (_, transitions, _seen_counter) = value_tree.current(); + + let seen_after_first_complication = + transitions.into_iter().collect::>(); + + // After the unseen transitions are removed, the shrink to delete the + // transition before the last seen one is applied + let last = seen_before_complication.pop().unwrap(); + seen_before_complication.pop(); + seen_before_complication.push(last); + + assert_eq!( + seen_before_complication, seen_after_first_complication, + "only seen transitions should be present after first simplification" + ); + } + /// The following is a definition of an reference state machine used for the /// tests. mod heap_state_machine { @@ -682,7 +805,7 @@ mod test { pub type TestState = Vec; - #[derive(Clone, Debug)] + #[derive(Clone, Debug, PartialEq)] pub enum TestTransition { PopNonEmpty, PopEmpty, @@ -839,22 +962,23 @@ mod test { Config::default(), TestRng::from_seed(Default::default(), &seed)); let result = runner.run( &FailIfLessThan::sequential_strategy(10..50_usize), - |(ref_state, transitions)| { + |(ref_state, transitions, seen_counter)| { Ok(FailIfLessThanTest::test_sequential( Default::default(), ref_state, transitions, + seen_counter, )) }, ); if let Err(TestError::Fail( _, - (FailIfLessThan(limit), transitions), + (FailIfLessThan(limit), transitions, _seen_counter), )) = result { assert_eq!(transitions.len(), 1, "The minimal failing case should be "); assert_eq!(limit, MIN_TRANSITION + 1); - assert!(transitions[0] < limit); + assert!(transitions.into_iter().next().unwrap() < limit); } else { prop_assume!(false, "If the state machine doesn't fail as intended, we need a case that fails."); diff --git a/proptest-state-machine/src/test_runner.rs b/proptest-state-machine/src/test_runner.rs index 56fb8c68..2d886384 100644 --- a/proptest-state-machine/src/test_runner.rs +++ b/proptest-state-machine/src/test_runner.rs @@ -9,8 +9,10 @@ //! Test declaration helpers and runners for abstract state machine testing. +use std::sync::atomic::{self, AtomicUsize}; +use std::sync::Arc; + use crate::strategy::ReferenceStateMachine; -use proptest::std_facade::Vec; use proptest::test_runner::Config; /// State machine test that relies on a reference state machine model @@ -72,6 +74,7 @@ pub trait StateMachineTest { transitions: Vec< ::Transition, >, + mut seen_counter: Option>, ) { #[cfg(feature = "std")] use proptest::test_runner::INFO_LOG; @@ -91,6 +94,15 @@ pub trait StateMachineTest { Self::check_invariants(&concrete_state, &ref_state); for (ix, transition) in transitions.into_iter().enumerate() { + // The counter is `Some` only before shrinking. When it's `Some` it + // must be incremented before every transition that's being applied + // to inform the strategy that the transition has been applied for + // the first step of its shrinking process which removes any unseen + // transitions. + if let Some(seen_counter) = seen_counter.as_mut() { + seen_counter.fetch_add(1, atomic::Ordering::SeqCst); + } + #[cfg(feature = "std")] if config.verbose >= INFO_LOG { eprintln!(); @@ -170,11 +182,11 @@ macro_rules! prop_state_machine { #![proptest_config($config)] $(#[$meta])* fn $test_name( - (initial_state, transitions) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) + (initial_state, transitions, seen_counter) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) ) { let config = $config.__sugar_to_owned(); - <$test $(::< $( $ty_param ),+ >)? as $crate::StateMachineTest>::test_sequential(config, initial_state, transitions) + <$test $(::< $( $ty_param ),+ >)? as $crate::StateMachineTest>::test_sequential(config, initial_state, transitions, seen_counter) } } )* @@ -189,10 +201,10 @@ macro_rules! prop_state_machine { ::proptest::proptest! { $(#[$meta])* fn $test_name( - (initial_state, transitions) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) + (initial_state, transitions, seen_counter) in <<$test $(< $( $ty_param ),+ >)? as $crate::StateMachineTest>::Reference as $crate::ReferenceStateMachine>::sequential_strategy($size) ) { <$test $(::< $( $ty_param ),+ >)? as $crate::StateMachineTest>::test_sequential( - ::proptest::test_runner::Config::default(), initial_state, transitions) + ::proptest::test_runner::Config::default(), initial_state, transitions, seen_counter) } } )* From bd4fc6612e457efeb4c398bf212bb3fd4d84c677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6=C3=B6h?= Date: Thu, 19 Oct 2023 10:46:11 +0200 Subject: [PATCH 16/20] add fallback to avoid panic --- proptest-state-machine/src/strategy.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/proptest-state-machine/src/strategy.rs b/proptest-state-machine/src/strategy.rs index 596ec4ce..d43b2fa1 100644 --- a/proptest-state-machine/src/strategy.rs +++ b/proptest-state-machine/src/strategy.rs @@ -313,12 +313,10 @@ impl< let included_count = self.included_transitions.count(); - if seen_count >= included_count { - // the test runner saw all the transitions and failed on the - // last one, so we cant delete any transitions from the end. - } else { + if seen_count < included_count { // the test runner did not see all the transitions so we can - // delete the transitions that were not seen and thus not executed. + // delete the transitions that were not seen because they were + // not executed let mut kept_count = 0; for ix in 0..self.transitions.len() { From 7634f326ed73592823374ceeabac35c6fc8a9947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Sj=C3=B6=C3=B6h?= Date: Fri, 20 Oct 2023 05:09:00 +0200 Subject: [PATCH 17/20] panic on misuse --- proptest-state-machine/src/strategy.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/proptest-state-machine/src/strategy.rs b/proptest-state-machine/src/strategy.rs index d43b2fa1..32e139d6 100644 --- a/proptest-state-machine/src/strategy.rs +++ b/proptest-state-machine/src/strategy.rs @@ -542,6 +542,12 @@ impl< type Value = (State, Vec, Option>); fn current(&self) -> Self::Value { + if let Some(seen_transitions_counter) = &self.seen_transitions_counter { + if seen_transitions_counter.load(atomic::Ordering::SeqCst) > 0 { + panic!("Unexpected non-zero `seen_transitions_counter`"); + } + } + ( self.last_valid_initial_state.clone(), // The current included acceptable transitions @@ -781,6 +787,26 @@ mod test { ); } + #[test] + fn test_call_to_current_with_non_zero_seen_counter() { + let result = std::panic::catch_unwind(|| { + let value_tree = deterministic_sequential_value_tree(); + + let (_, _transitions1, mut seen_counter) = value_tree.current(); + { + let seen_counter = seen_counter.as_mut().unwrap(); + seen_counter.store(1, atomic::Ordering::SeqCst); + } + drop(seen_counter); + + let _transitions2 = value_tree.current(); + }) + .expect_err("should panic"); + + let s = "Unexpected non-zero `seen_transitions_counter`"; + assert_eq!(result.downcast_ref::<&str>(), Some(&s)); + } + /// The following is a definition of an reference state machine used for the /// tests. mod heap_state_machine { From c7d383f8ed9fe3f3f7df6c31a44fa46f14e3a1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Mar 2024 20:32:05 +0000 Subject: [PATCH 18/20] changelog: add #388 --- proptest-state-machine/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proptest-state-machine/CHANGELOG.md b/proptest-state-machine/CHANGELOG.md index f64de622..f1a409b9 100644 --- a/proptest-state-machine/CHANGELOG.md +++ b/proptest-state-machine/CHANGELOG.md @@ -1,5 +1,10 @@ ## Unreleased +### New Features + +- Remove unseen transitions on a first step of shrinking. + ([\#388](https://github.com/proptest-rs/proptest/pull/388)) + ## 0.2.0 ### Other Notes From fdde49b202ceb62bf07e20686185fa5eebc6eb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Mar 2024 20:46:47 +0000 Subject: [PATCH 19/20] SM: bump minor version --- proptest-state-machine/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proptest-state-machine/Cargo.toml b/proptest-state-machine/Cargo.toml index ab14e9dc..110bac65 100644 --- a/proptest-state-machine/Cargo.toml +++ b/proptest-state-machine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proptest-state-machine" -version = "0.2.0" +version = "0.3.0" authors = ["Tomáš Zemanovič"] license = "MIT OR Apache-2.0" edition = "2018" From 69160452e80e6164fa3f1096acc6f5f1ec6b0202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Mar 2024 20:47:03 +0000 Subject: [PATCH 20/20] SM: update changelog for v0.3.0 --- proptest-state-machine/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proptest-state-machine/CHANGELOG.md b/proptest-state-machine/CHANGELOG.md index f1a409b9..6acebb51 100644 --- a/proptest-state-machine/CHANGELOG.md +++ b/proptest-state-machine/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +## 0.3.0 + ### New Features - Remove unseen transitions on a first step of shrinking.