1use anyhow::{Context, Error, format_err};
6use fidl::endpoints;
7use fidl::endpoints::{ClientEnd, Proxy};
8use fidl_fuchsia_component as fcomponent;
9use fidl_fuchsia_component_decl as fdecl;
10use fidl_fuchsia_component_runner as fcrunner;
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_test::CaseListenerRequest::Finished;
13use fidl_fuchsia_test::RunListenerRequest::{OnFinished, OnTestCaseStarted};
14use fidl_fuchsia_test::{Invocation, Result_ as TestResult, RunListenerRequestStream};
15use fidl_fuchsia_test_manager as ftest_manager;
16use fuchsia_async as fasync;
17use fuchsia_component::client::{self, connect_to_protocol_at_dir_root};
18use fuchsia_runtime::job_default;
19use futures::channel::mpsc;
20use futures::prelude::*;
21use namespace::{Namespace, NamespaceError};
22use std::collections::HashMap;
23use std::sync::Arc;
24use test_manager_test_lib::RunEvent;
25use test_runners_lib::elf::{BuilderArgs, Component};
26
27#[derive(PartialEq, Debug)]
28pub enum ListenerEvent {
29 StartTest(String),
30 FinishTest(String, TestResult),
31 FinishAllTests,
32}
33
34fn get_ord_index_and_name(event: &ListenerEvent) -> (usize, &str) {
35 match event {
36 ListenerEvent::StartTest(name) => (0, name),
37 ListenerEvent::FinishTest(name, _) => (1, name),
38 ListenerEvent::FinishAllTests => (2, ""),
39 }
40}
41
42impl Ord for ListenerEvent {
44 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
45 let (s_index, s_test_name) = get_ord_index_and_name(self);
46 let (o_index, o_test_name) = get_ord_index_and_name(other);
47 if s_test_name == o_test_name || s_index == 2 || o_index == 2 {
48 return s_index.cmp(&o_index);
49 }
50 return s_test_name.cmp(&o_test_name);
51 }
52}
53
54pub fn assert_event_ord(events: &Vec<ListenerEvent>) {
57 let mut tests = HashMap::new();
58 let mut all_finish = false;
59 for event in events {
60 assert!(!all_finish, "got FinishAllTests event twice: {:#?}", events);
61 match event {
62 ListenerEvent::StartTest(name) => {
63 assert!(
64 !tests.contains_key(&name),
65 "Multiple StartTest for test {}: {:#?}",
66 name,
67 events
68 );
69 tests.insert(name, false);
70 }
71 ListenerEvent::FinishTest(name, _) => {
72 assert!(
73 tests.contains_key(&name),
74 "Got finish before start event for test {}: {:#?}",
75 name,
76 events
77 );
78 assert!(
79 !tests.insert(name, true).unwrap(),
80 "Multiple FinishTest for test {}: {:#?}",
81 name,
82 events
83 );
84 }
85 ListenerEvent::FinishAllTests => {
86 all_finish = true;
87 }
88 }
89 }
90}
91
92impl PartialOrd for ListenerEvent {
93 fn partial_cmp(&self, other: &ListenerEvent) -> Option<core::cmp::Ordering> {
94 Some(self.cmp(other))
95 }
96}
97
98impl Eq for ListenerEvent {}
99
100impl ListenerEvent {
101 pub fn start_test(name: &str) -> ListenerEvent {
102 ListenerEvent::StartTest(name.to_string())
103 }
104 pub fn finish_test(name: &str, test_result: TestResult) -> ListenerEvent {
105 ListenerEvent::FinishTest(name.to_string(), test_result)
106 }
107 pub fn finish_all_test() -> ListenerEvent {
108 ListenerEvent::FinishAllTests
109 }
110}
111
112impl Clone for ListenerEvent {
113 fn clone(&self) -> Self {
114 match self {
115 ListenerEvent::StartTest(name) => ListenerEvent::start_test(name),
116 ListenerEvent::FinishTest(name, test_result) => ListenerEvent::finish_test(
117 name,
118 TestResult { status: test_result.status.clone(), ..Default::default() },
119 ),
120 ListenerEvent::FinishAllTests => ListenerEvent::finish_all_test(),
121 }
122 }
123}
124
125pub async fn collect_listener_event(
127 mut listener: RunListenerRequestStream,
128) -> Result<Vec<ListenerEvent>, Error> {
129 let mut ret = vec![];
130 let mut loggers = vec![];
132 while let Some(result_event) = listener.try_next().await? {
133 match result_event {
134 OnTestCaseStarted { invocation, std_handles, listener, .. } => {
135 let name = invocation.name.unwrap();
136 ret.push(ListenerEvent::StartTest(name.clone()));
137 loggers.push(std_handles);
138 let mut listener = listener.into_stream();
139 #[allow(clippy::never_loop)]
142 while let Some(result) = listener.try_next().await? {
143 match result {
144 Finished { result, .. } => {
145 ret.push(ListenerEvent::FinishTest(name, result));
146 break;
147 }
148 }
149 }
150 }
151 OnFinished { .. } => {
152 ret.push(ListenerEvent::FinishAllTests);
153 break;
154 }
155 }
156 }
157 Ok(ret)
158}
159
160pub fn names_to_invocation(names: Vec<&str>) -> Vec<Invocation> {
162 names
163 .iter()
164 .map(|s| Invocation { name: Some(s.to_string()), tag: None, ..Default::default() })
165 .collect()
166}
167
168pub async fn process_events(
170 suite_instance: test_manager_test_lib::SuiteRunInstance,
171 exclude_empty_logs: bool,
172) -> Result<(Vec<RunEvent>, Vec<String>), Error> {
173 let (sender, mut recv) = mpsc::channel(1);
174 let execution_task = fasync::Task::spawn(async move {
175 suite_instance.collect_events_with_watch(sender, true, true).await
176 });
177 let mut events = vec![];
178 let mut log_tasks = vec![];
179 let mut buffered_stdout = HashMap::new();
180 let mut buffered_stderr = HashMap::new();
181 while let Some(event) = recv.next().await {
182 match event.payload {
183 test_manager_test_lib::SuiteEventPayload::RunEvent(RunEvent::CaseStdout {
184 name,
185 stdout_message,
186 }) => {
187 let strings = line_buffer_std_message(
188 &name,
189 stdout_message,
190 exclude_empty_logs,
191 &mut buffered_stdout,
192 );
193 for s in strings {
194 events.push(RunEvent::case_stdout(name.clone(), s));
195 }
196 }
197 test_manager_test_lib::SuiteEventPayload::RunEvent(RunEvent::CaseStderr {
198 name,
199 stderr_message,
200 }) => {
201 let strings = line_buffer_std_message(
202 &name,
203 stderr_message,
204 exclude_empty_logs,
205 &mut buffered_stderr,
206 );
207 for s in strings {
208 events.push(RunEvent::case_stderr(name.clone(), s));
209 }
210 }
211 test_manager_test_lib::SuiteEventPayload::RunEvent(e) => events.push(e),
212 test_manager_test_lib::SuiteEventPayload::SuiteLog { log_stream } => {
213 let t = fasync::Task::spawn(log_stream.collect::<Vec<_>>());
214 log_tasks.push(t);
215 }
216 test_manager_test_lib::SuiteEventPayload::TestCaseLog { .. } => {
217 panic!("not supported yet!")
218 }
219 test_manager_test_lib::SuiteEventPayload::DebugData { .. } => {
220 panic!("not supported yet!")
221 }
222 }
223 }
224 execution_task.await.context("test execution failed")?;
225
226 for (name, log) in buffered_stdout {
227 events.push(RunEvent::case_stdout(name, log));
228 }
229 for (name, log) in buffered_stderr {
230 events.push(RunEvent::case_stderr(name, log));
231 }
232
233 let mut collected_logs = vec![];
234 for t in log_tasks {
235 let logs = t.await;
236 for log_result in logs {
237 let log = log_result?;
238 collected_logs.push(log.msg().unwrap().to_string());
239 }
240 }
241
242 Ok((events, collected_logs))
243}
244
245fn line_buffer_std_message(
247 name: &str,
248 std_message: String,
249 exclude_empty_logs: bool,
250 buffer: &mut HashMap<String, String>,
251) -> Vec<String> {
252 let mut ret = vec![];
253 let logs = std_message.split("\n");
254 let mut logs = logs.collect::<Vec<&str>>();
255 let mut last_incomplete_line = logs.pop();
257 if std_message.as_bytes().last() == Some(&b'\n') {
258 last_incomplete_line = None;
259 }
260 for log in logs {
261 if exclude_empty_logs && log.len() == 0 {
262 continue;
263 }
264 let mut msg = log.to_owned();
265 if let Some(prev_log) = buffer.remove(name) {
268 msg = format!("{}{}", prev_log, msg);
269 }
270 ret.push(msg);
271 }
272 if let Some(log) = last_incomplete_line {
273 let mut log = log.to_owned();
274 if let Some(prev_log) = buffer.remove(name) {
275 log = format!("{}{}", prev_log, log);
276 }
277 buffer.insert(name.to_string(), log);
278 }
279 ret
280}
281
282pub async fn connect_to_suite_runner() -> Result<ftest_manager::SuiteRunnerProxy, Error> {
284 let realm = client::connect_to_protocol::<fcomponent::RealmMarker>()
285 .context("could not connect to Realm service")?;
286
287 let child_ref = fdecl::ChildRef { name: "test_manager".to_owned(), collection: None };
288 let (dir, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>();
289 realm
290 .open_exposed_dir(&child_ref, server_end)
291 .await
292 .context("open_exposed_dir fidl call failed for test manager")?
293 .map_err(|e| format_err!("failed to create test manager: {:?}", e))?;
294
295 connect_to_protocol_at_dir_root::<ftest_manager::SuiteRunnerMarker>(&dir)
296 .context("failed to open test suite runner service")
297}
298
299fn create_ns_from_current_ns(
300 dir_paths: Vec<(&str, fio::Flags)>,
301) -> Result<Namespace, NamespaceError> {
302 let mut ns = vec![];
303 for (path, permission) in dir_paths {
304 let chan = fuchsia_fs::directory::open_in_namespace(path, permission)
305 .unwrap()
306 .into_channel()
307 .unwrap()
308 .into_zx_channel();
309 let handle = ClientEnd::new(chan);
310
311 ns.push(fcrunner::ComponentNamespaceEntry {
312 path: Some(path.to_string()),
313 directory: Some(handle),
314 ..Default::default()
315 });
316 }
317 Namespace::try_from(ns)
318}
319
320pub async fn test_component(
322 url: &str,
323 name: &str,
324 binary: &str,
325 args: Vec<String>,
326) -> Result<Arc<Component>, Error> {
327 let ns = create_ns_from_current_ns(vec![
328 ("/pkg", fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE),
329 ("/svc", fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE),
331 ])?;
332 let component = Component::create_for_tests(BuilderArgs {
333 url: url.to_string(),
334 name: name.to_string(),
335 binary: binary.to_string(),
336 args,
337 environ: None,
338 ns,
339 job: job_default().duplicate_handle(zx::Rights::SAME_RIGHTS)?,
340 options: zx::ProcessOptions::empty(),
341 config: None,
342 })
343 .await?;
344 Ok(Arc::new(component))
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350 use fidl_fuchsia_test::Status;
351 use maplit::hashmap;
352
353 #[test]
354 fn test_ordering_by_enum() {
355 let expected_events = vec![
356 ListenerEvent::start_test("a"),
357 ListenerEvent::finish_test(
358 "a",
359 TestResult { status: Some(Status::Passed), ..Default::default() },
360 ),
361 ListenerEvent::finish_all_test(),
362 ];
363
364 let mut events = expected_events.clone();
365 events.reverse();
366
367 assert_ne!(events, expected_events);
368 events.sort();
369 assert_eq!(events, expected_events);
370 }
371
372 #[test]
373 fn test_ordering_by_test_name() {
374 let mut events = vec![
375 ListenerEvent::start_test("b"),
376 ListenerEvent::start_test("a"),
377 ListenerEvent::finish_test(
378 "a",
379 TestResult { status: Some(Status::Passed), ..Default::default() },
380 ),
381 ListenerEvent::start_test("c"),
382 ListenerEvent::finish_test(
383 "b",
384 TestResult { status: Some(Status::Passed), ..Default::default() },
385 ),
386 ListenerEvent::finish_test(
387 "c",
388 TestResult { status: Some(Status::Passed), ..Default::default() },
389 ),
390 ListenerEvent::finish_all_test(),
391 ];
392
393 let expected_events = vec![
394 ListenerEvent::start_test("a"),
395 ListenerEvent::finish_test(
396 "a",
397 TestResult { status: Some(Status::Passed), ..Default::default() },
398 ),
399 ListenerEvent::start_test("b"),
400 ListenerEvent::finish_test(
401 "b",
402 TestResult { status: Some(Status::Passed), ..Default::default() },
403 ),
404 ListenerEvent::start_test("c"),
405 ListenerEvent::finish_test(
406 "c",
407 TestResult { status: Some(Status::Passed), ..Default::default() },
408 ),
409 ListenerEvent::finish_all_test(),
410 ];
411 events.sort();
412 assert_eq!(events, expected_events);
413 }
414
415 #[test]
416 fn line_buffer_std_message_incomplete_line() {
417 let mut buf = HashMap::new();
418 buf.insert("test".to_string(), "some_prev_text".to_string());
419 let strings = line_buffer_std_message("test", "a \nb\nc\nd".into(), false, &mut buf);
420 assert_eq!(strings, vec!["some_prev_texta ".to_owned(), "b".to_owned(), "c".to_owned()]);
421 assert_eq!(buf, hashmap! {"test".to_string() => "d".to_string()});
422 }
423
424 #[test]
425 fn line_buffer_std_message_complete_line() {
426 let mut buf = HashMap::new();
427 buf.insert("test".to_string(), "some_prev_text".to_string());
428 let strings = line_buffer_std_message("test", "a \nb\nc\n".into(), false, &mut buf);
429 assert_eq!(strings, vec!["some_prev_texta ".to_owned(), "b".to_owned(), "c".to_owned()]);
430 assert_eq!(buf.len(), 0);
431
432 let strings = line_buffer_std_message("test", "d \ne\nf\n".into(), false, &mut buf);
434 assert_eq!(strings, vec!["d ".to_owned(), "e".to_owned(), "f".to_owned()]);
435 assert_eq!(buf.len(), 0);
436 }
437}