arti/crates/tor-rtmock
eta b14c5f370e Make TlsConnector wrap TCP connections, not create its own
`tor-rtcompat`'s `TlsConnector` trait previously included a method to
create a TLS-over-TCP connection, which implied creating a TCP stream
inside that method. This commit changes that, and makes the function
wrap a TCP stream, as returned from the runtime's `TcpProvider` trait
implementation, instead.

This means you can actually override `TcpProvider` and have it apply to
*all* connections Arti makes, which is useful for issues like arti#235
and other cases where you want to have a custom TCP stream
implementation.

This required updating the mock TCP/TLS types in `tor-rtmock` slightly;
due to the change in API, we now store whether a `LocalStream` should
actually be a TLS stream inside the stream itself, and check this
property on reads/writes in order to detect misuse. The fake TLS wrapper
checks this property and removes it in order to "wrap" the stream,
making reads and writes work again.
2021-12-07 17:00:40 +00:00
..
src Make TlsConnector wrap TCP connections, not create its own 2021-12-07 17:00:40 +00:00
tests fix/silence clippy lints in test modules 2021-09-08 17:28:31 +02:00
Cargo.toml Bump every crate by one patch version. 2021-11-29 15:21:58 -05:00
README.md Move all crates into a `crates` subdirectory. 2021-08-27 09:53:09 -04:00

README.md

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 should only be used for writing tests.

Currently, we support mocking the passage of time (via [MockSleepRuntime]), and impersonating the internet (via [MockNetRuntime]).

Examples

Suppose you've written a function that relies on making a connection to the network and possibly timing out:

use tor_rtcompat::{Runtime,SleepProviderExt};
use std::{net::SocketAddr, io::Result, time::Duration, io::Error};
use futures::io::AsyncWriteExt;

async fn say_hi(runtime: impl Runtime, addr: &SocketAddr) -> Result<()> {
   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.close().await?;
      Ok::<_,Error>(())
   }).await??;
   Ok(())
}

But how should you test this function?

You might try connecting to a well-known website to test the connection case, and to a well-known black hole to test the timeout case... but that's a bit undesirable. Your tests might be running in a container with no internet access; and even if they aren't, it isn't so great for your tests to rely on the actual state of the internet. Similarly, if you make your timeout too long, your tests might block for a long time; but if your timeout is too short, the tests might fail on a slow machine or on a slow network.

Or, you could solve both of these problems by using tor-rtmock to replace the internet and the passage of time. (Here we're only replacing the internet.)

#
use tor_rtmock::{MockSleepRuntime,MockNetRuntime,net::MockNetwork};
use tor_rtcompat::{TcpProvider,TcpListener};
use futures::io::AsyncReadExt;

tor_rtcompat::test_with_all_runtimes!(|rt| async move {

   let addr1 = "198.51.100.7".parse().unwrap();
   let addr2 = "198.51.100.99".parse().unwrap();
   let sockaddr = "198.51.100.99:101".parse().unwrap();

   // Make a runtime that pretends that we are at the first address...
   let fake_internet = MockNetwork::new();
   let rt1 = fake_internet.builder().add_address(addr1).runtime(rt.clone());
   // ...and one that pretends we're listening at the second address.
   let rt2 = fake_internet.builder().add_address(addr2).runtime(rt);
   let listener = rt2.listen(&sockaddr).await.unwrap();

   // Now we can test our function!
   let (result1,output) = futures::join!(
          say_hi(rt1, &sockaddr),
          async {
              let (mut conn,addr) = listener.accept().await.unwrap();
              assert_eq!(addr.ip(), addr1);
              let mut output = Vec::new();
              conn.read_to_end(&mut output).await.unwrap();
              output
          });

   assert!(result1.is_ok());
   assert_eq!(&output[..], b"Hello world!\r\n");
});

(TODO: Add an example for the timeout case.)

License: MIT OR Apache-2.0