vfs/test_utils/
run.rs

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.
4
5//! Utilities to run asynchronous tests that use `pseudo-fs` objects.
6
7use 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;
13
14use 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;
20
21#[cfg(target_os = "fuchsia")]
22use std::task::Poll;
23
24/// 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
35    Marker: ProtocolMarker,
36    GetClient: FnOnce(Marker::Proxy) -> GetClientRes,
37    GetClientRes: Future<Output = ()>,
38{
39    test_server_client::<Marker, _, _>(flags, server, get_client).run();
40}
41
42/// 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
56    GetClient: FnOnce() -> GetClientRes,
57    GetClientRes: Future<Output = ()>,
58{
59    test_client(get_client).exec(exec).run();
60}
61
62/// [`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}
76
77impl<'test_refs> TestController<'test_refs> {
78    fn new(exec: TestExecutor, client: Pin<Box<dyn Future<Output = ()> + 'test_refs>>) -> Self {
79        Self { exec, client }
80    }
81
82    /// Runs the client test code until it is stalled.  Will panic if the test code runs to
83    /// completion.
84    #[cfg(target_os = "fuchsia")]
85    pub 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()));
93
94        let res = self.exec.run_until_stalled(&mut self.client);
95        assert_eq!(res, Poll::Pending, "Test was not expected to complete");
96    }
97
98    /// 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.
101    pub fn run_until_complete(self) {
102        // [`Drop::drop`] will actually do the final execution, when `self` is dropped.
103    }
104}
105
106#[cfg(target_os = "fuchsia")]
107impl<'test_refs> Drop for TestController<'test_refs> {
108    fn drop(&mut self) {
109        // See `run_until_stalled` above the a comment about timeouts.
110        let res = self.exec.run_until_stalled(&mut self.client);
111        assert_eq!(res, Poll::Ready(()), "Test did not complete");
112    }
113}
114
115#[cfg(not(target_os = "fuchsia"))]
116impl<'test_refs> Drop for TestController<'test_refs> {
117    fn drop(&mut self) {
118        self.exec.run_singlethreaded(&mut self.client);
119    }
120}
121
122/// 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
132    Marker: 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}
144
145/// 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
152    GetClient: 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}
161
162/// 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
167    Marker: ProtocolMarker,
168{
169    exec: Option<TestExecutor>,
170    flags: fio::OpenFlags,
171    server: Arc<dyn DirectoryEntry>,
172    get_client: Box<
173        dyn FnOnce(Marker::Proxy) -> Pin<Box<dyn Future<Output = ()> + 'test_refs>> + 'test_refs,
174    >,
175    coordinator: Option<Box<dyn FnOnce(TestController<'_>) + 'test_refs>>,
176}
177
178/// 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}
185
186macro_rules! field_setter {
187    ($name:ident, $type:ty) => {
188        pub fn $name(mut self, $name: $type) -> Self {
189            assert!(self.$name.is_none(), concat!("`", stringify!($name), "` is already set"));
190            self.$name = Some($name);
191            self
192        }
193    };
194}
195
196impl<'test_refs, Marker> AsyncServerClientTestParams<'test_refs, Marker>
197where
198    Marker: ProtocolMarker,
199{
200    field_setter!(exec, TestExecutor);
201
202    pub fn coordinator(
203        mut self,
204        get_coordinator: impl FnOnce(TestController<'_>) + 'test_refs,
205    ) -> Self {
206        assert!(self.coordinator.is_none(), "`coordinator` is already set");
207        self.coordinator = Some(Box::new(get_coordinator));
208        self
209    }
210
211    /// Runs the test based on the parameters specified in the [`test_server_client`] and other
212    /// method calls.
213    pub fn run(self) {
214        let exec = self.exec.unwrap_or_else(|| TestExecutor::new());
215
216        let (client_proxy, server_end) = create_proxy::<Marker>();
217
218        let root = Simple::new();
219        root.add_entry("server", self.server).unwrap();
220
221        root.open(
222            ExecutionScope::new(),
223            self.flags,
224            Path::validate_and_split("server").unwrap(),
225            server_end.into_channel().into(),
226        );
227
228        let client = (self.get_client)(client_proxy);
229
230        let coordinator = self.coordinator.unwrap_or_else(|| Box::new(|_controller| ()));
231
232        let controller = TestController::new(exec, client);
233        coordinator(controller);
234    }
235}
236
237impl<'test_refs> AsyncClientTestParams<'test_refs> {
238    field_setter!(exec, TestExecutor);
239
240    pub fn coordinator(
241        mut self,
242        get_coordinator: impl FnOnce(TestController<'_>) + 'test_refs,
243    ) -> Self {
244        assert!(self.coordinator.is_none(), "`coordinator` is already set");
245        self.coordinator = Some(Box::new(get_coordinator));
246        self
247    }
248
249    /// Runs the test based on the parameters specified in the [`test_server_client`] and other
250    /// method calls.
251    pub fn run(self) {
252        let exec = self.exec.unwrap_or_else(|| TestExecutor::new());
253
254        let client = (self.get_client)();
255
256        let coordinator = self.coordinator.unwrap_or_else(|| Box::new(|_controller| ()));
257
258        let controller = TestController::new(exec, client);
259        coordinator(controller);
260    }
261}