diff options
Diffstat (limited to 'src/test/fixtures')
| -rw-r--r-- | src/test/fixtures/channel.rs | 17 | ||||
| -rw-r--r-- | src/test/fixtures/event.rs | 83 | ||||
| -rw-r--r-- | src/test/fixtures/future.rs | 240 | ||||
| -rw-r--r-- | src/test/fixtures/invite.rs | 17 | ||||
| -rw-r--r-- | src/test/fixtures/message.rs | 10 | ||||
| -rw-r--r-- | src/test/fixtures/mod.rs | 1 |
6 files changed, 298 insertions, 70 deletions
diff --git a/src/test/fixtures/channel.rs b/src/test/fixtures/channel.rs index 8cb38ae..0c6480b 100644 --- a/src/test/fixtures/channel.rs +++ b/src/test/fixtures/channel.rs @@ -1,5 +1,3 @@ -use std::future; - use faker_rand::{ en_us::{addresses::CityName, names::FullName}, faker_impl_from_templates, @@ -10,7 +8,6 @@ use crate::{ app::App, channel::{self, Channel}, clock::RequestedAt, - event::Event, name::Name, }; @@ -31,20 +28,6 @@ faker_impl_from_templates! { NameTemplate; "{} {}", CityName, FullName; } -pub fn events(event: Event) -> future::Ready<Option<channel::Event>> { - future::ready(match event { - Event::Channel(channel) => Some(channel), - _ => None, - }) -} - -pub fn created(event: channel::Event) -> future::Ready<Option<channel::event::Created>> { - future::ready(match event { - channel::Event::Created(event) => Some(event), - channel::Event::Deleted(_) => None, - }) -} - pub fn fictitious() -> channel::Id { channel::Id::generate() } diff --git a/src/test/fixtures/event.rs b/src/test/fixtures/event.rs index fa4fbc0..de02d4d 100644 --- a/src/test/fixtures/event.rs +++ b/src/test/fixtures/event.rs @@ -1,8 +1,79 @@ -use crate::message::{Event, Message}; +use std::future::{self, Ready}; -pub fn message_sent(event: &Event, message: &Message) -> bool { - matches!( - &event, - Event::Sent(event) if message == &event.into() - ) +use crate::event::Event; + +pub fn channel(event: Event) -> Ready<Option<channel::Event>> { + future::ready(match event { + Event::Channel(channel) => Some(channel), + _ => None, + }) +} + +pub fn message(event: Event) -> Ready<Option<message::Event>> { + future::ready(match event { + Event::Message(event) => Some(event), + _ => None, + }) +} + +pub fn login(event: Event) -> Ready<Option<login::Event>> { + future::ready(match event { + Event::Login(event) => Some(event), + _ => None, + }) +} + +pub mod channel { + use std::future::{self, Ready}; + + use crate::channel::event; + pub use crate::channel::Event; + + pub fn created(event: Event) -> Ready<Option<event::Created>> { + future::ready(match event { + Event::Created(event) => Some(event), + Event::Deleted(_) => None, + }) + } + + pub fn deleted(event: Event) -> Ready<Option<event::Deleted>> { + future::ready(match event { + Event::Deleted(event) => Some(event), + Event::Created(_) => None, + }) + } +} + +pub mod message { + use std::future::{self, Ready}; + + use crate::message::event; + pub use crate::message::Event; + + pub fn sent(event: Event) -> Ready<Option<event::Sent>> { + future::ready(match event { + Event::Sent(event) => Some(event), + Event::Deleted(_) => None, + }) + } + + pub fn deleted(event: Event) -> future::Ready<Option<event::Deleted>> { + future::ready(match event { + Event::Deleted(event) => Some(event), + Event::Sent(_) => None, + }) + } +} + +pub mod login { + use std::future::{self, Ready}; + + use crate::login::event; + pub use crate::login::Event; + + pub fn created(event: Event) -> Ready<Option<event::Created>> { + future::ready(match event { + Event::Created(event) => Some(event), + }) + } } diff --git a/src/test/fixtures/future.rs b/src/test/fixtures/future.rs index bbdc9f8..2f810a3 100644 --- a/src/test/fixtures/future.rs +++ b/src/test/fixtures/future.rs @@ -1,55 +1,221 @@ -use std::{future::IntoFuture, time::Duration}; +use std::{future::Future, pin::Pin, task}; -use futures::{stream, Stream}; -use tokio::time::timeout; +use futures::stream; -async fn immediately<F>(fut: F) -> F::Output +// Combinators for futures that prevent waits, even when the underlying future +// would block. +// +// These are only useful for futures with no bound on how long they may wait, +// and this trait is only implemented on futures that are likely to have that +// characteristic. Trying to apply this to futures that already have some +// bounded wait time may make tests fail inappropriately and can hide other +// logic errors. +pub trait Expect: Sized { + // The returned future expects the underlying future to be ready immediately, + // and panics with the provided message if it is not. + // + // For stream operations, can be used to assert immediate completion. + fn expect_ready(self, message: &str) -> Ready<Self> + where + Self: Future; + + // The returned future expects the underlying future _not_ to be ready, and + // panics if it is. This is usually a useful proxy for "I expect this to never + // arrive" or "to not be here yet." The future is transformed to return `()`, + // since the underlying future can never provide a value. + // + // For stream operations, can be used to assert that completion hasn't happened + // yet. + fn expect_wait(self, message: &str) -> Wait<Self> + where + Self: Future; + + // The returned future expects the underlying future to resolve immediately, to + // a `Some` value. If it resolves to `None` or is not ready, it panics. The + // future is transformed to return the inner value from the `Some` case, like + // [`Option::expect`]. + // + // For stream operations, can be used to assert that the stream has at least one + // message. + fn expect_some<T>(self, message: &str) -> Some<Self> + where + Self: Future<Output = Option<T>>; + + // The returned future expects the underlying future to resolve immediately, to + // a `None` value. If it resolves to `Some(_)`, or is not ready, it panics. The + // future is transformed to return `()`, since the underlying future's value is + // fixed. + // + // For stream operations, can be used to assert that the stream has ended. + fn expect_none<T>(self, message: &str) -> None<Self> + where + Self: Future<Output = Option<T>>; +} + +impl<'a, St> Expect for stream::Next<'a, St> { + fn expect_ready(self, message: &str) -> Ready<Self> { + Ready { + future: self, + message, + } + } + + fn expect_wait(self, message: &str) -> Wait<Self> { + Wait { + future: self, + message, + } + } + + fn expect_some<T>(self, message: &str) -> Some<Self> + where + Self: Future<Output = Option<T>>, + { + Some { + future: self, + message, + } + } + + fn expect_none<T>(self, message: &str) -> None<Self> + where + Self: Future<Output = Option<T>>, + { + None { + future: self, + message, + } + } +} + +impl<St, C> Expect for stream::Collect<St, C> { + fn expect_ready(self, message: &str) -> Ready<Self> { + Ready { + future: self, + message, + } + } + + fn expect_wait(self, message: &str) -> Wait<Self> { + Wait { + future: self, + message, + } + } + + fn expect_some<T>(self, message: &str) -> Some<Self> + where + Self: Future<Output = Option<T>>, + { + Some { + future: self, + message, + } + } + + fn expect_none<T>(self, message: &str) -> None<Self> + where + Self: Future<Output = Option<T>>, + { + None { + future: self, + message, + } + } +} + +#[pin_project::pin_project] +pub struct Ready<'m, F> { + #[pin] + future: F, + message: &'m str, +} + +impl<'m, F> Future for Ready<'m, F> where - F: IntoFuture, + F: Future + std::fmt::Debug, { - // I haven't been particularly rigorous here. Zero delay _seems to work_, - // but this can be set higher; it makes tests that fail to meet the - // "immediate" expectation take longer, but gives slow tests time to - // succeed, as well. - let duration = Duration::from_nanos(0); - timeout(duration, fut) - .await - .expect("expected result immediately") -} - -// This is only intended for streams, since their `next()`, `collect()`, and -// so on can all block indefinitely on an empty stream. There's no need to -// force immediacy on futures that "can't" block forever, and it can hide logic -// errors if you do that. -// -// The impls below _could_ be replaced with a blanket impl for all future -// types, otherwise. The choice to restrict impls to stream futures is -// deliberate. -pub trait Immediately { - type Output; + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> { + let this = self.project(); + + if let task::Poll::Ready(value) = this.future.poll(cx) { + task::Poll::Ready(value) + } else { + panic!("{}", this.message); + } + } +} + +#[pin_project::pin_project] +pub struct Wait<'m, F> { + #[pin] + future: F, + message: &'m str, +} - async fn immediately(self) -> Self::Output; +impl<'m, F> Future for Wait<'m, F> +where + F: Future + std::fmt::Debug, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> { + let this = self.project(); + + if this.future.poll(cx).is_pending() { + task::Poll::Ready(()) + } else { + panic!("{}", this.message); + } + } } -impl<'a, St> Immediately for stream::Next<'a, St> +#[pin_project::pin_project] +pub struct Some<'m, F> { + #[pin] + future: F, + message: &'m str, +} + +impl<'m, F, T> Future for Some<'m, F> where - St: Stream + Unpin + ?Sized, + F: Future<Output = Option<T>> + std::fmt::Debug, { - type Output = Option<<St as Stream>::Item>; + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> { + let this = self.project(); - async fn immediately(self) -> Self::Output { - immediately(self).await + if let task::Poll::Ready(Option::Some(value)) = this.future.poll(cx) { + task::Poll::Ready(value) + } else { + panic!("{}", this.message) + } } } -impl<St, C> Immediately for stream::Collect<St, C> +#[pin_project::pin_project] +pub struct None<'m, F> { + #[pin] + future: F, + message: &'m str, +} + +impl<'m, F, T> Future for None<'m, F> where - St: Stream, - C: Default + Extend<<St as Stream>::Item>, + F: Future<Output = Option<T>> + std::fmt::Debug, { - type Output = C; + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> { + let this = self.project(); - async fn immediately(self) -> Self::Output { - immediately(self).await + if let task::Poll::Ready(Option::None) = this.future.poll(cx) { + task::Poll::Ready(()) + } else { + panic!("{}", this.message) + } } } diff --git a/src/test/fixtures/invite.rs b/src/test/fixtures/invite.rs new file mode 100644 index 0000000..654d1b4 --- /dev/null +++ b/src/test/fixtures/invite.rs @@ -0,0 +1,17 @@ +use crate::{ + app::App, + clock::DateTime, + invite::{self, Invite}, + login::Login, +}; + +pub async fn issue(app: &App, issuer: &Login, issued_at: &DateTime) -> Invite { + app.invites() + .issue(issuer, issued_at) + .await + .expect("issuing invites never fails") +} + +pub fn fictitious() -> invite::Id { + invite::Id::generate() +} diff --git a/src/test/fixtures/message.rs b/src/test/fixtures/message.rs index 3aebdd9..d3b4719 100644 --- a/src/test/fixtures/message.rs +++ b/src/test/fixtures/message.rs @@ -1,12 +1,9 @@ -use std::future; - use faker_rand::lorem::Paragraphs; use crate::{ app::App, channel::Channel, clock::RequestedAt, - event::Event, login::Login, message::{self, Body, Message}, }; @@ -24,13 +21,6 @@ pub fn propose() -> Body { rand::random::<Paragraphs>().to_string().into() } -pub fn events(event: Event) -> future::Ready<Option<message::Event>> { - future::ready(match event { - Event::Message(event) => Some(event), - _ => None, - }) -} - pub fn fictitious() -> message::Id { message::Id::generate() } diff --git a/src/test/fixtures/mod.rs b/src/test/fixtures/mod.rs index 9111811..2b7b6af 100644 --- a/src/test/fixtures/mod.rs +++ b/src/test/fixtures/mod.rs @@ -7,6 +7,7 @@ pub mod cookie; pub mod event; pub mod future; pub mod identity; +pub mod invite; pub mod login; pub mod message; |
