-
Notifications
You must be signed in to change notification settings - Fork 5
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
Runner for event streams #44
Conversation
…he counterexample length
@vitorenesduarte , @romac , would be grateful to hear your comments on this. @vitorenesduarte , this PR removes the previous TestRunner completely and I am not sure about that. What do you think, does it still make sense to keep it? |
Very nice, I like the little There's one thing, where I am not quite sure about using |
Glad you like the DSL, Romain! In fact, originally it was that the system has to be supplied as one of the parameters; I've changed it to |
@@ -288,12 +288,14 @@ impl<'a, System> Runner<'a, System> { | |||
&mut self, | |||
system: &mut System, | |||
stream: &mut dyn Iterator<Item = Event>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also work without the need for a trait object (dyn Trait
):
stream: &mut dyn Iterator<Item = Event>, | |
stream: impl Iterator<Item = Event>, |
The way you addressed this in 66211b3 looks good to me :) |
If you think it's useful, you can always add a |
}, | ||
|
||
/// A error that occurs when a test fails. | ||
#[error("Test failed: {message}\n {location}")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, should we use test
and system
in this error message?
/// A `modelator` [enum@Error]. | ||
#[error("Error while running modelator: {0}")] | ||
Modelator(Error), | ||
|
||
/// A error that occurs when a test fails. | ||
#[error("Test failed on step {step_index}/{step_count}:\nsteps: {steps:#?}")] | ||
#[error("Unhandled test: {test}")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use the test
field in this error message?
@@ -45,11 +45,11 @@ pub enum Event { | |||
/// Process the abstract action, modifying the system state. | |||
Action(Box<dyn Any>), | |||
/// Expect the provided outcome of the last action. | |||
Outcome(String), | |||
Expect(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these should be called TestStep
s instead of Event
s. It wasn't immediately obvious to me what an Event
was.
/// Check the assertion about the abstract system state. | ||
Check(Box<dyn Any>), | ||
/// Expect exactly the provided abstract system state. | ||
Expect(Box<dyn Any>), | ||
Equal(Box<dyn Any>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, this Event::Equal
is the reason why we need StateHandler::read
right?
I'm thinking that implementing StateHandler::read
can be a lot of effort (and some users may not even need it). Not only users have to provide a way to go from an abstract state to a concrete one (the bare minimum), now they also need to provide the opposite transformation. Moreover, now users have to implement two traits, StateHandler
and ActionHandler
.
Overall, I would be in favor of simplifying things (probably because I don't understand why we had to complicate them in the first place): I would have a single trait that only has two methods, init
and handle
. This would already provide basic functionality. Then, we should also support more complicated setups for which this is not enough, but not at the cost of complicating the rest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that what advocating for is very similar to the initial approach we had in the TestRunner
:
modelator/modelator/src/runner.rs
Lines 8 to 14 in e3e859d
pub trait TestRunner<S> { | |
/// Executes the first step against the runner. | |
fn initial_step(&mut self, step: S) -> bool; | |
/// Executes each next step against the runner. | |
fn next_step(&mut self, step: S) -> bool; | |
} |
Ha-ha, I am actually already in the process of bringing back the TestRunner :) I also came to the conclusion that it's better to keep them both -- the runner for events, and the more simple variant as you've had it originally. Now, why we need a more complex one, not all but a couple of reasons:
Just to make it a bit more example-based: compare the hand-written state-checking logic here, which is also incomplete, with the transparent transformations from concrete to abstract states for ICS02 and ICS03. I hope the difference speaks for itself. |
Another reason, which I forgot to mention, and which is purely software engineering one: the TestRunner core functionality is too "thin": it does too little to justify it's existence. And exactly because it doesn't do enough of the useful things, this means it pushes them to the users. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commits look good. I added some small comments. But IMHO some internal codes need some refactoring maybe. Not just because of Rust idioms, but to simplify the API. But that should be a future milestone. I really like the Runner
API.
outcome: String, | ||
} | ||
|
||
impl<'a, System> Default for Runner<'a, System> { | ||
impl<System: Debug> Default for EventRunner<System> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove these redundant Default
impls and ::new()
fns. Instead derive Default
maybe?
/// assert!(run_tla_steps(tla_tests_file, tla_config_file, &options, &mut system).is_ok()); | ||
/// } | ||
/// ``` | ||
pub fn run_tla_steps<P, System, Step>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make run_tla_steps
and run_tla_events
convenient struct methods. The function bodies are very small and very direct anyway. Maybe something like this,
Setup::default()
.set_options(&Options)
.config_file(P)
.tests_file(P)
.run_tla_steps_on(&mut System)
and something similar for run_tla_events
.
/// Self::is_even(step.b) | ||
/// // We define StateHandlers that are able to initialize your SUT from | ||
/// // these abstract states, as well as to read them at any point in time. | ||
/// impl StateHandler<A> for NumberSystem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use the power of AsRef
or Deref
instead?
} | ||
} | ||
|
||
impl ActionHandler<Action> for NumberSystem { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Action
is generic type and Outcome
is associated type? Action
does not have trait bounds. We can make Action
as an associated type too, no?
|
||
#[test] | ||
fn event_runner() { | ||
let events = EventStream::new() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha this is the best example !! so simple and short 👍
By the way, from what I understood, |
Also, I saw some |
Well, fundamentally everything is the same;) It's the question of convenience. The first one is about |
This PR replaces the previous Runner which was based on Steps, with a new Runner that is based on Event streams.
The intention is to create a unified interface (event streams) that can be used both for unit-testing the implementation, as well as for model-based testing.