1use anyhow::{Context as _, Error, bail};
8use fidl_fuchsia_io as fio;
9use fidl_fuchsia_test_manager::{
10 self as ftest_manager, EventDetailsUnknown, SuiteControllerProxy, SuiteEvent as FidlSuiteEvent,
11 SuiteEventPayload as FidlSuiteEventPayload, SuiteEventPayloadUnknown,
12};
13use fuchsia_async as fasync;
14use futures::channel::mpsc;
15use futures::prelude::*;
16use linked_hash_map::LinkedHashMap;
17use log::*;
18use moniker::ExtendedMoniker;
19use std::collections::HashMap;
20use std::sync::Arc;
21use test_diagnostics::zstd_compress::Decoder;
22use test_diagnostics::{LogStream, collect_and_send_string_output, collect_string_from_socket};
23
24pub fn default_run_option() -> ftest_manager::RunOptions {
25 ftest_manager::RunOptions {
26 parallel: None,
27 arguments: None,
28 run_disabled_tests: Some(false),
29 timeout: None,
30 case_filters_to_run: None,
31 log_iterator: None,
32 ..Default::default()
33 }
34}
35
36pub fn default_run_suite_options() -> ftest_manager::RunSuiteOptions {
37 ftest_manager::RunSuiteOptions { run_disabled_tests: Some(false), ..Default::default() }
38}
39
40#[derive(Debug, Eq, PartialEq)]
41pub struct AttributedLog {
42 pub log: String,
43 pub moniker: ExtendedMoniker,
44}
45
46pub async fn collect_suite_events(
47 suite_instance: SuiteRunInstance,
48) -> Result<(Vec<RunEvent>, Vec<AttributedLog>), Error> {
49 let (sender, mut recv) = mpsc::channel(1);
50 let execution_task =
51 fasync::Task::spawn(async move { suite_instance.collect_events(sender).await });
52 let mut events = vec![];
53 let mut log_tasks = vec![];
54 while let Some(event) = recv.next().await {
55 match event.payload {
56 SuiteEventPayload::RunEvent(RunEvent::CaseStdout { name, mut stdout_message }) => {
57 if stdout_message.ends_with("\n") {
58 stdout_message.truncate(stdout_message.len() - 1)
59 }
60 let logs = stdout_message.split("\n");
61 for log in logs {
62 if log.contains("Note: Randomizing tests' orders with a seed of") {
65 continue;
66 }
67 events.push(RunEvent::case_stdout(name.clone(), log.to_string()));
68 }
69 }
70 SuiteEventPayload::RunEvent(RunEvent::CaseStderr { name, mut stderr_message }) => {
71 if stderr_message.ends_with("\n") {
72 stderr_message.truncate(stderr_message.len() - 1)
73 }
74 let logs = stderr_message.split("\n");
75 for log in logs {
76 events.push(RunEvent::case_stderr(name.clone(), log.to_string()));
77 }
78 }
79 SuiteEventPayload::RunEvent(e) => events.push(e),
80 SuiteEventPayload::SuiteLog { log_stream } => {
81 let t = fasync::Task::spawn(log_stream.collect::<Vec<_>>());
82 log_tasks.push(t);
83 }
84 SuiteEventPayload::TestCaseLog { .. } => {
85 panic!("not supported yet!")
86 }
87 SuiteEventPayload::DebugData { .. } => {
88 panic!("not supported yet!")
89 }
90 }
91 }
92 execution_task.await.context("test execution failed")?;
93
94 let mut collected_logs = vec![];
95 for t in log_tasks {
96 let logs = t.await;
97 for log_result in logs {
98 let log = log_result?;
99 collected_logs
100 .push(AttributedLog { log: log.msg().unwrap().to_string(), moniker: log.moniker });
101 }
102 }
103
104 Ok((events, collected_logs))
105}
106
107pub async fn collect_suite_events_with_watch(
108 suite_instance: SuiteRunInstance,
109 filter_debug_data: bool,
110 compressed_debug_data: bool,
111) -> Result<(Vec<RunEvent>, Vec<AttributedLog>), Error> {
112 let (sender, mut recv) = mpsc::channel(1);
113 let execution_task = fasync::Task::spawn(async move {
114 suite_instance
115 .collect_events_with_watch(sender, filter_debug_data, compressed_debug_data)
116 .await
117 });
118 let mut events = vec![];
119 let mut log_tasks = vec![];
120 while let Some(event) = recv.next().await {
121 match event.payload {
122 SuiteEventPayload::RunEvent(RunEvent::CaseStdout { name, mut stdout_message }) => {
123 if stdout_message.ends_with("\n") {
124 stdout_message.truncate(stdout_message.len() - 1)
125 }
126 let logs = stdout_message.split("\n");
127 for log in logs {
128 if log.contains("Note: Randomizing tests' orders with a seed of") {
131 continue;
132 }
133 events.push(RunEvent::case_stdout(name.clone(), log.to_string()));
134 }
135 }
136 SuiteEventPayload::RunEvent(RunEvent::CaseStderr { name, mut stderr_message }) => {
137 if stderr_message.ends_with("\n") {
138 stderr_message.truncate(stderr_message.len() - 1)
139 }
140 let logs = stderr_message.split("\n");
141 for log in logs {
142 events.push(RunEvent::case_stderr(name.clone(), log.to_string()));
143 }
144 }
145 SuiteEventPayload::RunEvent(e) => events.push(e),
146 SuiteEventPayload::SuiteLog { log_stream } => {
147 let t = fasync::Task::spawn(log_stream.collect::<Vec<_>>());
148 log_tasks.push(t);
149 }
150 SuiteEventPayload::TestCaseLog { .. } => {
151 panic!("not supported yet!")
152 }
153 SuiteEventPayload::DebugData { filename, socket } => {
154 events.push(RunEvent::DebugData { filename, socket })
155 }
156 }
157 }
158 execution_task.await.context("test execution failed")?;
159
160 let mut collected_logs = vec![];
161 for t in log_tasks {
162 let logs = t.await;
163 for log_result in logs {
164 let log = log_result?;
165 collected_logs
166 .push(AttributedLog { log: log.msg().unwrap().to_string(), moniker: log.moniker });
167 }
168 }
169
170 Ok((events, collected_logs))
171}
172
173pub async fn collect_string_from_socket_helper(
175 socket: fidl::Socket,
176 compressed_debug_data: bool,
177) -> Result<String, anyhow::Error> {
178 if !compressed_debug_data {
179 return collect_string_from_socket(socket).await;
180 }
181 let mut async_socket = fidl::AsyncSocket::from_socket(socket);
182 let mut buf = vec![0u8; 1024 * 32];
183
184 let (mut decoder, mut receiver) = Decoder::new();
185 let task: fasync::Task<Result<(), anyhow::Error>> = fasync::Task::spawn(async move {
186 loop {
187 let l = async_socket.read(&mut buf).await?;
188 match l {
189 0 => {
190 decoder.finish().await?;
191 break;
192 }
193 _ => {
194 decoder.decompress(&buf[..l]).await?;
195 }
196 }
197 }
198 Ok(())
199 });
200
201 let mut decompressed_data = Vec::new();
202 while let Some(chunk) = receiver.next().await {
203 decompressed_data.extend_from_slice(&chunk);
204 }
205 task.await?;
206 Ok(String::from_utf8_lossy(decompressed_data.as_slice()).into())
207}
208pub struct SuiteRunner {
210 proxy: ftest_manager::SuiteRunnerProxy,
211}
212
213impl SuiteRunner {
214 pub fn new(proxy: ftest_manager::SuiteRunnerProxy) -> Self {
216 Self { proxy }
217 }
218
219 pub fn take_proxy(self) -> ftest_manager::SuiteRunnerProxy {
220 self.proxy
221 }
222
223 pub fn start_suite_run(
225 &self,
226 test_url: &str,
227 options: ftest_manager::RunSuiteOptions,
228 ) -> Result<SuiteRunInstance, Error> {
229 let (controller_proxy, controller) = fidl::endpoints::create_proxy();
230 self.proxy.run(test_url, options, controller).context("Error starting tests")?;
231
232 return Ok(SuiteRunInstance { controller_proxy: controller_proxy.into() });
233 }
234}
235
236pub struct TestBuilder {
238 proxy: ftest_manager::RunBuilderProxy,
239 filter_debug_data: bool,
240}
241
242impl TestBuilder {
243 pub fn new(proxy: ftest_manager::RunBuilderProxy) -> Self {
245 Self { proxy, filter_debug_data: false }
246 }
247
248 pub fn filter_debug_data(self) -> Self {
253 let Self { proxy, .. } = self;
254 Self { proxy, filter_debug_data: true }
255 }
256
257 pub fn take_proxy(self) -> ftest_manager::RunBuilderProxy {
258 self.proxy
259 }
260
261 pub fn set_scheduling_options(&self, accumulate_debug_data: bool) -> Result<(), Error> {
262 self.proxy
263 .with_scheduling_options(&ftest_manager::SchedulingOptions {
264 accumulate_debug_data: Some(accumulate_debug_data),
265 ..Default::default()
266 })
267 .map_err(Error::from)
268 }
269
270 pub async fn add_suite(
272 &self,
273 test_url: &str,
274 run_options: ftest_manager::RunOptions,
275 ) -> Result<SuiteRunInstance, Error> {
276 let (controller_proxy, controller) = fidl::endpoints::create_proxy();
277 self.proxy.add_suite(test_url, &run_options, controller)?;
278 Ok(SuiteRunInstance { controller_proxy: controller_proxy.into() })
279 }
280
281 pub async fn add_suite_in_realm(
283 &self,
284 realm: fidl::endpoints::ClientEnd<fidl_fuchsia_component::RealmMarker>,
285 offers: &[fidl_fuchsia_component_decl::Offer],
286 test_collection: &str,
287 test_url: &str,
288 run_options: ftest_manager::RunOptions,
289 ) -> Result<SuiteRunInstance, Error> {
290 let (controller_proxy, controller) = fidl::endpoints::create_proxy();
291 self.proxy.add_suite_in_realm(
292 realm,
293 offers,
294 test_collection,
295 test_url,
296 &run_options,
297 controller,
298 )?;
299 Ok(SuiteRunInstance { controller_proxy: controller_proxy.into() })
300 }
301
302 pub async fn run(self) -> Result<Vec<TestRunEvent>, Error> {
306 self.run_with_option(false).await
307 }
308
309 pub async fn run_with_option(
313 self,
314 get_compressed_debug_data: bool,
315 ) -> Result<Vec<TestRunEvent>, Error> {
316 let (controller_proxy, controller) = fidl::endpoints::create_proxy();
317 self.proxy.build(controller).context("Error starting tests")?;
318 let mut events = vec![];
320 loop {
321 let fidl_events = controller_proxy.get_events().await.context("Get run events")?;
322 if fidl_events.is_empty() {
323 break;
324 }
325 for fidl_event in fidl_events {
326 match fidl_event.payload.expect("Details cannot be empty") {
327 ftest_manager::RunEventPayload::Artifact(
328 ftest_manager::Artifact::DebugData(iterator),
329 ) => {
330 if !self.filter_debug_data {
331 let proxy = iterator.into_proxy();
332 loop {
333 let data = match get_compressed_debug_data {
334 true => proxy.get_next_compressed().await?,
335 false => proxy.get_next().await?,
336 };
337 if data.is_empty() {
338 break;
339 }
340 for data_file in data {
341 let socket = data_file.socket.expect("File cannot be empty");
342 events.push(TestRunEvent::debug_data(
343 fidl_event.timestamp,
344 data_file.name.expect("Name cannot be empty"),
345 socket,
346 ));
347 }
348 }
349 }
350 }
351 other => bail!("Expected only debug data run events but got {:?}", other),
352 }
353 }
354 }
355 Ok(events)
356 }
357}
358
359#[derive(Debug)]
360pub struct TestRunEvent {
361 pub timestamp: Option<i64>,
362 pub payload: TestRunEventPayload,
363}
364
365impl TestRunEvent {
366 pub fn debug_data<S: Into<String>>(
367 timestamp: Option<i64>,
368 filename: S,
369 socket: fidl::Socket,
370 ) -> Self {
371 Self {
372 timestamp,
373 payload: TestRunEventPayload::DebugData { filename: filename.into(), socket },
374 }
375 }
376}
377
378#[derive(Debug)]
379pub enum TestRunEventPayload {
380 DebugData { filename: String, socket: fidl::Socket },
381}
382
383pub struct SuiteEvent {
385 pub timestamp: Option<i64>,
386 pub payload: SuiteEventPayload,
387}
388
389impl SuiteEvent {
390 pub fn debug_data<S: Into<String>>(
392 timestamp: Option<i64>,
393 filename: S,
394 socket: fidl::Socket,
395 ) -> Self {
396 Self {
397 timestamp,
398 payload: SuiteEventPayload::DebugData { filename: filename.into(), socket },
399 }
400 }
401
402 pub fn case_found(timestamp: Option<i64>, name: String) -> Self {
403 SuiteEvent { timestamp, payload: SuiteEventPayload::RunEvent(RunEvent::case_found(name)) }
404 }
405
406 pub fn case_started(timestamp: Option<i64>, name: String) -> Self {
407 SuiteEvent { timestamp, payload: SuiteEventPayload::RunEvent(RunEvent::case_started(name)) }
408 }
409
410 pub fn case_stdout<N, L>(timestamp: Option<i64>, name: N, stdout_message: L) -> Self
411 where
412 N: Into<String>,
413 L: Into<String>,
414 {
415 SuiteEvent {
416 timestamp,
417 payload: SuiteEventPayload::RunEvent(RunEvent::case_stdout(
418 name.into(),
419 stdout_message.into(),
420 )),
421 }
422 }
423
424 pub fn case_stderr<N, L>(timestamp: Option<i64>, name: N, stderr_message: L) -> Self
425 where
426 N: Into<String>,
427 L: Into<String>,
428 {
429 SuiteEvent {
430 timestamp,
431 payload: SuiteEventPayload::RunEvent(RunEvent::case_stderr(
432 name.into(),
433 stderr_message.into(),
434 )),
435 }
436 }
437
438 pub fn case_stopped(
439 timestamp: Option<i64>,
440 name: String,
441 status: ftest_manager::TestCaseResult,
442 ) -> Self {
443 SuiteEvent {
444 timestamp,
445 payload: SuiteEventPayload::RunEvent(RunEvent::case_stopped(name, status)),
446 }
447 }
448
449 pub fn case_finished(timestamp: Option<i64>, name: String) -> Self {
450 SuiteEvent {
451 timestamp,
452 payload: SuiteEventPayload::RunEvent(RunEvent::case_finished(name)),
453 }
454 }
455
456 pub fn suite_stopped(timestamp: Option<i64>, status: ftest_manager::SuiteResult) -> Self {
457 SuiteEvent {
458 timestamp,
459 payload: SuiteEventPayload::RunEvent(RunEvent::suite_stopped(status)),
460 }
461 }
462
463 pub fn suite_custom(
464 timestamp: Option<i64>,
465 component: String,
466 filename: String,
467 contents: String,
468 ) -> Self {
469 SuiteEvent {
470 timestamp,
471 payload: SuiteEventPayload::RunEvent(RunEvent::suite_custom(
472 component, filename, contents,
473 )),
474 }
475 }
476
477 pub fn suite_log(timestamp: Option<i64>, log_stream: LogStream) -> Self {
478 SuiteEvent { timestamp, payload: SuiteEventPayload::SuiteLog { log_stream } }
479 }
480
481 pub fn test_case_log(timestamp: Option<i64>, name: String, log_stream: LogStream) -> Self {
482 SuiteEvent { timestamp, payload: SuiteEventPayload::TestCaseLog { name, log_stream } }
483 }
484}
485
486pub enum SuiteEventPayload {
487 SuiteLog {
489 log_stream: LogStream,
490 },
491
492 TestCaseLog {
494 name: String,
495 log_stream: LogStream,
496 },
497
498 RunEvent(RunEvent),
500
501 DebugData {
503 filename: String,
504 socket: fidl::Socket,
505 },
506}
507
508#[derive(PartialEq, Debug, Eq, Hash, Ord, PartialOrd)]
509pub enum RunEvent {
510 CaseFound { name: String },
511 CaseStarted { name: String },
512 CaseStdout { name: String, stdout_message: String },
513 CaseStderr { name: String, stderr_message: String },
514 CaseStopped { name: String, status: ftest_manager::TestCaseResult },
515 CaseFinished { name: String },
516 SuiteStarted,
517 SuiteCustom { component: String, filename: String, contents: String },
518 SuiteStopped { status: ftest_manager::SuiteResult },
519 DebugData { filename: String, socket: fidl::Socket },
520}
521
522impl RunEvent {
523 pub fn case_found<S>(name: S) -> Self
524 where
525 S: Into<String>,
526 {
527 Self::CaseFound { name: name.into() }
528 }
529
530 pub fn case_started<S>(name: S) -> Self
531 where
532 S: Into<String>,
533 {
534 Self::CaseStarted { name: name.into() }
535 }
536
537 pub fn case_stdout<S, L>(name: S, stdout_message: L) -> Self
538 where
539 S: Into<String>,
540 L: Into<String>,
541 {
542 Self::CaseStdout { name: name.into(), stdout_message: stdout_message.into() }
543 }
544
545 pub fn case_stderr<S, L>(name: S, stderr_message: L) -> Self
546 where
547 S: Into<String>,
548 L: Into<String>,
549 {
550 Self::CaseStderr { name: name.into(), stderr_message: stderr_message.into() }
551 }
552
553 pub fn case_stopped<S>(name: S, status: ftest_manager::TestCaseResult) -> Self
554 where
555 S: Into<String>,
556 {
557 Self::CaseStopped { name: name.into(), status }
558 }
559
560 pub fn case_finished<S>(name: S) -> Self
561 where
562 S: Into<String>,
563 {
564 Self::CaseFinished { name: name.into() }
565 }
566
567 pub fn suite_started() -> Self {
568 Self::SuiteStarted
569 }
570
571 pub fn suite_custom<T, U, V>(component: T, filename: U, contents: V) -> Self
572 where
573 T: Into<String>,
574 U: Into<String>,
575 V: Into<String>,
576 {
577 Self::SuiteCustom {
578 component: component.into(),
579 filename: filename.into(),
580 contents: contents.into(),
581 }
582 }
583
584 pub fn suite_stopped(status: ftest_manager::SuiteResult) -> Self {
585 Self::SuiteStopped { status }
586 }
587
588 pub fn debug_data<S>(filename: S, socket: fidl::Socket) -> Self
589 where
590 S: Into<String>,
591 {
592 Self::DebugData { filename: filename.into(), socket }
593 }
594
595 pub fn test_case_name(&self) -> Option<&String> {
597 match self {
598 RunEvent::CaseFound { name }
599 | RunEvent::CaseStarted { name }
600 | RunEvent::CaseStdout { name, .. }
601 | RunEvent::CaseStderr { name, .. }
602 | RunEvent::CaseStopped { name, .. }
603 | RunEvent::CaseFinished { name } => Some(name),
604 RunEvent::SuiteStarted
605 | RunEvent::SuiteStopped { .. }
606 | RunEvent::SuiteCustom { .. }
607 | RunEvent::DebugData { .. } => None,
608 }
609 }
610
611 pub fn owned_test_case_name(&self) -> Option<String> {
613 self.test_case_name().map(String::from)
614 }
615}
616
617#[derive(Default, Debug, Eq, PartialEq)]
620pub struct GroupedRunEvents {
621 pub non_artifact_events: Vec<RunEvent>,
623 pub stdout_events: Vec<RunEvent>,
625 pub stderr_events: Vec<RunEvent>,
627}
628
629pub trait GroupRunEventByTestCase: Iterator<Item = RunEvent> + Sized {
631 fn group_by_test_case_ordered(self) -> LinkedHashMap<Option<String>, GroupedRunEvents> {
646 let mut map = LinkedHashMap::new();
647 for run_event in self {
648 match run_event {
649 RunEvent::CaseStderr { .. } => map
650 .entry(run_event.owned_test_case_name())
651 .or_insert_with(|| GroupedRunEvents::default())
652 .stderr_events
653 .push(run_event),
654
655 RunEvent::CaseStdout { .. } => map
656 .entry(run_event.owned_test_case_name())
657 .or_insert_with(|| GroupedRunEvents::default())
658 .stdout_events
659 .push(run_event),
660
661 _ => map
662 .entry(run_event.owned_test_case_name())
663 .or_insert_with(|| GroupedRunEvents::default())
664 .non_artifact_events
665 .push(run_event),
666 }
667 }
668 map
669 }
670
671 fn group_by_test_case_unordered(self) -> HashMap<Option<String>, GroupedRunEvents> {
674 let mut map = HashMap::new();
675 for run_event in self {
676 match run_event {
677 RunEvent::CaseStderr { .. } => map
678 .entry(run_event.owned_test_case_name())
679 .or_insert_with(|| GroupedRunEvents::default())
680 .stderr_events
681 .push(run_event),
682
683 RunEvent::CaseStdout { .. } => map
684 .entry(run_event.owned_test_case_name())
685 .or_insert_with(|| GroupedRunEvents::default())
686 .stdout_events
687 .push(run_event),
688
689 _ => map
690 .entry(run_event.owned_test_case_name())
691 .or_insert_with(|| GroupedRunEvents::default())
692 .non_artifact_events
693 .push(run_event),
694 }
695 }
696 map
697 }
698
699 fn group(self) -> GroupedRunEvents {
701 let mut events = GroupedRunEvents::default();
702 for run_event in self {
703 match run_event {
704 RunEvent::CaseStderr { .. } => events.stderr_events.push(run_event),
705
706 RunEvent::CaseStdout { .. } => events.stdout_events.push(run_event),
707
708 _ => events.non_artifact_events.push(run_event),
709 }
710 }
711 events
712 }
713}
714
715impl<T> GroupRunEventByTestCase for T where T: Iterator<Item = RunEvent> + Sized {}
716
717#[derive(Default)]
718struct FidlSuiteEventProcessor {
719 case_map: HashMap<u32, String>,
720 std_output_map: HashMap<u32, Vec<fasync::Task<Result<(), Error>>>>,
721}
722
723impl FidlSuiteEventProcessor {
724 fn new() -> Self {
725 FidlSuiteEventProcessor::default()
726 }
727
728 fn get_test_case_name(&self, identifier: u32) -> String {
729 self.case_map
730 .get(&identifier)
731 .unwrap_or_else(|| panic!("invalid test case identifier: {:?}", identifier))
732 .clone()
733 }
734
735 async fn process(
736 &mut self,
737 event: FidlSuiteEvent,
738 mut sender: mpsc::Sender<SuiteEvent>,
739 ) -> Result<(), Error> {
740 let timestamp = event.timestamp;
741 let e = match event.payload.expect("Details cannot be null, please file bug.") {
742 FidlSuiteEventPayload::CaseFound(cf) => {
743 self.case_map.insert(cf.identifier, cf.test_case_name.clone());
744 SuiteEvent::case_found(timestamp, cf.test_case_name).into()
745 }
746 FidlSuiteEventPayload::CaseStarted(cs) => {
747 let test_case_name = self.get_test_case_name(cs.identifier);
748 SuiteEvent::case_started(timestamp, test_case_name).into()
749 }
750 FidlSuiteEventPayload::CaseStopped(cs) => {
751 let test_case_name = self.get_test_case_name(cs.identifier);
752 if let Some(outputs) = self.std_output_map.remove(&cs.identifier) {
753 for s in outputs {
754 s.await.context(format!(
755 "error collecting stdout/stderr of {}",
756 test_case_name
757 ))?;
758 }
759 }
760 SuiteEvent::case_stopped(timestamp, test_case_name, to_case_result(cs.status))
761 .into()
762 }
763 FidlSuiteEventPayload::CaseFinished(cf) => {
764 let test_case_name = self.get_test_case_name(cf.identifier);
765 SuiteEvent::case_finished(timestamp, test_case_name).into()
766 }
767 FidlSuiteEventPayload::CaseArtifact(ca) => {
768 let name = self.get_test_case_name(ca.identifier);
769 match ca.artifact {
770 ftest_manager::Artifact::Stdout(stdout) => {
771 let (s, mut r) = mpsc::channel(1024);
772 let stdout_task =
773 fasync::Task::spawn(collect_and_send_string_output(stdout, s));
774 let mut sender_clone = sender.clone();
775 let send_stdout_task = fasync::Task::spawn(async move {
776 while let Some(msg) = r.next().await {
777 sender_clone
778 .send(SuiteEvent::case_stdout(None, &name, msg))
779 .await
780 .context(format!("cannot send logs for {}", name))?;
781 }
782 Ok(())
783 });
784 match self.std_output_map.get_mut(&ca.identifier) {
785 Some(v) => {
786 v.push(stdout_task);
787 v.push(send_stdout_task);
788 }
789 None => {
790 self.std_output_map
791 .insert(ca.identifier, vec![stdout_task, send_stdout_task]);
792 }
793 }
794 None
795 }
796 ftest_manager::Artifact::Stderr(stderr) => {
797 let (s, mut r) = mpsc::channel(1024);
798 let stderr_task =
799 fasync::Task::spawn(collect_and_send_string_output(stderr, s));
800 let mut sender_clone = sender.clone();
801 let send_stderr_task = fasync::Task::spawn(async move {
802 while let Some(msg) = r.next().await {
803 sender_clone
804 .send(SuiteEvent::case_stderr(None, &name, msg))
805 .await
806 .context(format!("cannot send logs for {}", name))?;
807 }
808 Ok(())
809 });
810 match self.std_output_map.get_mut(&ca.identifier) {
811 Some(v) => {
812 v.push(stderr_task);
813 v.push(send_stderr_task);
814 }
815 None => {
816 self.std_output_map
817 .insert(ca.identifier, vec![stderr_task, send_stderr_task]);
818 }
819 }
820 None
821 }
822 ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
823 Ok(log_stream) => {
824 SuiteEvent::test_case_log(timestamp, name, log_stream).into()
825 }
826 Err(e) => {
827 warn!("Cannot collect logs for test suite: {:?}", e);
828 None
829 }
830 },
831 _ => {
832 panic!("not supported")
833 }
834 }
835 }
836 FidlSuiteEventPayload::SuiteArtifact(sa) => match sa.artifact {
837 ftest_manager::Artifact::Stdout(_) => {
838 panic!("not supported")
839 }
840 ftest_manager::Artifact::Stderr(_) => {
841 panic!("not supported")
842 }
843 ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
844 Ok(log_stream) => SuiteEvent::suite_log(timestamp, log_stream).into(),
845 Err(e) => {
846 warn!("Cannot collect logs for test suite: {:?}", e);
847 None
848 }
849 },
850 ftest_manager::Artifact::Custom(custom_artifact) => {
851 let ftest_manager::DirectoryAndToken { directory, token } =
852 custom_artifact.directory_and_token.unwrap();
853 let component_moniker = custom_artifact.component_moniker.unwrap();
854 let mut sender_clone = sender.clone();
855 fasync::Task::spawn(async move {
856 let directory = directory.into_proxy();
857 let entries: Vec<_> =
858 fuchsia_fs::directory::readdir_recursive(&directory, None)
859 .try_collect()
860 .await
861 .expect("read custom artifact directory");
862 for entry in entries.into_iter() {
863 let file = fuchsia_fs::directory::open_file_async(
864 &directory,
865 &entry.name,
866 fio::PERM_READABLE,
867 )
868 .unwrap();
869 let contents = fuchsia_fs::file::read_to_string(&file).await.unwrap();
870 sender_clone
871 .send(SuiteEvent::suite_custom(
872 timestamp,
873 component_moniker.clone(),
874 entry.name,
875 contents,
876 ))
877 .await
878 .unwrap();
879 }
880 drop(token);
883 })
884 .detach();
885 None
886 }
887 _ => {
888 panic!("not supported")
889 }
890 },
891 FidlSuiteEventPayload::SuiteStarted(_started) => SuiteEvent {
892 timestamp,
893 payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStarted),
894 }
895 .into(),
896 FidlSuiteEventPayload::SuiteStopped(stopped) => SuiteEvent {
897 timestamp,
898 payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStopped {
899 status: to_suite_result(stopped.status),
900 }),
901 }
902 .into(),
903 SuiteEventPayloadUnknown!() => panic!("Unrecognized SuiteEvent"),
904 };
905 if let Some(item) = e {
906 sender.send(item).await.context("Cannot send event")?;
907 }
908 Ok(())
909 }
910
911 async fn process_event(
912 &mut self,
913 event: ftest_manager::Event,
914 mut sender: mpsc::Sender<SuiteEvent>,
915 filter_debug_data: bool,
916 compressed_debug_data: bool,
917 ) -> Result<(), Error> {
918 let timestamp = event.timestamp;
919 let e = match event.details.expect("Details cannot be null, please file bug.") {
920 ftest_manager::EventDetails::TestCaseFound(cf) => {
921 let test_case_name =
922 cf.test_case_name.expect("test_case_name must be specified, please file bug.");
923 self.case_map.insert(
924 cf.test_case_id.expect("test_case_id must be specified, please file bug."),
925 test_case_name.clone(),
926 );
927 SuiteEvent::case_found(timestamp, test_case_name).into()
928 }
929 ftest_manager::EventDetails::TestCaseStarted(cs) => {
930 let test_case_name = self.get_test_case_name(
931 cs.test_case_id.expect("test_case_id must be specified, please file bug."),
932 );
933 SuiteEvent::case_started(timestamp, test_case_name).into()
934 }
935 ftest_manager::EventDetails::TestCaseStopped(cs) => {
936 let test_case_name = self.get_test_case_name(
937 cs.test_case_id.expect("test_case_id must be specified, please file bug."),
938 );
939 if let Some(outputs) = self.std_output_map.remove(
940 &cs.test_case_id.expect("test_case_id must be specified, please file bug."),
941 ) {
942 for s in outputs {
943 s.await.context(format!(
944 "error collecting stdout/stderr of {}",
945 test_case_name
946 ))?;
947 }
948 }
949 SuiteEvent::case_stopped(
950 timestamp,
951 test_case_name,
952 cs.result.expect("result must be specified, please file bug."),
953 )
954 .into()
955 }
956 ftest_manager::EventDetails::TestCaseFinished(cf) => {
957 let test_case_name = self.get_test_case_name(
958 cf.test_case_id.expect("test_case_id must be specified, please file bug."),
959 );
960 SuiteEvent::case_finished(timestamp, test_case_name).into()
961 }
962 ftest_manager::EventDetails::TestCaseArtifactGenerated(ca) => {
963 let name = self.get_test_case_name(
964 ca.test_case_id.expect("test_case_id must be specified, please file bug."),
965 );
966 match ca.artifact.expect("artifact must be specified, please file bug.") {
967 ftest_manager::Artifact::Stdout(stdout) => {
968 let (s, mut r) = mpsc::channel(1024);
969 let stdout_task =
970 fasync::Task::spawn(collect_and_send_string_output(stdout, s));
971 let mut sender_clone = sender.clone();
972 let send_stdout_task = fasync::Task::spawn(async move {
973 while let Some(msg) = r.next().await {
974 sender_clone
975 .send(SuiteEvent::case_stdout(None, &name, msg))
976 .await
977 .context(format!("cannot send logs for {}", name))?;
978 }
979 Ok(())
980 });
981 match self.std_output_map.get_mut(
982 &ca.test_case_id
983 .expect("test_case_id must be specified, please file bug."),
984 ) {
985 Some(v) => {
986 v.push(stdout_task);
987 v.push(send_stdout_task);
988 }
989 None => {
990 self.std_output_map.insert(
991 ca.test_case_id
992 .expect("test_case_id must be specified, please file bug."),
993 vec![stdout_task, send_stdout_task],
994 );
995 }
996 }
997 None
998 }
999 ftest_manager::Artifact::Stderr(stderr) => {
1000 let (s, mut r) = mpsc::channel(1024);
1001 let stderr_task =
1002 fasync::Task::spawn(collect_and_send_string_output(stderr, s));
1003 let mut sender_clone = sender.clone();
1004 let send_stderr_task = fasync::Task::spawn(async move {
1005 while let Some(msg) = r.next().await {
1006 sender_clone
1007 .send(SuiteEvent::case_stderr(None, &name, msg))
1008 .await
1009 .context(format!("cannot send logs for {}", name))?;
1010 }
1011 Ok(())
1012 });
1013 match self.std_output_map.get_mut(
1014 &ca.test_case_id
1015 .expect("test_case_id must be specified, please file bug."),
1016 ) {
1017 Some(v) => {
1018 v.push(stderr_task);
1019 v.push(send_stderr_task);
1020 }
1021 None => {
1022 self.std_output_map.insert(
1023 ca.test_case_id
1024 .expect("test_case_id must be specified, please file bug."),
1025 vec![stderr_task, send_stderr_task],
1026 );
1027 }
1028 }
1029 None
1030 }
1031 ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
1032 Ok(log_stream) => {
1033 SuiteEvent::test_case_log(timestamp, name, log_stream).into()
1034 }
1035 Err(e) => {
1036 warn!("Cannot collect logs for test suite: {:?}", e);
1037 None
1038 }
1039 },
1040 _ => {
1041 panic!("not supported")
1042 }
1043 }
1044 }
1045 ftest_manager::EventDetails::SuiteArtifactGenerated(sa) => {
1046 match sa.artifact.expect("artifact must be specified, please file bug.") {
1047 ftest_manager::Artifact::Stdout(_) => {
1048 panic!("not supported")
1049 }
1050 ftest_manager::Artifact::Stderr(_) => {
1051 panic!("not supported")
1052 }
1053 ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
1054 Ok(log_stream) => SuiteEvent::suite_log(timestamp, log_stream).into(),
1055 Err(e) => {
1056 warn!("Cannot collect logs for test suite: {:?}", e);
1057 None
1058 }
1059 },
1060 ftest_manager::Artifact::Custom(custom_artifact) => {
1061 let ftest_manager::DirectoryAndToken { directory, token } =
1062 custom_artifact.directory_and_token.unwrap();
1063 let component_moniker = custom_artifact.component_moniker.unwrap();
1064 let mut sender_clone = sender.clone();
1065 fasync::Task::spawn(async move {
1066 let directory = directory.into_proxy();
1067 let entries: Vec<_> =
1068 fuchsia_fs::directory::readdir_recursive(&directory, None)
1069 .try_collect()
1070 .await
1071 .expect("read custom artifact directory");
1072 for entry in entries.into_iter() {
1073 let file = fuchsia_fs::directory::open_file_async(
1074 &directory,
1075 &entry.name,
1076 fio::PERM_READABLE,
1077 )
1078 .unwrap();
1079 let contents =
1080 fuchsia_fs::file::read_to_string(&file).await.unwrap();
1081 sender_clone
1082 .send(SuiteEvent::suite_custom(
1083 timestamp,
1084 component_moniker.clone(),
1085 entry.name,
1086 contents,
1087 ))
1088 .await
1089 .unwrap();
1090 }
1091 drop(token);
1094 })
1095 .detach();
1096 None
1097 }
1098 ftest_manager::Artifact::DebugData(iterator) => {
1099 if !filter_debug_data {
1100 let mut sender_clone = sender.clone();
1101 let proxy = iterator.into_proxy();
1102 fasync::Task::spawn(async move {
1103 loop {
1104 let data = match compressed_debug_data {
1105 true => proxy.get_next_compressed().await.unwrap(),
1106 false => proxy.get_next().await.unwrap(),
1107 };
1108 if data.is_empty() {
1109 break;
1110 }
1111 for data_file in data {
1112 let socket =
1113 data_file.socket.expect("File cannot be empty");
1114 sender_clone
1115 .send(SuiteEvent::debug_data(
1116 timestamp,
1117 data_file.name.expect("Name cannot be empty"),
1118 socket,
1119 ))
1120 .await
1121 .unwrap();
1122 }
1123 }
1124 })
1125 .detach();
1126 }
1127 None
1128 }
1129 _ => {
1130 panic!("not supported")
1131 }
1132 }
1133 }
1134 ftest_manager::EventDetails::SuiteStarted(_started) => SuiteEvent {
1135 timestamp,
1136 payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStarted),
1137 }
1138 .into(),
1139 ftest_manager::EventDetails::SuiteStopped(stopped) => SuiteEvent {
1140 timestamp,
1141 payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStopped {
1142 status: stopped.result.expect("result must be specified, please file bug."),
1143 }),
1144 }
1145 .into(),
1146 EventDetailsUnknown!() => panic!("Unrecognized EventDetails"),
1147 };
1148 if let Some(item) = e {
1149 sender.send(item).await.context("Cannot send event")?;
1150 }
1151 Ok(())
1152 }
1153}
1154
1155#[derive(Debug, thiserror::Error, Eq, PartialEq, Copy, Clone)]
1156pub enum SuiteLaunchError {
1157 #[error("Cannot enumerate tests")]
1158 CaseEnumeration,
1159
1160 #[error("Cannot resolve test url")]
1161 InstanceCannotResolve,
1162
1163 #[error("Invalid arguments passed")]
1164 InvalidArgs,
1165
1166 #[error("Cannot connect to test suite")]
1167 FailedToConnectToTestSuite,
1168
1169 #[error("resource unavailable")]
1170 ResourceUnavailable,
1171
1172 #[error("Some internal error occurred. Please file bug")]
1173 InternalError,
1174
1175 #[error("No test cases matched the provided filters")]
1176 NoMatchingCases,
1177}
1178
1179impl From<ftest_manager::LaunchError> for SuiteLaunchError {
1180 fn from(error: ftest_manager::LaunchError) -> Self {
1181 match error {
1182 ftest_manager::LaunchError::ResourceUnavailable => {
1183 SuiteLaunchError::ResourceUnavailable
1184 }
1185 ftest_manager::LaunchError::InstanceCannotResolve => {
1186 SuiteLaunchError::InstanceCannotResolve
1187 }
1188 ftest_manager::LaunchError::InvalidArgs => SuiteLaunchError::InvalidArgs,
1189 ftest_manager::LaunchError::FailedToConnectToTestSuite => {
1190 SuiteLaunchError::FailedToConnectToTestSuite
1191 }
1192 ftest_manager::LaunchError::CaseEnumeration => SuiteLaunchError::CaseEnumeration,
1193 ftest_manager::LaunchError::InternalError => SuiteLaunchError::InternalError,
1194 ftest_manager::LaunchError::NoMatchingCases => SuiteLaunchError::NoMatchingCases,
1195 ftest_manager::LaunchErrorUnknown!() => panic!("Encountered unknown launch error"),
1196 }
1197 }
1198}
1199
1200pub struct SuiteRunInstance {
1202 controller_proxy: Arc<SuiteControllerProxy>,
1203}
1204
1205impl SuiteRunInstance {
1206 pub fn controller(&self) -> Arc<SuiteControllerProxy> {
1207 self.controller_proxy.clone()
1208 }
1209
1210 pub async fn collect_events(&self, sender: mpsc::Sender<SuiteEvent>) -> Result<(), Error> {
1211 let controller_proxy = self.controller_proxy.clone();
1212 let mut processor = FidlSuiteEventProcessor::new();
1213 loop {
1214 match controller_proxy.get_events().await? {
1215 Err(e) => return Err(SuiteLaunchError::from(e).into()),
1216 Ok(events) => {
1217 if events.len() == 0 {
1218 break;
1219 }
1220 for event in events {
1221 if let Err(e) = processor.process(event, sender.clone()).await {
1222 warn!("error running test suite: {:?}", e);
1223 let _ = controller_proxy.kill();
1224 return Ok(());
1225 }
1226 }
1227 }
1228 }
1229 }
1230 Ok(())
1231 }
1232
1233 pub async fn collect_events_with_watch(
1234 &self,
1235 sender: mpsc::Sender<SuiteEvent>,
1236 filter_debug_data: bool,
1237 compressed_debug_data: bool,
1238 ) -> Result<(), Error> {
1239 let controller_proxy = self.controller_proxy.clone();
1240 let mut processor = FidlSuiteEventProcessor::new();
1241 loop {
1242 match controller_proxy.watch_events().await? {
1243 Err(e) => return Err(SuiteLaunchError::from(e).into()),
1244 Ok(events) => {
1245 if events.len() == 0 {
1246 break;
1247 }
1248 for event in events {
1249 if let Err(e) = processor
1250 .process_event(
1251 event,
1252 sender.clone(),
1253 filter_debug_data,
1254 compressed_debug_data,
1255 )
1256 .await
1257 {
1258 warn!("error running test suite: {:?}", e);
1259 let _ = controller_proxy.kill();
1260 return Ok(());
1261 }
1262 }
1263 }
1264 }
1265 }
1266 Ok(())
1267 }
1268}
1269
1270fn to_case_result(outcome: ftest_manager::CaseStatus) -> ftest_manager::TestCaseResult {
1271 match outcome {
1272 ftest_manager::CaseStatus::Passed => ftest_manager::TestCaseResult::Passed,
1273 ftest_manager::CaseStatus::Failed => ftest_manager::TestCaseResult::Failed,
1274 ftest_manager::CaseStatus::TimedOut => ftest_manager::TestCaseResult::TimedOut,
1275 ftest_manager::CaseStatus::Skipped => ftest_manager::TestCaseResult::Skipped,
1276 ftest_manager::CaseStatus::Error => ftest_manager::TestCaseResult::Error,
1277 _ => ftest_manager::TestCaseResult::Error,
1278 }
1279}
1280
1281fn to_suite_result(outcome: ftest_manager::SuiteStatus) -> ftest_manager::SuiteResult {
1282 match outcome {
1283 ftest_manager::SuiteStatus::Passed => ftest_manager::SuiteResult::Finished,
1284 ftest_manager::SuiteStatus::Failed => ftest_manager::SuiteResult::Failed,
1285 ftest_manager::SuiteStatus::DidNotFinish => ftest_manager::SuiteResult::DidNotFinish,
1286 ftest_manager::SuiteStatus::TimedOut => ftest_manager::SuiteResult::TimedOut,
1287 ftest_manager::SuiteStatus::Stopped => ftest_manager::SuiteResult::Stopped,
1288 ftest_manager::SuiteStatus::InternalError => ftest_manager::SuiteResult::InternalError,
1289 _ => ftest_manager::SuiteResult::InternalError,
1290 }
1291}