tor-rtmock
Support for mocking with tor-rtcompat
asynchronous runtimes.
Overview
The tor-rtcompat
crate defines a Runtime
trait that represents
most of the common functionality of . This crate provides mock
implementations that override a Runtime
, in whole or in part,
for testing purposes.
This crate is part of
Arti, a project to
implement Tor in Rust.
It is used to write tests for higher-level
crates in Arti that rely on asynchronous runtimes.
This crate should only be used for writing tests.
The principal entrypoint for writing tests is [MockRuntime
],
particularly test_with_various
.
It supports mocking the passage of time
(via SimpleMockTimeProvider
and
MockExecutor
),
and impersonating the internet (via [MockNetRuntime
]).
Comprehensive example
Suppose you've written a function that relies on making a
connection to the network and possibly timing out.
With tor-rtmock
you can test this function,
replacing the internet, and the passage of time.
The test runs instantly, without actually blocking,
even though it tests a timeout.
And it tests the function against your mocked server,
without making any actual network connections.
use tor_rtcompat::{Runtime, SleepProviderExt as _n};
use std::{io, net::{IpAddr, SocketAddr}, time::Duration};
use futures::{channel::oneshot, io::{AsyncReadExt as _, AsyncWriteExt as _}, poll};
use std::io::ErrorKind;
use tor_rtmock::{MockRuntime, net::MockNetwork};
use tor_rtcompat::{TcpProvider as _, TcpListener as _};
async fn converse(runtime: impl Runtime, addr: &SocketAddr) -> io::Result<Vec<u8>> {
let delay = Duration::new(5,0);
runtime.timeout(delay, async {
let mut conn = runtime.connect(addr).await?;
conn.write_all(b"Hello world!\r\n").await?;
conn.flush().await?;
let mut response = vec![];
conn.read_to_end(&mut response).await?;
io::Result::Ok(response)
}).await?
}
MockRuntime::test_with_various(|rt| async move {
let fake_internet = MockNetwork::new();
let sip: IpAddr = "198.51.100.99".parse().unwrap();
let srt = fake_internet.builder().add_address(sip).runtime(rt.clone());
let cip: IpAddr = "198.51.100.7".parse().unwrap();
let crt = fake_internet.builder().add_address(cip).runtime(rt.clone());
let spawn_test = |saddr| {
let (ret_tx, ret_rx) = oneshot::channel();
let crt = crt.clone();
rt.spawn_identified("function under test", async move {
let ret = converse(crt, &saddr).await;
ret_tx.send(ret).unwrap();
});
ret_rx
};
eprintln!("First test. Nothing is listening.");
let saddr = SocketAddr::new(sip, 1);
let ret = spawn_test(saddr).await.unwrap();
assert_eq!(ret.unwrap_err().kind(), ErrorKind::ConnectionRefused);
eprintln!("Second test. Listening, but no-one picks up the phone: timeout.");
let saddr = SocketAddr::new(sip, 2);
let listener = srt.listen(&saddr).await.unwrap();
let mut ret_fut = spawn_test(saddr);
rt.progress_until_stalled().await; assert!(ret_fut.try_recv().unwrap().is_none()); assert!(poll!(&mut ret_fut).is_pending()); rt.advance_by(Duration::from_secs(4)).await; assert!(ret_fut.try_recv().unwrap().is_none()); rt.advance_by(Duration::from_secs(1)).await; let ret = ret_fut.try_recv().unwrap().unwrap();
assert_eq!(ret.unwrap_err().kind(), ErrorKind::TimedOut);
eprintln!("Third test. Working.");
let saddr = SocketAddr::new(sip, 3);
let listener = srt.listen(&saddr).await.unwrap();
let mut ret_fut = spawn_test(saddr);
let (mut conn, caddr) = listener.accept().await.unwrap();
eprintln!("listener accepted from {caddr:?}");
assert_eq!(caddr.ip(), cip);
let expect = b"Hello world!\r\n";
let mut output = vec![b'X'; expect.len()];
conn.read_exact(&mut output).await.unwrap();
eprintln!("listener received {output:?}");
assert_eq!(output, expect);
let reply_data = b"reply data";
conn.write(reply_data).await.unwrap();
conn.close().await.unwrap();
let ret = ret_fut.await.unwrap();
assert_eq!(ret.unwrap(), reply_data);
});
License: MIT OR Apache-2.0