use std::{error, fmt, io};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::sync::oneshot;
use tokio::task::{AbortHandle, JoinError, JoinSet};
#[derive(Debug)]
pub enum CopyError {
Read(io::Error),
Write(io::Error),
}
impl fmt::Display for CopyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Read(e) => write!(f, "Read error: {e}"),
Self::Write(e) => write!(f, "Write error: {e}"),
}
}
}
impl error::Error for CopyError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Read(e) => Some(e),
Self::Write(e) => Some(e),
}
}
}
impl CopyError {
fn map_err_mode(self, mode: TransferMode) -> TransferError<Self> {
match (mode, &self) {
(TransferMode::NetworkToApplication, Self::Read(_))
| (TransferMode::ApplicationToNetwork, Self::Write(_)) => TransferError::Network(self),
(TransferMode::NetworkToApplication, Self::Write(_))
| (TransferMode::ApplicationToNetwork, Self::Read(_)) => {
TransferError::Application(self)
}
}
}
}
impl From<CopyError> for io::Error {
fn from(value: CopyError) -> Self {
match value {
CopyError::Read(error) => error,
CopyError::Write(error) => error,
}
}
}
pub async fn copy<R, W>(
reader: R,
writer: W,
error_on_eof: Option<io::Error>,
) -> Result<usize, CopyError>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
copy_inner(reader, writer, error_on_eof, false).await
}
pub async fn yielding_copy<R, W>(
reader: R,
writer: W,
error_on_eof: Option<io::Error>,
) -> Result<usize, CopyError>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
copy_inner(reader, writer, error_on_eof, true).await
}
async fn copy_inner<R, W>(
mut reader: R,
mut writer: W,
error_on_eof: Option<io::Error>,
yielding: bool,
) -> Result<usize, CopyError>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let mut buf = [0u8; 8192];
let mut total = 0;
loop {
let n = reader.read(&mut buf).await.map_err(CopyError::Read)?;
if n == 0 {
break;
}
writer
.write_all(&buf[..n])
.await
.map_err(CopyError::Write)?;
total += n;
if yielding {
writer.flush().await.map_err(CopyError::Write)?;
tokio::task::yield_now().await;
}
}
if total > 0 {
writer.flush().await.map_err(CopyError::Write)?;
}
error_on_eof.map_or(Ok(total), |e| Err(CopyError::Read(e)))
}
#[derive(Debug)]
pub enum TransferError<E> {
Network(E),
Application(E),
Internal(E),
Unfinished(JoinError),
}
impl<E: fmt::Display> fmt::Display for TransferError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Network(e) => write!(f, "Network-side error: {e}"),
Self::Application(e) => write!(f, "Application-side error: {e}"),
Self::Internal(e) => write!(f, "Internal error: {e}"),
Self::Unfinished(e) => write!(f, "Transfer unfinished due to task error: {e}"),
}
}
}
impl<E: error::Error + 'static> error::Error for TransferError<E> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Network(e) => Some(e),
Self::Application(e) => Some(e),
Self::Internal(e) => Some(e),
Self::Unfinished(e) => Some(e),
}
}
}
impl<E> From<TransferError<E>> for io::Error
where
io::Error: From<E>,
{
fn from(value: TransferError<E>) -> Self {
match value {
TransferError::Network(error) => error.into(),
TransferError::Application(error) => error.into(),
TransferError::Internal(error) => error.into(),
TransferError::Unfinished(error) => error.into(),
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TransferMode {
NetworkToApplication,
ApplicationToNetwork,
}
pub async fn transfer<R, W>(
mut reader: R,
mut writer: W,
mode: TransferMode,
error_on_eof: Option<io::Error>,
) -> Result<usize, TransferError<CopyError>>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
copy(&mut reader, &mut writer, error_on_eof)
.await
.map_err(|e| e.map_err_mode(mode))
}
pub async fn copy_join<AppR, AppW, NetR, NetW>(
mut app_reader: AppR,
mut app_writer: AppW,
mut net_reader: NetR,
mut net_writer: NetW,
error_on_app_eof: Option<io::Error>,
error_on_net_eof: Option<io::Error>,
) -> (
Result<usize, TransferError<CopyError>>,
Result<usize, TransferError<CopyError>>,
)
where
AppR: AsyncRead + Unpin,
AppW: AsyncWrite + Unpin,
NetR: AsyncRead + Unpin,
NetW: AsyncWrite + Unpin,
{
tokio::join!(
transfer(
&mut net_reader,
&mut app_writer,
TransferMode::NetworkToApplication,
error_on_net_eof
),
transfer(
&mut app_reader,
&mut net_writer,
TransferMode::ApplicationToNetwork,
error_on_app_eof
)
)
}
pub async fn copy_try_join<AppR, AppW, NetR, NetW>(
mut app_reader: AppR,
mut app_writer: AppW,
mut net_reader: NetR,
mut net_writer: NetW,
error_on_app_eof: Option<io::Error>,
error_on_net_eof: Option<io::Error>,
) -> Result<(usize, usize), TransferError<CopyError>>
where
AppR: AsyncRead + Unpin,
AppW: AsyncWrite + Unpin,
NetR: AsyncRead + Unpin,
NetW: AsyncWrite + Unpin,
{
tokio::try_join!(
transfer(
&mut net_reader,
&mut app_writer,
TransferMode::NetworkToApplication,
error_on_net_eof
),
transfer(
&mut app_reader,
&mut net_writer,
TransferMode::ApplicationToNetwork,
error_on_app_eof
)
)
}
pub async fn transfer_join<T, U>(
mut app_to_net: T,
mut net_to_app: U,
) -> (
Result<usize, TransferError<T::Error>>,
Result<usize, TransferError<T::Error>>,
)
where
T: Transfer + Unpin,
U: Transfer<Error = T::Error> + Unpin,
{
tokio::join!(net_to_app.transfer(), app_to_net.transfer())
}
pub async fn transfer_try_join<T, U>(
mut app_to_net: T,
mut net_to_app: U,
) -> Result<(usize, usize), TransferError<T::Error>>
where
T: Transfer + Unpin,
U: Transfer<Error = T::Error> + Unpin,
{
tokio::try_join!(net_to_app.transfer(), app_to_net.transfer())
}
pub async fn copy_spawn_join<AppR, AppW, NetR, NetW>(
app_reader: AppR,
app_writer: AppW,
net_reader: NetR,
net_writer: NetW,
error_on_app_eof: Option<io::Error>,
error_on_net_eof: Option<io::Error>,
) -> (
(AppR, AppW, NetR, NetW),
Result<usize, TransferError<CopyError>>,
Result<usize, TransferError<CopyError>>,
)
where
AppR: AsyncRead + Unpin + Send + 'static,
AppW: AsyncWrite + Unpin + Send + 'static,
NetR: AsyncRead + Unpin + Send + 'static,
NetW: AsyncWrite + Unpin + Send + 'static,
{
let (app_to_net, net_to_app) = copiers_from_io(
app_reader,
app_writer,
net_reader,
net_writer,
error_on_app_eof,
error_on_net_eof,
);
let (io_objects, net_to_app_res, app_to_net_res) =
transfer_spawn_join(app_to_net, net_to_app).await;
(io_from_copiers(io_objects), net_to_app_res, app_to_net_res)
}
pub async fn copy_spawn_try_join<AppR, AppW, NetR, NetW>(
app_reader: AppR,
app_writer: AppW,
net_reader: NetR,
net_writer: NetW,
error_on_app_eof: Option<io::Error>,
error_on_net_eof: Option<io::Error>,
) -> (
(AppR, AppW, NetR, NetW),
Result<(usize, usize), TransferError<CopyError>>,
)
where
AppR: AsyncRead + Unpin + Send + 'static,
AppW: AsyncWrite + Unpin + Send + 'static,
NetR: AsyncRead + Unpin + Send + 'static,
NetW: AsyncWrite + Unpin + Send + 'static,
{
let (app_to_net, net_to_app) = copiers_from_io(
app_reader,
app_writer,
net_reader,
net_writer,
error_on_app_eof,
error_on_net_eof,
);
let (io_objects, result) = transfer_spawn_try_join(app_to_net, net_to_app).await;
(io_from_copiers(io_objects), result)
}
pub async fn transfer_spawn_join<T, U>(
app_to_net: T,
net_to_app: U,
) -> (
(T, U),
Result<usize, TransferError<T::Error>>,
Result<usize, TransferError<T::Error>>,
)
where
T: Transfer + Send + 'static,
<T as Transfer>::Error: Send,
U: Transfer<Error = T::Error> + Send + 'static,
{
let (io_objects, first, second) =
transfer_spawn_join_inner(app_to_net, net_to_app, false).await;
let (mode_first, res_first) = first;
let (_, res_second) = second;
if mode_first == TransferMode::NetworkToApplication {
(io_objects, res_first, res_second)
} else {
(io_objects, res_second, res_first)
}
}
pub async fn transfer_spawn_try_join<T, U>(
app_to_net: T,
net_to_app: U,
) -> ((T, U), Result<(usize, usize), TransferError<T::Error>>)
where
T: Transfer + Send + 'static,
<T as Transfer>::Error: Send,
U: Transfer<Error = T::Error> + Send + 'static,
{
let (io_objects, first, second) = transfer_spawn_join_inner(app_to_net, net_to_app, true).await;
let (mode_first, res_first) = first;
let (_, res_second) = second;
let n_first = match res_first {
Ok(n) => n,
Err(e) => return (io_objects, Err(e)),
};
let n_second = match res_second {
Ok(n) => n,
Err(e) => return (io_objects, Err(e)),
};
if mode_first == TransferMode::NetworkToApplication {
(io_objects, Ok((n_first, n_second)))
} else {
(io_objects, Ok((n_second, n_first)))
}
}
pub trait Transfer {
type Error;
fn transfer(
&mut self,
) -> impl Future<Output = Result<usize, TransferError<Self::Error>>> + Send;
}
struct Copier<R, W>
where
R: AsyncRead + Unpin + Send,
W: AsyncWrite + Unpin + Send,
{
reader: R,
writer: W,
mode: TransferMode,
error_on_eof: Option<io::Error>,
}
impl<R, W> Copier<R, W>
where
R: AsyncRead + Unpin + Send,
W: AsyncWrite + Unpin + Send,
{
pub fn new(reader: R, writer: W, mode: TransferMode, error_on_eof: Option<io::Error>) -> Self {
Self {
reader,
writer,
mode,
error_on_eof,
}
}
pub fn into_inner(self) -> (R, W) {
(self.reader, self.writer)
}
}
impl<R, W> Transfer for Copier<R, W>
where
R: AsyncRead + Unpin + Send,
W: AsyncWrite + Unpin + Send,
{
type Error = CopyError;
async fn transfer(&mut self) -> Result<usize, TransferError<Self::Error>> {
copy(&mut self.reader, &mut self.writer, self.error_on_eof.take())
.await
.map_err(|e| e.map_err_mode(self.mode))
}
}
fn io_from_copiers<AppR, AppW, NetR, NetW>(
copiers: (Copier<AppR, NetW>, Copier<NetR, AppW>),
) -> (AppR, AppW, NetR, NetW)
where
AppR: AsyncRead + Unpin + Send,
AppW: AsyncWrite + Unpin + Send,
NetR: AsyncRead + Unpin + Send,
NetW: AsyncWrite + Unpin + Send,
{
let (app_to_net, net_to_app) = copiers;
let (app_reader, net_writer) = app_to_net.into_inner();
let (net_reader, app_writer) = net_to_app.into_inner();
(app_reader, app_writer, net_reader, net_writer)
}
fn copiers_from_io<AppR, AppW, NetR, NetW>(
app_reader: AppR,
app_writer: AppW,
net_reader: NetR,
net_writer: NetW,
error_on_app_eof: Option<io::Error>,
error_on_net_eof: Option<io::Error>,
) -> (Copier<AppR, NetW>, Copier<NetR, AppW>)
where
AppR: AsyncRead + Unpin + Send,
AppW: AsyncWrite + Unpin + Send,
NetR: AsyncRead + Unpin + Send,
NetW: AsyncWrite + Unpin + Send,
{
let app_to_net = Copier::new(
app_reader,
net_writer,
TransferMode::ApplicationToNetwork,
error_on_app_eof,
);
let net_to_app = Copier::new(
net_reader,
app_writer,
TransferMode::NetworkToApplication,
error_on_net_eof,
);
(app_to_net, net_to_app)
}
struct RecoveryGuard<T> {
inner: Option<T>,
sender: Option<oneshot::Sender<T>>,
}
impl<T> Drop for RecoveryGuard<T> {
fn drop(&mut self) {
if let (Some(inner), Some(sender)) = (self.inner.take(), self.sender.take()) {
let _ = sender.send(inner);
}
}
}
fn transfer_spawn_recoverable<T>(
set: &mut JoinSet<Result<usize, TransferError<T::Error>>>,
transfer: T,
) -> (AbortHandle, oneshot::Receiver<T>)
where
T: Transfer + Send + 'static,
<T as Transfer>::Error: Send,
{
let (tx, rx) = oneshot::channel();
let handle = set.spawn(async move {
let mut guard = RecoveryGuard {
inner: Some(transfer),
sender: Some(tx),
};
let inner = guard.inner.as_mut().unwrap();
inner.transfer().await
});
(handle, rx)
}
async fn transfer_spawn_join_inner<T, U>(
app_to_net: T,
net_to_app: U,
abort_on_error: bool,
) -> (
(T, U),
(TransferMode, Result<usize, TransferError<T::Error>>),
(TransferMode, Result<usize, TransferError<T::Error>>),
)
where
T: Transfer + Send + 'static,
<T as Transfer>::Error: Send,
U: Transfer + Send + 'static,
U: Transfer<Error = T::Error>,
<U as Transfer>::Error: Send,
{
let mut set = JoinSet::new();
let (app_net_handle, mut app_net_rx) = transfer_spawn_recoverable(&mut set, app_to_net);
let (net_app_handle, mut net_app_rx) = transfer_spawn_recoverable(&mut set, net_to_app);
let (app_net_id, net_app_id) = (app_net_handle.id(), net_app_handle.id());
let mut first = None;
let mut second = None;
while let Some(join_res) = set.join_next_with_id().await {
let (id, task_res) = match join_res {
Ok((id, task_res)) => (id, task_res),
Err(join_err) => (join_err.id(), Err(TransferError::Unfinished(join_err))),
};
if task_res.is_err() && abort_on_error {
set.abort_all();
}
let mode = if id == net_app_id {
TransferMode::NetworkToApplication
} else if id == app_net_id {
TransferMode::ApplicationToNetwork
} else {
log::error!("Unknown task {id}, expected {net_app_id} or {app_net_id}");
continue;
};
if first.is_none() {
first = Some((mode, task_res))
} else if second.is_none() {
second = Some((mode, task_res))
} else {
log::error!("Too many tasks, expected only two");
}
}
let app_to_net = app_net_rx.try_recv().unwrap();
let net_to_app = net_app_rx.try_recv().unwrap();
let io_objects = (app_to_net, net_to_app);
(io_objects, first.unwrap(), second.unwrap())
}
#[cfg(test)]
mod tests {
use std::io;
use tokio_test::io::{Builder, Mock};
use super::*;
fn mock_both_success() -> (Mock, Mock, Mock, Mock) {
(
Builder::new().read(b"a").build(),
Builder::new().write(b"n").build(),
Builder::new().read(b"n").build(),
Builder::new().write(b"a").build(),
)
}
fn mock_one_error() -> (Mock, Mock, Mock, Mock) {
(
Builder::new().read_error(io::Error::other("err")).build(),
Builder::new().write(b"n").build(),
Builder::new().read(b"n").build(),
Builder::new().build(),
)
}
fn mock_both_error() -> (Mock, Mock, Mock, Mock) {
(
Builder::new().read_error(io::Error::other("err")).build(),
Builder::new().build(),
Builder::new().read_error(io::Error::other("err")).build(),
Builder::new().build(),
)
}
fn mock_eofs() -> (Mock, Mock, Mock, Mock) {
(
Builder::new().build(),
Builder::new().build(),
Builder::new().build(),
Builder::new().build(),
)
}
#[tokio::test]
async fn test_copy_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (r1, r2) = copy_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_ok() && r2.is_ok());
}
#[tokio::test]
async fn test_copy_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (r1, r2) = copy_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_ok() && r2.is_err());
}
#[tokio::test]
async fn test_copy_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (r1, r2) = copy_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_err() && r2.is_err());
}
#[tokio::test]
async fn test_copy_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (r1, r2) = copy_join(ar, aw, nr, nw, None, net_eof_err).await;
assert!(r1.is_err() && r2.is_ok());
}
#[tokio::test]
async fn test_copy_try_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let res = copy_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_copy_try_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let res = copy_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_copy_try_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let res = copy_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_copy_try_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let res = copy_try_join(ar, aw, nr, nw, None, net_eof_err).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_copy_spawn_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (_, r1, r2) = copy_spawn_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_ok() && r2.is_ok());
}
#[tokio::test]
async fn test_copy_spawn_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (_, r1, r2) = copy_spawn_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_ok() && r2.is_err());
}
#[tokio::test]
async fn test_copy_spawn_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (_, r1, r2) = copy_spawn_join(ar, aw, nr, nw, None, None).await;
assert!(r1.is_err() && r2.is_err());
}
#[tokio::test]
async fn test_copy_spawn_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (_, r1, r2) = copy_spawn_join(ar, aw, nr, nw, None, net_eof_err).await;
assert!(r1.is_err() && r2.is_ok());
}
#[tokio::test]
async fn test_copy_spawn_try_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (_, res) = copy_spawn_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_copy_spawn_try_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (_, res) = copy_spawn_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_copy_spawn_try_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (_, res) = copy_spawn_try_join(ar, aw, nr, nw, None, None).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_copy_spawn_try_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (_, res) = copy_spawn_try_join(ar, aw, nr, nw, None, net_eof_err).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (r1, r2) = transfer_join(app_to_net, net_to_app).await;
assert!(r1.is_ok() && r2.is_ok());
}
#[tokio::test]
async fn test_transfer_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (r1, r2) = transfer_join(app_to_net, net_to_app).await;
assert!(r1.is_ok() && r2.is_err());
}
#[tokio::test]
async fn test_transfer_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (r1, r2) = transfer_join(app_to_net, net_to_app).await;
assert!(r1.is_err() && r2.is_err());
}
#[tokio::test]
async fn test_transfer_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, net_eof_err);
let (r1, r2) = transfer_join(app_to_net, net_to_app).await;
assert!(r1.is_err() && r2.is_ok());
}
#[tokio::test]
async fn test_transfer_try_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let res = transfer_try_join(app_to_net, net_to_app).await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_transfer_try_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let res = transfer_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_try_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let res = transfer_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_try_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, net_eof_err);
let res = transfer_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_spawn_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, r1, r2) = transfer_spawn_join(app_to_net, net_to_app).await;
assert!(r1.is_ok() && r2.is_ok());
}
#[tokio::test]
async fn test_transfer_spawn_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, r1, r2) = transfer_spawn_join(app_to_net, net_to_app).await;
assert!(r1.is_ok() && r2.is_err());
}
#[tokio::test]
async fn test_transfer_spawn_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, r1, r2) = transfer_spawn_join(app_to_net, net_to_app).await;
assert!(r1.is_err() && r2.is_err());
}
#[tokio::test]
async fn test_transfer_spawn_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, net_eof_err);
let (_, r1, r2) = transfer_spawn_join(app_to_net, net_to_app).await;
assert!(r1.is_err() && r2.is_ok());
}
#[tokio::test]
async fn test_transfer_spawn_try_join_success() {
let (ar, aw, nr, nw) = mock_both_success();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, res) = transfer_spawn_try_join(app_to_net, net_to_app).await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_transfer_spawn_try_join_one_error() {
let (ar, aw, nr, nw) = mock_one_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, res) = transfer_spawn_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_spawn_try_join_both_errors() {
let (ar, aw, nr, nw) = mock_both_error();
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, None);
let (_, res) = transfer_spawn_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_transfer_spawn_try_join_eof_error() {
let (ar, aw, nr, nw) = mock_eofs();
let net_eof_err = Some(io::ErrorKind::UnexpectedEof.into());
let (app_to_net, net_to_app) = copiers_from_io(ar, aw, nr, nw, None, net_eof_err);
let (_, res) = transfer_spawn_try_join(app_to_net, net_to_app).await;
assert!(res.is_err());
}
}