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::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
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(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)]
156pub 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
211trait 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 pub fn moniker(self, moniker: impl Into<String>) -> Self {
272 self.monikers(&[moniker.into()])
273 }
274
275 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 pub fn moniker_regex(self, moniker: impl Into<String>) -> Self {
288 self.monikers_regex(&[moniker.into()])
289 }
290
291 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 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 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 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 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}