Skip to main content

test_runners_test_lib/
test_lib.rs

1#![allow(unused_crate_dependencies)]
2// Copyright 2020 The Fuchsia Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5
6use anyhow::{Context, Error, format_err};
7use fidl::endpoints;
8use fidl::endpoints::{ClientEnd, Proxy};
9use fidl_fuchsia_component as fcomponent;
10use fidl_fuchsia_component_decl as fdecl;
11use fidl_fuchsia_component_runner as fcrunner;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_test::CaseListenerRequest::Finished;
14use fidl_fuchsia_test::RunListenerRequest::{OnFinished, OnTestCaseStarted};
15use fidl_fuchsia_test::{Invocation, Result_ as TestResult, RunListenerRequestStream};
16use fidl_fuchsia_test_manager as ftest_manager;
17use fuchsia_async as fasync;
18use fuchsia_component::client::{self, connect_to_protocol_at_dir_root};
19use fuchsia_runtime::job_default;
20use futures::channel::mpsc;
21use futures::prelude::*;
22use namespace::{Namespace, NamespaceError};
23use std::collections::HashMap;
24use std::sync::Arc;
25use test_manager_test_lib::RunEvent;
26use test_runners_lib::elf::{BuilderArgs, Component};
27
28#[derive(PartialEq, Debug)]
29pub enum ListenerEvent {
30    StartTest(String),
31    FinishTest(String, TestResult),
32    FinishAllTests,
33}
34
35fn get_ord_index_and_name(event: &ListenerEvent) -> (usize, &str) {
36    match event {
37        ListenerEvent::StartTest(name) => (0, name),
38        ListenerEvent::FinishTest(name, _) => (1, name),
39        ListenerEvent::FinishAllTests => (2, ""),
40    }
41}
42
43// Orders by test name and then event type.
44impl Ord for ListenerEvent {
45    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
46        let (s_index, s_test_name) = get_ord_index_and_name(self);
47        let (o_index, o_test_name) = get_ord_index_and_name(other);
48        if s_test_name == o_test_name || s_index == 2 || o_index == 2 {
49            return s_index.cmp(&o_index);
50        }
51        return s_test_name.cmp(&o_test_name);
52    }
53}
54
55// Makes sure that FinishTest event never shows up before StartTest and FinishAllTests is always
56// last.
57pub fn assert_event_ord(events: &Vec<ListenerEvent>) {
58    let mut tests = HashMap::new();
59    let mut all_finish = false;
60    for event in events {
61        assert!(!all_finish, "got FinishAllTests event twice: {:#?}", events);
62        match event {
63            ListenerEvent::StartTest(name) => {
64                assert!(
65                    !tests.contains_key(&name),
66                    "Multiple StartTest for test {}: {:#?}",
67                    name,
68                    events
69                );
70                tests.insert(name, false);
71            }
72            ListenerEvent::FinishTest(name, _) => {
73                assert!(
74                    tests.contains_key(&name),
75                    "Got finish before start event for test {}: {:#?}",
76                    name,
77                    events
78                );
79                assert!(
80                    !tests.insert(name, true).unwrap(),
81                    "Multiple FinishTest for test {}: {:#?}",
82                    name,
83                    events
84                );
85            }
86            ListenerEvent::FinishAllTests => {
87                all_finish = true;
88            }
89        }
90    }
91}
92
93impl PartialOrd for ListenerEvent {
94    fn partial_cmp(&self, other: &ListenerEvent) -> Option<core::cmp::Ordering> {
95        Some(self.cmp(other))
96    }
97}
98
99impl Eq for ListenerEvent {}
100
101impl ListenerEvent {
102    pub fn start_test(name: &str) -> ListenerEvent {
103        ListenerEvent::StartTest(name.to_string())
104    }
105    pub fn finish_test(name: &str, test_result: TestResult) -> ListenerEvent {
106        ListenerEvent::FinishTest(name.to_string(), test_result)
107    }
108    pub fn finish_all_test() -> ListenerEvent {
109        ListenerEvent::FinishAllTests
110    }
111}
112
113impl Clone for ListenerEvent {
114    fn clone(&self) -> Self {
115        match self {
116            ListenerEvent::StartTest(name) => ListenerEvent::start_test(name),
117            ListenerEvent::FinishTest(name, test_result) => ListenerEvent::finish_test(
118                name,
119                TestResult { status: test_result.status.clone(), ..Default::default() },
120            ),
121            ListenerEvent::FinishAllTests => ListenerEvent::finish_all_test(),
122        }
123    }
124}
125
126/// Collects all the listener event as they come and return in a vector.
127pub async fn collect_listener_event(
128    mut listener: RunListenerRequestStream,
129) -> Result<Vec<ListenerEvent>, Error> {
130    let mut ret = vec![];
131    // collect loggers so that they do not die.
132    let mut loggers = vec![];
133    while let Some(result_event) = listener.try_next().await? {
134        match result_event {
135            OnTestCaseStarted { invocation, std_handles, listener, .. } => {
136                let name = invocation.name.unwrap();
137                ret.push(ListenerEvent::StartTest(name.clone()));
138                loggers.push(std_handles);
139                let mut listener = listener.into_stream();
140                // We want exhaustive match, and if we add more variants in the future we'd need to
141                // handle the requests in a loop, so allow this lint violation.
142                #[allow(clippy::never_loop)]
143                while let Some(result) = listener.try_next().await? {
144                    match result {
145                        Finished { result, .. } => {
146                            ret.push(ListenerEvent::FinishTest(name, result));
147                            break;
148                        }
149                    }
150                }
151            }
152            OnFinished { .. } => {
153                ret.push(ListenerEvent::FinishAllTests);
154                break;
155            }
156        }
157    }
158    Ok(ret)
159}
160
161/// Helper method to convert names to `Invocation`.
162pub fn names_to_invocation(names: Vec<&str>) -> Vec<Invocation> {
163    names
164        .iter()
165        .map(|s| Invocation { name: Some(s.to_string()), tag: None, ..Default::default() })
166        .collect()
167}
168
169// process events by parsing and normalizing logs. Returns `RunEvents` and collected logs.
170pub async fn process_events(
171    suite_instance: test_manager_test_lib::SuiteRunInstance,
172    exclude_empty_logs: bool,
173) -> Result<(Vec<RunEvent>, Vec<String>), Error> {
174    let (sender, mut recv) = mpsc::channel(1);
175    let execution_task = fasync::Task::spawn(async move {
176        suite_instance.collect_events_with_watch(sender, true, true).await
177    });
178    let mut events = vec![];
179    let mut log_tasks = vec![];
180    let mut buffered_stdout = HashMap::new();
181    let mut buffered_stderr = HashMap::new();
182    while let Some(event) = recv.next().await {
183        match event.payload {
184            test_manager_test_lib::SuiteEventPayload::RunEvent(RunEvent::CaseStdout {
185                name,
186                stdout_message,
187            }) => {
188                let strings = line_buffer_std_message(
189                    &name,
190                    stdout_message,
191                    exclude_empty_logs,
192                    &mut buffered_stdout,
193                );
194                for s in strings {
195                    events.push(RunEvent::case_stdout(name.clone(), s));
196                }
197            }
198            test_manager_test_lib::SuiteEventPayload::RunEvent(RunEvent::CaseStderr {
199                name,
200                stderr_message,
201            }) => {
202                let strings = line_buffer_std_message(
203                    &name,
204                    stderr_message,
205                    exclude_empty_logs,
206                    &mut buffered_stderr,
207                );
208                for s in strings {
209                    events.push(RunEvent::case_stderr(name.clone(), s));
210                }
211            }
212            test_manager_test_lib::SuiteEventPayload::RunEvent(e) => events.push(e),
213            test_manager_test_lib::SuiteEventPayload::SuiteLog { log_stream } => {
214                let t = fasync::Task::spawn(log_stream.collect::<Vec<_>>());
215                log_tasks.push(t);
216            }
217            test_manager_test_lib::SuiteEventPayload::TestCaseLog { .. } => {
218                panic!("not supported yet!")
219            }
220            test_manager_test_lib::SuiteEventPayload::DebugData { .. } => {
221                panic!("not supported yet!")
222            }
223        }
224    }
225    execution_task.await.context("test execution failed")?;
226
227    for (name, log) in buffered_stdout {
228        events.push(RunEvent::case_stdout(name, log));
229    }
230    for (name, log) in buffered_stderr {
231        events.push(RunEvent::case_stderr(name, log));
232    }
233
234    let mut collected_logs = vec![];
235    for t in log_tasks {
236        let logs = t.await;
237        for log_result in logs {
238            let log = log_result?;
239            collected_logs.push(log.msg().unwrap().to_string());
240        }
241    }
242
243    Ok((events, collected_logs))
244}
245
246// Process stdout/stderr messages and return Vec of processed strings
247fn line_buffer_std_message(
248    name: &str,
249    std_message: String,
250    exclude_empty_logs: bool,
251    buffer: &mut HashMap<String, String>,
252) -> Vec<String> {
253    let mut ret = vec![];
254    let logs = std_message.split("\n");
255    let mut logs = logs.collect::<Vec<&str>>();
256    // discard last empty log(if it ended in newline, or  store im-complete line)
257    let mut last_incomplete_line = logs.pop();
258    if std_message.as_bytes().last() == Some(&b'\n') {
259        last_incomplete_line = None;
260    }
261    for log in logs {
262        if exclude_empty_logs && log.len() == 0 {
263            continue;
264        }
265        let mut msg = log.to_owned();
266        // This is only executed for first log line and used to concat previous
267        // buffered line.
268        if let Some(prev_log) = buffer.remove(name) {
269            msg = format!("{}{}", prev_log, msg);
270        }
271        ret.push(msg);
272    }
273    if let Some(log) = last_incomplete_line {
274        let mut log = log.to_owned();
275        if let Some(prev_log) = buffer.remove(name) {
276            log = format!("{}{}", prev_log, log);
277        }
278        buffer.insert(name.to_string(), log);
279    }
280    ret
281}
282
283// Binds to test manager component and returns suite runner service.
284pub async fn connect_to_suite_runner() -> Result<ftest_manager::SuiteRunnerProxy, Error> {
285    let realm = client::connect_to_protocol::<fcomponent::RealmMarker>()
286        .context("could not connect to Realm service")?;
287
288    let child_ref = fdecl::ChildRef { name: "test_manager".to_owned(), collection: None };
289    let (dir, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>();
290    realm
291        .open_exposed_dir(&child_ref, server_end)
292        .await
293        .context("open_exposed_dir fidl call failed for test manager")?
294        .map_err(|e| format_err!("failed to create test manager: {:?}", e))?;
295
296    connect_to_protocol_at_dir_root::<ftest_manager::SuiteRunnerMarker>(&dir)
297        .context("failed to open test suite runner service")
298}
299
300fn create_ns_from_current_ns(
301    dir_paths: Vec<(&str, fio::Flags)>,
302) -> Result<Namespace, NamespaceError> {
303    let mut ns = vec![];
304    for (path, permission) in dir_paths {
305        let chan = fuchsia_fs::directory::open_in_namespace(path, permission)
306            .unwrap()
307            .into_channel()
308            .unwrap()
309            .into_zx_channel();
310        let handle = ClientEnd::new(chan);
311
312        ns.push(fcrunner::ComponentNamespaceEntry {
313            path: Some(path.to_string()),
314            directory: Some(handle),
315            ..Default::default()
316        });
317    }
318    Namespace::try_from(ns)
319}
320
321/// Create a new component object for testing purposes.
322pub async fn test_component(
323    url: &str,
324    name: &str,
325    binary: &str,
326    args: Vec<String>,
327) -> Result<Arc<Component>, Error> {
328    let ns = create_ns_from_current_ns(vec![
329        ("/pkg", fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE),
330        // TODO(b/376735013): Restrict this to LogSink instead of all of /svc.
331        ("/svc", fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE),
332    ])?;
333    let component = Component::create_for_tests(BuilderArgs {
334        url: url.to_string(),
335        name: name.to_string(),
336        binary: binary.to_string(),
337        args,
338        environ: None,
339        ns,
340        job: job_default().duplicate_handle(zx::Rights::SAME_RIGHTS)?,
341        options: zx::ProcessOptions::empty(),
342        config: None,
343    })
344    .await?;
345    Ok(Arc::new(component))
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351    use fidl_fuchsia_test::Status;
352    use maplit::hashmap;
353
354    #[test]
355    fn test_ordering_by_enum() {
356        let expected_events = vec![
357            ListenerEvent::start_test("a"),
358            ListenerEvent::finish_test(
359                "a",
360                TestResult { status: Some(Status::Passed), ..Default::default() },
361            ),
362            ListenerEvent::finish_all_test(),
363        ];
364
365        let mut events = expected_events.clone();
366        events.reverse();
367
368        assert_ne!(events, expected_events);
369        events.sort();
370        assert_eq!(events, expected_events);
371    }
372
373    #[test]
374    fn test_ordering_by_test_name() {
375        let mut events = vec![
376            ListenerEvent::start_test("b"),
377            ListenerEvent::start_test("a"),
378            ListenerEvent::finish_test(
379                "a",
380                TestResult { status: Some(Status::Passed), ..Default::default() },
381            ),
382            ListenerEvent::start_test("c"),
383            ListenerEvent::finish_test(
384                "b",
385                TestResult { status: Some(Status::Passed), ..Default::default() },
386            ),
387            ListenerEvent::finish_test(
388                "c",
389                TestResult { status: Some(Status::Passed), ..Default::default() },
390            ),
391            ListenerEvent::finish_all_test(),
392        ];
393
394        let expected_events = vec![
395            ListenerEvent::start_test("a"),
396            ListenerEvent::finish_test(
397                "a",
398                TestResult { status: Some(Status::Passed), ..Default::default() },
399            ),
400            ListenerEvent::start_test("b"),
401            ListenerEvent::finish_test(
402                "b",
403                TestResult { status: Some(Status::Passed), ..Default::default() },
404            ),
405            ListenerEvent::start_test("c"),
406            ListenerEvent::finish_test(
407                "c",
408                TestResult { status: Some(Status::Passed), ..Default::default() },
409            ),
410            ListenerEvent::finish_all_test(),
411        ];
412        events.sort();
413        assert_eq!(events, expected_events);
414    }
415
416    #[test]
417    fn line_buffer_std_message_incomplete_line() {
418        let mut buf = HashMap::new();
419        buf.insert("test".to_string(), "some_prev_text".to_string());
420        let strings = line_buffer_std_message("test", "a \nb\nc\nd".into(), false, &mut buf);
421        assert_eq!(strings, vec!["some_prev_texta ".to_owned(), "b".to_owned(), "c".to_owned()]);
422        assert_eq!(buf, hashmap! {"test".to_string() => "d".to_string()});
423    }
424
425    #[test]
426    fn line_buffer_std_message_complete_line() {
427        let mut buf = HashMap::new();
428        buf.insert("test".to_string(), "some_prev_text".to_string());
429        let strings = line_buffer_std_message("test", "a \nb\nc\n".into(), false, &mut buf);
430        assert_eq!(strings, vec!["some_prev_texta ".to_owned(), "b".to_owned(), "c".to_owned()]);
431        assert_eq!(buf.len(), 0);
432
433        // test when initial buf is empty
434        let strings = line_buffer_std_message("test", "d \ne\nf\n".into(), false, &mut buf);
435        assert_eq!(strings, vec!["d ".to_owned(), "e".to_owned(), "f".to_owned()]);
436        assert_eq!(buf.len(), 0);
437    }
438}