Skip to main content

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_lite::Regex;
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(Vec<Regex>),
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(
118            monikers
119                .into_iter()
120                .map(|moniker_pattern| Regex::new(moniker_pattern.as_ref()).unwrap())
121                .collect::<Vec<Regex>>(),
122        )
123    }
124
125    fn direct<I, S>(monikers: I) -> Self
126    where
127        S: AsRef<str>,
128        I: IntoIterator<Item = S>,
129    {
130        let monikers =
131            monikers.into_iter().map(|m| Moniker::try_from(m.as_ref()).unwrap()).collect();
132        Self::Direct(monikers)
133    }
134}
135
136impl fmt::Display for MonikerMatcher {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Self::Regex(regex) => write!(f, "{:?}", regex),
140            Self::Direct(monikers) => write!(f, "{:?}", monikers),
141        }
142    }
143}
144
145impl RawFieldMatcher<String> for MonikerMatcher {
146    const NAME: &'static str = "target_monikers";
147
148    fn matches(&self, other: &String) -> bool {
149        let moniker_result = Moniker::from_str(other);
150        match self {
151            Self::Regex(regexes) => regexes.iter().any(|regex| regex.is_match(other)),
152            Self::Direct(monikers) => match moniker_result {
153                Ok(try_moniker) => monikers.iter().any(|m| m == &try_moniker),
154                Err(_) => false,
155            },
156        }
157    }
158}
159
160#[derive(Debug, PartialEq, Eq, Clone, Ord, PartialOrd)]
161/// Used for matching against events. If the matcher doesn't crash the exit code
162/// then `AnyCrash` can be used to match against any Stopped event caused by a crash.
163/// that indicate failure are crushed into `Crash`.
164pub enum ExitStatusMatcher {
165    Clean,
166    AnyCrash,
167    Crash(i32),
168}
169
170impl fmt::Display for ExitStatusMatcher {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(f, "{:?}", self)
173    }
174}
175
176impl RawFieldMatcher<ExitStatus> for ExitStatusMatcher {
177    const NAME: &'static str = "exit_status";
178
179    fn matches(&self, other: &ExitStatus) -> bool {
180        match (self, other) {
181            (ExitStatusMatcher::Clean, ExitStatus::Clean) => true,
182            (ExitStatusMatcher::AnyCrash, ExitStatus::Crash(_)) => true,
183            (ExitStatusMatcher::Crash(exit_code), ExitStatus::Crash(other_exit_code)) => {
184                exit_code == other_exit_code
185            }
186            _ => false,
187        }
188    }
189}
190
191#[derive(Clone, Debug, PartialEq)]
192pub struct EventIsOkMatcher {
193    event_is_ok: bool,
194}
195
196impl EventIsOkMatcher {
197    fn new(event_is_ok: bool) -> Self {
198        Self { event_is_ok }
199    }
200}
201
202impl fmt::Display for EventIsOkMatcher {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        write!(f, "{}", self.event_is_ok)
205    }
206}
207
208impl RawFieldMatcher<bool> for EventIsOkMatcher {
209    const NAME: &'static str = "event_is_ok";
210
211    fn matches(&self, other: &bool) -> bool {
212        self.event_is_ok == *other
213    }
214}
215
216/// A field matcher is an optional matcher that compares against an optional field.
217/// If there is a mismatch, an error string is generated. If there is no matcher specified,
218/// then there is no error. If there is matcher without a corresponding field then that's a missing
219/// field error. Otherwise, the FieldMatcher delegates to the RawFieldMatcher to determine if the
220/// matcher matches against the raw field.
221trait FieldMatcher<T> {
222    fn matches(&self, other: &Option<T>) -> Result<(), FieldMatcherError>;
223}
224
225impl<LeftHandSide, RightHandSide> FieldMatcher<RightHandSide> for Option<LeftHandSide>
226where
227    LeftHandSide: RawFieldMatcher<RightHandSide>,
228    RightHandSide: std::fmt::Debug,
229{
230    fn matches(&self, other: &Option<RightHandSide>) -> Result<(), FieldMatcherError> {
231        match (self, other) {
232            (Some(value), Some(other_value)) => match value.matches(other_value) {
233                true => Ok(()),
234                false => Err(FieldMatcherError::FieldMismatch {
235                    field_name: LeftHandSide::NAME,
236                    expected: value.to_string(),
237                    actual: format!("{:?}", other_value),
238                }),
239            },
240            (Some(_), None) => {
241                Err(FieldMatcherError::MissingField { field_name: LeftHandSide::NAME })
242            }
243            (None, _) => Ok(()),
244        }
245    }
246}
247
248#[derive(Clone, Debug, Default)]
249pub struct EventMatcher {
250    pub event_type: Option<EventTypeMatcher>,
251    pub target_monikers: Option<MonikerMatcher>,
252    pub capability_name: Option<CapabilityNameMatcher>,
253    pub exit_status: Option<ExitStatusMatcher>,
254    pub event_is_ok: Option<EventIsOkMatcher>,
255}
256
257impl EventMatcher {
258    pub fn ok() -> Self {
259        let mut matcher = EventMatcher::default();
260        matcher.event_is_ok = Some(EventIsOkMatcher::new(true));
261        matcher
262    }
263
264    pub fn err() -> Self {
265        let mut matcher = EventMatcher::default();
266        matcher.event_is_ok = Some(EventIsOkMatcher::new(false));
267        matcher
268    }
269
270    pub fn r#type(mut self, event_type: fcomponent::EventType) -> Self {
271        self.event_type = Some(EventTypeMatcher::new(event_type));
272        self
273    }
274
275    /// The expected target moniker. Will panic if the moniker is invalid.
276    pub fn moniker(self, moniker: impl Into<String>) -> Self {
277        self.monikers(&[moniker.into()])
278    }
279
280    /// The expected target monikers. Will panic if any moniker is invalid.
281    pub fn monikers<I, S>(mut self, monikers: I) -> Self
282    where
283        S: AsRef<str>,
284        I: IntoIterator<Item = S>,
285    {
286        self.target_monikers = Some(MonikerMatcher::direct(monikers));
287        self
288    }
289
290    /// The expected target moniker as a regular expression.
291    /// If the exact moniker is known, use the `moniker` method instead.
292    pub fn moniker_regex(self, moniker: impl Into<String>) -> Self {
293        self.monikers_regex(&[moniker.into()])
294    }
295
296    /// The expected target monikers as regular expressions. This will match against
297    /// regular expression in the iterator provided. If the exact monikers are known,
298    /// use the `monikers` method instead.
299    pub fn monikers_regex<I, S>(mut self, monikers: I) -> Self
300    where
301        S: AsRef<str>,
302        I: IntoIterator<Item = S>,
303    {
304        self.target_monikers = Some(MonikerMatcher::regex(monikers));
305        self
306    }
307
308    /// The expected capability name.
309    pub fn capability_name(mut self, capability_name: impl Into<String>) -> Self {
310        self.capability_name = Some(CapabilityNameMatcher::new(capability_name));
311        self
312    }
313
314    /// The expected exit status. Only applies to the Stop event.
315    pub fn stop(mut self, exit_status: Option<ExitStatusMatcher>) -> Self {
316        self.event_type = Some(EventTypeMatcher::new(fcomponent::EventType::Stopped));
317        self.exit_status = exit_status;
318        self
319    }
320
321    /// Expects the next event to match the provided EventMatcher.
322    /// Returns the casted type if successful and an error otherwise.
323    pub async fn expect_match<T: Event>(&mut self, event_stream: &mut EventStream) -> T {
324        let event = event_stream.next().await.unwrap();
325        let descriptor = EventDescriptor::try_from(&event).unwrap();
326        let event = T::try_from(event).unwrap();
327        self.matches(&descriptor).unwrap();
328        event
329    }
330
331    /// Waits for an event matching the matcher.
332    /// Implicitly resumes all other events.
333    /// Returns the casted type if successful and an error otherwise.
334    pub async fn wait<T: Event>(self, event_stream: &mut EventStream) -> Result<T, Error> {
335        let expected_event_matcher = self.r#type(T::TYPE);
336        loop {
337            let event = event_stream.next().await.ok_or(EventStreamError::StreamClosed)?;
338            let descriptor = EventDescriptor::try_from(&event)?;
339            if expected_event_matcher.matches(&descriptor).is_ok() {
340                return T::try_from(event);
341            }
342        }
343    }
344
345    pub fn matches(&self, other: &EventDescriptor) -> Result<(), EventMatcherError> {
346        let mut field_matcher_errors = vec![];
347
348        if let Err(e) = self.event_type.matches(&other.event_type) {
349            field_matcher_errors.push(e);
350        }
351        if let Err(e) = self.target_monikers.matches(&other.target_moniker) {
352            field_matcher_errors.push(e);
353        }
354        if let Err(e) = self.capability_name.matches(&other.capability_name) {
355            field_matcher_errors.push(e);
356        }
357        if let Err(e) = self.exit_status.matches(&other.exit_status) {
358            field_matcher_errors.push(e);
359        }
360        if let Err(e) = self.event_is_ok.matches(&other.event_is_ok) {
361            field_matcher_errors.push(e);
362        }
363        if !field_matcher_errors.is_empty() {
364            return Err(EventMatcherError::FieldMatcherErrors {
365                errors: FieldMatcherErrors { field_matcher_errors },
366            });
367        }
368        Ok(())
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[fuchsia::test]
377    async fn event_matcher_errors() {
378        let matcher =
379            EventMatcher::ok().capability_name("foobar").stop(Some(ExitStatusMatcher::AnyCrash));
380        let descriptor = EventDescriptor {
381            event_type: None,
382            capability_name: None,
383            target_moniker: None,
384            exit_status: Some(ExitStatus::Clean),
385            event_is_ok: Some(false),
386        };
387        let EventMatcherError::FieldMatcherErrors { errors } =
388            matcher.matches(&descriptor).unwrap_err();
389        assert!(
390            errors
391                .field_matcher_errors
392                .contains(&FieldMatcherError::MissingField { field_name: "event_type" })
393        );
394        assert!(
395            errors
396                .field_matcher_errors
397                .contains(&FieldMatcherError::MissingField { field_name: "capability_name" })
398        );
399        assert!(errors.field_matcher_errors.contains(&FieldMatcherError::FieldMismatch {
400            field_name: "event_is_ok",
401            expected: "true".to_string(),
402            actual: "false".to_string()
403        }));
404        assert!(errors.field_matcher_errors.contains(&FieldMatcherError::FieldMismatch {
405            field_name: "exit_status",
406            expected: "AnyCrash".to_string(),
407            actual: "Clean".to_string()
408        }));
409    }
410}