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.
45use 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;
1314/// 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(
18mut stream: DetectControllerRequestStream,
19 detection_runner: Arc<Mutex<impl RunsDetection>>,
20) {
21while let Some(Ok(request)) = stream.next().await {
22match request {
23 DetectControllerRequest::EnterTestMode { test_controller, responder } => {
24let mut detection_runner = detection_runner.lock().await;
25let _: Result<_, _> = responder.send();
26 run_test_case_service(test_controller.into_stream(), &mut *detection_runner).await;
27 }
28 }
29 }
30}
3132async fn run_test_case_service(
33mut stream: TestCaseControllerRequestStream,
34 detection_runner: &mut impl RunsDetection,
35) {
36while let Some(Ok(request)) = stream.next().await {
37let TestCaseControllerRequest::RunDefaultCycle { responder } = request;
38 detection_runner.run_detection(DetectionOpts { cpu_test: true }).await;
39let _: Result<_, _> = responder.send();
40 }
41}
4243#[cfg(test)]
44mod test {
45use super::*;
46use crate::Stats;
47use fidl_fuchsia_diagnostics_test::{
48 DetectControllerMarker, DetectControllerProxy, TestCaseControllerMarker,
49 };
50use fuchsia_async::{Scope, TestExecutor};
51use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
5253struct TestDetectionRunner {
54 run_sender: UnboundedSender<()>,
55 stats: Stats,
56 }
5758impl TestDetectionRunner {
59fn create() -> (Arc<Mutex<Self>>, UnboundedReceiver<()>) {
60let (run_sender, run_receiver) = mpsc::unbounded();
61 (Arc::new(Mutex::new(Self { run_sender, stats: Stats::default() })), run_receiver)
62 }
63 }
6465impl RunsDetection for TestDetectionRunner {
66async fn run_detection(&mut self, _opts: DetectionOpts) {
67self.run_sender.unbounded_send(()).unwrap();
68 }
6970fn stats(&self) -> &Stats {
71&self.stats
72 }
73 }
7475trait CountsMessages {
76async fn expect_n_messages(&mut self, n: usize);
77 }
7879impl CountsMessages for UnboundedReceiver<()> {
80async fn expect_n_messages(&mut self, n: usize) {
81for _ in 0..n {
82assert_eq!(self.next().await, Some(()));
83 }
84assert!(self.try_next().is_err());
85 }
86 }
8788fn get_controller_proxy(
89 scope: &Scope,
90 detection_runner: Arc<Mutex<impl RunsDetection + Send + 'static>>,
91 ) -> DetectControllerProxy {
92let (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 }
9798/// 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)]
101async fn exercise_server() {
102let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
103let scope = Scope::new();
104let controller_proxy = get_controller_proxy(&scope, detection_runner);
105// Create the tearoff for a test-mode session
106let (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 }
118119/// 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)]
122async fn ensure_non_overlapping_sessions() {
123let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
124let scope = Scope::new();
125let 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.
128let (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();
131let controller_proxy_clone = controller_proxy.clone();
132// The second test-mode session should run, but only after the first is done.
133scope.spawn(async move {
134let (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.
141let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
142// Yup, no tests ran yet - right?
143run_receiver.expect_n_messages(0).await;
144// End the first session. Now the second session should run.
145std::mem::drop(first_test_case_proxy);
146 run_receiver.expect_n_messages(1).await;
147 std::mem::drop(controller_proxy);
148 scope.await;
149 }
150151/// 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)]
154async fn ensure_non_overlapping_connections() {
155let (detection_runner, mut run_receiver) = TestDetectionRunner::create();
156let scope = Scope::new();
157let 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.
160let (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?
163let 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.
166controller_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.
172scope.spawn(async move {
173let (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.
180let _ = 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().)
184run_receiver.expect_n_messages(0).await;
185// End the first session. Now the second session (on the second controller) should run.
186std::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)
190std::mem::drop(controller_proxy);
191 scope.await;
192 }
193}