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