summaryrefslogtreecommitdiff
path: root/src/test/fixtures
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/fixtures')
-rw-r--r--src/test/fixtures/channel.rs17
-rw-r--r--src/test/fixtures/event.rs83
-rw-r--r--src/test/fixtures/future.rs240
-rw-r--r--src/test/fixtures/invite.rs17
-rw-r--r--src/test/fixtures/message.rs10
-rw-r--r--src/test/fixtures/mod.rs1
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;