blob: e5a5c1bb5fdd231c1e3c844212f2fbf29c27a4e7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::fuzzer::{Fuzzer, FuzzerState};
use anyhow::{Context as _, Error, Result};
use fidl::endpoints::{create_proxy, DiscoverableProtocolMarker, ServerEnd};
use futures::channel::mpsc;
use futures::StreamExt;
use fuzz::RegistryProxy;
use std::cell::RefCell;
use std::collections::HashMap;
use test_manager::{
RunBuilderMarker, RunControllerMarker, RunControllerProxy, RunOptions, SuiteControllerMarker,
SuiteControllerProxy,
};
use tracing::warn;
use url::Url;
use {
fidl_fuchsia_fuzzer as fuzz, fidl_fuchsia_test_manager as test_manager, fuchsia_zircon as zx,
};
// If this much time elapses from a test suite's start without it connecting to the fuzz-registry,
// the test is assumed to not be a fuzz test.
pub const DEFAULT_TIMEOUT_IN_SECONDS: i64 = 15;
// Helper trait makes connecting to the test_manager configurable. In particular, unit tests provide
// an alternate implementation of this trait that connects to |test_support::FakeTestManager|.
pub trait FidlEndpoint<T: DiscoverableProtocolMarker> {
fn create_proxy(&self) -> Result<T::Proxy, Error>;
}
pub struct Manager<T: FidlEndpoint<RunBuilderMarker>> {
// Currently active fuzzers.
fuzzers: RefCell<HashMap<Url, Fuzzer>>,
// Connection the fuzz-registry.
registry: RegistryProxy,
// Produces connections to the test_manager.
run_builder: T,
}
impl<T: FidlEndpoint<RunBuilderMarker>> Manager<T> {
pub fn new(registry: RegistryProxy, run_builder: T) -> Self {
Self { fuzzers: RefCell::new(HashMap::new()), registry, run_builder }
}
/// Serves requests from `receiver`.
///
/// The fuzz-manager is resilient to error encountered when handling individual requests. Errors
/// may be logged and/or returned to callers via FIDL responses, but the fuzz-manager itself
/// will try to continue serving requests.
pub async fn serve(
&self,
mut receiver: mpsc::UnboundedReceiver<fuzz::ManagerRequest>,
) -> Result<()> {
while let Some(request) = receiver.next().await {
let result = match request {
fuzz::ManagerRequest::Connect { fuzzer_url, controller, responder } => {
let response = self
.connect(&fuzzer_url, controller)
.await
.map_err(|status| status.into_raw());
responder.send(response)
}
fuzz::ManagerRequest::GetOutput { fuzzer_url, output, socket, responder } => {
let result = self.get_output(&fuzzer_url, output, socket).await;
let response = match result {
Ok(()) => Ok(()),
Err(e) => {
let _ = self.stop(&fuzzer_url).await;
Err(e.into_raw())
}
};
responder.send(response)
}
fuzz::ManagerRequest::Stop { fuzzer_url, responder } => {
let response = self.stop(&fuzzer_url).await.map_err(|status| status.into_raw());
responder.send(response)
}
};
if let Err(e) = result {
warn!("failed to send FIDL response: {:?}", e);
}
}
Ok(())
}
fn build(&self, url: &Url) -> Result<(RunControllerProxy, SuiteControllerProxy)> {
let run_builder = self
.run_builder
.create_proxy()
.context("failed to connect to fuchsia.test_manager.RunBuilder")?;
let (run_proxy, run_controller) = create_proxy::<RunControllerMarker>()
.context("failed to create fuchsia.test_manager.RunController")?;
let (suite_proxy, suite_controller) = create_proxy::<SuiteControllerMarker>()
.context("failed to create fuchsia.test_manager.SuiteController")?;
let run_options =
RunOptions { arguments: Some(vec![fuzz::FUZZ_MODE.to_string()]), ..Default::default() };
run_builder
.add_suite(url.as_str(), &run_options, suite_controller)
.map_err(Error::msg)
.context("fuchsia.test_manager.RunBuilder/AddSuite")?;
run_builder
.build(run_controller)
.map_err(Error::msg)
.context("fuchsia.test_manager.RunBuilder/Build")?;
Ok((run_proxy, suite_proxy))
}
// Requests that given |controller| be connected to the fuzzer given by |fuzzer_url|, starting
// it if necessary. Returns a result containg the FIDL response.
async fn connect(
&self,
fuzzer_url: &str,
controller: ServerEnd<fuzz::ControllerMarker>,
) -> Result<(), zx::Status> {
let url = parse_url(fuzzer_url)?;
// Extract or create the fuzzer. If it is stopped or previously failed to start, try
// starting it.
let mut fuzzer = self.take_fuzzer(&url).unwrap_or_default();
match fuzzer.get_state() {
FuzzerState::Stopped | FuzzerState::Failed(_) => {
let (run_proxy, suite_proxy) = self.build(&url).map_err(warn_internal::<()>)?;
fuzzer.start(run_proxy, suite_proxy).await;
}
_ => {}
};
// Check again.
match fuzzer.get_state() {
FuzzerState::Running => {}
FuzzerState::Failed(status) => {
warn!("failed to connect {}: {}", fuzzer_url, status);
fuzzer.kill().await?;
return Err(status);
}
_ => unreachable!("invalid fuzzer state"),
};
// Now connect the controller via the registry.
let timeout = zx::Duration::from_seconds(DEFAULT_TIMEOUT_IN_SECONDS).into_nanos();
let result = self
.registry
.connect(url.as_str(), controller, timeout)
.await
.context("fuchsia.fuzzer/Registry.Connect")
.map_err(warn_internal::<i32>)?;
if let Err(e) = result {
warn!("failed to connect {}: fuzz-registry returned: {}", fuzzer_url, e);
fuzzer.kill().await?;
return Err(zx::Status::from_raw(e));
}
self.put_fuzzer(&url, fuzzer)
}
// Installs the given socket to receive the given type of fuzzer output.
async fn get_output(
&self,
fuzzer_url: &str,
output: fuzz::TestOutput,
socket: zx::Socket,
) -> Result<(), zx::Status> {
let url = parse_url(fuzzer_url)?;
let mut fuzzer = self.take_fuzzer(&url).ok_or_else(|| {
warn!("failed to get output {}: fuzzer was not found", fuzzer_url);
zx::Status::NOT_FOUND
})?;
match fuzzer.get_state() {
FuzzerState::Running => {
fuzzer.get_output(output, socket).await.map_err(warn_internal::<()>)?;
}
_ => {
warn!("failed to get output {}: fuzzer is not running", fuzzer_url);
return Err(zx::Status::BAD_STATE);
}
}
self.put_fuzzer(&url, fuzzer)
}
// Requests that the fuzzer given by |fuzzer_url| stop executing, and waits for it to finish.
// Returns a result containing the FIDL response.
async fn stop(&self, fuzzer_url: &str) -> Result<(), zx::Status> {
let url = parse_url(fuzzer_url)?;
let fuzzer = self.take_fuzzer(&url);
let result = self
.registry
.disconnect(url.as_str())
.await
.context("fuchsia.fuzzer/Registry.Disconnect")
.map_err(warn_internal::<zx::Status>)?;
if let Err(e) = result {
warn!("failed to stop {}: fuzz-registry returned: {}", fuzzer_url, e);
return Err(zx::Status::from_raw(e));
}
if let Some(mut fuzzer) = fuzzer {
let timeout = zx::Duration::from_seconds(DEFAULT_TIMEOUT_IN_SECONDS);
if let Err(e) = fuzzer.stop(Some(timeout)).await {
warn!("killing fuzzer after stop returned ZX_ERR_{}", e);
fuzzer.kill().await?;
}
}
Ok(())
}
fn take_fuzzer(&self, url: &Url) -> Option<Fuzzer> {
let mut fuzzers = self.fuzzers.borrow_mut();
fuzzers.remove(url)
}
fn put_fuzzer(&self, url: &Url, fuzzer: Fuzzer) -> Result<(), zx::Status> {
let mut fuzzers = self.fuzzers.borrow_mut();
fuzzers.insert(url.clone(), fuzzer);
Ok(())
}
}
fn parse_url(fuzzer_url: &str) -> Result<Url, zx::Status> {
Url::parse(fuzzer_url).map_err(|e| {
warn!("failed to parse URL: {:?}", e);
zx::Status::INVALID_ARGS
})
}
fn warn_internal<T>(e: Error) -> zx::Status {
warn!("{:?}", e);
zx::Status::INTERNAL
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{connect_to_manager, read_async, serve_test_realm, TestRealm};
use fidl::endpoints::{create_endpoints, Proxy};
use fidl_fuchsia_fuzzer as fuzz;
use futures::join;
use std::rc::Rc;
use test_manager::LaunchError;
use zx::AsHandleRef;
static FOO_URL: &str = "fuchsia-pkg://fuchsia.com/fuzz-manager-unittests#meta/foo.cm";
static BAR_URL: &str = "fuchsia-pkg://fuchsia.com/fuzz-manager-unittests#meta/bar.cm";
// Unit tests.
#[fuchsia::test]
async fn test_stop() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let test_fut = || async move {
let (client, server) = create_proxy::<fuzz::ControllerMarker>()
.context("failed to create Controller endpoints")?;
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
let result = fuzz_manager.stop(FOO_URL).await.context("failed to stop")?;
assert_eq!(result, Ok(()));
assert!(client.is_closed());
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_artifacts() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let test_realm_clone = Rc::clone(&test_realm);
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
test_realm_clone
.borrow()
.write_stdout(FOO_URL, "stdout")
.await
.context("failed to write 'stderr' to stderr")?;
let (stdout, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Stdout, socket).await?;
assert_eq!(result, Ok(()));
test_realm_clone
.borrow()
.write_stderr(FOO_URL, "stderr")
.await
.context("failed to write 'stderr' to stderr")?;
let (stderr, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Stderr, socket).await?;
assert_eq!(result, Ok(()));
test_realm_clone
.borrow()
.write_syslog(FOO_URL, "syslog")
.await
.context("failed to write 'syslog' to syslog")?;
let (syslog, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Syslog, socket).await?;
assert_eq!(result, Ok(()));
let msg = read_async(&stdout).await.context("failed to read stdout")?;
assert_eq!(msg, "stdout");
let msg = read_async(&stderr).await.context("failed to read stderr")?;
assert_eq!(msg, "stderr");
let msg = read_async(&syslog).await.context("failed to read syslog")?;
assert_eq!(msg, "syslog");
let result = fuzz_manager.stop(FOO_URL).await.context("failed to stop")?;
assert_eq!(result, Ok(()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_concurrent() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let test_realm_foo = Rc::clone(&test_realm);
let fuzz_manager_foo =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let foo_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager_foo
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
let (stderr, socket) = zx::Socket::create_stream();
let result =
fuzz_manager_foo.get_output(FOO_URL, fuzz::TestOutput::Stderr, socket).await?;
assert_eq!(result, Ok(()));
test_realm_foo
.borrow()
.write_stderr(FOO_URL, "foofoofoo")
.await
.context("failed to write to stderr")?;
let msg = read_async(&stderr).await.context("failed to read stderr")?;
assert_eq!(msg, "foofoofoo");
let result = fuzz_manager_foo.stop(FOO_URL).await.context("failed to stop")?;
assert_eq!(result, Ok(()));
Ok::<(), Error>(())
};
let test_realm_bar = Rc::clone(&test_realm);
let fuzz_manager_bar =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let bar_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager_bar
.connect(BAR_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
let (stderr, socket) = zx::Socket::create_stream();
let result =
fuzz_manager_bar.get_output(BAR_URL, fuzz::TestOutput::Stderr, socket).await?;
assert_eq!(result, Ok(()));
test_realm_bar
.borrow()
.write_stderr(BAR_URL, "barbarbar")
.await
.context("failed to write to stderr")?;
let msg = read_async(&stderr).await.context("failed to read stderr")?;
assert_eq!(msg, "barbarbar");
let result = fuzz_manager_bar.stop(BAR_URL).await.context("failed to stop")?;
assert_eq!(result, Ok(()));
Ok::<(), Error>(())
};
let results = join!(foo_fut(), bar_fut(), serve_test_realm(test_realm));
results.0.context("'foo' test failed")?;
results.1.context("'bar' test failed")?;
results.2.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_reconnect() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager1 =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let fuzz_manager2 =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let test_fut = || async move {
let (client1, server) = create_proxy::<fuzz::ControllerMarker>()
.context("failed to create Controller endpoints")?;
let result = fuzz_manager1
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
assert!(!client1.is_closed());
// Reconnecting should disconnect previous client.
let (client2, server) = create_proxy::<fuzz::ControllerMarker>()
.context("failed to create Controller endpoints")?;
let result = fuzz_manager2
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
assert!(client1.is_closed());
assert!(!client2.is_closed());
let result = fuzz_manager2.stop(FOO_URL).await.context("failed to stop")?;
assert_eq!(result, Ok(()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_not_fuzzer() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
// Simulate a non-fuzzer test by not registering a controller provider.
{
let mut test_realm_mut = test_realm.borrow_mut();
test_realm_mut.registry_status = zx::Status::TIMED_OUT;
}
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::TIMED_OUT.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_premature_stop() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let test_realm_clone = Rc::clone(&test_realm);
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
let test_fut = || async move {
let (_, server) = create_proxy::<fuzz::ControllerMarker>()
.context("failed to create Controller endpoints")?;
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Ok(()));
let (stdout, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Stdout, socket).await?;
assert_eq!(result, Ok(()));
let (stderr, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Stderr, socket).await?;
assert_eq!(result, Ok(()));
let (syslog, socket) = zx::Socket::create_stream();
let result = fuzz_manager.get_output(FOO_URL, fuzz::TestOutput::Syslog, socket).await?;
assert_eq!(result, Ok(()));
// Simulate an unexpected suite stop event.
test_realm_clone
.borrow()
.send_suite_stopped(FOO_URL)
.context("failed to stop suite")?;
test_realm_clone.borrow().send_run_stopped(FOO_URL).context("failed to run suite")?;
let msg = read_async(&stdout).await.context("failed to read stdout")?;
assert_eq!(msg, "");
assert!(stdout
.wait_handle(zx::Signals::SOCKET_PEER_CLOSED, zx::Time::INFINITE_PAST)
.is_ok());
let msg = read_async(&stderr).await.context("failed to read stderr")?;
assert_eq!(msg, "");
assert!(stderr
.wait_handle(zx::Signals::SOCKET_PEER_CLOSED, zx::Time::INFINITE_PAST)
.is_ok());
let msg = read_async(&syslog).await.context("failed to read syslog")?;
assert_eq!(msg, "");
assert!(syslog
.wait_handle(zx::Signals::SOCKET_PEER_CLOSED, zx::Time::INFINITE_PAST)
.is_ok());
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_resource_unavailable() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::ResourceUnavailable);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::NO_RESOURCES.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_instance_cannot_resolve() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::InstanceCannotResolve);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::NOT_FOUND.into_raw()));
let result = fuzz_manager.stop(FOO_URL).await.context("fuchsia.fuzzer/Manager.Stop")?;
assert_eq!(result, Err(zx::Status::NOT_FOUND.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_invalid_args() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::InvalidArgs);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::INVALID_ARGS.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_failed_to_connect_to_test_suite() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::FailedToConnectToTestSuite);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::NOT_SUPPORTED.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_case_enumeration() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::CaseEnumeration);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::INTERNAL.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
#[fuchsia::test]
async fn test_internal_error() -> Result<()> {
let test_realm = Rc::new(RefCell::new(TestRealm::new()));
let fuzz_manager =
connect_to_manager(Rc::clone(&test_realm)).context("failed to connect to manager")?;
test_realm.borrow_mut().launch_error = Some(LaunchError::InternalError);
let test_fut = || async move {
let (_, server) = create_endpoints::<fuzz::ControllerMarker>();
let result = fuzz_manager
.connect(FOO_URL, server)
.await
.context("fuchsia.fuzzer/Manager.Connect")?;
assert_eq!(result, Err(zx::Status::INTERNAL.into_raw()));
Ok::<(), Error>(())
};
let results = join!(test_fut(), serve_test_realm(test_realm));
results.0.context("test failed")?;
results.1.context("failed to serve test realm")?;
Ok(())
}
}