blob: 48598965358e6b46de3aaf52758da0b28d280e44 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
fidl_fuchsia_test::{
CaseListenerRequest::Finished,
Invocation, Result_ as TestResult,
RunListenerRequest::{OnFinished, OnTestCaseStarted},
RunListenerRequestStream,
},
std::collections::HashMap,
test_executor::TestEvent,
{anyhow::Error, futures::prelude::*},
};
#[derive(PartialEq, Debug)]
pub enum ListenerEvent {
StartTest(String),
FinishTest(String, TestResult),
FinishAllTests,
}
fn get_ord_index_and_name(event: &ListenerEvent) -> (usize, &str) {
match event {
ListenerEvent::StartTest(name) => (0, name),
ListenerEvent::FinishTest(name, _) => (1, name),
ListenerEvent::FinishAllTests => (2, ""),
}
}
// Orders by test name and then event type.
impl Ord for ListenerEvent {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
let (s_index, s_test_name) = get_ord_index_and_name(self);
let (o_index, o_test_name) = get_ord_index_and_name(other);
if s_test_name == o_test_name || s_index == 2 || o_index == 2 {
return s_index.cmp(&o_index);
}
return s_test_name.cmp(&o_test_name);
}
}
// Makes sure that FinishTest event never shows up before StartTest and FinishAllTests is always
// last.
pub fn assert_event_ord(events: &Vec<ListenerEvent>) {
let mut tests = HashMap::new();
let mut all_finish = false;
for event in events {
assert!(!all_finish, "got FinishAllTests event twice: {:#?}", events);
match event {
ListenerEvent::StartTest(name) => {
assert!(
!tests.contains_key(&name),
"Multiple StartTest for test {}: {:#?}",
name,
events
);
tests.insert(name, false);
}
ListenerEvent::FinishTest(name, _) => {
assert!(
tests.contains_key(&name),
"Got finish before start event for test {}: {:#?}",
name,
events
);
assert!(
!tests.insert(name, true).unwrap(),
"Multiple FinishTest for test {}: {:#?}",
name,
events
);
}
ListenerEvent::FinishAllTests => {
all_finish = true;
}
}
}
}
impl PartialOrd for ListenerEvent {
fn partial_cmp(&self, other: &ListenerEvent) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Eq for ListenerEvent {}
impl ListenerEvent {
pub fn start_test(name: &str) -> ListenerEvent {
ListenerEvent::StartTest(name.to_string())
}
pub fn finish_test(name: &str, test_result: TestResult) -> ListenerEvent {
ListenerEvent::FinishTest(name.to_string(), test_result)
}
pub fn finish_all_test() -> ListenerEvent {
ListenerEvent::FinishAllTests
}
}
impl Clone for ListenerEvent {
fn clone(&self) -> Self {
match self {
ListenerEvent::StartTest(name) => ListenerEvent::start_test(name),
ListenerEvent::FinishTest(name, test_result) => ListenerEvent::finish_test(
name,
TestResult { status: test_result.status.clone(), ..TestResult::EMPTY },
),
ListenerEvent::FinishAllTests => ListenerEvent::finish_all_test(),
}
}
}
/// Collects all the listener event as they come and return in a vector.
pub async fn collect_listener_event(
mut listener: RunListenerRequestStream,
) -> Result<Vec<ListenerEvent>, Error> {
let mut ret = vec![];
// collect loggers so that they do not die.
let mut loggers = vec![];
while let Some(result_event) = listener.try_next().await? {
match result_event {
OnTestCaseStarted { invocation, primary_log, listener, .. } => {
let name = invocation.name.unwrap();
ret.push(ListenerEvent::StartTest(name.clone()));
loggers.push(primary_log);
let mut listener = listener.into_stream()?;
while let Some(result) = listener.try_next().await? {
match result {
Finished { result, .. } => {
ret.push(ListenerEvent::FinishTest(name, result));
break;
}
}
}
}
OnFinished { .. } => {
ret.push(ListenerEvent::FinishAllTests);
break;
}
}
}
Ok(ret)
}
/// Helper method to convert names to `Invocation`.
pub fn names_to_invocation(names: Vec<&str>) -> Vec<Invocation> {
names
.iter()
.map(|s| Invocation { name: Some(s.to_string()), tag: None, ..Invocation::EMPTY })
.collect()
}
// process events by parsing and normalizing logs
pub fn process_events(events: Vec<TestEvent>, exclude_empty_logs: bool) -> Vec<TestEvent> {
let mut test_events = vec![];
// map to buffer incomplete logs(that did not end in newline) by test case.
let mut buffered_logs = HashMap::new();
// break logs as they can be grouped in any way.
for event in events {
match event {
TestEvent::LogMessage { test_case_name, msg } => {
let logs = msg.split("\n");
let mut logs = logs.collect::<Vec<&str>>();
// discard last empty log(if it ended in newline, or store im-complete line)
let mut last_incomplete_line = logs.pop();
if msg.as_bytes().last() == Some(&b'\n') {
last_incomplete_line = None;
}
for log in logs {
if exclude_empty_logs && log.len() == 0 {
continue;
}
let mut msg = log.to_owned();
// This is only executed for first log line and used to concat previous
// buffered line.
if let Some(prev_log) = buffered_logs.remove(&test_case_name) {
msg = format!("{}{}", prev_log, msg);
}
test_events.push(TestEvent::LogMessage {
test_case_name: test_case_name.clone(),
msg: msg,
});
}
if let Some(log) = last_incomplete_line {
let mut log = log.to_owned();
if let Some(prev_log) = buffered_logs.remove(&test_case_name) {
log = format!("{}{}", prev_log, log);
}
buffered_logs.insert(test_case_name.clone(), log);
}
}
event => {
test_events.push(event);
}
};
}
for (test_case_name, log) in buffered_logs {
test_events.push(TestEvent::LogMessage { test_case_name: test_case_name, msg: log });
}
test_events
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_test::Status;
#[test]
fn test_ordering_by_enum() {
let expected_events = vec![
ListenerEvent::start_test("a"),
ListenerEvent::finish_test(
"a",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
let mut events = expected_events.clone();
events.reverse();
assert_ne!(events, expected_events);
events.sort();
assert_eq!(events, expected_events);
}
#[test]
fn test_ordering_by_test_name() {
let mut events = vec![
ListenerEvent::start_test("b"),
ListenerEvent::start_test("a"),
ListenerEvent::finish_test(
"a",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("c"),
ListenerEvent::finish_test(
"b",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_test(
"c",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
let expected_events = vec![
ListenerEvent::start_test("a"),
ListenerEvent::finish_test(
"a",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("b"),
ListenerEvent::finish_test(
"b",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::start_test("c"),
ListenerEvent::finish_test(
"c",
TestResult { status: Some(Status::Passed), ..TestResult::EMPTY },
),
ListenerEvent::finish_all_test(),
];
events.sort();
assert_eq!(events, expected_events);
}
}