component_events/
matcher.rs

1// Copyright 2020 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::descriptor::EventDescriptor;
6use crate::events::{Event, EventStream, EventStreamError, ExitStatus};
7use anyhow::Error;
8use fidl_fuchsia_component as fcomponent;
9use futures::StreamExt;
10use moniker::Moniker;
11use regex::RegexSet;
12use std::fmt;
13use std::str::FromStr;
14use thiserror::Error;
15
16#[derive(Debug, Error, PartialEq, Eq)]
17pub enum FieldMatcherError {
18    #[error("Missing field: `{field_name}`")]
19    MissingField { field_name: &'static str },
20    #[error("Field `{field_name}` mismatch. Expected: `{expected}`, Actual: `{actual}`")]
21    FieldMismatch { field_name: &'static str, expected: String, actual: String },
22}
23
24#[derive(Debug)]
25pub struct FieldMatcherErrors {
26    field_matcher_errors: Vec<FieldMatcherError>,
27}
28
29impl fmt::Display for FieldMatcherErrors {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        for err in &self.field_matcher_errors {
32            writeln!(f, "{}", err)?;
33        }
34        Ok(())
35    }
36}
37
38#[derive(Debug, Error)]
39pub enum EventMatcherError {
40    #[error("{errors}")]
41    FieldMatcherErrors { errors: FieldMatcherErrors },
42}
43
44// A matcher that implements this trait is able to match against values of type `T`.
45// A matcher corresponds to a field named `NAME`.
46trait RawFieldMatcher<T>: Clone + std::fmt::Debug + ToString {
47    const NAME: &'static str;
48    fn matches(&self, other: &T) -> bool;
49}
50
51#[derive(Clone, Debug)]
52pub struct EventTypeMatcher {
53    event_type: fcomponent::EventType,
54}
55
56impl EventTypeMatcher {
57    fn new(event_type: fcomponent::EventType) -> Self {
58        Self { event_type }
59    }
60
61    pub fn value(&self) -> &fcomponent::EventType {
62        &self.event_type
63    }
64}
65
66impl fmt::Display for EventTypeMatcher {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "{:?}", self.event_type)
69    }
70}
71
72impl RawFieldMatcher<fcomponent::EventType> for EventTypeMatcher {
73    const NAME: &'static str = "event_type";
74
75    fn matches(&self, other: &fcomponent::EventType) -> bool {
76        self.event_type == *other
77    }
78}
79
80#[derive(Clone, Debug, PartialEq)]
81pub struct CapabilityNameMatcher {
82    capability_name: String,
83}
84
85impl CapabilityNameMatcher {
86    fn new(capability_name: impl Into<String>) -> Self {
87        Self { capability_name: capability_name.into() }
88    }
89}
90
91impl fmt::Display for CapabilityNameMatcher {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}", self.capability_name)
94    }
95}
96
97impl RawFieldMatcher<String> for CapabilityNameMatcher {
98    const NAME: &'static str = "capability_name";
99
100    fn matches(&self, other: &String) -> bool {
101        self.capability_name == *other
102    }
103}
104
105#[derive(Clone, Debug)]
106pub enum MonikerMatcher {
107    Regex(RegexSet),
108    Direct(Vec<Moniker>),
109}
110
111impl MonikerMatcher {
112    fn regex<I, S>(monikers: I) -> Self
113    where
114        S: AsRef<str>,
115        I: IntoIterator<Item = S>,
116    {
117        Self::Regex(RegexSet::new(monikers).unwrap())
118    }
119
120    fn direct<I, S>(monikers: I) -> Self
121    where
122        S: AsRef<str>,
123        I: IntoIterator<Item = S>,
124    {
125        let monikers =
126            monikers.into_iter().map(|m| Moniker::try_from(m.as_ref()).unwrap()).collect();
127        Self::Direct(monikers)
128    }
129}
130
131impl fmt::Display for MonikerMatcher {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            Self::Regex(regex) => write!(f, "{:?}", regex),
135            Self::Direct(monikers) => write!(f, "{:?}", monikers),
136        }
137    }
138}
139
140impl RawFieldMatcher<String> for MonikerMatcher {
141    const NAME: &'static str = "target_monikers";
142
143    fn matches(&self, other: &String) -> bool {
144        let moniker_result = Moniker::from_str(other);
145        match self {
146            Self::Regex(regex_set) => regex_set.is_match(other),
147            Self::Direct(monikers) => match moniker_result {
148                Ok(try_moniker) => monikers.iter().any(|m| m == &try_moniker),
149                Err(_) => false,
150            },
151        }
152    }
153}
154
155#[derive(Debug, PartialEq, Eq, Clone, Ord, PartialOrd)]
156/// Used for matching against events. If the matcher doesn't crash the exit code
157/// then `AnyCrash` can be used to match against any Stopped event caused by a crash.
158/// that indicate failure are crushed into `Crash`.
159pub enum ExitStatusMatcher {
160    Clean,
161    AnyCrash,
162    Crash(i32),
163}
164
165impl fmt::Display for ExitStatusMatcher {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        write!(f, "{:?}", self)
168    }
169}
170
171impl RawFieldMatcher<ExitStatus> for ExitStatusMatcher {
172    const NAME: &'static str = "exit_status";
173
174    fn matches(&self, other: &ExitStatus) -> bool {
175        match (self, other) {
176            (ExitStatusMatcher::Clean, ExitStatus::Clean) => true,
177            (ExitStatusMatcher::AnyCrash, ExitStatus::Crash(_)) => true,
178            (ExitStatusMatcher::Crash(exit_code), ExitStatus::Crash(other_exit_code)) => {
179                exit_code == other_exit_code
180            }
181            _ => false,
182        }
183    }
184}
185
186#[derive(Clone, Debug, PartialEq)]
187pub struct EventIsOkMatcher {
188    event_is_ok: bool,
189}
190
191impl EventIsOkMatcher {
192    fn new(event_is_ok: bool) -> Self {
193        Self { event_is_ok }
194    }
195}
196
197impl fmt::Display for EventIsOkMatcher {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(f, "{}", self.event_is_ok)
200    }
201}
202
203impl RawFieldMatcher<bool> for EventIsOkMatcher {
204    const NAME: &'static str = "event_is_ok";
205
206    fn matches(&self, other: &bool) -> bool {
207        self.event_is_ok == *other
208    }
209}
210
211/// A field matcher is an optional matcher that compares against an optional field.
212/// If there is a mismatch, an error string is generated. If there is no matcher specified,
213/// then there is no error. If there is matcher without a corresponding field then that's a missing
214/// field error. Otherwise, the FieldMatcher delegates to the RawFieldMatcher to determine if the
215/// matcher matches against the raw field.
216trait FieldMatcher<T> {
217    fn matches(&self, other: &Option<T>) -> Result<(), FieldMatcherError>;
218}
219
220impl<LeftHandSide, RightHandSide> FieldMatcher<RightHandSide> for Option<LeftHandSide>
221where
222    LeftHandSide: RawFieldMatcher<RightHandSide>,
223    RightHandSide: std::fmt::Debug,
224{
225    fn matches(&self, other: &Option<RightHandSide>) -> Result<(), FieldMatcherError> {
226        match (self, other) {
227            (Some(value), Some(other_value)) => match value.matches(other_value) {
228                true => Ok(()),
229                false => Err(FieldMatcherError::FieldMismatch {
230                    field_name: LeftHandSide::NAME,
231                    expected: value.to_string(),
232                    actual: format!("{:?}", other_value),
233                }),
234            },
235            (Some(_), None) => {
236                Err(FieldMatcherError::MissingField { field_name: LeftHandSide::NAME })
237            }
238            (None, _) => Ok(()),
239        }
240    }
241}
242
243#[derive(Clone, Debug, Default)]
244pub struct EventMatcher {
245    pub event_type: Option<EventTypeMatcher>,
246    pub target_monikers: Option<MonikerMatcher>,
247    pub capability_name: Option<CapabilityNameMatcher>,
248    pub exit_status: Option<ExitStatusMatcher>,
249    pub event_is_ok: Option<EventIsOkMatcher>,
250}
251
252impl EventMatcher {
253    pub fn ok() -> Self {
254        let mut matcher = EventMatcher::default();
255        matcher.event_is_ok = Some(EventIsOkMatcher::new(true));
256        matcher
257    }
258
259    pub fn err() -> Self {
260        let mut matcher = EventMatcher::default();
261        matcher.event_is_ok = Some(EventIsOkMatcher::new(false));
262        matcher
263    }
264
265    pub fn r#type(mut self, event_type: fcomponent::EventType) -> Self {
266        self.event_type = Some(EventTypeMatcher::new(event_type));
267        self
268    }
269
270    /// The expected target moniker. Will panic if the moniker is invalid.
271    pub fn moniker(self, moniker: impl Into<String>) -> Self {
272        self.monikers(&[moniker.into()])
273    }
274
275    /// The expected target monikers. Will panic if any moniker is invalid.
276    pub fn monikers<I, S>(mut self, monikers: I) -> Self
277    where
278        S: AsRef<str>,
279        I: IntoIterator<Item = S>,
280    {
281        self.target_monikers = Some(MonikerMatcher::direct(monikers));
282        self
283    }
284
285    /// The expected target moniker as a regular expression.
286    /// If the exact moniker is known, use the `moniker` method instead.
287    pub fn moniker_regex(self, moniker: impl Into<String>) -> Self {
288        self.monikers_regex(&[moniker.into()])
289    }
290
291    /// The expected target monikers as regular expressions. This will match against
292    /// regular expression in the iterator provided. If the exact monikers are known,
293    /// use the `monikers` method instead.
294    pub fn monikers_regex<I, S>(mut self, monikers: I) -> Self
295    where
296        S: AsRef<str>,
297        I: IntoIterator<Item = S>,
298    {
299        self.target_monikers = Some(MonikerMatcher::regex(monikers));
300        self
301    }
302
303    /// The expected capability name.
304    pub fn capability_name(mut self, capability_name: impl Into<String>) -> Self {
305        self.capability_name = Some(CapabilityNameMatcher::new(capability_name));
306        self
307    }
308
309    /// The expected exit status. Only applies to the Stop event.
310    pub fn stop(mut self, exit_status: Option<ExitStatusMatcher>) -> Self {
311        self.event_type = Some(EventTypeMatcher::new(fcomponent::EventType::Stopped));
312        self.exit_status = exit_status;
313        self
314    }
315
316    /// Expects the next event to match the provided EventMatcher.
317    /// Returns the casted type if successful and an error otherwise.
318    pub async fn expect_match<T: Event>(&mut self, event_stream: &mut EventStream) -> T {
319        let event = event_stream.next().await.unwrap();
320        let descriptor = EventDescriptor::try_from(&event).unwrap();
321        let event = T::try_from(event).unwrap();
322        self.matches(&descriptor).unwrap();
323        event
324    }
325
326    /// Waits for an event matching the matcher.
327    /// Implicitly resumes all other events.
328    /// Returns the casted type if successful and an error otherwise.
329    pub async fn wait<T: Event>(self, event_stream: &mut EventStream) -> Result<T, Error> {
330        let expected_event_matcher = self.r#type(T::TYPE);
331        loop {
332            let event = event_stream.next().await.ok_or(EventStreamError::StreamClosed)?;
333            let descriptor = EventDescriptor::try_from(&event)?;
334            if expected_event_matcher.matches(&descriptor).is_ok() {
335                return T::try_from(event);
336            }
337        }
338    }
339
340    pub fn matches(&self, other: &EventDescriptor) -> Result<(), EventMatcherError> {
341        let mut field_matcher_errors = vec![];
342
343        if let Err(e) = self.event_type.matches(&other.event_type) {
344            field_matcher_errors.push(e);
345        }
346        if let Err(e) = self.target_monikers.matches(&other.target_moniker) {
347            field_matcher_errors.push(e);
348        }
349        if let Err(e) = self.capability_name.matches(&other.capability_name) {
350            field_matcher_errors.push(e);
351        }
352        if let Err(e) = self.exit_status.matches(&other.exit_status) {
353            field_matcher_errors.push(e);
354        }
355        if let Err(e) = self.event_is_ok.matches(&other.event_is_ok) {
356            field_matcher_errors.push(e);
357        }
358        if !field_matcher_errors.is_empty() {
359            return Err(EventMatcherError::FieldMatcherErrors {
360                errors: FieldMatcherErrors { field_matcher_errors },
361            });
362        }
363        Ok(())
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[fuchsia::test]
372    async fn event_matcher_errors() {
373        let matcher =
374            EventMatcher::ok().capability_name("foobar").stop(Some(ExitStatusMatcher::AnyCrash));
375        let descriptor = EventDescriptor {
376            event_type: None,
377            capability_name: None,
378            target_moniker: None,
379            exit_status: Some(ExitStatus::Clean),
380            event_is_ok: Some(false),
381        };
382        let EventMatcherError::FieldMatcherErrors { errors } =
383            matcher.matches(&descriptor).unwrap_err();
384        assert!(
385            errors
386                .field_matcher_errors
387                .contains(&FieldMatcherError::MissingField { field_name: "event_type" })
388        );
389        assert!(
390            errors
391                .field_matcher_errors
392                .contains(&FieldMatcherError::MissingField { field_name: "capability_name" })
393        );
394        assert!(errors.field_matcher_errors.contains(&FieldMatcherError::FieldMismatch {
395            field_name: "event_is_ok",
396            expected: "true".to_string(),
397            actual: "false".to_string()
398        }));
399        assert!(errors.field_matcher_errors.contains(&FieldMatcherError::FieldMismatch {
400            field_name: "exit_status",
401            expected: "AnyCrash".to_string(),
402            actual: "Clean".to_string()
403        }));
404    }
405}