Skip to main content

test_manager_test_lib/
test_lib.rs

1// Copyright 2021 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//! This crate provides helper functions for testing architecture tests.
6
7use anyhow::{Context as _, Error};
8use fidl_fuchsia_io as fio;
9use fidl_fuchsia_test_manager::{self as ftest_manager, EventDetailsUnknown, SuiteControllerProxy};
10use fuchsia_async as fasync;
11use futures::channel::mpsc;
12use futures::prelude::*;
13use linked_hash_map::LinkedHashMap;
14use log::*;
15use moniker::ExtendedMoniker;
16use std::collections::HashMap;
17use std::sync::Arc;
18use test_diagnostics::zstd_compress::Decoder;
19use test_diagnostics::{LogStream, collect_and_send_string_output, collect_string_from_socket};
20
21pub fn default_run_suite_options() -> ftest_manager::RunSuiteOptions {
22    ftest_manager::RunSuiteOptions { run_disabled_tests: Some(false), ..Default::default() }
23}
24
25#[derive(Debug, Eq, PartialEq)]
26pub struct AttributedLog {
27    pub log: String,
28    pub moniker: ExtendedMoniker,
29}
30
31pub async fn collect_suite_events_with_watch(
32    suite_instance: SuiteRunInstance,
33    filter_debug_data: bool,
34    compressed_debug_data: bool,
35) -> Result<(Vec<RunEvent>, Vec<AttributedLog>), Error> {
36    let (sender, mut recv) = mpsc::channel(1);
37    let execution_task = fasync::Task::spawn(async move {
38        suite_instance
39            .collect_events_with_watch(sender, filter_debug_data, compressed_debug_data)
40            .await
41    });
42    let mut events = vec![];
43    let mut log_tasks = vec![];
44    while let Some(event) = recv.next().await {
45        match event.payload {
46            SuiteEventPayload::RunEvent(RunEvent::CaseStdout { name, mut stdout_message }) => {
47                if stdout_message.ends_with("\n") {
48                    stdout_message.truncate(stdout_message.len() - 1)
49                }
50                let logs = stdout_message.split("\n");
51                for log in logs {
52                    // gtest produces this line when tests are randomized. As of
53                    // this writing, our gtest_main binary *always* randomizes.
54                    if log.contains("Note: Randomizing tests' orders with a seed of") {
55                        continue;
56                    }
57                    events.push(RunEvent::case_stdout(name.clone(), log.to_string()));
58                }
59            }
60            SuiteEventPayload::RunEvent(RunEvent::CaseStderr { name, mut stderr_message }) => {
61                if stderr_message.ends_with("\n") {
62                    stderr_message.truncate(stderr_message.len() - 1)
63                }
64                let logs = stderr_message.split("\n");
65                for log in logs {
66                    events.push(RunEvent::case_stderr(name.clone(), log.to_string()));
67                }
68            }
69            SuiteEventPayload::RunEvent(e) => events.push(e),
70            SuiteEventPayload::SuiteLog { log_stream } => {
71                let t = fasync::Task::spawn(log_stream.collect::<Vec<_>>());
72                log_tasks.push(t);
73            }
74            SuiteEventPayload::TestCaseLog { .. } => {
75                panic!("not supported yet!")
76            }
77            SuiteEventPayload::DebugData { filename, socket } => {
78                events.push(RunEvent::DebugData { filename, socket })
79            }
80        }
81    }
82    execution_task.await.context("test execution failed")?;
83
84    let mut collected_logs = vec![];
85    for t in log_tasks {
86        let logs = t.await;
87        for log_result in logs {
88            let log = log_result?;
89            collected_logs
90                .push(AttributedLog { log: log.msg().unwrap().to_string(), moniker: log.moniker });
91        }
92    }
93
94    Ok((events, collected_logs))
95}
96
97/// Collect bytes from the socket, decompress if required and return the string
98pub async fn collect_string_from_socket_helper(
99    socket: fidl::Socket,
100    compressed_debug_data: bool,
101) -> Result<String, anyhow::Error> {
102    if !compressed_debug_data {
103        return collect_string_from_socket(socket).await;
104    }
105    let mut async_socket = fidl::AsyncSocket::from_socket(socket);
106    let mut buf = vec![0u8; 1024 * 32];
107
108    let (mut decoder, mut receiver) = Decoder::new();
109    let task: fasync::Task<Result<(), anyhow::Error>> = fasync::Task::spawn(async move {
110        loop {
111            let l = async_socket.read(&mut buf).await?;
112            match l {
113                0 => {
114                    decoder.finish().await?;
115                    break;
116                }
117                _ => {
118                    decoder.decompress(&buf[..l]).await?;
119                }
120            }
121        }
122        Ok(())
123    });
124
125    let mut decompressed_data = Vec::new();
126    while let Some(chunk) = receiver.next().await {
127        decompressed_data.extend_from_slice(&chunk);
128    }
129    task.await?;
130    Ok(String::from_utf8_lossy(decompressed_data.as_slice()).into())
131}
132/// Runs a test suite.
133pub struct SuiteRunner {
134    proxy: ftest_manager::SuiteRunnerProxy,
135}
136
137impl SuiteRunner {
138    /// Create new instance
139    pub fn new(proxy: ftest_manager::SuiteRunnerProxy) -> Self {
140        Self { proxy }
141    }
142
143    pub fn take_proxy(self) -> ftest_manager::SuiteRunnerProxy {
144        self.proxy
145    }
146
147    /// Starts the suite run, returning the suite run controller wrapped in a SuiteRunInstance.
148    pub fn start_suite_run(
149        &self,
150        test_url: &str,
151        options: ftest_manager::RunSuiteOptions,
152    ) -> Result<SuiteRunInstance, Error> {
153        let (controller_proxy, controller) = fidl::endpoints::create_proxy();
154        self.proxy.run(test_url, options, controller).context("Error starting tests")?;
155
156        return Ok(SuiteRunInstance { controller_proxy: controller_proxy.into() });
157    }
158}
159
160/// Events produced by test suite.
161pub struct SuiteEvent {
162    pub timestamp: Option<i64>,
163    pub payload: SuiteEventPayload,
164}
165
166impl SuiteEvent {
167    // Note: This is only used with SuiteRunner, not RunBuilder.
168    pub fn debug_data<S: Into<String>>(
169        timestamp: Option<i64>,
170        filename: S,
171        socket: fidl::Socket,
172    ) -> Self {
173        Self {
174            timestamp,
175            payload: SuiteEventPayload::DebugData { filename: filename.into(), socket },
176        }
177    }
178
179    pub fn case_found(timestamp: Option<i64>, name: String) -> Self {
180        SuiteEvent { timestamp, payload: SuiteEventPayload::RunEvent(RunEvent::case_found(name)) }
181    }
182
183    pub fn case_started(timestamp: Option<i64>, name: String) -> Self {
184        SuiteEvent { timestamp, payload: SuiteEventPayload::RunEvent(RunEvent::case_started(name)) }
185    }
186
187    pub fn case_stdout<N, L>(timestamp: Option<i64>, name: N, stdout_message: L) -> Self
188    where
189        N: Into<String>,
190        L: Into<String>,
191    {
192        SuiteEvent {
193            timestamp,
194            payload: SuiteEventPayload::RunEvent(RunEvent::case_stdout(
195                name.into(),
196                stdout_message.into(),
197            )),
198        }
199    }
200
201    pub fn case_stderr<N, L>(timestamp: Option<i64>, name: N, stderr_message: L) -> Self
202    where
203        N: Into<String>,
204        L: Into<String>,
205    {
206        SuiteEvent {
207            timestamp,
208            payload: SuiteEventPayload::RunEvent(RunEvent::case_stderr(
209                name.into(),
210                stderr_message.into(),
211            )),
212        }
213    }
214
215    pub fn case_stopped(
216        timestamp: Option<i64>,
217        name: String,
218        status: ftest_manager::TestCaseResult,
219    ) -> Self {
220        SuiteEvent {
221            timestamp,
222            payload: SuiteEventPayload::RunEvent(RunEvent::case_stopped(name, status)),
223        }
224    }
225
226    pub fn case_finished(timestamp: Option<i64>, name: String) -> Self {
227        SuiteEvent {
228            timestamp,
229            payload: SuiteEventPayload::RunEvent(RunEvent::case_finished(name)),
230        }
231    }
232
233    pub fn suite_stopped(timestamp: Option<i64>, status: ftest_manager::SuiteResult) -> Self {
234        SuiteEvent {
235            timestamp,
236            payload: SuiteEventPayload::RunEvent(RunEvent::suite_stopped(status)),
237        }
238    }
239
240    pub fn suite_custom(
241        timestamp: Option<i64>,
242        component: String,
243        filename: String,
244        contents: String,
245    ) -> Self {
246        SuiteEvent {
247            timestamp,
248            payload: SuiteEventPayload::RunEvent(RunEvent::suite_custom(
249                component, filename, contents,
250            )),
251        }
252    }
253
254    pub fn suite_log(timestamp: Option<i64>, log_stream: LogStream) -> Self {
255        SuiteEvent { timestamp, payload: SuiteEventPayload::SuiteLog { log_stream } }
256    }
257
258    pub fn test_case_log(timestamp: Option<i64>, name: String, log_stream: LogStream) -> Self {
259        SuiteEvent { timestamp, payload: SuiteEventPayload::TestCaseLog { name, log_stream } }
260    }
261}
262
263pub enum SuiteEventPayload {
264    /// Logger for test suite
265    SuiteLog {
266        log_stream: LogStream,
267    },
268
269    /// Logger for a test case in suite.
270    TestCaseLog {
271        name: String,
272        log_stream: LogStream,
273    },
274
275    /// Test events.
276    RunEvent(RunEvent),
277
278    // Debug data. Note: This is only used with SuiteRunner, not RunBuilder.
279    DebugData {
280        filename: String,
281        socket: fidl::Socket,
282    },
283}
284
285#[derive(PartialEq, Debug, Eq, Hash, Ord, PartialOrd)]
286pub enum RunEvent {
287    CaseFound { name: String },
288    CaseStarted { name: String },
289    CaseStdout { name: String, stdout_message: String },
290    CaseStderr { name: String, stderr_message: String },
291    CaseStopped { name: String, status: ftest_manager::TestCaseResult },
292    CaseFinished { name: String },
293    SuiteStarted,
294    SuiteCustom { component: String, filename: String, contents: String },
295    SuiteStopped { status: ftest_manager::SuiteResult },
296    DebugData { filename: String, socket: fidl::Socket },
297}
298
299impl RunEvent {
300    pub fn case_found<S>(name: S) -> Self
301    where
302        S: Into<String>,
303    {
304        Self::CaseFound { name: name.into() }
305    }
306
307    pub fn case_started<S>(name: S) -> Self
308    where
309        S: Into<String>,
310    {
311        Self::CaseStarted { name: name.into() }
312    }
313
314    pub fn case_stdout<S, L>(name: S, stdout_message: L) -> Self
315    where
316        S: Into<String>,
317        L: Into<String>,
318    {
319        Self::CaseStdout { name: name.into(), stdout_message: stdout_message.into() }
320    }
321
322    pub fn case_stderr<S, L>(name: S, stderr_message: L) -> Self
323    where
324        S: Into<String>,
325        L: Into<String>,
326    {
327        Self::CaseStderr { name: name.into(), stderr_message: stderr_message.into() }
328    }
329
330    pub fn case_stopped<S>(name: S, status: ftest_manager::TestCaseResult) -> Self
331    where
332        S: Into<String>,
333    {
334        Self::CaseStopped { name: name.into(), status }
335    }
336
337    pub fn case_finished<S>(name: S) -> Self
338    where
339        S: Into<String>,
340    {
341        Self::CaseFinished { name: name.into() }
342    }
343
344    pub fn suite_started() -> Self {
345        Self::SuiteStarted
346    }
347
348    pub fn suite_custom<T, U, V>(component: T, filename: U, contents: V) -> Self
349    where
350        T: Into<String>,
351        U: Into<String>,
352        V: Into<String>,
353    {
354        Self::SuiteCustom {
355            component: component.into(),
356            filename: filename.into(),
357            contents: contents.into(),
358        }
359    }
360
361    pub fn suite_stopped(status: ftest_manager::SuiteResult) -> Self {
362        Self::SuiteStopped { status }
363    }
364
365    pub fn debug_data<S>(filename: S, socket: fidl::Socket) -> Self
366    where
367        S: Into<String>,
368    {
369        Self::DebugData { filename: filename.into(), socket }
370    }
371
372    /// Returns the name of the test case to which the event belongs, if applicable.
373    pub fn test_case_name(&self) -> Option<&String> {
374        match self {
375            RunEvent::CaseFound { name }
376            | RunEvent::CaseStarted { name }
377            | RunEvent::CaseStdout { name, .. }
378            | RunEvent::CaseStderr { name, .. }
379            | RunEvent::CaseStopped { name, .. }
380            | RunEvent::CaseFinished { name } => Some(name),
381            RunEvent::SuiteStarted
382            | RunEvent::SuiteStopped { .. }
383            | RunEvent::SuiteCustom { .. }
384            | RunEvent::DebugData { .. } => None,
385        }
386    }
387
388    /// Same as `test_case_name`, but returns an owned `Option<String>`.
389    pub fn owned_test_case_name(&self) -> Option<String> {
390        self.test_case_name().map(String::from)
391    }
392}
393
394/// Groups events by stdout, stderr and non stdout/stderr events to make it easy to compare them
395/// in tests.
396#[derive(Default, Debug, Eq, PartialEq)]
397pub struct GroupedRunEvents {
398    // order of events is maintained.
399    pub non_artifact_events: Vec<RunEvent>,
400    // order of stdout events is maintained.
401    pub stdout_events: Vec<RunEvent>,
402    // order of stderr events is maintained.
403    pub stderr_events: Vec<RunEvent>,
404}
405
406/// Trait allowing iterators over `RunEvent` to be partitioned by test case name.
407pub trait GroupRunEventByTestCase: Iterator<Item = RunEvent> + Sized {
408    /// Groups the `RunEvent`s by test case name into a map that preserves insertion order of
409    /// various types of events.
410    /// The overall order of test cases (by first event) and the orders of events within each test
411    /// case are preserved, but events from different test cases are effectively de-interleaved.
412    ///
413    /// Example:
414    /// ```rust
415    /// use test_diagnostics::{RunEvent, GroupRunEventByTestCase as _};
416    /// use linked_hash_map::LinkedHashMap;
417    ///
418    /// let events: Vec<RunEvent> = get_events();
419    /// let grouped: LinkedHashMap<Option<String>, GroupedRunEvents> =
420    ///     events.into_iter().group_by_test_case_ordered();
421    /// ```
422    fn group_by_test_case_ordered(self) -> LinkedHashMap<Option<String>, GroupedRunEvents> {
423        let mut map = LinkedHashMap::new();
424        for run_event in self {
425            match run_event {
426                RunEvent::CaseStderr { .. } => map
427                    .entry(run_event.owned_test_case_name())
428                    .or_insert_with(|| GroupedRunEvents::default())
429                    .stderr_events
430                    .push(run_event),
431
432                RunEvent::CaseStdout { .. } => map
433                    .entry(run_event.owned_test_case_name())
434                    .or_insert_with(|| GroupedRunEvents::default())
435                    .stdout_events
436                    .push(run_event),
437
438                _ => map
439                    .entry(run_event.owned_test_case_name())
440                    .or_insert_with(|| GroupedRunEvents::default())
441                    .non_artifact_events
442                    .push(run_event),
443            }
444        }
445        map
446    }
447
448    /// Groups the `RunEvent`s by test case name into an unordered map. The orders of events within
449    /// each test case are preserved, but the test cases themselves are not in a defined order.
450    fn group_by_test_case_unordered(self) -> HashMap<Option<String>, GroupedRunEvents> {
451        let mut map = HashMap::new();
452        for run_event in self {
453            match run_event {
454                RunEvent::CaseStderr { .. } => map
455                    .entry(run_event.owned_test_case_name())
456                    .or_insert_with(|| GroupedRunEvents::default())
457                    .stderr_events
458                    .push(run_event),
459
460                RunEvent::CaseStdout { .. } => map
461                    .entry(run_event.owned_test_case_name())
462                    .or_insert_with(|| GroupedRunEvents::default())
463                    .stdout_events
464                    .push(run_event),
465
466                _ => map
467                    .entry(run_event.owned_test_case_name())
468                    .or_insert_with(|| GroupedRunEvents::default())
469                    .non_artifact_events
470                    .push(run_event),
471            }
472        }
473        map
474    }
475
476    /// Group `RunEvent`s by stdout, stderr and non-stdout/err events and returns `GroupedRunEvents`.
477    fn group(self) -> GroupedRunEvents {
478        let mut events = GroupedRunEvents::default();
479        for run_event in self {
480            match run_event {
481                RunEvent::CaseStderr { .. } => events.stderr_events.push(run_event),
482
483                RunEvent::CaseStdout { .. } => events.stdout_events.push(run_event),
484
485                _ => events.non_artifact_events.push(run_event),
486            }
487        }
488        events
489    }
490}
491
492impl<T> GroupRunEventByTestCase for T where T: Iterator<Item = RunEvent> + Sized {}
493
494#[derive(Default)]
495struct FidlSuiteEventProcessor {
496    case_map: HashMap<u32, String>,
497    std_output_map: HashMap<u32, Vec<fasync::Task<Result<(), Error>>>>,
498}
499
500impl FidlSuiteEventProcessor {
501    fn new() -> Self {
502        FidlSuiteEventProcessor::default()
503    }
504
505    fn get_test_case_name(&self, identifier: u32) -> String {
506        self.case_map
507            .get(&identifier)
508            .unwrap_or_else(|| panic!("invalid test case identifier: {:?}", identifier))
509            .clone()
510    }
511
512    async fn process_event(
513        &mut self,
514        event: ftest_manager::Event,
515        mut sender: mpsc::Sender<SuiteEvent>,
516        filter_debug_data: bool,
517        compressed_debug_data: bool,
518    ) -> Result<(), Error> {
519        let timestamp = event.timestamp;
520        let e = match event.details.expect("Details cannot be null, please file bug.") {
521            ftest_manager::EventDetails::TestCaseFound(cf) => {
522                let test_case_name =
523                    cf.test_case_name.expect("test_case_name must be specified, please file bug.");
524                self.case_map.insert(
525                    cf.test_case_id.expect("test_case_id must be specified, please file bug."),
526                    test_case_name.clone(),
527                );
528                SuiteEvent::case_found(timestamp, test_case_name).into()
529            }
530            ftest_manager::EventDetails::TestCaseStarted(cs) => {
531                let test_case_name = self.get_test_case_name(
532                    cs.test_case_id.expect("test_case_id must be specified, please file bug."),
533                );
534                SuiteEvent::case_started(timestamp, test_case_name).into()
535            }
536            ftest_manager::EventDetails::TestCaseStopped(cs) => {
537                let test_case_name = self.get_test_case_name(
538                    cs.test_case_id.expect("test_case_id must be specified, please file bug."),
539                );
540                if let Some(outputs) = self.std_output_map.remove(
541                    &cs.test_case_id.expect("test_case_id must be specified, please file bug."),
542                ) {
543                    for s in outputs {
544                        s.await.context(format!(
545                            "error collecting stdout/stderr of {}",
546                            test_case_name
547                        ))?;
548                    }
549                }
550                SuiteEvent::case_stopped(
551                    timestamp,
552                    test_case_name,
553                    cs.result.expect("result must be specified, please file bug."),
554                )
555                .into()
556            }
557            ftest_manager::EventDetails::TestCaseFinished(cf) => {
558                let test_case_name = self.get_test_case_name(
559                    cf.test_case_id.expect("test_case_id must be specified, please file bug."),
560                );
561                SuiteEvent::case_finished(timestamp, test_case_name).into()
562            }
563            ftest_manager::EventDetails::TestCaseArtifactGenerated(ca) => {
564                let name = self.get_test_case_name(
565                    ca.test_case_id.expect("test_case_id must be specified, please file bug."),
566                );
567                match ca.artifact.expect("artifact must be specified, please file bug.") {
568                    ftest_manager::Artifact::Stdout(stdout) => {
569                        let (s, mut r) = mpsc::channel(1024);
570                        let stdout_task =
571                            fasync::Task::spawn(collect_and_send_string_output(stdout, s));
572                        let mut sender_clone = sender.clone();
573                        let send_stdout_task = fasync::Task::spawn(async move {
574                            while let Some(msg) = r.next().await {
575                                sender_clone
576                                    .send(SuiteEvent::case_stdout(None, &name, msg))
577                                    .await
578                                    .context(format!("cannot send logs for {}", name))?;
579                            }
580                            Ok(())
581                        });
582                        match self.std_output_map.get_mut(
583                            &ca.test_case_id
584                                .expect("test_case_id must be specified, please file bug."),
585                        ) {
586                            Some(v) => {
587                                v.push(stdout_task);
588                                v.push(send_stdout_task);
589                            }
590                            None => {
591                                self.std_output_map.insert(
592                                    ca.test_case_id
593                                        .expect("test_case_id must be specified, please file bug."),
594                                    vec![stdout_task, send_stdout_task],
595                                );
596                            }
597                        }
598                        None
599                    }
600                    ftest_manager::Artifact::Stderr(stderr) => {
601                        let (s, mut r) = mpsc::channel(1024);
602                        let stderr_task =
603                            fasync::Task::spawn(collect_and_send_string_output(stderr, s));
604                        let mut sender_clone = sender.clone();
605                        let send_stderr_task = fasync::Task::spawn(async move {
606                            while let Some(msg) = r.next().await {
607                                sender_clone
608                                    .send(SuiteEvent::case_stderr(None, &name, msg))
609                                    .await
610                                    .context(format!("cannot send logs for {}", name))?;
611                            }
612                            Ok(())
613                        });
614                        match self.std_output_map.get_mut(
615                            &ca.test_case_id
616                                .expect("test_case_id must be specified, please file bug."),
617                        ) {
618                            Some(v) => {
619                                v.push(stderr_task);
620                                v.push(send_stderr_task);
621                            }
622                            None => {
623                                self.std_output_map.insert(
624                                    ca.test_case_id
625                                        .expect("test_case_id must be specified, please file bug."),
626                                    vec![stderr_task, send_stderr_task],
627                                );
628                            }
629                        }
630                        None
631                    }
632                    ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
633                        Ok(log_stream) => {
634                            SuiteEvent::test_case_log(timestamp, name, log_stream).into()
635                        }
636                        Err(e) => {
637                            warn!("Cannot collect logs for test suite: {:?}", e);
638                            None
639                        }
640                    },
641                    _ => {
642                        panic!("not supported")
643                    }
644                }
645            }
646            ftest_manager::EventDetails::SuiteArtifactGenerated(sa) => {
647                match sa.artifact.expect("artifact must be specified, please file bug.") {
648                    ftest_manager::Artifact::Stdout(_) => {
649                        panic!("not supported")
650                    }
651                    ftest_manager::Artifact::Stderr(_) => {
652                        panic!("not supported")
653                    }
654                    ftest_manager::Artifact::Log(log) => match LogStream::from_syslog(log) {
655                        Ok(log_stream) => SuiteEvent::suite_log(timestamp, log_stream).into(),
656                        Err(e) => {
657                            warn!("Cannot collect logs for test suite: {:?}", e);
658                            None
659                        }
660                    },
661                    ftest_manager::Artifact::Custom(custom_artifact) => {
662                        let ftest_manager::DirectoryAndToken { directory, token } =
663                            custom_artifact.directory_and_token.unwrap();
664                        let component_moniker = custom_artifact.component_moniker.unwrap();
665                        let mut sender_clone = sender.clone();
666                        fasync::Task::spawn(async move {
667                            let directory = directory.into_proxy();
668                            let entries: Vec<_> =
669                                fuchsia_fs::directory::readdir_recursive(&directory, None)
670                                    .try_collect()
671                                    .await
672                                    .expect("read custom artifact directory");
673                            for entry in entries.into_iter() {
674                                let file = fuchsia_fs::directory::open_file_async(
675                                    &directory,
676                                    &entry.name,
677                                    fio::PERM_READABLE,
678                                )
679                                .unwrap();
680                                let contents =
681                                    fuchsia_fs::file::read_to_string(&file).await.unwrap();
682                                sender_clone
683                                    .send(SuiteEvent::suite_custom(
684                                        timestamp,
685                                        component_moniker.clone(),
686                                        entry.name,
687                                        contents,
688                                    ))
689                                    .await
690                                    .unwrap();
691                            }
692                            // Drop the token here - we must keep the token open for the duration that
693                            // the directory is in use.
694                            drop(token);
695                        })
696                        .detach();
697                        None
698                    }
699                    ftest_manager::Artifact::DebugData(iterator) => {
700                        if !filter_debug_data {
701                            let mut sender_clone = sender.clone();
702                            let proxy = iterator.into_proxy();
703                            fasync::Task::spawn(async move {
704                                loop {
705                                    let data = match compressed_debug_data {
706                                        true => proxy.get_next_compressed().await.unwrap(),
707                                        false => proxy.get_next().await.unwrap(),
708                                    };
709                                    if data.is_empty() {
710                                        break;
711                                    }
712                                    for data_file in data {
713                                        let socket =
714                                            data_file.socket.expect("File cannot be empty");
715                                        sender_clone
716                                            .send(SuiteEvent::debug_data(
717                                                timestamp,
718                                                data_file.name.expect("Name cannot be empty"),
719                                                socket,
720                                            ))
721                                            .await
722                                            .unwrap();
723                                    }
724                                }
725                            })
726                            .detach();
727                        }
728                        None
729                    }
730                    _ => {
731                        panic!("not supported")
732                    }
733                }
734            }
735            ftest_manager::EventDetails::SuiteStarted(_started) => SuiteEvent {
736                timestamp,
737                payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStarted),
738            }
739            .into(),
740            ftest_manager::EventDetails::SuiteStopped(stopped) => SuiteEvent {
741                timestamp,
742                payload: SuiteEventPayload::RunEvent(RunEvent::SuiteStopped {
743                    status: stopped.result.expect("result must be specified, please file bug."),
744                }),
745            }
746            .into(),
747            EventDetailsUnknown!() => panic!("Unrecognized EventDetails"),
748        };
749        if let Some(item) = e {
750            sender.send(item).await.context("Cannot send event")?;
751        }
752        Ok(())
753    }
754}
755
756#[derive(Debug, thiserror::Error, Eq, PartialEq, Copy, Clone)]
757pub enum SuiteLaunchError {
758    #[error("Cannot enumerate tests")]
759    CaseEnumeration,
760
761    #[error("Cannot resolve test url")]
762    InstanceCannotResolve,
763
764    #[error("Invalid arguments passed")]
765    InvalidArgs,
766
767    #[error("Cannot connect to test suite")]
768    FailedToConnectToTestSuite,
769
770    #[error("resource unavailable")]
771    ResourceUnavailable,
772
773    #[error("Some internal error occurred. Please file bug")]
774    InternalError,
775
776    #[error("No test cases matched the provided filters")]
777    NoMatchingCases,
778}
779
780impl From<ftest_manager::LaunchError> for SuiteLaunchError {
781    fn from(error: ftest_manager::LaunchError) -> Self {
782        match error {
783            ftest_manager::LaunchError::ResourceUnavailable => {
784                SuiteLaunchError::ResourceUnavailable
785            }
786            ftest_manager::LaunchError::InstanceCannotResolve => {
787                SuiteLaunchError::InstanceCannotResolve
788            }
789            ftest_manager::LaunchError::InvalidArgs => SuiteLaunchError::InvalidArgs,
790            ftest_manager::LaunchError::FailedToConnectToTestSuite => {
791                SuiteLaunchError::FailedToConnectToTestSuite
792            }
793            ftest_manager::LaunchError::CaseEnumeration => SuiteLaunchError::CaseEnumeration,
794            ftest_manager::LaunchError::InternalError => SuiteLaunchError::InternalError,
795            ftest_manager::LaunchError::NoMatchingCases => SuiteLaunchError::NoMatchingCases,
796            ftest_manager::LaunchErrorUnknown!() => panic!("Encountered unknown launch error"),
797        }
798    }
799}
800
801/// Instance to control a single test suite run.
802pub struct SuiteRunInstance {
803    controller_proxy: Arc<SuiteControllerProxy>,
804}
805
806impl SuiteRunInstance {
807    pub fn controller(&self) -> Arc<SuiteControllerProxy> {
808        self.controller_proxy.clone()
809    }
810
811    pub async fn collect_events_with_watch(
812        &self,
813        sender: mpsc::Sender<SuiteEvent>,
814        filter_debug_data: bool,
815        compressed_debug_data: bool,
816    ) -> Result<(), Error> {
817        let controller_proxy = self.controller_proxy.clone();
818        let mut processor = FidlSuiteEventProcessor::new();
819        loop {
820            match controller_proxy.watch_events().await? {
821                Err(e) => return Err(SuiteLaunchError::from(e).into()),
822                Ok(events) => {
823                    if events.len() == 0 {
824                        break;
825                    }
826                    for event in events {
827                        if let Err(e) = processor
828                            .process_event(
829                                event,
830                                sender.clone(),
831                                filter_debug_data,
832                                compressed_debug_data,
833                            )
834                            .await
835                        {
836                            warn!("error running test suite: {:?}", e);
837                            let _ = controller_proxy.kill();
838                            return Ok(());
839                        }
840                    }
841                }
842            }
843        }
844        Ok(())
845    }
846}