| // 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 { |
| crate::{ |
| descriptor::EventDescriptor, |
| events::{event_name, EventStream}, |
| matcher::EventMatcher, |
| }, |
| anyhow::{format_err, Error}, |
| }; |
| |
| /// Determines whether an EventGroup allows events to be verified in any order |
| /// or only in the order specified in the group. |
| #[derive(Clone)] |
| pub enum Ordering { |
| Ordered, |
| Unordered, |
| } |
| |
| /// Determines whether an EventGroup requires all observed events to match |
| /// an EventMatcher in the group, or ignores events that don't match. |
| #[derive(Clone, PartialEq)] |
| pub enum Contains { |
| All, |
| Subset, |
| } |
| |
| #[derive(Clone)] |
| pub struct EventSequence { |
| groups: Vec<EventGroup>, |
| } |
| |
| impl EventSequence { |
| pub fn new() -> Self { |
| Self { groups: vec![] } |
| } |
| |
| pub fn then(self, matcher: EventMatcher) -> Self { |
| self.all_of(vec![matcher], Ordering::Ordered) |
| } |
| |
| /// Adds a group of matchers to verify all following events in the sequence match |
| /// with the given ordering. |
| /// |
| /// The sequence will fail to match if contains events that don't match the group. |
| pub fn all_of(mut self, events: Vec<EventMatcher>, ordering: Ordering) -> Self { |
| self.groups.push(EventGroup::new(events, ordering, Contains::All)); |
| self |
| } |
| |
| /// Adds a group of matchers to verify that the sequence contains a subset of |
| /// events that match with the given ordering. |
| /// |
| /// Events in the sequence that don't match will be ignored. Subsequent matchers |
| /// outside of this group will not be able to match against ignored events. |
| pub fn has_subset(mut self, events: Vec<EventMatcher>, ordering: Ordering) -> Self { |
| self.groups.push(EventGroup::new(events, ordering, Contains::Subset)); |
| self |
| } |
| |
| /// Verify that the events in this sequence are received from the provided EventStream. |
| pub async fn expect(self, event_stream: EventStream) -> Result<(), Error> { |
| self.expect_and_giveback(event_stream).await.map(|_| ()) |
| } |
| |
| /// Verify that the events in this sequence are received from the provided EventStream, |
| /// and gives back the event stream on success. |
| pub async fn expect_and_giveback( |
| mut self, |
| mut event_stream: EventStream, |
| ) -> Result<EventStream, Error> { |
| while !self.groups.is_empty() { |
| match event_stream.next().await { |
| Err(e) => return Err(e.into()), |
| Ok(event) => { |
| let actual_event = EventDescriptor::try_from(&event)?; |
| let _ = self.next(&actual_event)?; |
| } |
| } |
| } |
| Ok(event_stream) |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.groups.is_empty() |
| } |
| |
| pub fn event_names(&self) -> Result<Vec<String>, Error> { |
| let mut event_names = vec![]; |
| for group in &self.groups { |
| let mut group_event_names = group.event_names()?; |
| event_names.append(&mut group_event_names); |
| } |
| event_names.dedup(); |
| Ok(event_names) |
| } |
| |
| /// Tests an EventDescriptor and against the first EventGroup. |
| /// |
| /// If an EventGroup has been entirely consumed (no further EventMatchers |
| /// to match against) then the EventGroup is dropped. |
| /// |
| /// Returns an error if the EventSequence have not been entirely consumed, |
| /// and the incoming EventDescriptor does not match the first sequence. |
| /// |
| /// Returns Ok(true) if there is a positive match. |
| /// Returns Ok(false) if the EventSequence is empty. |
| pub fn next(&mut self, event: &EventDescriptor) -> Result<(), Error> { |
| loop { |
| if self.groups.is_empty() { |
| return Ok(()); |
| } |
| let group = &mut self.groups[0]; |
| if group.next(event)? { |
| if group.is_empty() { |
| self.groups.remove(0); |
| } |
| return Ok(()); |
| } |
| self.groups.remove(0); |
| } |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct EventGroup { |
| events: Vec<EventMatcher>, |
| ordering: Ordering, |
| contains: Contains, |
| } |
| |
| impl EventGroup { |
| pub fn new(events: Vec<EventMatcher>, ordering: Ordering, contains: Contains) -> Self { |
| Self { events, ordering, contains } |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.events.is_empty() |
| } |
| |
| pub fn event_names(&self) -> Result<Vec<String>, Error> { |
| let mut event_names = vec![]; |
| for event in &self.events { |
| if let Some(event_type) = &event.event_type { |
| event_names.push(event_name(&event_type.value())); |
| } else { |
| return Err(format_err!("No event name or type set for matcher {:?}", event)); |
| } |
| } |
| event_names.dedup(); |
| Ok(event_names) |
| } |
| |
| /// Returns true if `event` matches an event matcher in this group. |
| /// |
| /// If the group ordering is Ordered, the event must match the first matcher. |
| /// If the group ordering is Unordered, the event can match any matcher in the group. |
| /// The matcher is removed after a successful match. |
| /// |
| /// Returns an error if the event does not match a matcher and the contains |
| /// policy is All, indicating that the unknown event did not match the group. |
| /// |
| /// Returns Ok(true) if there is a positive match. |
| /// Returns Ok(false) if the EventGroup is empty. |
| pub fn next(&mut self, event: &EventDescriptor) -> Result<bool, Error> { |
| if self.events.is_empty() { |
| return Ok(Contains::Subset == self.contains); |
| } |
| match self.ordering { |
| Ordering::Ordered => { |
| let matches = self.events.get(0).unwrap().matches(event); |
| if matches.is_ok() { |
| self.events.remove(0); |
| Ok(true) |
| } else { |
| // There was no matcher that matched this event. |
| // This is an error only if the group expects all events to be matched. |
| match self.contains { |
| Contains::All => Err(Error::new(matches.unwrap_err())), |
| Contains::Subset => Ok(true), |
| } |
| } |
| } |
| Ordering::Unordered => { |
| if let Some((index, _)) = self |
| .events |
| .iter() |
| .enumerate() |
| .find(|&matcher| matcher.1.matches(&event).is_ok()) |
| { |
| self.events.remove(index); |
| Ok(true) |
| } else { |
| match self.contains { |
| Contains::All => Err(format_err!("Failed to find event: {:?}", event)), |
| Contains::Subset => Ok(true), |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::events::{Event, Started}; |
| use anyhow::Context; |
| use fidl_fuchsia_component as fcomponent; |
| use futures::StreamExt; |
| |
| async fn run_server( |
| events: Vec<fcomponent::Event>, |
| mut server: fcomponent::EventStreamRequestStream, |
| ) { |
| let (tx, mut rx) = futures::channel::mpsc::unbounded(); |
| for event in events { |
| tx.unbounded_send(event).unwrap(); |
| } |
| // The tests expect the event_stream to terminate once all events have been consumed. |
| // This behavior does not match that of component_manager but is needed for negative |
| // proof tests (such as the event_stream does NOT contain a given event). |
| drop(tx); |
| while let Some(Ok(request)) = server.next().await { |
| match request { |
| fcomponent::EventStreamRequest::GetNext { responder } => { |
| if let Some(event) = rx.next().await { |
| responder.send(vec![event]).unwrap(); |
| } else { |
| return; |
| } |
| } |
| fcomponent::EventStreamRequest::WaitForReady { responder } => { |
| responder.send().unwrap() |
| } |
| } |
| } |
| } |
| |
| async fn make_event_stream( |
| events: Vec<fcomponent::Event>, |
| ) -> Result<(EventStream, fuchsia_async::Task<()>), Error> { |
| let (proxy, server) = fidl::endpoints::create_proxy::<fcomponent::EventStreamMarker>() |
| .context("failed to make EventStream proxy")?; |
| Ok(( |
| EventStream::new(proxy), |
| fuchsia_async::Task::spawn(run_server(events, server.into_stream()?)), |
| )) |
| } |
| |
| // Returns a successful Started event for the given moniker. |
| fn make_event<M: Into<String>>(moniker: M) -> fcomponent::Event { |
| fcomponent::Event { |
| header: Some(fcomponent::EventHeader { |
| event_type: Some(fcomponent::EventType::Started), |
| moniker: Some(moniker.into()), |
| ..Default::default() |
| }), |
| payload: Some(fcomponent::EventPayload::Started(fcomponent::StartedPayload::default())), |
| ..Default::default() |
| } |
| } |
| |
| // Returns a matcher for a successful Started event for the given moniker. |
| fn make_matcher<M: Into<String>>(moniker: M) -> EventMatcher { |
| EventMatcher::ok().r#type(Started::TYPE).moniker(moniker) |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_empty() { |
| let (event_stream, _server) = |
| make_event_stream(vec![]).await.expect("failed to make event stream"); |
| EventSequence::new() |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_then() { |
| let moniker = "./foo:0"; |
| let (event_stream, _server) = make_event_stream(vec![make_event(moniker)]) |
| .await |
| .expect("failed to make event stream"); |
| EventSequence::new() |
| .then(make_matcher(moniker)) |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_all_of_ordered_ok() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| let events = monikers.iter().copied().map(make_event).collect(); |
| let matchers = monikers.iter().copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| EventSequence::new() |
| .all_of(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_all_of_ordered_fail_order() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| // Events are in reverse order of the matchers. |
| let events = monikers.iter().rev().copied().map(make_event).collect(); |
| let matchers = monikers.iter().copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| assert!(EventSequence::new() |
| .all_of(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .is_err()); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_all_of_ordered_fail_missing_event() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| // The first event is missing. |
| let events = monikers.iter().skip(1).copied().map(make_event).collect(); |
| let matchers = monikers.iter().copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| assert!(EventSequence::new() |
| .all_of(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .is_err()); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_all_of_ordered_fail_missing_matcher() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| let events = monikers.iter().copied().map(make_event).collect(); |
| // The first matcher is missing, so the first event is unmatched. |
| let matchers = monikers.iter().skip(1).copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| assert!(EventSequence::new() |
| .all_of(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .is_err()); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_all_of_unordered_ok_reversed() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| // Events are in reverse order of the matchers. |
| let events = monikers.iter().rev().copied().map(make_event).collect(); |
| let matchers = monikers.iter().copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| EventSequence::new() |
| .all_of(matchers, Ordering::Unordered) |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_has_subset_ordered_ok() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| let events = monikers.iter().copied().map(make_event).collect(); |
| // The first matcher is missing, so the first event is ignored. |
| let matchers = monikers.iter().skip(1).copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| EventSequence::new() |
| .has_subset(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_has_subset_ordered_missing_event() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| // The first two events are missing. |
| let events = monikers.iter().skip(2).copied().map(make_event).collect(); |
| // The first matcher is missing, so the first event is ignored. |
| let matchers = monikers.iter().skip(1).copied().map(make_matcher).collect(); |
| |
| // Matching should fail because the matcher for "./bar:0" can't find the event. |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| assert!(EventSequence::new() |
| .has_subset(matchers, Ordering::Ordered) |
| .expect(event_stream) |
| .await |
| .is_err()); |
| } |
| |
| #[fuchsia::test] |
| async fn event_sequence_has_subset_unordered_ok_reversed() { |
| let monikers = vec!["./foo:0", "./bar:0", "./baz:0"]; |
| // Events are in reverse order of the matchers. |
| let events = monikers.iter().rev().copied().map(make_event).collect(); |
| // The first matcher is missing, so the first event is ignored. |
| let matchers = monikers.iter().skip(1).copied().map(make_matcher).collect(); |
| |
| let (event_stream, _server) = |
| make_event_stream(events).await.expect("failed to make event stream"); |
| EventSequence::new() |
| .has_subset(matchers, Ordering::Unordered) |
| .expect(event_stream) |
| .await |
| .expect("event sequence did not match expected"); |
| } |
| } |