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