-
Notifications
You must be signed in to change notification settings - Fork 636
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
Initial testing utilities #1176
Conversation
d7a989c
to
fd4ac19
Compare
I'd prefer the name |
futures-test/src/task/context.rs
Outdated
use crate::task::{executor, wake}; | ||
use futures_core::{ | ||
future::Future, | ||
task::{local_waker, Context, Executor, Poll, Wake}, |
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.
- For consistency with the rest of our codebase the non-nested style would be better
Context
should not be imported directly. Instead it should always be referred to astask::Context
. This decision was made in the 0.2 days. I wasn't around back then, but I think it's because the word "context" is so nondescript.
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 think in this case it's better to use a bare Context
, this is explicitly a helper for managing that exact type. I think I'll change what's used in the docs though.
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.
In terms of non-nested style, what about
use crate::task::{executor, wake};
use std::{marker::Unpin, mem::PinMut, sync::Arc};
I assume that the first is definitely ok, but what about the second? What's the actual limit on nesting that you want to enforce?
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'd prefer to expand the second one as well. I'd like to align our code style with the code style of the standard library and compiler. I browsed the code just now and it seems that they consistently expand such things. Also, our existing code does so as well:
use core::marker::Unpin;
use core::mem::PinMut;
can be found many many times in futures-rs 🙂
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.
Alright, so nesting the final segment in a path is ok, but no more than that.
(I assume the main reason that the compiler expands everything is that being able to nest more than a single segment is relatively recent, only since March)
I think the symlinked license files are missing |
Maybe |
futures-test/src/task/context.rs
Outdated
/// | ||
/// let mut future = PinBox::new(async { | ||
/// 5 | ||
/// }); |
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 suggest
let mut future = async { 5 };
pin_mut!(future);
I think we should prefer the pin_mut!
macro in examples because users should reach for it first to avoid the heap allocation
Not for this case, since you can't use turbofish when calling the function to fill in the 2 required parameters. |
😭 features and dev-dependencies seem impossible to work with (I knew there were going to be issues with using |
And that's the entirety of |
futures-test/src/assert.rs
Outdated
/// #![feature(async_await, futures_api, pin)] | ||
/// | ||
/// #[macro_use] | ||
/// extern crate futures_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.
In other doc example we write this as:
#![feature(async_await, futures_api, pin)]
#[macro_use] extern crate futures_test;
futures-test/src/assert.rs
Outdated
/// extern crate futures_test; | ||
/// | ||
/// #[macro_use] | ||
/// extern crate futures; |
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 think this isn't required. We rely on the Rust 2018 module system and there's also no macro from the futures crate in use
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.
pin_mut!
is coming from futures
still, this can change to pin_utils
or whatever once that's 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.
ah right ^^'
futures-test/src/assert.rs
Outdated
/// #[macro_use] | ||
/// extern crate futures; | ||
/// | ||
/// # fn main() { |
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.
Main function doesn't need to be explicit
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 does when using #[macro_use]
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.
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.
Ah, but only when #[macro_use]
is on the same line as extern crate
...
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 ^^' interesting limitation
/// let mut stream = stream::once((async { 5 }).delay()); | ||
/// pin_mut!(stream); | ||
/// | ||
/// assert_stream_pending!(stream); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
I'm not so sure about the
Here is a suggestion for an alternative API: let cx = Context::new(&NoopWaker, &NoopExecutor);
let cx = noop_context(); // Short hand
let cx = Context::new(&NoopWaker, &PanicExecutor);
let cx = no_spawn_context(); // Short hand
let wake_counter = WakeCounter::new();
let cx = noop_context().with_waker(wake_counter.local_waker());
...
wake_counter.count(); // usize
let spawn_recorder = SpawnRecorder::new();
let cx = noop_context().with_executor(spawn_recorder.executor());
...
spawn_recorder.futures(); // Vec<FutureObj<'static, ()>>, later Vec<Box<dyn Future<Output = ()> + Send + 'static>> Everything except |
futures-test/src/lib.rs
Outdated
#[macro_use] | ||
extern crate futures_util; | ||
|
||
#[macro_export] |
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.
#[macro_export]
above modules is not required
The closest I can work out how to do is: let wake = wake::panic();
let mut executor = executor::Panic;
let cx = Context::new(&wake, &mut executor); the waker is a That's the main reason I wanted a type like I don't think being |
And how about introducing: with_noop_context(|cx| { ... }); // waker and executor are noops
with_no_spawn_context(|cx| { ... }); // waker is a noop, executor panics on spawn attempts
let cx = test_cx.context(); // For use with NLL |
My issue was that using assert_eq!(Poll::Pending, test_cx.with(|cx| abortable_rx.poll_unpin(cx))); I think that would be helped a lot with the assert_eq!(Poll::Pending, abortable_rx.poll_unpin(&mut test_cx.context()));
👍
Yep, I realized that a I'm wondering if there's some lifetime finagling that would make it possible to have let cx = noop_context();
let cx = panic_context();
let cx = no_spawn_context(); Since those wakers/executors don't actually do anything having multiple mutable references to a static executor should be safe (but I'm going to have to investigate how to make it sound...), |
@Nemo157 If you could make future.poll(noop_context()); Edit: Depends of course on the return type (if it's a reference or a value) There could also be two functions: One that returns a ref, one that returns a value |
2363782
to
5a16b71
Compare
futures-test/src/assert.rs
Outdated
let cx = &mut $crate::task::no_spawn_context(); | ||
match $crate::futures_core_reexport::stream::Stream::poll_next( | ||
stream, cx, | ||
) { |
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 recommend using
if poll.is_ready() {}
futures-test/src/task.rs
Outdated
@@ -1,10 +1,19 @@ | |||
//! Task related utilities. | |||
//! | |||
//! For the majority of usecases you can use the functions exported below |
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.
In the majority of use cases
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.
and "functions" is mentioned twice in this sentence
/// | ||
/// let mut cx = panic_context(); | ||
/// let mut executor = executor::Noop::new(); | ||
/// let cx = &mut cx.with_executor(&mut executor); |
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.
Two issues with Context
's current APIs here:
The first (that's repeated in most of the examples) is that with_executor
and with_waker
take &mut self
, that's useful for being able to create a sub-context with a different executor inside a poll
function, but it is what makes the first of these three lines necessary. If with_executor
instead took self
by value we should be able to merge the first and third lines into
let cx = &mut panic_context().with_executor(&mut executor);
Second, with_executor
requires E: Sized
so we can't use the unsized Noop::executor_mut()
, if we removed this bound then we can merge the second and third lines into
let cx = &mut cx.with_executor(executor::Noop::executor_mut());
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 way to allow taking the context by value while still being able to create a sub-context inside poll
could be to add
impl<'a> Context<'a> {
pub fn reborrow<'b>(&'b mut self) -> Context<'b> {
Context {
local_waker: self.local_waker,
executor: self.executor,
}
}
Then the WithExecutor
combinator would just have to change
diff --git futures-util/src/future/with_executor.rs futures-util/src/future/with_executor.rs
index 72b7322e8..f283b7b5a 100644
--- futures-util/src/future/with_executor.rs
+++ futures-util/src/future/with_executor.rs
@@ -32,6 +32,6 @@ impl<Fut, E> Future for WithExecutor<Fut, E>
let this = unsafe { PinMut::get_mut_unchecked(self) };
let fut = unsafe { PinMut::new_unchecked(&mut this.future) };
let exec = &mut this.executor;
- fut.poll(&mut cx.with_executor(exec))
+ fut.poll(&mut cx.reborrow().with_executor(exec))
}
}
futures-test/src/task.rs
Outdated
//! to use in your tests. | ||
//! | ||
//! For more complex test cases you can take a `Context` from one of these | ||
//! functions then use the |
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.
*and * then
futures-test/src/assert.rs
Outdated
/// ``` | ||
/// #![feature(async_await, futures_api, pin)] | ||
/// | ||
/// use futures::stream; |
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.
Our code examples usually have no free line between #![feature]
and use
statements
futures-test/src/task/executor.rs
Outdated
@@ -0,0 +1,8 @@ | |||
//! Implementations of [`Executor`][futures_core::task::Executor] with various |
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.
Maybe rename this to executor/mod.rs
instead.
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, I've been getting quite annoyed at the new file scheme breaking tab completion, so will switch this back.
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.
They changed it because the old system can lead to loads of mod.rs
file tabs in an editor. However, that's IMO fine if these files are just lightweight files that just reexport the right things from their child modules. So, I continue to like mod.rs
files 🙂
futures-test/src/task/executor.rs
Outdated
mod panic; | ||
mod record; | ||
|
||
pub use self::{noop::Noop, panic::Panic, record::Record}; |
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 prefer the
mod noop;
use self::noop::Noop;
mod panic;
use self::panic::Panic;
// --snip--
style
23dd60b
to
0473cda
Compare
0473cda
to
8a5f1a2
Compare
@Nemo157 I changed various things. Can you have a look? |
I'm not a massive fan of how the generics worked out here, especially around
with_context
needing two_, _
at the end... any suggestion on a more ergonomic API?CC #1169