1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45//! Utilities to run asynchronous tests that use `pseudo-fs` objects.
67use crate::directory::entry::DirectoryEntry;
8use crate::directory::entry_container::Directory;
9use crate::directory::helper::DirectlyMutable;
10use crate::directory::immutable::Simple;
11use crate::execution_scope::ExecutionScope;
12use crate::path::Path;
1314use fidl::endpoints::{create_proxy, ProtocolMarker};
15use fidl_fuchsia_io as fio;
16use fuchsia_async::TestExecutor;
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::Arc;
2021#[cfg(target_os = "fuchsia")]
22use std::task::Poll;
2324/// A helper to connect a pseudo fs server to a client and the run the client on a single threaded
25/// executor. Execution is run until the executor reports the execution has stalled. The client
26/// must complete it's execution at this point. It is a failure if the client is stalled.
27///
28/// This is the most common case for the test execution, and is actualy just forwarding to
29/// [`test_server_client()`] followed immediately by a [`AsyncClientTestParams::run()`] call.
30pub fn run_server_client<Marker, GetClient, GetClientRes>(
31 flags: fio::OpenFlags,
32 server: Arc<dyn DirectoryEntry>,
33 get_client: GetClient,
34) where
35Marker: ProtocolMarker,
36 GetClient: FnOnce(Marker::Proxy) -> GetClientRes,
37 GetClientRes: Future<Output = ()>,
38{
39 test_server_client::<Marker, _, _>(flags, server, get_client).run();
40}
4142/// Similar to [`run_server_client()`] but does not automatically connect the server and the
43/// client. The client is expected to connect to the server on it's own. Otherwise behaviour is
44/// the same as for [`run_server_client()`], except the executor is not implicit, but is specified
45/// via the `exec` argument.
46///
47/// For example, this way the client can control when the first `open()` call will happen on the
48/// server and/or perform additional `open()` calls on the server. With [`run_server_client()`]
49/// the first call to `open()` is already finished by the time client code starts running.
50///
51/// This is the second most common case for the test execution, and, similarly to
52/// [`run_server_client`] it is actually just forwarding to [`test_client()`] followed by a
53/// [`AsyncClientTestParams::run()`] call.
54pub fn run_client<GetClient, GetClientRes>(exec: TestExecutor, get_client: GetClient)
55where
56GetClient: FnOnce() -> GetClientRes,
57 GetClientRes: Future<Output = ()>,
58{
59 test_client(get_client).exec(exec).run();
60}
6162/// [`test_server_client`] and [`test_client`] allow for a "coordinator" closure - something
63/// responsible for coordinating test execution. In particular in certain tests it is important to
64/// make sure all the operations have completed before running the next portion of the test.
65///
66/// This type represents a controller that the coordinator uses to achieve this effect.
67/// Coordinator will use `oneshot` or `mpsc` channels to synchronize with the test execution and
68/// will call [`TestController::run_until_stalled()`] to separate portions of the test, optinally
69/// followed by [`TestController::run_until_complete()`]. In any case, [`TestController`] will
70/// ensure that the test execution
71/// finishes completely, not just stalls.
72pub struct TestController<'test_refs> {
73 exec: TestExecutor,
74 client: Pin<Box<dyn Future<Output = ()> + 'test_refs>>,
75}
7677impl<'test_refs> TestController<'test_refs> {
78fn new(exec: TestExecutor, client: Pin<Box<dyn Future<Output = ()> + 'test_refs>>) -> Self {
79Self { exec, client }
80 }
8182/// Runs the client test code until it is stalled. Will panic if the test code runs to
83 /// completion.
84#[cfg(target_os = "fuchsia")]
85pub fn run_until_stalled(&mut self) {
86// TODO: How to limit the execution time? run_until_stalled() does not trigger timers, so
87 // I can not do this:
88 //
89 // let timeout = zx::MonotonicDuration::from_millis(300);
90 // let client = self.client.on_timeout(
91 // timeout.after_now(),
92 // || panic!("Test did not finish in {}ms", timeout.millis()));
9394let res = self.exec.run_until_stalled(&mut self.client);
95assert_eq!(res, Poll::Pending, "Test was not expected to complete");
96 }
9798/// Runs the client test code to completion. As this will consume the controller, this method
99 /// can only be called last. Note that the controller will effectively run this methods for
100 /// you when it is dropped, if you do not do it explicitly.
101pub fn run_until_complete(self) {
102// [`Drop::drop`] will actually do the final execution, when `self` is dropped.
103}
104}
105106#[cfg(target_os = "fuchsia")]
107impl<'test_refs> Drop for TestController<'test_refs> {
108fn drop(&mut self) {
109// See `run_until_stalled` above the a comment about timeouts.
110let res = self.exec.run_until_stalled(&mut self.client);
111assert_eq!(res, Poll::Ready(()), "Test did not complete");
112 }
113}
114115#[cfg(not(target_os = "fuchsia"))]
116impl<'test_refs> Drop for TestController<'test_refs> {
117fn drop(&mut self) {
118self.exec.run_singlethreaded(&mut self.client);
119 }
120}
121122/// Collects a basic required set of parameters for a server/client test. Additional parameters
123/// can be specified using `exec` and `coordinator` methods via a builder patter.
124/// Actual execution of the test happen when [`AsyncServerClientTestParams::run()`] method is
125/// invoked.
126pub fn test_server_client<'test_refs, Marker, GetClient, GetClientRes>(
127 flags: fio::OpenFlags,
128 server: Arc<dyn DirectoryEntry>,
129 get_client: GetClient,
130) -> AsyncServerClientTestParams<'test_refs, Marker>
131where
132Marker: ProtocolMarker,
133 GetClient: FnOnce(Marker::Proxy) -> GetClientRes + 'test_refs,
134 GetClientRes: Future<Output = ()> + 'test_refs,
135{
136 AsyncServerClientTestParams {
137 exec: None,
138 flags,
139 server,
140 get_client: Box::new(move |proxy| Box::pin(get_client(proxy))),
141 coordinator: None,
142 }
143}
144145/// Collects a basic required set of parameters for a client-only test. Additional parameteres can
146/// be specified using `exec`, and `coordinator` methods via a builder patter. Actual
147/// execution of the test happen when [`AsyncClientTestParams::run()`] method is invoked.
148pub fn test_client<'test_refs, GetClient, GetClientRes>(
149 get_client: GetClient,
150) -> AsyncClientTestParams<'test_refs>
151where
152GetClient: FnOnce() -> GetClientRes + 'test_refs,
153 GetClientRes: Future<Output = ()> + 'test_refs,
154{
155 AsyncClientTestParams {
156 exec: None,
157 get_client: Box::new(move || Box::pin(get_client())),
158 coordinator: None,
159 }
160}
161162/// A helper that holds all the parameters necessary to run an async test with a server and a
163/// client.
164#[must_use = "Need to call `run` to actually run the test"]
165pub struct AsyncServerClientTestParams<'test_refs, Marker>
166where
167Marker: ProtocolMarker,
168{
169 exec: Option<TestExecutor>,
170 flags: fio::OpenFlags,
171 server: Arc<dyn DirectoryEntry>,
172 get_client: Box<
173dyn FnOnce(Marker::Proxy) -> Pin<Box<dyn Future<Output = ()> + 'test_refs>> + 'test_refs,
174 >,
175 coordinator: Option<Box<dyn FnOnce(TestController<'_>) + 'test_refs>>,
176}
177178/// A helper that holds all the parameters necessary to run an async client-only test.
179#[must_use = "Need to call `run` to actually run the test"]
180pub struct AsyncClientTestParams<'test_refs> {
181 exec: Option<TestExecutor>,
182 get_client: Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'test_refs>> + 'test_refs>,
183 coordinator: Option<Box<dyn FnOnce(TestController<'_>) + 'test_refs>>,
184}
185186macro_rules! field_setter {
187 ($name:ident, $type:ty) => {
188pub fn $name(mut self, $name: $type) -> Self {
189assert!(self.$name.is_none(), concat!("`", stringify!($name), "` is already set"));
190self.$name = Some($name);
191self
192}
193 };
194}
195196impl<'test_refs, Marker> AsyncServerClientTestParams<'test_refs, Marker>
197where
198Marker: ProtocolMarker,
199{
200field_setter!(exec, TestExecutor);
201202pub fn coordinator(
203mut self,
204 get_coordinator: impl FnOnce(TestController<'_>) + 'test_refs,
205 ) -> Self {
206assert!(self.coordinator.is_none(), "`coordinator` is already set");
207self.coordinator = Some(Box::new(get_coordinator));
208self
209}
210211/// Runs the test based on the parameters specified in the [`test_server_client`] and other
212 /// method calls.
213pub fn run(self) {
214let exec = self.exec.unwrap_or_else(|| TestExecutor::new());
215216let (client_proxy, server_end) = create_proxy::<Marker>();
217218let root = Simple::new();
219 root.add_entry("server", self.server).unwrap();
220221 root.open(
222 ExecutionScope::new(),
223self.flags,
224 Path::validate_and_split("server").unwrap(),
225 server_end.into_channel().into(),
226 );
227228let client = (self.get_client)(client_proxy);
229230let coordinator = self.coordinator.unwrap_or_else(|| Box::new(|_controller| ()));
231232let controller = TestController::new(exec, client);
233 coordinator(controller);
234 }
235}
236237impl<'test_refs> AsyncClientTestParams<'test_refs> {
238field_setter!(exec, TestExecutor);
239240pub fn coordinator(
241mut self,
242 get_coordinator: impl FnOnce(TestController<'_>) + 'test_refs,
243 ) -> Self {
244assert!(self.coordinator.is_none(), "`coordinator` is already set");
245self.coordinator = Some(Box::new(get_coordinator));
246self
247}
248249/// Runs the test based on the parameters specified in the [`test_server_client`] and other
250 /// method calls.
251pub fn run(self) {
252let exec = self.exec.unwrap_or_else(|| TestExecutor::new());
253254let client = (self.get_client)();
255256let coordinator = self.coordinator.unwrap_or_else(|| Box::new(|_controller| ()));
257258let controller = TestController::new(exec, client);
259 coordinator(controller);
260 }
261}