1
1
//! Support for mocking with `tor-rtcompat` asynchronous runtimes.
2
//!
3
//! # Overview
4
//!
5
//! The `tor-rtcompat` crate defines a `Runtime` trait that represents
6
//! most of the common functionality of .  This crate provides mock
7
//! implementations that override a `Runtime`, in whole or in part,
8
//! for testing purposes.
9
//!
10
//! This crate is part of
11
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
12
//! implement [Tor](https://www.torproject.org/) in Rust.
13
//! It is used to write tests for higher-level
14
//! crates in Arti that rely on asynchronous runtimes.
15
//!
16
//! This crate should should only be used for writing tests.
17
//!
18
//! Currently, we support mocking the passage of time (via
19
//! [`MockSleepRuntime`]), and impersonating the internet (via
20
//! [`MockNetRuntime`]).
21
//!
22
//! # Examples
23
//!
24
//! Suppose you've written a function that relies on making a
25
//! connection to the network and possibly timing out:
26
//!
27
//! ```
28
//! use tor_rtcompat::{Runtime,SleepProviderExt};
29
//! use std::{net::SocketAddr, io::Result, time::Duration, io::Error};
30
//! use futures::io::AsyncWriteExt;
31
//!
32
//! async fn say_hi(runtime: impl Runtime, addr: &SocketAddr) -> Result<()> {
33
//!    let delay = Duration::new(5,0);
34
//!    runtime.timeout(delay, async {
35
//!       let mut conn = runtime.connect(addr).await?;
36
//!       conn.write_all(b"Hello world!\r\n").await?;
37
//!       conn.close().await?;
38
//!       Ok::<_,Error>(())
39
//!    }).await??;
40
//!    Ok(())
41
//! }
42
//! ```
43
//!
44
//! But how should you test this function?
45
//!
46
//! You might try connecting to a well-known website to test the
47
//! connection case, and to a well-known black hole to test the
48
//! timeout case... but that's a bit undesirable.  Your tests might be
49
//! running in a container with no internet access; and even if they
50
//! aren't, it isn't so great for your tests to rely on the actual
51
//! state of the internet.  Similarly, if you make your timeout too long,
52
//! your tests might block for a long time; but if your timeout is too short,
53
//! the tests might fail on a slow machine or on a slow network.
54
//!
55
//! Or, you could solve both of these problems by using `tor-rtmock`
56
//! to replace the internet _and_ the passage of time.  (Here we're only
57
//! replacing the internet.)
58
//!
59
//! ```
60
//! # use tor_rtcompat::{Runtime,SleepProviderExt};
61
//! # use std::{net::SocketAddr, io::Result, time::Duration, io::Error};
62
//! # use futures::io::AsyncWriteExt;
63
//! #
64
//! # async fn say_hi(runtime: impl Runtime, addr: &SocketAddr) -> Result<()> {
65
//! #   let delay = Duration::new(5,0);
66
//! #   runtime.timeout(delay, async {
67
//! #      let mut conn = runtime.connect(addr).await?;
68
//! #      conn.write_all(b"Hello world!\r\n").await?;
69
//! #      conn.close().await?;
70
//! #      dbg!("okay apparently");
71
//! #      Ok::<_,Error>(())
72
//! #   }).await??;
73
//! #   Ok(())
74
//! # }
75
//! use tor_rtmock::{MockSleepRuntime,MockNetRuntime,net::MockNetwork};
76
//! use tor_rtcompat::{TcpProvider,TcpListener};
77
//! use futures::io::AsyncReadExt;
78
//!
79
//! tor_rtcompat::test_with_all_runtimes!(|rt| async move {
80
//!
81
//!    let addr1 = "198.51.100.7".parse().unwrap();
82
//!    let addr2 = "198.51.100.99".parse().unwrap();
83
//!    let sockaddr = "198.51.100.99:101".parse().unwrap();
84
//!
85
//!    // Make a runtime that pretends that we are at the first address...
86
//!    let fake_internet = MockNetwork::new();
87
//!    let rt1 = fake_internet.builder().add_address(addr1).runtime(rt.clone());
88
//!    // ...and one that pretends we're listening at the second address.
89
//!    let rt2 = fake_internet.builder().add_address(addr2).runtime(rt);
90
//!    let listener = rt2.listen(&sockaddr).await.unwrap();
91
//!
92
//!    // Now we can test our function!
93
//!    let (result1,output) = futures::join!(
94
//!           say_hi(rt1, &sockaddr),
95
//!           async {
96
//!               let (mut conn,addr) = listener.accept().await.unwrap();
97
//!               assert_eq!(addr.ip(), addr1);
98
//!               let mut output = Vec::new();
99
//!               conn.read_to_end(&mut output).await.unwrap();
100
//!               output
101
//!           });
102
//!
103
//!    assert!(result1.is_ok());
104
//!    assert_eq!(&output[..], b"Hello world!\r\n");
105
//! });
106
//! ```
107
//!
108
//! (TODO: Add an example for the timeout case.)
109

            
110
#![deny(missing_docs)]
111
#![warn(noop_method_call)]
112
#![deny(unreachable_pub)]
113
#![warn(clippy::all)]
114
#![deny(clippy::await_holding_lock)]
115
#![deny(clippy::cargo_common_metadata)]
116
#![deny(clippy::cast_lossless)]
117
#![deny(clippy::checked_conversions)]
118
#![warn(clippy::cognitive_complexity)]
119
#![deny(clippy::debug_assert_with_mut_call)]
120
#![deny(clippy::exhaustive_enums)]
121
#![deny(clippy::exhaustive_structs)]
122
#![deny(clippy::expl_impl_clone_on_copy)]
123
#![deny(clippy::fallible_impl_from)]
124
#![deny(clippy::implicit_clone)]
125
#![deny(clippy::large_stack_arrays)]
126
#![warn(clippy::manual_ok_or)]
127
#![deny(clippy::missing_docs_in_private_items)]
128
#![deny(clippy::missing_panics_doc)]
129
#![warn(clippy::needless_borrow)]
130
#![warn(clippy::needless_pass_by_value)]
131
#![warn(clippy::option_option)]
132
#![warn(clippy::rc_buffer)]
133
#![deny(clippy::ref_option_ref)]
134
#![warn(clippy::semicolon_if_nothing_returned)]
135
#![warn(clippy::trait_duplication_in_bounds)]
136
#![deny(clippy::unnecessary_wraps)]
137
#![warn(clippy::unseparated_literal_suffix)]
138
#![deny(clippy::unwrap_used)]
139

            
140
pub mod io;
141
pub mod net;
142
pub mod time;
143

            
144
mod net_runtime;
145
mod sleep_runtime;
146

            
147
pub use net_runtime::MockNetRuntime;
148
pub use sleep_runtime::MockSleepRuntime;