detect/
test_invoker.rs

1// Copyright 2024 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
5use crate::{DetectionOpts, RunsDetection};
6use fidl_fuchsia_diagnostics_test::{
7    DetectControllerRequest, DetectControllerRequestStream, TestCaseControllerRequest,
8    TestCaseControllerRequestStream,
9};
10use futures::lock::Mutex;
11use futures::StreamExt;
12use std::sync::Arc;
13
14/// This file serves the fuchsia.diagnostics.test.DetectController protocol,
15/// which is used for performance testing.
16/// This file is unrelated to lib.rs::Mode::Testing which is used for integration testing.
17pub(crate) async fn run_test_service(
18    mut stream: DetectControllerRequestStream,
19    detection_runner: Arc<Mutex<impl RunsDetection>>,
20) {
21    while let Some(Ok(request)) = stream.next().await {
22        match request {
23            DetectControllerRequest::EnterTestMode { test_controller, responder } => {
24                let mut detection_runner = detection_runner.lock().await;
25                let _: Result<_, _> = responder.send();
26                run_test_case_service(test_controller.into_stream(), &mut *detection_runner).await;
27            }
28        }
29    }
30}
31
32async fn run_test_case_service(
33    mut stream: TestCaseControllerRequestStream,
34    detection_runner: &mut impl RunsDetection,
35) {
36    while let Some(Ok(request)) = stream.next().await {
37        let TestCaseControllerRequest::RunDefaultCycle { responder } = request;
38        detection_runner.run_detection(DetectionOpts { cpu_test: true }).await;
39        let _: Result<_, _> = responder.send();
40    }
41}
42
43#[cfg(test)]
44mod test {
45    use super::*;
46    use crate::Stats;
47    use fidl_fuchsia_diagnostics_test::{
48        DetectControllerMarker, DetectControllerProxy, TestCaseControllerMarker,
49    };
50    use fuchsia_async::{Scope, TestExecutor};
51    use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
52
53    struct TestDetectionRunner {
54        run_sender: UnboundedSender<()>,
55        stats: Stats,
56    }
57
58    impl TestDetectionRunner {
59        fn create() -> (Arc<Mutex<Self>>, UnboundedReceiver<()>) {
60            let (run_sender, run_receiver) = mpsc::unbounded();
61            (Arc::new(Mutex::new(Self { run_sender, stats: Stats::default() })), run_receiver)
62        }
63    }
64
65    impl RunsDetection for TestDetectionRunner {
66        async fn run_detection(&mut self, _opts: DetectionOpts) {
67            self.run_sender.unbounded_send(()).unwrap();
68        }
69
70        fn stats(&self) -> &Stats {
71            &self.stats
72        }
73    }
74
75    trait CountsMessages {
76        async fn expect_n_messages(&mut self, n: usize);
77    }
78
79    impl CountsMessages for UnboundedReceiver<()> {
80        async fn expect_n_messages(&mut self, n: usize) {
81            for _ in 0..n {
82                assert_eq!(self.next().await, Some(()));
83            }
84            assert!(self.try_next().is_err());
85        }
86    }
87
88    fn get_controller_proxy(
89        scope: &Scope,
90        detection_runner: Arc<Mutex<impl RunsDetection + Send + 'static>>,
91    ) -> DetectControllerProxy {
92        let (client_end, server_end) =
93            fidl::endpoints::create_endpoints::<DetectControllerMarker>();
94        scope.spawn(run_test_service(server_end.into_stream(), detection_runner));
95        client_end.into_proxy()
96    }
97
98    /// Make sure that the FIDL server doesn't hang or crash, and does call run_detection
99    /// when it should.
100    #[fuchsia::test(allow_stalls = false)]
101    async fn exercise_server() {
102        let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
103        let scope = Scope::new();
104        let controller_proxy = get_controller_proxy(&scope, detection_runner);
105        // Create the tearoff for a test-mode session
106        let (test_case_proxy, first_test_case_server_end) =
107            fidl::endpoints::create_proxy::<TestCaseControllerMarker>();
108        controller_proxy.enter_test_mode(first_test_case_server_end).await.unwrap();
109        run_receiver.expect_n_messages(0).await;
110        test_case_proxy.run_default_cycle().await.unwrap();
111        run_receiver.expect_n_messages(1).await;
112        test_case_proxy.run_default_cycle().await.unwrap();
113        run_receiver.expect_n_messages(1).await;
114        std::mem::drop(test_case_proxy);
115        std::mem::drop(controller_proxy);
116        scope.await;
117    }
118
119    /// Make sure that the program doesn't run a test in a second session while
120    /// a first session is still open.
121    #[fuchsia::test(allow_stalls = false)]
122    async fn ensure_non_overlapping_sessions() {
123        let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
124        let scope = Scope::new();
125        let controller_proxy = get_controller_proxy(&scope, detection_runner);
126        // Create the tearoff for a test-mode session. This test should demonstrate correct behavior
127        // even though we don't run a cycle on this proxy.
128        let (first_test_case_proxy, first_test_case_server_end) =
129            fidl::endpoints::create_proxy::<TestCaseControllerMarker>();
130        controller_proxy.enter_test_mode(first_test_case_server_end).await.unwrap();
131        let controller_proxy_clone = controller_proxy.clone();
132        // The second test-mode session should run, but only after the first is done.
133        scope.spawn(async move {
134            let (second_test_case_proxy, second_test_case_server_end) =
135                fidl::endpoints::create_proxy::<TestCaseControllerMarker>();
136            controller_proxy_clone.enter_test_mode(second_test_case_server_end).await.unwrap();
137            second_test_case_proxy.run_default_cycle().await.unwrap();
138        });
139        // Test the test-mode-lockout logic by giving the spawned second_test_case code an
140        // opportunity to run as far as it can. It shouldn't run its test cycle yet.
141        let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
142        // Yup, no tests ran yet - right?
143        run_receiver.expect_n_messages(0).await;
144        // End the first session. Now the second session should run.
145        std::mem::drop(first_test_case_proxy);
146        run_receiver.expect_n_messages(1).await;
147        std::mem::drop(controller_proxy);
148        scope.await;
149    }
150
151    /// Make sure the program doesn't run a test in a second session while a first session is open,
152    /// even if the sessions are on different connections.
153    #[fuchsia::test(allow_stalls = false)]
154    async fn ensure_non_overlapping_connections() {
155        let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
156        let scope = Scope::new();
157        let controller_proxy = get_controller_proxy(&scope, detection_runner.clone());
158        // Create the tearoff for a test-mode session. This test should demonstrate correct behavior
159        // even though we don't run a cycle on this proxy.
160        let (first_test_case_proxy, first_test_case_server_end) =
161            fidl::endpoints::create_proxy::<TestCaseControllerMarker>();
162        // We've had controller proxy. What about second controller proxy?
163        let second_controller_proxy = get_controller_proxy(&scope, detection_runner);
164        // Second controller connected, but it's not in test mode, so first controller should work
165        // normally.
166        controller_proxy.enter_test_mode(first_test_case_server_end).await.unwrap();
167        run_receiver.expect_n_messages(0).await;
168        first_test_case_proxy.run_default_cycle().await.unwrap();
169        run_receiver.expect_n_messages(1).await;
170        // Now we'll try to run a test cycle on second controller, while first controller is still
171        // in test mode.
172        scope.spawn(async move {
173            let (second_test_case_proxy, second_test_case_server_end) =
174                fidl::endpoints::create_proxy::<TestCaseControllerMarker>();
175            second_controller_proxy.enter_test_mode(second_test_case_server_end).await.unwrap();
176            second_test_case_proxy.run_default_cycle().await.unwrap();
177        });
178        // Test the test-mode-lockout logic by giving the spawned second_test_case code an
179        // opportunity to run as far as it can. It shouldn't run its test cycle yet.
180        let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
181        // The first controller is still in test mode. The second was able to connect,
182        // but can't enter test mode to run its test cycle. (It'll be waiting for a
183        // response to enter_test_mode().)
184        run_receiver.expect_n_messages(0).await;
185        // End the first session. Now the second session (on the second controller) should run.
186        std::mem::drop(first_test_case_proxy);
187        run_receiver.expect_n_messages(1).await;
188        // Drop the controller_proxy client end of the FIDL server
189        // (second_controller_proxy was dropped in the spawned block)
190        std::mem::drop(controller_proxy);
191        scope.await;
192    }
193}