1use 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
44trait 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)]
161pub 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
216trait 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 pub fn moniker(self, moniker: impl Into<String>) -> Self {
277 self.monikers(&[moniker.into()])
278 }
279
280 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 pub fn moniker_regex(self, moniker: impl Into<String>) -> Self {
293 self.monikers_regex(&[moniker.into()])
294 }
295
296 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 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 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 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 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}