Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iterative runner #396

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

define-null
Copy link

This PR adds possibility to perform state machine testing with model that is aware of the system under test, as suggested in the #378 I don't expect this code to be merged as is because while the approach is a fork with some modifications of the existing test_runner, there are some nuances here as well.

Some important changes additionally to iterative way of generating the transitions:

  • additional pass to try to Delete once all the transitions have tested for shrinking
  • when doing shrinking precondition check is less strict then when generating initial test. That's due to a good chance that operations will depend on each other, so as long as the test still fails after shrinking - droping more transitions because of the precondition is probably fine
  • initial state shrinking is dropped here, as I considered that to be a bit problematic with the iterative runner, but if necessary that could be revisioned
  • the approach that I picked doesn't change anything in the existing proptest library and could be potentially used as a standalone crate, but it will benefit from more tight integration, because of the way how the proptest runner functionality works.

Would love to hear some feedback about the PR

Copy link
Member

@matthew-russo matthew-russo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't gone through all the nitty gritty details of the implementation but on first pass my main meta question/comment is:

Why would someone use the SequentialRunner instead of just using the IterativeRunner with no checking of the SutState? Can we just incorporate the SutState in to the sequential runner and remove the need for users to have to decide which to use? We purposely extracted the state machine code in to its own crate versioned below 1.0 so we can make breaking changes as we find the right abstsractions

@@ -0,0 +1,861 @@
use std::{cell::RefCell, marker::PhantomData};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add some module docs to the crate root to explain the differences in iterative runner from the regular runner and why you would choose one over the other.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and in general throughout this file, we should add some doc comments on relevant traits / structs / public methods

test_runner::{Config, Reason, TestError, TestRunner},
};

pub fn test_sequential<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_iterative?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if theres some way to unify the sequential and iterative versions a bit more so we don't need separate definitions for things like this. this is nearly the same as the test_sequential function in test_runner.rs

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure current types would allow to do that easily. The rational to have this function at all for IterativeRunner is to be able to reproduce the failing test. I will rename it to test_iterative as you suggested.

S::teardown(sut_state, mod_state)
}

pub trait IterativeModelStateMachine {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshed: i'd like to think through the names for the types in this crate a bit more so we have a consistent scheme/glossary. this mirrors ReferenceStateMachine in strategy.rs so we're now using Model and Reference as interchangeable it seems.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model in the IterativeRunner is an entity that supports generating of the test scenarios rather than the reference model. If the idea is to have both IterativeRunner and the current SequentialRunner in the crate I would say it might be desired to have explicit distinction between the two.

Comment on lines +81 to +83
/// Generate transition based on the state of the model. System under
/// test can be consulted here as well to reduce number of generated
/// transitions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bikeshed: Generate transition based on the state of the model and system under test.

Comment on lines +84 to +87
fn transition(
mod_state: &Self::ModelState,
sut_state: &Self::SutState,
) -> BoxedStrategy<Self::Transition>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there an implicit invariant here that ModelState and SutState are valid together? can they diverge and if so, what is an implementation supposed to do since the generation of the Transition is infallible -- panic?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ModelState and SutState may diverge, and what is more important and differs this from the intent of SequentialRuner is that ModelState here is a supporting entity, rather than the reference model.

Transition is indeed infallible, and perhaps the better way would be to treat that in the same way as failure to apply a transition to the model or to the system under test.

Comment on lines +131 to +141
fn apply(
sut_state: Self::SutState,
mod_state: &Self::ModelState,
transition: Self::Transition,
) -> Self::SutState;

/// Check some invariant on the SUT state after every transition.
fn check_invariants(sut_state: &Self::SutState, mod_state: &Self::ModelState) {
// This is to avoid `unused_variables` warning
let _ = (sut_state, mod_state);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i know you're copying this from the existing trait so not necessarily new but I wonder if we should remove mod_state/ref_state from apply and that is only used for applying a Transition to the System-Under-Test. The fact that apply can check state and check_invariants also exists is a bit redundant and it may be clearer to have it so we have one method for applying a change and one method for validating that change against the reference/model

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest given that Model in IterativeRunner is a less formulated and strict, I would prefer to keep this API as is, as it provides flexibility. For example for actual problem where I needed to use iterative runner the model is implementing both the simplified (not complete) model of the system under test, and also controls the external resources that system under test interacts with. For example the model may not be aware that system under test is issuing the calls towards the external subsystems (because the model is not complete), but system under test would be able to get this data from the model in the apply call. Somewhat similar in the check_invariants API depending on how the model is used, there might be desire to check that state of the model and state of the system under test are in fact in sync here and invariant holds based on the state of the model.

I understand that this explanation is a bit vague, but one of the intents of the IterativeRunner is to provide flexibility that otherwise can't be provided by the existing SequentialRunner.

@define-null
Copy link
Author

Thank you for spending time to reviewing this.

Why would someone use the SequentialRunner instead of just using the
IterativeRunner with no checking of the SutState?

One of the use-cases for the existing SequentialRunner that I can see is when the
developer has a well-defined self-contained reference model. If that's the case
and one would like to have strong guarantees that model implementation is
separate from the system under test - SequentialRunner would be prefered. Thus, I
guess, the choice of the terminology - Reference, as reference model, rather
than just the Model as in the IterativeRunner case.

But otherwise everything that one may want to implement using SequentialRunner
would be possible to do with IterativeRunner as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants