-
Notifications
You must be signed in to change notification settings - Fork 24
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
Implement test environments and before/after closures #30
Conversation
Cargo.toml
Outdated
name = "rspec" | ||
readme = "README.md" | ||
repository = "https://github.com/mackwic/rspec" | ||
version = "1.0.0-beta.3" |
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 would be a .4
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.
Followed by hopefully quite a bunch of betas and a final RC, yeah.
Cargo.toml
Outdated
|
||
# [dependencies.expectest] | ||
# optional = true | ||
# version = "0.8.0" |
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.
remove dead code
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.
Yeah, this would have to be bumped to 0.9.1
and the other git branch one removed.
Cargo.toml
Outdated
branch = "consistency" | ||
|
||
# [dev-dependencies] | ||
# expectest = "0.8.0" |
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.
dead code to remove
Cargo.toml
Outdated
|
||
[dev-dependencies.expectest] | ||
git = "https://github.com/regexident/expectest" | ||
branch = "consistency" |
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.
You shouldn't need that
Cargo.toml
Outdated
[dependencies.expectest] | ||
optional = true | ||
git = "https://github.com/regexident/expectest" | ||
branch = "consistency" |
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 believe your PR has been merged in the v0.9.1 ? (congrats btw)
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.
Thanks. expectest + rspec are a beautiful match, imho. 😉
src/example_report.rs
Outdated
|
||
impl Failure { | ||
pub fn new<S>(message: Option<S>) -> Self | ||
where String: From<S> |
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.
Where String implements From<S>
? Why not S : ToString
?
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.
Sure. To be honest I didn't have ToString
on my screen at all, when I wrote this. 😇
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.
To be frank, it's not the same. There is no blanket impl String : From<T: ToString>
so it you depends on this bound, it will break something.
Where do you use this ?
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.
It is used for Failure
creation from either a str
(literal) or a String
(format!(…)
).
src/example_report.rs
Outdated
} | ||
|
||
// #[derive(Clone, PartialEq, Eq, Debug)] | ||
// pub struct ExampleReport(Result<(), 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.
dead code to remove
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.
Yup.
src/formatter/simple.rs
Outdated
if result.is_err() && !self.name_stack.is_empty() { | ||
let failure_name = self.name_stack.join(" | "); | ||
self.failures.push(failure_name); | ||
fn exit_suite(&mut self, report: &ContextReport) { |
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.
too much logic here
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 have a second PR almost ready however which completely parallelizes the entire test suite execution (configurable) via rayon. It completely messes up the console log for obvious reasons. With this in mind I'd suggest to just ignore this one for now when deciding when and how to merge this PR, as I plan to rework this part (and add unit tests) in the very next PR to follow. But for the sake of clarity I'd rather not any further major refactors to this PR.
The next PR also turns the entire suite/context/example hierarchy immutable and makes the runner execution immutable (as in …(&self, …)
, plus some Arc<Mutex<…>>
on the EventHandler
s) as well. This whole immutability thing is what enables the fearless concurrency cough to begin with.
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.
As I said, I am OK with a complete big PR. Sometimes refactoring are large and it's OK.
Why don't you push it all here, and we work the rest together ?
At least please place block comments that signal where the moving code is, but I suspect a change in type might break your next PR...
src/runner.rs
Outdated
pub fn run(mut self) -> ContextReport { | ||
let suite = mem::replace(&mut self.suite, None).expect("Expected context"); | ||
panic::set_hook(Box::new(|_panic_info| { | ||
// silently swallows panics |
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 fail the test, no ?
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.
You mean the // silently swallows panics
? Oh that just prevents the panic from getting printed to the console.
It's this that does the actual fail catching.
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.
Oh ! Okay, so then maybe change the comment to: // XXX panics already catched at the test call site, don't output the trace in stdout
or something equivalent ?
( I like the convention XXX
== weird behavior if you are new to the code base)
src/runner.rs
Outdated
result = test_res.or(result); | ||
pub fn run_or_exit(self) { | ||
if self.run().failed > 0 { | ||
::std::process::exit(101); |
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 101 ?
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 basically checked what $ cargo test
returns for a failing test (via $ echo $?
). Turns out it's 101
.
https://www.reddit.com/r/rust/comments/3qdvez/guarantees_about_exit_codes/cwf46cb/
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.
Excellent ! I suggest you put this link in comment just before the process::exit
line. Something like that:
pub fn run_or_exit(self) {
if self.run().failed > 0 {
// XXX Cargo test failure returns 101. Ref: https://www.reddit.com/r/rust/comments/3qdvez/guarantees_about_exit_codes/cwf46cb/
::std::process::exit(101);
There is tremendous work here, I can see it. I it is impressing. And I quite like the look of it ! this was a quick look at the code, I need to load it in my IDE and play with it a bit to suggest modifications. This weekend I'll make better remarks. |
Awesome, thanks!
Cool. As already mentioned in one of the comments I have another PR in the works (as a follow-up for this one) that turns the whole thing into a parallel executed test Runner that walks over a strictly immutable hierarchy of suites/contexts/examples with the only mutable thing being the environment that is being passed around safely and with strict mutability isolation per context. For all this I need to completely overhaul the formatter. And while I'm doing that I also plan to add back the unit test that I had to remove from runner and formatter. |
src/runner.rs
Outdated
// silently swallows panics | ||
})); | ||
self.visit(&suite); | ||
let _ = panic::take_hook(); |
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 is not clear and missed it at first review. Add a comment // XXX unregister panic hook
?
I just pushed the WIP that makes the entire test execution immutable (with exception of the environment that's getting passed around and forked). It further more adds support for using a single runner with multiple suites, and removes lots of debris from the past, such as unused explicit lifetimes and The formatter's output breaks when run in parallel mode with Please feel free to point out any other parts that still need some work to get merged. |
src/context_member.rs
Outdated
} | ||
|
||
unsafe impl<T> Send for ContextMember<T> where T: Send {} | ||
unsafe impl<T> Sync for ContextMember<T> where T: Sync {} |
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.
these two needs XXX comment on why they are here
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.
Necessary for parallelization. Good point, will document.
src/example.rs
Outdated
} | ||
|
||
unsafe impl<T> Send for Example<T> where T: Send {} | ||
unsafe impl<T> Sync for Example<T> where T: Sync {} |
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.
same here, need an explanation in comment
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.
Necessary for parallelization. Good point, will document.
src/runner.rs
Outdated
self.handlers.broadcast(&Event::FinishedRunner(result)); | ||
result | ||
impl Runner { | ||
pub fn run<T>(self, suite: (Suite<T>, T)) -> SuiteReport |
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 don't understand the difference between run
and run_with
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.
(same for run_and_exit
and its run_and_exit_with
counterpart)
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.
One takes an explicit configuration, one uses the implicit default.
However given that test initiation has become quite cumbersome with the latest changes it doesn't make much a change, I guess. Maybe just drop the default config one and remove the _with
from the explicitly configured variant.
My plan is to at some point (i.e. once the overall architecture has settled) re-introduce some API akin to rdescribe(…)
, which would wrap all this:
let simple = rspec::formatter::Simple::new(io::stdout());
let formatter = Arc::new(Mutex::new(simple));
let configuration = Configuration::default(); // .parallel(false);
let runner = Runner::new(configuration, vec![formatter]);
#[derive(Clone, Debug)]
struct Environment {
// …
}
let environment = Environment {
// …
};
runner.run_or_exit(rspec::given("a BTreeSet", environment, |ctx| {
// …
}));
into something more palatable, like this:
#[derive(Clone, Debug, Rspec)]
struct Environment {
// …
}
rspec::run_or_exit(rspec::given("a BTreeSet", Environment {
// …
}, |ctx| {
// …
}));
Are you OK if I push a Rustfmt commit on this branch ? For the next changes modifications, I'd like to make PRs on this PR. |
Oh please, do. 🙂
Fine with me. I don't plan to do any major changes on this one, other than cleanups. I'm currently looking into the concurrent formatter thing. |
Great ! |
So, anything in particular you'd like me to fix prior to merging? |
Yes, there is a lot of work to be done in this PR before being ready to merge. Documentation, tests, examples are the obvious one. I also saw some bigger changes but I am not sure I understand how it could break the rest (like duplication). I am currently playing with it in my IDE to test changes. When it's a little thing I can put a comment. But when it's a big change, as I already tested it in my IDE, I make a change proposal via external PRs (eg: #31 and #32 ), all dependants PRs needs to be merged before merging this one. It's like a comment on your diff, but I already tested the code. The goal is to keep the debate contained in little changes and improve progressively. You can disagree on my proposal (it's fine !), or have better ideas on how to do it. When we agree, we merge, we move forward, and we have better code. 👌 Is this fine by you ? |
Good point. I will add a couple more examples and extend the existing documentation.
Cool.
👍
Absolutely! |
move Context modules in a directory
src/runner/mod.rs
Outdated
fn it_can_be_called() { | ||
// arrange | ||
let runner = Runner::default(); | ||
let block = Block::Example(Example::new(ExampleHeader::default(), |_| ExampleReport::Success)); |
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.
seems like a bug to me (or at least a regression).
This wont compile:
let block = Block::Example(Example::new(ExampleHeader::default(), |_| ()));
It should !
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.
Same for false
:
/Users/thomas/.cargo/bin/cargo test --color=always --package rspec --lib runner::tests::impl_visitor_block_for_runner -- --nocapture
Compiling rspec v1.0.0-beta.4 (file:///Users/thomas/projects/rust/rspec)
error[E0308]: mismatched types
--> src/runner/mod.rs:607:83
|
607 | let block = Block::Example(Example::new(ExampleHeader::default(), |_| false));
| ^^^^^ expected enum `report::example::ExampleReport`, found bool
|
= note: expected type `report::example::ExampleReport`
found type `bool`
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.
No, this is correct. Example::new(…)
takes a closure which handles the conversion from ()
/bool
/… to ExampleReport
. One could use F: 'static + Fn(&T) -> impl Into<ExampleReport>
instead of F: 'static + Fn(&T) -> ExampleReport
in future, but that's not available yet. Also Example::new(…)
is not intended for public use anyway. That's what ctx.then(…)
is for.
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.
okay I see
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.
However if you see a way to make this cleaner I'm all for it. :)
455da72
to
137be3e
Compare
Lemme take the opportunity to thank you for your great and detailed comments! They helped a lot! At some point it can become difficult to see the forest for the trees.
Yeah, this was one of the main goals of my overall concept.
Yes.
True. Your tests look good! 👍
😎
I'm glad you like it! I already have timing implemented (yet will have to rebase it and probably rewrite some parts of it now). And filtering/focussing shouldn't be too difficult to implement now either. |
7d44ee0
to
97cab2b
Compare
With 97cab2b That being said I plan to add realtime progress for parallel execution via the indicatif crate with a later, separate PR. What is your take on the progress of this PR? What's still missing? What should I fix? |
LGTM ! I think this PR is now in an acceptable state :) |
Great! Feel free to rebase and merge it at your discretion then. Thanks for the ride, @mackwic! 😎 |
🎉 |
Cool, but sure you want this massive PR to be squashed into a single commit? I think for a massive change of API/implementation like this it would be more than useful to have a more finely-grained log of changes, no? It would also give a better picture of the activity of this project, which with a single commit might give the false impression of still being in some kind of stagnation. |
I don't want to optimize by the number of commits. It's true, some people look at it but it's a meaningless metric. I want this PR to be squashed so that we can't checkout an intermediate state from master. |
Fair point. I personally don't care either.
Ah I see, this makes sense. |
Hi @mackwic,
consider this the first in a series of PRs aiming to improve rspec, as layed out in tracking issue #29.
It implements a shared context/example environment with mutability isolation, which aims to enable sophisticated test scenarios as well as hopefully allowing us to safely implement concurrent test execution without having to worry about environment corruption, thanks to the power of Rust's borrow checker. (as such it aims to fix issue #7)
Further more it adds support for specifying multiple
before_all
(akabefore
),after_all
(akaafter
) closures (with guarantees of preserved execution order) to be executed prior/posterior to execution of a context's context or example members. (as such it aims to fix issue #20)Last but not least it adds support for specifying multiple
before_each
,after_each
closures (with guarantees of preserved execution order) to be executed prior/posterior to execution of each of a context's context or example members. (as such it aims to fix issue #8)Please note that during the Runner's complete overhaul I removed its unit tests at some point and haven't yet found the time to add them back. So consider this a WIP, I guess?
Anyway, I'd love to hear your feedback.
Ps: I also upped the Rust version on TravisCI from 1.9 to 1.18, for the sake of being able to use
pub(crate)
and other neat things. I hope that's fine with you?Pps: Also expect the next PRs to be more finely grained. 😉