Skip to content

Commit

Permalink
Merge pull request #45 from audunhalland/async-io
Browse files Browse the repository at this point in the history
feat: Mock async read/write traits from tokio and futures
  • Loading branch information
audunhalland committed Feb 28, 2024
2 parents 365796c + d5106c3 commit 0e58b97
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 120 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The function passed to `applies()` API can mutate all its inputs freely.
The downside to this new mechanism is that its return type can't be generic (i.e. `Ret: IntoResponse`).
Flexible return types are still supported though, but now a response has to be created explicitly calling `unimock::respond(return_value)`.
### Added
- Mocks for `tokio-3` and `futures-0-3` async read/write traits
### Fixed
- Fix `matching!` against references to number literals ([#42](https://github.com/audunhalland/unimock/pull/42))

Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ std = ["once_cell/std"]
spin-lock = ["dep:spin"]
mock-core = []
mock-std = ["std", "mock-core"]
mock-futures-0-3 = ["std", "dep:futures-0-3"]
mock-tokio-1 = ["std", "dep:tokio-1"]
nightly-tests = []
unstable-doc-cfg = []
critical-section = ["once_cell/critical-section"]
Expand All @@ -29,11 +31,13 @@ once_cell = { version = "1.17", default-features = false }
polonius-the-crab = "0.3"
pretty_assertions = { version = "1.3", optional = true }
spin = { version = "0.9.8", optional = true }
futures-0-3 = { package = "futures", version = "0.3", optional = true }
tokio-1 = { package = "tokio", version = "1", default-features = false, optional = true }

[dev-dependencies]
async-trait = "0.1"
critical-section = { version = "1.1.2", features = ["std"] }
tokio = { version = "1", features = ["full"] }
tokio-1 = { package = "tokio", version = "1", features = ["full"] }
rustversion = "1"

[lib]
Expand Down
65 changes: 65 additions & 0 deletions src/mock/futures_0_3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Mock APIs for `futures 0.3` traits

/// Mock APIs for `futures::io` traits
#[cfg(feature = "mock-futures-0-3")]
pub mod io {
use core::pin::Pin;
use core::task::{Context, Poll};
use std::io::{IoSlice, IoSliceMut};

use futures_0_3::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, Error, SeekFrom};

use crate::unimock;

#[unimock(prefix=crate, api=AsyncBufReadMock, mirror=AsyncBufRead)]
pub trait AsyncBufRead {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<&[u8], Error>>;
fn consume(self: Pin<&mut Self>, amt: usize);
}

#[unimock(prefix=crate, api=AsyncReadMock, mirror=AsyncRead)]
pub trait AsyncRead {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<Result<usize, Error>>;

// Provided method
fn poll_read_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &mut [IoSliceMut<'_>],
) -> Poll<Result<usize, Error>> {
}
}

#[unimock(prefix=crate, api=AsyncSeekMock, mirror=AsyncSeek)]
pub trait AsyncSeek {
fn poll_seek(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<Result<u64, Error>>;
}

#[unimock(prefix=crate, api=AsyncWriteMock, mirror=AsyncWrite)]
pub trait AsyncWrite {
// Required methods
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, Error>>;
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>>;
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>>;

// Provided method
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, Error>> {
}
}
}
2 changes: 2 additions & 0 deletions src/mock/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub mod core;
pub mod futures_0_3;
pub mod std;
pub mod tokio_1;
56 changes: 56 additions & 0 deletions src/mock/tokio_1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Mock APIs for `tokio` traits

/// Mock APIs for `tokio::io` traits
#[cfg(feature = "mock-tokio-1")]
pub mod io {
use core::pin::Pin;
use core::task::{Context, Poll};
use std::io::IoSlice;

use tokio_1::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf, Result, SeekFrom};

use crate::unimock;

#[unimock(prefix=crate, api=AsyncBufReadMock, mirror=AsyncBufRead)]
pub trait AsyncBufRead {
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<&[u8]>>;
fn consume(self: Pin<&mut Self>, amt: usize);
}

#[unimock(prefix=crate, api=AsyncReadMock, mirror=AsyncRead)]
pub trait AsyncRead {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<()>>;
}

#[unimock(prefix=crate, api=AsyncSeekMock, mirror=AsyncSeek)]
pub trait AsyncSeek {
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()>;
fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<u64>>;
}

#[unimock(prefix=crate, api=AsyncWriteMock, mirror=AsyncWrite)]
pub trait AsyncWrite {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize>>;

fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>>;

fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>>;

fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize>> {
}

fn is_write_vectored(&self) -> bool {}
}
}
50 changes: 30 additions & 20 deletions tests/it/async_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@
mod r#async {
use unimock::*;

use crate::AsyncTest;

#[unimock(api = TraitMock)]
trait Trait {
async fn a(&self, arg: i32) -> i32;
async fn b(&self) -> &i32;
async fn c(&self) -> Option<&i32>;
}

#[tokio::test]
async fn test_it() {
let deps = Unimock::new((
TraitMock::a.next_call(matching!(_)).returns(42),
TraitMock::b.next_call(matching!()).returns(42),
TraitMock::c.next_call(matching!()).returns(Some(42)),
));

assert_eq!(42, deps.a(5).await);
assert_eq!(&42, deps.b().await);
assert_eq!(Some(&42), deps.c().await);
#[test]
fn test_it() {
async {
let deps = Unimock::new((
TraitMock::a.next_call(matching!(_)).returns(42),
TraitMock::b.next_call(matching!()).returns(42),
TraitMock::c.next_call(matching!()).returns(Some(42)),
));

assert_eq!(42, deps.a(5).await);
assert_eq!(&42, deps.b().await);
assert_eq!(Some(&42), deps.c().await);
}
.test()
}
}

Expand All @@ -29,6 +34,8 @@ mod rpit_future {

use unimock::*;

use crate::AsyncTest;

#[unimock(api = RpitFutureMock)]
trait RpitFuture {
fn m1(&self) -> impl Future<Output = i32>;
Expand All @@ -37,14 +44,17 @@ mod rpit_future {
fn m4(&self) -> impl core::future::Future<Output = i32> + Send;
}

#[tokio::test]
async fn rpit() {
let u = Unimock::new((
RpitFutureMock::m1.next_call(matching!()).returns(1337),
RpitFutureMock::m2.next_call(matching!(42)).returns(1338),
));

assert_eq!(u.m1().await, 1337);
assert_eq!(u.m2(42).await, 1338);
#[test]
fn rpit() {
async {
let u = Unimock::new((
RpitFutureMock::m1.next_call(matching!()).returns(1337),
RpitFutureMock::m2.next_call(matching!(42)).returns(1338),
));

assert_eq!(u.m1().await, 1337);
assert_eq!(u.m2(42).await, 1338);
}
.test()
}
}
Loading

0 comments on commit 0e58b97

Please sign in to comment.