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(dir, fio::PERM_READABLE | fio::PERM_WRITABLE);
472
473        let directory_client =
474            fidl::endpoints::ClientEnd::new(directory_proxy.into_channel().unwrap().into());
475
476        let (_pair_1, pair_2) = zx::EventPair::create();
477
478        let events = vec![
479            ftest_manager::Event {
480                timestamp: Some(1000),
481                details: Some(ftest_manager::EventDetails::SuiteStarted(
482                    ftest_manager::SuiteStartedEventDetails { ..Default::default() },
483                )),
484                ..Default::default()
485            },
486            ftest_manager::Event {
487                details: Some(ftest_manager::EventDetails::SuiteArtifactGenerated(
488                    ftest_manager::SuiteArtifactGeneratedEventDetails {
489                        artifact: Some(ftest_manager::Artifact::Custom(
490                            ftest_manager::CustomArtifact {
491                                directory_and_token: Some(ftest_manager::DirectoryAndToken {
492                                    directory: directory_client,
493                                    token: pair_2,
494                                }),
495                                ..Default::default()
496                            },
497                        )),
498                        ..Default::default()
499                    },
500                )),
501                ..Default::default()
502            },
503            ftest_manager::Event {
504                timestamp: Some(2000),
505                details: Some(ftest_manager::EventDetails::SuiteStopped(
506                    ftest_manager::SuiteStoppedEventDetails {
507                        result: Some(ftest_manager::SuiteResult::Finished),
508                        ..Default::default()
509                    },
510                )),
511                ..Default::default()
512            },
513        ];
514
515        let fake_fut = fake_running_all_suites(
516            suite_runner_stream,
517            hashmap! {
518                "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm" => events
519            },
520        );
521
522        assert_eq!(join(run_fut, fake_fut).await.0, Outcome::Passed,);
523
524        let reports = reporter.get_reports();
525        assert_eq!(2usize, reports.len());
526        let run = reports.iter().find(|e| e.id == EntityId::Suite).expect("find run report");
527        assert_eq!(1usize, run.report.directories.len());
528        let dir = &run.report.directories[0];
529        let files = dir.1.files.lock();
530        assert_eq!(1usize, files.len());
531        let (name, file) = &files[0];
532        assert_eq!(name.to_string_lossy(), "test_file.txt".to_string());
533        assert_eq!(file.get_contents(), b"Hello, World!");
534    }
535
536    #[fuchsia::test]
537    async fn record_output_after_internal_error() {
538        let (runner_proxy, suite_runner_stream) =
539            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
540
541        let reporter = InMemoryReporter::new();
542        let run_reporter = RunReporter::new(reporter.clone());
543        let run_fut = call_run_tests(ParamsForRunTests {
544            runner_proxy,
545            test_params: TestParams {
546                test_url: "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm".to_string(),
547                ..TestParams::default()
548            },
549            run_reporter,
550        });
551
552        let fake_fut = fake_running_all_suites(
553            suite_runner_stream,
554            hashmap! {
555                // return an internal error from the test.
556                "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm" => vec![
557                    ftest_manager::Event {
558                        timestamp: Some(1000),
559                        details: Some(ftest_manager::EventDetails::SuiteStarted(ftest_manager::SuiteStartedEventDetails {..Default::default()})),
560                        ..Default::default()
561                    },
562                    ftest_manager::Event {
563                        timestamp: Some(2000),
564                        details: Some(ftest_manager::EventDetails::SuiteStopped(ftest_manager::SuiteStoppedEventDetails {
565                            result: Some(ftest_manager::SuiteResult::InternalError),
566                            ..Default::default()
567                        },
568                        )),
569                        ..Default::default()
570                    },
571                ],
572            },
573        );
574
575        assert_matches!(join(run_fut, fake_fut).await.0, Outcome::Error { .. });
576
577        let reports = reporter.get_reports();
578        assert_eq!(2usize, reports.len());
579        let invalid_suite = reports
580            .iter()
581            .find(|e| e.report.name == "fuchsia-pkg://fuchsia.com/invalid#meta/invalid.cm")
582            .expect("find run report");
583        assert_eq!(invalid_suite.report.outcome, Some(output::ReportedOutcome::Error));
584        assert!(invalid_suite.report.is_finished);
585
586        // The results for the run should also be saved.
587        let run = reports.iter().find(|e| e.id == EntityId::TestRun).expect("find run report");
588        assert_eq!(run.report.outcome, Some(output::ReportedOutcome::Error));
589        assert!(run.report.is_finished);
590        assert!(run.report.started_time.is_some());
591    }
592
593    #[cfg(target_os = "fuchsia")]
594    #[fuchsia::test]
595    async fn single_run_debug_data() {
596        let (runner_proxy, suite_runner_stream) =
597            create_proxy_and_stream::<ftest_manager::SuiteRunnerMarker>();
598
599        let reporter = InMemoryReporter::new();
600        let run_reporter = RunReporter::new(reporter.clone());
601        let run_fut = call_run_tests(ParamsForRunTests {
602            runner_proxy,
603            test_params: TestParams {
604                test_url: "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm".to_string(),
605                ..TestParams::default()
606            },
607            run_reporter,
608        });
609
610        let (debug_client, debug_service) =
611            fidl::endpoints::create_endpoints::<ftest_manager::DebugDataIteratorMarker>();
612        let debug_data_fut = async move {
613            let (client, server) = zx::Socket::create_stream();
614            let mut compressor = zstd::bulk::Compressor::new(0).unwrap();
615            let bytes = compressor.compress(b"Not a real profile").unwrap();
616            let _ = server.write(bytes.as_slice()).unwrap();
617            let mut service = debug_service.into_stream();
618            let mut data = vec![ftest_manager::DebugData {
619                name: Some("test_file.profraw".to_string()),
620                socket: Some(client.into()),
621                ..Default::default()
622            }];
623            drop(server);
624            while let Ok(Some(request)) = service.try_next().await {
625                match request {
626                    ftest_manager::DebugDataIteratorRequest::GetNext { .. } => {
627                        panic!("Not Implemented");
628                    }
629                    ftest_manager::DebugDataIteratorRequest::GetNextCompressed {
630                        responder,
631                        ..
632                    } => {
633                        let _ = responder.send(std::mem::take(&mut data));
634                    }
635                }
636            }
637        };
638
639        let events = vec![
640            ftest_manager::Event {
641                timestamp: Some(1000),
642                details: Some(ftest_manager::EventDetails::SuiteStarted(
643                    ftest_manager::SuiteStartedEventDetails { ..Default::default() },
644                )),
645                ..Default::default()
646            },
647            ftest_manager::Event {
648                details: Some(ftest_manager::EventDetails::SuiteArtifactGenerated(
649                    ftest_manager::SuiteArtifactGeneratedEventDetails {
650                        artifact: Some(ftest_manager::Artifact::DebugData(debug_client)),
651                        ..Default::default()
652                    },
653                )),
654                ..Default::default()
655            },
656            ftest_manager::Event {
657                timestamp: Some(2000),
658                details: Some(ftest_manager::EventDetails::SuiteStopped(
659                    ftest_manager::SuiteStoppedEventDetails {
660                        result: Some(ftest_manager::SuiteResult::Finished),
661                        ..Default::default()
662                    },
663                )),
664                ..Default::default()
665            },
666        ];
667
668        let fake_fut = fake_running_all_suites(
669            suite_runner_stream,
670            hashmap! {
671                "fuchsia-pkg://fuchsia.com/nothing#meta/nothing.cm" => events,
672            },
673        );
674
675        assert_eq!(join3(run_fut, debug_data_fut, fake_fut).await.0, Outcome::Passed);
676
677        let reports = reporter.get_reports();
678        assert_eq!(2usize, reports.len());
679        let run = reports.iter().find(|e| e.id == EntityId::Suite).expect("find run report");
680        assert_eq!(1usize, run.report.directories.len());
681        let dir = &run.report.directories[0];
682        let files = dir.1.files.lock();
683        assert_eq!(1usize, files.len());
684        let (name, file) = &files[0];
685        assert_eq!(name.to_string_lossy(), "test_file.profraw".to_string());
686        assert_eq!(file.get_contents(), b"Not a real profile");
687    }
688}