Merge branch 'da-task' into 'main'

RFC: tor-rtmock: Use derive-adhoc for composite runtimes

See merge request tpo/core/arti!1381
This commit is contained in:
gabi-250 2023-07-10 10:53:23 +00:00
commit 2db3e73434
6 changed files with 101 additions and 72 deletions

28
Cargo.lock generated
View File

@ -1080,6 +1080,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "derive-adhoc"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf4f1be2a84b4ba6f6443854fc9c3b8942ee3ffadadc7106fa777659a946cd4"
dependencies = [
"derive-adhoc-macros",
"heck",
]
[[package]]
name = "derive-adhoc-macros"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a7134a81d247901c0d03ef80194867644796cd8f858e82bf1b433dfc1c35485"
dependencies = [
"heck",
"itertools 0.10.5",
"proc-macro-crate",
"proc-macro2",
"quote",
"sha3",
"strum",
"syn 1.0.109",
"void",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.1"
@ -4827,6 +4854,7 @@ version = "0.8.2"
dependencies = [
"amplify",
"async-trait",
"derive-adhoc",
"educe",
"futures",
"futures-await-test",

View File

@ -14,6 +14,7 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[dependencies]
amplify = { version = "4", default-features = false, features = ["derive"] }
async-trait = "0.1.54"
derive-adhoc = "0.6"
educe = "0.4.6"
futures = "0.3.14"
humantime = "2"

View File

@ -9,11 +9,14 @@ use crate::net::MockNetProvider;
/// A wrapper Runtime that overrides the SleepProvider trait for the
/// underlying runtime.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Adhoc)]
#[derive_adhoc(SomeMockRuntime)]
pub struct MockNetRuntime<R: Runtime> {
/// The underlying runtime. Most calls get delegated here.
#[adhoc(mock(task, sleep))]
runtime: R,
/// A MockNetProvider. Network-related calls get delegated here.
#[adhoc(mock(net))]
net: MockNetProvider,
}
@ -34,10 +37,3 @@ impl<R: Runtime> MockNetRuntime<R> {
&self.net
}
}
impl_runtime! {
[ <R: Runtime> ] MockNetRuntime<R>,
task: runtime,
sleep: runtime: R,
net: net: MockNetProvider,
}

View File

@ -61,14 +61,18 @@ use crate::time::MockSleepProvider;
///
/// * Anything provided by a Rust runtime/executor project (eg anything from Tokio),
/// unless it is definitively established that it's runtime-agnostic.
#[derive(Debug, Default, Clone, Getters)]
#[derive(Debug, Default, Clone, Getters, Adhoc)]
#[derive_adhoc(SomeMockRuntime)]
#[getter(prefix = "mock_")]
pub struct MockRuntime {
/// Tasks
#[adhoc(mock(task))]
task: MockExecutor,
/// Time provider
#[adhoc(mock(sleep))]
sleep: MockSleepProvider,
/// Net provider
#[adhoc(mock(net))]
net: MockNetProvider,
}
@ -81,13 +85,6 @@ pub struct MockRuntimeBuilder {
starting_wallclock: Option<SystemTime>,
}
impl_runtime! {
[ ] MockRuntime,
task: task,
sleep: sleep: MockSleepProvider,
net: net: MockNetProvider,
}
impl MockRuntime {
/// Create a new `MockRuntime` with default parameters
pub fn new() -> Self {

View File

@ -9,11 +9,14 @@ use crate::util::impl_runtime_prelude::*;
/// A wrapper Runtime that overrides the SleepProvider trait for the
/// underlying runtime.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Adhoc)]
#[derive_adhoc(SomeMockRuntime)]
pub struct MockSleepRuntime<R: Runtime> {
/// The underlying runtime. Most calls get delegated here.
#[adhoc(mock(task, net))]
runtime: R,
/// A MockSleepProvider. Time-related calls get delegated here.
#[adhoc(mock(sleep))]
sleep: MockSleepProvider,
}
@ -79,13 +82,6 @@ impl<R: Runtime> MockSleepRuntime<R> {
}
}
impl_runtime! {
[ <R: Runtime> ] MockSleepRuntime<R>,
task: runtime,
sleep: sleep: MockSleepProvider,
net: runtime: R,
}
/// A future that advances time until another future is ready to complete.
#[pin_project]
pub struct WaitFor<F> {

View File

@ -1,109 +1,119 @@
//! Internal utilities for `tor_rtmock`
use derive_adhoc::define_derive_adhoc;
define_derive_adhoc! {
/// Implements `Runtime` for a struct made of multiple sub-providers
///
/// The `$SomeMockRuntime` type must be a struct containing
/// The type must be a struct containing
/// field(s) which implement `SleepProvider`, `NetProvider`, etc.
///
/// The entry for `task` is used for both `BlockOn` and `Spawn`.
/// The corresponding fields must be decorated with:
///
/// `$gens` are the generics, written as (for example) `[ <R: Runtime> ]`.
///
/// The remaining arguments are the fields.
/// For each field there's:
/// - the short name of what is being provided (a fixed identifier)
/// - the field name in `$SockMockRuntime`
/// - for some cases, the type of that field
///
/// The fields must be specified in the expected order!
/// * `#[adhoc(mock(task))]` to indicate the field implementing `Spawn + BlockOn`
/// * `#[adhoc(mock(net))]` to indicate the field implementing `NetProvider`
/// * `#[adhoc(mock(sleep))]` to indicate the field implementing `SleepProvider`
// It would be nice to be able to reject misspelled or obsolete `#[adhoc(mock(THING))]`,
// but derive-adhoc only allows us to look up known entries, not iterate.
// However, a misspelling would result in a missing trait impl, leading to compile error.
// Likewise, duplicates result in duplicate trait impls.
//
// This could be further reduced with more macrology:
// This could perhaps be further reduced:
// ambassador might be able to remove most of the body (although does it do async well?)
// derive-adhoc would allow a more natural input syntax and avoid restating field types
macro_rules! impl_runtime { {
[ $($gens:tt)* ] $SomeMockRuntime:ty,
task: $task:ident,
sleep: $sleep:ident: $SleepProvider:ty,
net: $net:ident: $NetProvider:ty,
} => {
impl $($gens)* Spawn for $SomeMockRuntime {
SomeMockRuntime for struct, expect items =
$(
${when fmeta(mock(task))}
impl <$tgens> Spawn for $ttype {
fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> {
self.$task.spawn_obj(future)
self.$fname.spawn_obj(future)
}
}
impl $($gens)* BlockOn for $SomeMockRuntime {
impl <$tgens> BlockOn for $ttype {
fn block_on<F: Future>(&self, future: F) -> F::Output {
self.$task.block_on(future)
self.$fname.block_on(future)
}
}
)
$(
${when fmeta(mock(net))}
#[async_trait]
impl $($gens)* TcpProvider for $SomeMockRuntime {
type TcpStream = <$NetProvider as TcpProvider>::TcpStream;
type TcpListener = <$NetProvider as TcpProvider>::TcpListener;
impl <$tgens> TcpProvider for $ttype {
type TcpStream = <$ftype as TcpProvider>::TcpStream;
type TcpListener = <$ftype as TcpProvider>::TcpListener;
async fn connect(&self, addr: &SocketAddr) -> IoResult<Self::TcpStream> {
self.$net.connect(addr).await
self.$fname.connect(addr).await
}
async fn listen(&self, addr: &SocketAddr) -> IoResult<Self::TcpListener> {
self.$net.listen(addr).await
self.$fname.listen(addr).await
}
}
impl $($gens)* TlsProvider<<$NetProvider as TcpProvider>::TcpStream> for $SomeMockRuntime {
type Connector = <$NetProvider as TlsProvider<
<$NetProvider as TcpProvider>::TcpStream
impl <$tgens> TlsProvider<<$ftype as TcpProvider>::TcpStream> for $ttype {
type Connector = <$ftype as TlsProvider<
<$ftype as TcpProvider>::TcpStream
>>::Connector;
type TlsStream = <$NetProvider as TlsProvider<
<$NetProvider as TcpProvider>::TcpStream
type TlsStream = <$ftype as TlsProvider<
<$ftype as TcpProvider>::TcpStream
>>::TlsStream;
fn tls_connector(&self) -> Self::Connector {
self.$net.tls_connector()
self.$fname.tls_connector()
}
}
#[async_trait]
impl $($gens)* UdpProvider for $SomeMockRuntime {
type UdpSocket = <$NetProvider as UdpProvider>::UdpSocket;
impl <$tgens> UdpProvider for $ttype {
type UdpSocket = <$ftype as UdpProvider>::UdpSocket;
#[inline]
async fn bind(&self, addr: &SocketAddr) -> IoResult<Self::UdpSocket> {
self.$net.bind(addr).await
self.$fname.bind(addr).await
}
}
impl $($gens)* SleepProvider for $SomeMockRuntime {
type SleepFuture = <$SleepProvider as SleepProvider>::SleepFuture;
)
$(
${when fmeta(mock(sleep))}
impl <$tgens> SleepProvider for $ttype {
type SleepFuture = <$ftype as SleepProvider>::SleepFuture;
fn sleep(&self, dur: Duration) -> Self::SleepFuture {
self.$sleep.sleep(dur)
self.$fname.sleep(dur)
}
fn now(&self) -> Instant {
self.$sleep.now()
self.$fname.now()
}
fn wallclock(&self) -> SystemTime {
self.$sleep.wallclock()
self.$fname.wallclock()
}
fn block_advance<T: Into<String>>(&self, reason: T) {
self.$sleep.block_advance(reason);
self.$fname.block_advance(reason);
}
fn release_advance<T: Into<String>>(&self, reason: T) {
self.$sleep.release_advance(reason);
self.$fname.release_advance(reason);
}
fn allow_one_advance(&self, dur: Duration) {
self.$sleep.allow_one_advance(dur);
self.$fname.allow_one_advance(dur);
}
}
)
// TODO this wants to be assert_impl but it fails at generics
const _: fn() = || {
fn x(_: impl Runtime) { }
fn check_impl_runtime $($gens)* (t: $SomeMockRuntime) { x(t) }
fn check_impl_runtime<$tgens>(t: $ttype) { x(t) }
};
} }
}
/// Prelude that must be imported to use [`impl_runtime!`](impl_runtime)
/// Prelude that must be imported to derive
/// [`SomeMockRuntime`](derive_adhoc_template_SomeMockRuntime)
//
// This could have been part of the expansion of `impl_runtime!`,
// but it seems rather too exciting for a macro to import things as a side gig.
@ -115,6 +125,7 @@ macro_rules! impl_runtime { {
// to allow it to refer to the macro in the doc comment.
pub(crate) mod impl_runtime_prelude {
pub(crate) use async_trait::async_trait;
pub(crate) use derive_adhoc::Adhoc;
pub(crate) use futures::task::{FutureObj, Spawn, SpawnError};
pub(crate) use futures::Future;
pub(crate) use std::io::Result as IoResult;