Skip to main content

run_test_suite_lib/
run.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::cancel::{Cancelled, NamedFutureExt, OrCancel};
6use crate::connector::SuiteRunnerConnector;
7use crate::diagnostics::{self, LogDisplayConfiguration};
8use crate::outcome::{ExtendedOutcome, Outcome, RunTestSuiteError};
9use crate::output::{self, RunReporter, Timestamp};
10use crate::params::{RunParams, TestParams, TimeoutBehavior};
11use crate::running_suite::{RunningSuite, WaitForStartArgs, run_suite_and_collect_logs};
12use diagnostics_data::LogTextDisplayOptions;
13use flex_client::ProxyHasDomain;
14use flex_fuchsia_test_manager::{self as ftest_manager, SuiteRunnerProxy};
15use futures::prelude::*;
16use log::warn;
17use std::io::Write;
18use std::path::PathBuf;
19
20struct RunState<'a> {
21    run_params: &'a RunParams,
22    final_outcome: Option<Outcome>,
23    failed_suites: u32,
24    timeout_occurred: bool,
25    cancel_occurred: bool,
26    internal_error_occurred: bool,
27    setup_succeeded: Option<bool>,
28    teardown_succeeded: Option<bool>,
29}
30
31impl<'a> RunState<'a> {
32    fn new(run_params: &'a RunParams) -> Self {
33        Self {
34            run_params,
35            final_outcome: None,
36            failed_suites: 0,
37            timeout_occurred: false,
38            cancel_occurred: false,
39            internal_error_occurred: false,
40            setup_succeeded: None,
41            teardown_succeeded: None,
42        }
43    }
44
45    fn cancel_run(&mut self, final_outcome: Outcome) {
46        self.final_outcome = Some(final_outcome);
47        self.cancel_occurred = true;
48    }
49
50    fn record_next_outcome(&mut self, next_outcome: ExtendedOutcome) {
51        self.setup_succeeded = next_outcome.setup_succeeded;
52        self.teardown_succeeded = next_outcome.teardown_succeeded;
53        let next_outcome = next_outcome.outcome;
54        if next_outcome != Outcome::Passed {
55            self.failed_suites += 1;
56        }
57        match &next_outcome {
58            Outcome::Timedout => self.timeout_occurred = true,
59            Outcome::Cancelled => self.cancel_occurred = true,
60            Outcome::Error { origin } if origin.is_internal_error() => {
61                self.internal_error_occurred = true;
62            }
63            Outcome::Passed
64            | Outcome::Failed
65            | Outcome::Inconclusive
66            | Outcome::DidNotFinish
67            | Outcome::Error { .. } => (),
68        }
69
70        self.final_outcome = match (self.final_outcome.take(), next_outcome) {
71            (None, first_outcome) => Some(first_outcome),
72            (Some(outcome), Outcome::Passed) => Some(outcome),
73            (Some(_), failing_outcome) => Some(failing_outcome),
74        };
75    }
76
77    fn should_stop_run(&mut self) -> bool {
78        let stop_due_to_timeout = self.run_params.timeout_behavior
79            == TimeoutBehavior::TerminateRemaining
80            && self.timeout_occurred;
81        let stop_due_to_failures = match self.run_params.stop_after_failures.as_ref() {
82            Some(threshold) => self.failed_suites >= threshold.get(),
83            None => false,
84        };
85        stop_due_to_timeout
86            || stop_due_to_failures
87            || self.cancel_occurred
88            || self.internal_error_occurred
89    }
90
91    fn final_outcome(self) -> ExtendedOutcome {
92        ExtendedOutcome {
93            outcome: self.final_outcome.unwrap_or(Outcome::Passed),
94            setup_succeeded: self.setup_succeeded,
95            teardown_succeeded: self.teardown_succeeded,
96        }
97    }
98}
99
100/// Schedule and run the tests specified in |test_params|, and collect the results.
101/// Note this currently doesn't record the result or call finished() on run_reporter,
102/// the caller should do this instead.
103async fn run_test_chunk<'a, F: 'a + Future<Output = ()> + Unpin>(
104    runner_proxy: SuiteRunnerProxy,
105    test_params: TestParams,
106    run_state: &'a mut RunState<'_>,
107    run_params: &'a RunParams,
108    run_reporter: &'a RunReporter,
109    cancel_fut: F,
110) -> Result<(), RunTestSuiteError> {
111    let timeout = test_params
112        .timeout_seconds
113        .map(|seconds| std::time::Duration::from_secs(seconds.get() as u64));
114
115    // If the test spec includes minimum log severity, combine that with any selectors we
116    // got from the command line.
117    let mut combined_log_interest = run_params.min_severity_logs.clone();
118    combined_log_interest.extend(test_params.min_severity_logs.iter().cloned());
119
120    let mut run_options = flex_fuchsia_test_manager::RunSuiteOptions {
121        max_concurrent_test_case_runs: test_params.parallel,
122        arguments: Some(test_params.test_args),
123        timeout: timeout.map(|duration| duration.as_nanos() as i64),
124        run_disabled_tests: Some(test_params.also_run_disabled_tests),
125        test_case_filters: test_params.test_filters,
126        break_on_failure: Some(test_params.break_on_failure),
127        logs_iterator_type: Some(
128            run_params.log_protocol.unwrap_or_else(diagnostics::get_logs_iterator_type),
129        ),
130        log_interest: Some(combined_log_interest),
131        no_exception_channel: Some(test_params.no_exception_channel),
132        ..Default::default()
133    };
134    let suite = run_reporter.new_suite(&test_params.test_url)?;
135    suite.set_tags(test_params.tags);
136    if let Some(realm) = test_params.realm.as_ref() {
137        run_options.realm_options = Some(flex_fuchsia_test_manager::RealmOptions {
138            realm: Some(realm.get_realm_client()?),
139            offers: Some(realm.offers()),
140            test_collection: Some(realm.collection().to_string()),
141            ..Default::default()
142        });
143    }
144    let (suite_controller, suite_server_end) =
145        runner_proxy.domain().create_proxy::<ftest_manager::SuiteControllerMarker>();
146    let suite_start_fut = RunningSuite::wait_for_start(WaitForStartArgs {
147        proxy: suite_controller,
148        max_severity_logs: test_params.max_severity_logs,
149        timeout,
150        timeout_grace: std::time::Duration::from_secs(run_params.timeout_grace_seconds as u64),
151        max_pipelined: None,
152        no_cases_equals_success: test_params.no_cases_equals_success,
153    });
154
155    runner_proxy.run(&test_params.test_url, run_options, suite_server_end)?;
156
157    let cancel_fut = cancel_fut.shared();
158
159    let handle_suite_fut = async move {
160        // There's only one suite, but we loop so we can use break for flow control.
161        'block: {
162            let suite_stop_fut = cancel_fut.clone().map(|_| Outcome::Cancelled);
163
164            let running_suite =
165                match suite_start_fut.named("suite_start").or_cancelled(suite_stop_fut).await {
166                    Ok(running_suite) => running_suite,
167                    Err(Cancelled(final_outcome)) => {
168                        run_state.cancel_run(final_outcome);
169                        break 'block;
170                    }
171                };
172
173            let log_display = LogDisplayConfiguration {
174                interest: run_params.min_severity_logs.clone(),
175                text_options: LogTextDisplayOptions {
176                    show_full_moniker: run_params.show_full_moniker,
177                    ..Default::default()
178                },
179            };
180
181            let result =
182                run_suite_and_collect_logs(running_suite, &suite, log_display, cancel_fut.clone())
183                    .await;
184            // `run_suite_and_collect_logs` return an error only when reporters fail *after*
185            // event collection is complete. In that case, setup has succeeded, but there was
186            // an issue during teardown.
187            let suite_outcome = result.unwrap_or_else(|err| ExtendedOutcome {
188                outcome: Outcome::error(err),
189                setup_succeeded: Some(true),
190                teardown_succeeded: Some(false),
191            });
192            // We should always persist results, even if something failed.
193            suite.finished()?;
194            run_state.record_next_outcome(suite_outcome);
195            if run_state.should_stop_run() {
196                break 'block;
197            }
198        }
199        Result::<_, RunTestSuiteError>::Ok(())
200    };
201
202    handle_suite_fut.boxed_local().await.map_err(|e| e.into())
203}
204
205async fn run_tests<'a, F: 'a + Future<Output = ()> + Unpin>(
206    connector: impl SuiteRunnerConnector,
207    test_params: TestParams,
208    run_params: RunParams,
209    run_reporter: &'a RunReporter,
210    cancel_fut: F,
211) -> Result<ExtendedOutcome, RunTestSuiteError> {
212    let mut run_state = RunState::new(&run_params);
213    let cancel_fut = cancel_fut.shared();
214    match run_state.should_stop_run() {
215        true => {
216            // This indicates we need to terminate early. The suite wasn't recorded at all,
217            // so we need to drain and record it wasn't started.
218            let suite_reporter = run_reporter.new_suite(&test_params.test_url)?;
219            suite_reporter.set_tags(test_params.tags);
220            suite_reporter.finished()?;
221        }
222        false => {
223            let runner_proxy = connector.connect().await?;
224            run_test_chunk(
225                runner_proxy,
226                test_params,
227                &mut run_state,
228                &run_params,
229                run_reporter,
230                cancel_fut.clone(),
231            )
232            .await?;
233        }
234    }
235
236    Ok(run_state.final_outcome())
237}
238
239/// Runs test specified in |test_params| and reports the results to
240/// |run_reporter|.
241///
242/// Options specifying how the test run is executed are passed in via |run_params|.
243/// Options specific to how a single suite is run are passed in via the entry for
244/// the suite in |test_params|.
245/// |cancel_fut| is used to gracefully stop execution of tests. Tests are
246/// terminated and recorded when the future resolves. The caller can control when the
247/// future resolves by passing in the receiver end of a `future::channel::oneshot`
248/// channel.
249pub async fn run_test_and_get_outcome<F>(
250    connector: impl SuiteRunnerConnector,
251    test_params: TestParams,
252    run_params: RunParams,
253    run_reporter: RunReporter,
254    cancel_fut: F,
255) -> ExtendedOutcome
256where
257    F: Future<Output = ()>,
258{
259    match run_reporter.started(Timestamp::Unknown) {
260        Ok(()) => (),
261        Err(e) => {
262            return ExtendedOutcome {
263                outcome: Outcome::error(e),
264                setup_succeeded: None,
265                teardown_succeeded: None,
266            };
267        }
268    }
269    let test_outcome = match run_tests(
270        connector,
271        test_params,
272        run_params,
273        &run_reporter,
274        cancel_fut.boxed_local(),
275    )
276    .await
277    {
278        Ok(s) => s,
279        Err(e) => {
280            return ExtendedOutcome {
281                outcome: Outcome::error(e),
282                setup_succeeded: None,
283                teardown_succeeded: None,
284            };
285        }
286    };
287
288    let report_result =
289        match run_reporter.stopped(&test_outcome.outcome.clone().into(), Timestamp::Unknown) {
290            Ok(()) => run_reporter.finished(),
291            Err(e) => Err(e),
292        };
293    if let Err(e) = report_result {
294        warn!("Failed to record results: {:?}", e);
295    }
296
297    test_outcome
298}
299
300pub struct DirectoryReporterOptions {
301    /// Root path of the directory.
302    pub root_path: PathBuf,
303}
304
305/// Create a reporter for use with |run_tests_and_get_outcome|.
306pub fn create_reporter<W: 'static + Write + Send + Sync>(
307    filter_ansi: bool,
308    dir: Option<DirectoryReporterOptions>,
309    writer: W,
310) -> Result<output::RunReporter, anyhow::Error> {
311    let stdout_reporter = output::ShellReporter::new(writer);
312    let dir_reporter = dir
313        .map(|dir| {
314            output::DirectoryWithStdoutReporter::new(dir.root_path, output::SchemaVersion::V1)
315        })
316        .transpose()?;
317    let reporter = match (dir_reporter, filter_ansi) {
318        (Some(dir_reporter), false) => output::RunReporter::new(output::MultiplexedReporter::new(
319            stdout_reporter,
320            dir_reporter,
321        )),
322        (Some(dir_reporter), true) => output::RunReporter::new_ansi_filtered(
323            output::MultiplexedReporter::new(stdout_reporter, dir_reporter),
324        ),
325        (None, false) => output::RunReporter::new(stdout_reporter),
326        (None, true) => output::RunReporter::new_ansi_filtered(stdout_reporter),
327    };
328    Ok(reporter)
329}
330
331#[cfg(test)]
332mod test {
333    use super::*;
334    use crate::connector::SingleRunConnector;
335    use crate::output::{EntityId, InMemoryReporter};
336    use assert_matches::assert_matches;
337    use fidl::endpoints::{Proxy, create_proxy_and_stream};
338    use flex_fuchsia_test_manager as ftest_manager;
339    use futures::future::join;
340    use futures::stream::futures_unordered::FuturesUnordered;
341    use maplit::hashmap;
342    use std::collections::HashMap;
343    #[cfg(target_os = "fuchsia")]
344    use {
345        flex_fuchsia_io as fio, futures::future::join3, vfs::file::vmo::read_only,
346        vfs::pseudo_directory, zx,
347    };
348
349    // TODO(https://fxbug.dev/42180532): add unit tests for suite artifacts too.
350
351    async fn fake_running_all_suites(
352        mut stream: ftest_manager::SuiteRunnerRequestStream,
353        mut suite_events: HashMap<&str, Vec<ftest_manager::Event>>,
354    ) {
355        let mut suite_streams = vec![];
356
357        if let Ok(Some(req)) = stream.try_next().await {
358            match req {
359                ftest_manager::SuiteRunnerRequest::Run { test_suite_url, controller, .. } => {
360                    let events = suite_events
361                        .remove(test_suite_url.as_str())
362                        .expect("Got a request for an unexpected test URL");
363                    suite_streams.push((controller.into_stream(), events));
364                }
365                ftest_manager::SuiteRunnerRequest::_UnknownMethod { ordinal, .. } => {
366                    panic!("Not expecting unknown request: {}", ordinal)
367                }
368            }
369        }
370        assert!(suite_events.is_empty(), "Expected AddSuite to be called for all specified suites");
371
372        // Each suite just reports that it started and passed.
373        let mut suite_streams = suite_streams
374            .into_iter()
375            .map(|(mut stream, events)| {
376                async move {
377                    let mut maybe_events = Some(events);
378                    while let Ok(Some(req)) = stream.try_next().await {
379                        match req {
380                            ftest_manager::SuiteControllerRequest::WatchEvents {
381                                responder,
382                                ..
383                            } => {
384                                let send_events = maybe_events.take().unwrap_or(vec![]);
385                                let _ = responder.send(Ok(send_events));
386                            }
387                            _ => {
388                                // ignore all other requests
389                            }
390                        }
391                    }
392                }
393            })
394            .collect::<FuturesUnordered<_>>();
395
396        async move { while let Some(_) = suite_streams.next().await {} }.await;
397    }
398
399    struct ParamsForRunTests {
400        runner_proxy: ftest_manager::SuiteRunnerProxy,
401        test_params: TestParams,
402        run_reporter: RunReporter,
403    }
404
405    fn create_empty_suite_events() -> Vec<ftest_manager::Event> {
406        vec![
407            ftest_manager::Event {
408                timestamp: Some(1000),
409                details: Some(ftest_manager::EventDetails::SuiteStarted(
410                    ftest_manager::SuiteStartedEventDetails { ..Default::default() },
411                )),
412                ..Default::default()
413            },
414            ftest_manager::Event {
415                timestamp: Some(2000),
416                details: Some(ftest_manager::EventDetails::SuiteStopped(
417                    ftest_manager::SuiteStoppedEventDetails {
418                        result: Some(ftest_manager::SuiteResult::Finished),
419                        ..Default::default()
420                    },
421                )),
422                ..Default::default()
423            },
424        ]
425    }
426
427    async fn call_run_tests(params: ParamsForRunTests) -> ExtendedOutcome {
428        run_test_and_get_outcome(
429            SingleRunConnector::new(params.runner_proxy),
430            params.test_params,
431            RunParams {
432                timeout_behavior: TimeoutBehavior::Continue,
433                timeout_grace_seconds: 0,
434                stop_after_failures: None,
435                accumulate_debug_data: false,
436                log_protocol: None,
437                min_severity_logs: vec![],
438                show_full_moniker: false,
439            },
440            params.run_reporter,
441            futures::future::pending(),
442        )
443        .await
444    }
445
446    #[fuchsia::test]
447    async fn single_run_no_events() {
448        let (runner_proxy, suite_runner_stream) =
449            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
450
451        let reporter = InMemoryReporter::new();
452        let run_reporter = RunReporter::new(reporter.clone());
453        let run_fut = call_run_tests(ParamsForRunTests {
454            runner_proxy,
455            test_params: TestParams {
456                test_url: "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm".to_string(),
457                ..TestParams::default()
458            },
459            run_reporter,
460        });
461        let fake_fut = fake_running_all_suites(
462            suite_runner_stream,
463            hashmap! {
464                "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm" => create_empty_suite_events()
465            },
466        );
467
468        assert_eq!(
469            join(run_fut, fake_fut).await.0,
470            ExtendedOutcome {
471                outcome: Outcome::Passed,
472                setup_succeeded: Some(true),
473                teardown_succeeded: Some(true),
474            }
475        );
476
477        let reports = reporter.get_reports();
478        assert_eq!(2usize, reports.len());
479        assert!(reports[0].report.artifacts.is_empty());
480        assert!(reports[0].report.directories.is_empty());
481        assert!(reports[1].report.artifacts.is_empty());
482        assert!(reports[1].report.directories.is_empty());
483    }
484
485    #[cfg(target_os = "fuchsia")]
486    #[fuchsia::test]
487    async fn single_run_custom_directory() {
488        let (runner_proxy, suite_runner_stream) =
489            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
490
491        let reporter = InMemoryReporter::new();
492        let run_reporter = RunReporter::new(reporter.clone());
493        let run_fut = call_run_tests(ParamsForRunTests {
494            runner_proxy,
495            test_params: TestParams {
496                test_url: "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm".to_string(),
497                ..TestParams::default()
498            },
499            run_reporter,
500        });
501
502        let dir = pseudo_directory! {
503            "test_file.txt" => read_only("Hello, World!"),
504        };
505
506        let directory_proxy = vfs::directory::serve(
507            dir,
508            vfs::execution_scope::ExecutionScope::new(),
509            fio::PERM_READABLE | fio::PERM_WRITABLE,
510        );
511
512        let directory_client =
513            fidl::endpoints::ClientEnd::new(directory_proxy.into_channel().unwrap().into());
514
515        let (_pair_1, pair_2) = zx::EventPair::create();
516
517        let events = vec![
518            ftest_manager::Event {
519                timestamp: Some(1000),
520                details: Some(ftest_manager::EventDetails::SuiteStarted(
521                    ftest_manager::SuiteStartedEventDetails { ..Default::default() },
522                )),
523                ..Default::default()
524            },
525            ftest_manager::Event {
526                details: Some(ftest_manager::EventDetails::SuiteArtifactGenerated(
527                    ftest_manager::SuiteArtifactGeneratedEventDetails {
528                        artifact: Some(ftest_manager::Artifact::Custom(
529                            ftest_manager::CustomArtifact {
530                                directory_and_token: Some(ftest_manager::DirectoryAndToken {
531                                    directory: directory_client,
532                                    token: pair_2,
533                                }),
534                                ..Default::default()
535                            },
536                        )),
537                        ..Default::default()
538                    },
539                )),
540                ..Default::default()
541            },
542            ftest_manager::Event {
543                timestamp: Some(2000),
544                details: Some(ftest_manager::EventDetails::SuiteStopped(
545                    ftest_manager::SuiteStoppedEventDetails {
546                        result: Some(ftest_manager::SuiteResult::Finished),
547                        ..Default::default()
548                    },
549                )),
550                ..Default::default()
551            },
552        ];
553
554        let fake_fut = fake_running_all_suites(
555            suite_runner_stream,
556            hashmap! {
557                "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm" => events
558            },
559        );
560
561        assert_eq!(
562            join(run_fut, fake_fut).await.0,
563            ExtendedOutcome {
564                outcome: Outcome::Passed,
565                setup_succeeded: Some(true),
566                teardown_succeeded: Some(true),
567            },
568        );
569
570        let reports = reporter.get_reports();
571        assert_eq!(2usize, reports.len());
572        let run = reports.iter().find(|e| e.id == EntityId::Suite).expect("find run report");
573        assert_eq!(1usize, run.report.directories.len());
574        let dir = &run.report.directories[0];
575        let files = dir.1.files.lock();
576        assert_eq!(1usize, files.len());
577        let (name, file) = &files[0];
578        assert_eq!(name.to_string_lossy(), "test_file.txt".to_string());
579        assert_eq!(file.get_contents(), b"Hello, World!");
580    }
581
582    #[fuchsia::test]
583    async fn record_output_after_internal_error() {
584        let (runner_proxy, suite_runner_stream) =
585            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
586
587        let reporter = InMemoryReporter::new();
588        let run_reporter = RunReporter::new(reporter.clone());
589        let run_fut = call_run_tests(ParamsForRunTests {
590            runner_proxy,
591            test_params: TestParams {
592                test_url: "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm".to_string(),
593                ..TestParams::default()
594            },
595            run_reporter,
596        });
597
598        let fake_fut = fake_running_all_suites(
599            suite_runner_stream,
600            hashmap! {
601                // return an internal error from the test.
602                "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm" => vec![
603                    ftest_manager::Event {
604                        timestamp: Some(1000),
605                        details: Some(ftest_manager::EventDetails::SuiteStarted(ftest_manager::SuiteStartedEventDetails {..Default::default()})),
606                        ..Default::default()
607                    },
608                    ftest_manager::Event {
609                        timestamp: Some(2000),
610                        details: Some(ftest_manager::EventDetails::SuiteStopped(ftest_manager::SuiteStoppedEventDetails {
611                            result: Some(ftest_manager::SuiteResult::InternalError),
612                            ..Default::default()
613                        },
614                        )),
615                        ..Default::default()
616                    },
617                ],
618            },
619        );
620
621        assert_matches!(
622            join(run_fut, fake_fut).await.0,
623            ExtendedOutcome {
624                outcome: Outcome::Error { .. },
625                setup_succeeded: Some(true),
626                teardown_succeeded: Some(true)
627            }
628        );
629
630        let reports = reporter.get_reports();
631        assert_eq!(2usize, reports.len());
632        let invalid_suite = reports
633            .iter()
634            .find(|e| e.report.name == "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm")
635            .expect("find run report");
636        assert_eq!(invalid_suite.report.outcome, Some(output::ReportedOutcome::Error));
637        assert!(invalid_suite.report.is_finished);
638
639        // The results for the run should also be saved.
640        let run = reports.iter().find(|e| e.id == EntityId::TestRun).expect("find run report");
641        assert_eq!(run.report.outcome, Some(output::ReportedOutcome::Error));
642        assert!(run.report.is_finished);
643        assert!(run.report.started_time.is_some());
644    }
645
646    #[fuchsia::test]
647    async fn record_output_after_launch_error() {
648        let (runner_proxy, mut suite_runner_stream) =
649            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
650
651        let reporter = InMemoryReporter::new();
652        let run_reporter = RunReporter::new(reporter.clone());
653        let run_fut = call_run_tests(ParamsForRunTests {
654            runner_proxy,
655            test_params: TestParams {
656                test_url: "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm".to_string(),
657                ..TestParams::default()
658            },
659            run_reporter,
660        });
661
662        let fake_fut = async move {
663            if let Ok(Some(ftest_manager::SuiteRunnerRequest::Run { controller, .. })) =
664                suite_runner_stream.try_next().await
665            {
666                let mut stream = controller.into_stream();
667                while let Ok(Some(req)) = stream.try_next().await {
668                    if let ftest_manager::SuiteControllerRequest::WatchEvents {
669                        responder, ..
670                    } = req
671                    {
672                        let _ =
673                            responder.send(Err(ftest_manager::LaunchError::InstanceCannotResolve));
674                    }
675                }
676            }
677        };
678
679        assert_matches!(
680            join(run_fut, fake_fut).await.0,
681            ExtendedOutcome {
682                outcome: Outcome::Error { .. },
683                setup_succeeded: Some(false),
684                teardown_succeeded: None
685            }
686        );
687    }
688
689    #[cfg(target_os = "fuchsia")]
690    #[fuchsia::test]
691    async fn single_run_debug_data() {
692        let (runner_proxy, suite_runner_stream) =
693            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
694
695        let reporter = InMemoryReporter::new();
696        let run_reporter = RunReporter::new(reporter.clone());
697        let run_fut = call_run_tests(ParamsForRunTests {
698            runner_proxy,
699            test_params: TestParams {
700                test_url: "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm".to_string(),
701                ..TestParams::default()
702            },
703            run_reporter,
704        });
705
706        let (debug_client, debug_service) =
707            fidl::endpoints::create_endpoints::<ftest_manager::DebugDataIteratorMarker>();
708        let debug_data_fut = async move {
709            let (client, server) = zx::Socket::create_stream();
710            let mut compressor = zstd::bulk::Compressor::new(0).unwrap();
711            let bytes = compressor.compress(b"Not a real profile").unwrap();
712            let _ = server.write(bytes.as_slice()).unwrap();
713            let mut service = debug_service.into_stream();
714            let mut data = vec![ftest_manager::DebugData {
715                name: Some("test_file.profraw".to_string()),
716                socket: Some(client.into()),
717                ..Default::default()
718            }];
719            drop(server);
720            while let Ok(Some(request)) = service.try_next().await {
721                match request {
722                    ftest_manager::DebugDataIteratorRequest::GetNext { .. } => {
723                        panic!("Not Implemented");
724                    }
725                    ftest_manager::DebugDataIteratorRequest::GetNextCompressed {
726                        responder,
727                        ..
728                    } => {
729                        let _ = responder.send(std::mem::take(&mut data));
730                    }
731                }
732            }
733        };
734
735        let events = vec![
736            ftest_manager::Event {
737                timestamp: Some(1000),
738                details: Some(ftest_manager::EventDetails::SuiteStarted(
739                    ftest_manager::SuiteStartedEventDetails { ..Default::default() },
740                )),
741                ..Default::default()
742            },
743            ftest_manager::Event {
744                details: Some(ftest_manager::EventDetails::SuiteArtifactGenerated(
745                    ftest_manager::SuiteArtifactGeneratedEventDetails {
746                        artifact: Some(ftest_manager::Artifact::DebugData(debug_client)),
747                        ..Default::default()
748                    },
749                )),
750                ..Default::default()
751            },
752            ftest_manager::Event {
753                timestamp: Some(2000),
754                details: Some(ftest_manager::EventDetails::SuiteStopped(
755                    ftest_manager::SuiteStoppedEventDetails {
756                        result: Some(ftest_manager::SuiteResult::Finished),
757                        ..Default::default()
758                    },
759                )),
760                ..Default::default()
761            },
762        ];
763
764        let fake_fut = fake_running_all_suites(
765            suite_runner_stream,
766            hashmap! {
767                "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm" => events,
768            },
769        );
770
771        assert_eq!(
772            join3(run_fut, debug_data_fut, fake_fut).await.0,
773            ExtendedOutcome {
774                outcome: Outcome::Passed,
775                setup_succeeded: Some(true),
776                teardown_succeeded: Some(true),
777            }
778        );
779
780        let reports = reporter.get_reports();
781        assert_eq!(2usize, reports.len());
782        let run = reports.iter().find(|e| e.id == EntityId::Suite).expect("find run report");
783        assert_eq!(1usize, run.report.directories.len());
784        let dir = &run.report.directories[0];
785        let files = dir.1.files.lock();
786        assert_eq!(1usize, files.len());
787        let (name, file) = &files[0];
788        assert_eq!(name.to_string_lossy(), "test_file.profraw".to_string());
789        assert_eq!(file.get_contents(), b"Not a real profile");
790    }
791}