1pub(crate) mod arithmetic;
6pub(crate) mod context;
7pub(crate) mod fetch;
8pub(crate) mod metric_value;
9pub(crate) mod parse;
10pub(crate) mod variable;
11
12use fetch::{Fetcher, FileDataFetcher, SelectorString, TrialDataFetcher};
13use metric_value::{MetricValue, Problem};
14use regex::Regex;
15use serde::{Deserialize, Serialize};
16use std::cell::RefCell;
17use std::clone::Clone;
18use std::cmp::min;
19use std::collections::{HashMap, HashSet};
20use variable::VariableName;
21
22#[derive(Clone, Debug, PartialEq, Serialize)]
24pub(crate) enum Metric {
25 Selector(Vec<SelectorString>),
30 Eval(ExpressionContext),
32 #[cfg(test)]
34 Hardcoded(MetricValue),
35}
36
37impl std::fmt::Display for Metric {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 Metric::Selector(s) => write!(f, "{s:?}"),
41 Metric::Eval(s) => write!(f, "{s}"),
42 #[cfg(test)]
43 Metric::Hardcoded(value) => write!(f, "{value:?}"),
44 }
45 }
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize)]
51pub struct ValueSource {
52 pub(crate) metric: Metric,
53 pub cached_value: RefCell<Option<MetricValue>>,
54}
55
56impl ValueSource {
57 pub(crate) fn new(metric: Metric) -> Self {
58 Self { metric, cached_value: RefCell::new(None) }
59 }
60
61 pub(crate) fn try_from_expression_with_namespace(
62 expr: &str,
63 namespace: &str,
64 ) -> Result<Self, anyhow::Error> {
65 Ok(ValueSource::new(Metric::Eval(ExpressionContext::try_from_expression_with_namespace(
66 expr, namespace,
67 )?)))
68 }
69
70 #[cfg(test)]
71 pub(crate) fn try_from_expression_with_default_namespace(
72 expr: &str,
73 ) -> Result<Self, anyhow::Error> {
74 ValueSource::try_from_expression_with_namespace(expr, "")
75 }
76}
77
78impl std::fmt::Display for ValueSource {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "{:?}", self.metric)
81 }
82}
83
84pub type Metrics = HashMap<String, HashMap<String, ValueSource>>;
86
87pub struct MetricState<'a> {
94 pub metrics: &'a Metrics,
95 pub fetcher: Fetcher<'a>,
96 now: Option<i64>,
97 stack: RefCell<HashSet<String>>,
98}
99
100#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
101pub enum MathFunction {
102 Add,
103 Sub,
104 Mul,
105 FloatDiv,
106 IntDiv,
107 FloatDivChecked,
108 IntDivChecked,
109 Greater,
110 Less,
111 GreaterEq,
112 LessEq,
113 Max,
114 Min,
115 Abs,
116}
117
118#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
119pub enum Function {
120 Math(MathFunction),
121 Equals,
124 NotEq,
125 And,
126 Or,
127 Not,
128 KlogHas,
129 SyslogHas,
130 BootlogHas,
131 Missing,
132 UnhandledType,
133 Problem,
134 Annotation,
135 Lambda,
136 Apply,
137 Map,
138 Fold,
139 All,
140 Any,
141 Filter,
142 Count,
143 CountChildren,
144 CountProperties,
145 Nanos,
146 Micros,
147 Millis,
148 Seconds,
149 Minutes,
150 Hours,
151 Days,
152 Now,
153 OptionF,
154 StringMatches,
155 True,
156 False,
157}
158
159#[derive(Deserialize, Debug, Clone, Serialize)]
162pub struct Lambda {
163 parameters: Vec<String>,
164 body: ExpressionTree,
165}
166
167impl Lambda {
168 fn valid_parameters(parameters: &ExpressionTree) -> Result<Vec<String>, MetricValue> {
169 match parameters {
170 ExpressionTree::Vector(parameters) => parameters
171 .iter()
172 .map(|param| match param {
173 ExpressionTree::Variable(name) => {
174 if name.includes_namespace() {
175 Err(syntax_error("Namespaces not allowed in function params"))
176 } else {
177 Ok(name.original_name().to_string())
178 }
179 }
180 _ => Err(syntax_error("Function params must be valid identifier names")),
181 })
182 .collect::<Result<Vec<_>, _>>(),
183 _ => Err(syntax_error("Function params must be a vector of names")),
184 }
185 }
186
187 fn as_metric_value(definition: &[ExpressionTree]) -> MetricValue {
188 if definition.len() != 2 {
189 return syntax_error("Function needs two parameters, list of params and expression");
190 }
191 let parameters = match Self::valid_parameters(&definition[0]) {
192 Ok(names) => names,
193 Err(problem) => return problem,
194 };
195 let body = definition[1].clone();
196 MetricValue::Lambda(Box::new(Lambda { parameters, body }))
197 }
198}
199
200#[derive(Copy, Clone, Debug)]
202enum ShortCircuitBehavior {
203 True,
205 False,
207}
208
209struct MapFoldBoolArgs<'a> {
210 pub namespace: &'a str,
211 pub operands: &'a [ExpressionTree],
212 pub function_name: &'a str,
213 pub default_when_empty: bool,
214 pub function: &'a dyn Fn(bool, bool) -> bool,
215 pub short_circuit_behavior: ShortCircuitBehavior,
216}
217
218#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
222pub(crate) enum ExpressionTree {
223 Function(Function, Vec<ExpressionTree>),
227 Vector(Vec<ExpressionTree>),
228 Variable(VariableName),
229 Value(MetricValue),
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize)]
235pub(crate) struct ExpressionContext {
236 pub(crate) raw_expression: String,
237 pub(crate) parsed_expression: ExpressionTree,
238}
239
240impl ExpressionContext {
241 pub fn try_from_expression_with_namespace(
242 raw_expression: &str,
243 namespace: &str,
244 ) -> Result<Self, anyhow::Error> {
245 let parsed_expression = parse::parse_expression(raw_expression, namespace)?;
246 Ok(Self { raw_expression: raw_expression.to_string(), parsed_expression })
247 }
248
249 pub fn try_from_expression_with_default_namespace(
250 raw_expression: &str,
251 ) -> Result<Self, anyhow::Error> {
252 ExpressionContext::try_from_expression_with_namespace(
253 raw_expression,
254 "",
255 )
256 }
257}
258
259impl std::fmt::Display for ExpressionContext {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 write!(f, "{:?}", self.raw_expression)
262 }
263}
264
265fn unwrap_for_math(value: &MetricValue) -> &MetricValue {
270 match value {
271 MetricValue::Vector(v) if v.len() == 1 => &v[0],
272 v => v,
273 }
274}
275
276fn missing(message: impl AsRef<str>) -> MetricValue {
278 MetricValue::Problem(Problem::Missing(message.as_ref().to_string()))
279}
280
281fn syntax_error(message: impl AsRef<str>) -> MetricValue {
282 MetricValue::Problem(Problem::SyntaxError(message.as_ref().to_string()))
283}
284
285fn value_error(message: impl AsRef<str>) -> MetricValue {
286 MetricValue::Problem(Problem::ValueError(message.as_ref().to_string()))
287}
288
289fn internal_bug(message: impl AsRef<str>) -> MetricValue {
290 MetricValue::Problem(Problem::InternalBug(message.as_ref().to_string()))
291}
292
293fn unhandled_type(message: impl AsRef<str>) -> MetricValue {
294 MetricValue::Problem(Problem::UnhandledType(message.as_ref().to_string()))
295}
296
297fn evaluation_error(message: impl AsRef<str>) -> MetricValue {
298 MetricValue::Problem(Problem::EvaluationError(message.as_ref().to_string()))
299}
300
301pub fn safe_float_to_int(float: f64) -> Option<i64> {
302 if !float.is_finite() {
303 return None;
304 }
305 if float > i64::MAX as f64 {
306 return Some(i64::MAX);
307 }
308 if float < i64::MIN as f64 {
309 return Some(i64::MIN);
310 }
311 Some(float as i64)
312}
313
314fn first_usable_value(values: impl Iterator<Item = MetricValue>) -> MetricValue {
315 let mut found_empty = false;
316 for value in values {
317 match value {
318 MetricValue::Problem(Problem::Missing(_)) => {}
319 MetricValue::Vector(ref v)
320 if v.len() == 1 && matches!(v[0], MetricValue::Problem(Problem::Missing(_))) => {}
321 MetricValue::Vector(v) if v.is_empty() => found_empty = true,
322 value => return value,
323 }
324 }
325 if found_empty {
326 return MetricValue::Vector(vec![]);
327 }
328 missing("Every value was missing")
330}
331
332impl<'a> MetricState<'a> {
333 pub fn new(metrics: &'a Metrics, fetcher: Fetcher<'a>, now: Option<i64>) -> MetricState<'a> {
335 MetricState { metrics, fetcher, now, stack: RefCell::new(HashSet::new()) }
336 }
337
338 pub fn evaluate_all_metrics(&self) {
340 for (namespace, metrics) in self.metrics.iter() {
341 for (name, _value_source) in metrics.iter() {
342 self.evaluate_variable(namespace, &VariableName::new(name.clone()));
343 }
344 }
345 }
346
347 fn metric_value_for_trial(
352 &self,
353 fetcher: &TrialDataFetcher<'_>,
354 namespace: &str,
355 variable: &VariableName,
356 ) -> MetricValue {
357 let name = variable.original_name();
358 if fetcher.has_entry(name) {
359 return fetcher.fetch(name);
360 }
361 if variable.includes_namespace() {
362 return syntax_error(format!(
363 "Name {name} not in test values and refers outside the file",
364 ));
365 }
366 match self.metrics.get(namespace) {
367 None => internal_bug(format!("BUG! Bad namespace '{namespace}'")),
368 Some(metric_map) => match metric_map.get(name) {
369 None => syntax_error(format!("Metric '{name}' Not Found in '{namespace}'")),
370 Some(value_source) => {
371 let resolved_value: MetricValue;
372 {
373 let cached_value_cell = value_source.cached_value.borrow();
374 match &*cached_value_cell {
375 None => {
376 resolved_value = match &value_source.metric {
377 Metric::Selector(_) => missing(format!(
378 "Selector {name} can't be used in tests; please supply a value",
379 )),
380 Metric::Eval(expression) => {
381 self.evaluate(namespace, &expression.parsed_expression)
382 }
383 #[cfg(test)]
384 Metric::Hardcoded(value) => value.clone(),
385 };
386 }
387 Some(cached_value) => return cached_value.clone(),
388 }
389 }
390 let mut cached_value_cell = value_source.cached_value.borrow_mut();
391 *cached_value_cell = Some(resolved_value.clone());
392 resolved_value
393 }
394 },
395 }
396 }
397
398 fn metric_value_for_file(
401 &self,
402 fetcher: &FileDataFetcher<'_>,
403 namespace: &str,
404 name: &VariableName,
405 ) -> MetricValue {
406 if let Some((real_namespace, real_name)) = name.name_parts(namespace) {
407 match self.metrics.get(real_namespace) {
408 None => syntax_error(format!("Bad namespace '{real_namespace}'")),
409 Some(metric_map) => match metric_map.get(real_name) {
410 None => syntax_error(format!(
411 "Metric '{real_name}' Not Found in '{real_namespace}'",
412 )),
413 Some(value_source) => {
414 if let Some(cached_value) = value_source.cached_value.borrow().as_ref() {
415 return cached_value.clone();
416 }
417 let resolved_value = match &value_source.metric {
418 Metric::Selector(selectors) => first_usable_value(
419 selectors.iter().map(|selector| fetcher.fetch(selector)),
420 ),
421 Metric::Eval(expression) => {
422 self.evaluate(real_namespace, &expression.parsed_expression)
423 }
424 #[cfg(test)]
425 Metric::Hardcoded(value) => value.clone(),
426 };
427 let mut cached_value_cell = value_source.cached_value.borrow_mut();
428 *cached_value_cell = Some(resolved_value.clone());
429 resolved_value
430 }
431 },
432 }
433 } else {
434 syntax_error(format!("Bad name '{}'", name.original_name()))
435 }
436 }
437
438 fn evaluate_variable(&self, namespace: &str, name: &VariableName) -> MetricValue {
440 if self.stack.borrow().contains(&name.full_name(namespace)) {
444 let _ = self.stack.replace(HashSet::new());
446 return evaluation_error(format!(
447 "Cycle encountered while evaluating variable {} in the expression",
448 name.name
449 ));
450 }
451 self.stack.borrow_mut().insert(name.full_name(namespace));
453
454 let value = match &self.fetcher {
455 Fetcher::FileData(fetcher) => self.metric_value_for_file(fetcher, namespace, name),
456 Fetcher::TrialData(fetcher) => self.metric_value_for_trial(fetcher, namespace, name),
457 };
458
459 self.stack.borrow_mut().remove(&name.full_name(namespace));
460 value
461 }
462
463 pub(crate) fn eval_action_metric(
465 &self,
466 namespace: &str,
467 value_source: &ValueSource,
468 ) -> MetricValue {
469 if let Some(cached_value) = &*value_source.cached_value.borrow() {
470 return cached_value.clone();
471 }
472
473 let resolved_value = match &value_source.metric {
474 Metric::Selector(_) => syntax_error("Selectors aren't allowed in action triggers"),
475 Metric::Eval(expression) => {
476 unwrap_for_math(&self.evaluate(namespace, &expression.parsed_expression)).clone()
477 }
478 #[cfg(test)]
479 Metric::Hardcoded(value) => value.clone(),
480 };
481
482 let mut cached_value_cell = value_source.cached_value.borrow_mut();
483 *cached_value_cell = Some(resolved_value.clone());
484 resolved_value
485 }
486
487 #[cfg(test)]
488 fn evaluate_value(&self, namespace: &str, expression: &str) -> MetricValue {
489 match parse::parse_expression(expression, namespace) {
490 Ok(expr) => self.evaluate(namespace, &expr),
491 Err(e) => syntax_error(format!("Expression parse error\n{e}")),
492 }
493 }
494
495 pub(crate) fn evaluate_math(expr: &str) -> MetricValue {
497 let tree = match ExpressionContext::try_from_expression_with_default_namespace(expr) {
498 Ok(expr_context) => expr_context.parsed_expression,
499 Err(err) => return syntax_error(format!("Failed to parse '{expr}': {err}")),
500 };
501 Self::evaluate_const_expression(&tree)
502 }
503
504 pub(crate) fn evaluate_const_expression(tree: &ExpressionTree) -> MetricValue {
505 let values = HashMap::new();
506 let fetcher = Fetcher::TrialData(TrialDataFetcher::new(&values));
507 let files = HashMap::new();
508 let metric_state = MetricState::new(&files, fetcher, None);
509 metric_state.evaluate("", tree)
510 }
511
512 #[cfg(test)]
513 pub(crate) fn evaluate_expression(&self, e: &ExpressionTree) -> MetricValue {
514 self.evaluate("", e)
515 }
516
517 fn evaluate_function(
518 &self,
519 namespace: &str,
520 function: &Function,
521 operands: &[ExpressionTree],
522 ) -> MetricValue {
523 match function {
524 Function::Math(operation) => arithmetic::calculate(
525 operation,
526 &operands.iter().map(|o| self.evaluate(namespace, o)).collect::<Vec<MetricValue>>(),
527 ),
528 Function::Equals => self.apply_boolean_function(namespace, &|a, b| a == b, operands),
529 Function::NotEq => self.apply_boolean_function(namespace, &|a, b| a != b, operands),
530 Function::And => {
531 self.fold_bool(namespace, &|a, b| a && b, operands, ShortCircuitBehavior::False)
532 }
533 Function::Or => {
534 self.fold_bool(namespace, &|a, b| a || b, operands, ShortCircuitBehavior::True)
535 }
536 Function::Not => self.not_bool(namespace, operands),
537 Function::KlogHas | Function::SyslogHas | Function::BootlogHas => {
538 self.log_contains(function, namespace, operands)
539 }
540 Function::Missing => self.is_missing(namespace, operands),
541 Function::UnhandledType => self.is_unhandled_type(namespace, operands),
542 Function::Problem => self.is_problem(namespace, operands),
543 Function::Annotation => self.annotation(namespace, operands),
544 Function::Lambda => Lambda::as_metric_value(operands),
545 Function::Apply => self.apply(namespace, operands),
546 Function::Map => self.map(namespace, operands),
547 Function::Fold => self.fold(namespace, operands),
548 Function::All => self.map_fold_bool(MapFoldBoolArgs {
549 namespace,
550 operands,
551 function_name: "All",
552 default_when_empty: true,
553 function: &|a, b| a && b,
554 short_circuit_behavior: ShortCircuitBehavior::False,
555 }),
556 Function::Any => self.map_fold_bool(MapFoldBoolArgs {
557 namespace,
558 operands,
559 function_name: "Any",
560 default_when_empty: false,
561 function: &|a, b| a || b,
562 short_circuit_behavior: ShortCircuitBehavior::True,
563 }),
564 Function::Filter => self.filter(namespace, operands),
565 Function::Count | Function::CountProperties => {
566 self.count_properties(namespace, operands)
567 }
568 Function::CountChildren => self.count_children(namespace, operands),
569 Function::Nanos => self.time(namespace, operands, 1),
570 Function::Micros => self.time(namespace, operands, 1_000),
571 Function::Millis => self.time(namespace, operands, 1_000_000),
572 Function::Seconds => self.time(namespace, operands, 1_000_000_000),
573 Function::Minutes => self.time(namespace, operands, 1_000_000_000 * 60),
574 Function::Hours => self.time(namespace, operands, 1_000_000_000 * 60 * 60),
575 Function::Days => self.time(namespace, operands, 1_000_000_000 * 60 * 60 * 24),
576 Function::Now => self.now(operands),
577 Function::OptionF => self.option(namespace, operands),
578 Function::StringMatches => self.regex(namespace, operands),
579 Function::True => self.boolean(operands, true),
580 Function::False => self.boolean(operands, false),
581 }
582 }
583
584 fn regex(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
585 if operands.len() != 2 {
586 return syntax_error(
587 "StringMatches(metric, regex) needs one string metric and one string regex",
588 );
589 }
590
591 let (value, regex) = match (
592 self.evaluate(namespace, &operands[0]),
593 self.evaluate(namespace, &operands[1]),
594 ) {
595 (MetricValue::String(value), MetricValue::String(regex)) => (value, regex),
596 _ => {
597 return syntax_error("Arguments to StringMatches must be strings");
598 }
599 };
600
601 let regex = match Regex::new(®ex) {
602 Ok(v) => v,
603 Err(_) => {
604 return syntax_error(format!("Could not parse `{regex}` as regex"));
605 }
606 };
607
608 MetricValue::Bool(regex.is_match(&value))
609 }
610
611 fn option(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
612 first_usable_value(operands.iter().map(|expression| self.evaluate(namespace, expression)))
613 }
614
615 fn now(&self, operands: &'a [ExpressionTree]) -> MetricValue {
616 if !operands.is_empty() {
617 return syntax_error("Now() requires no operands.");
618 }
619 match self.now {
620 Some(time) => MetricValue::Int(time),
621 None => missing("No valid time available"),
622 }
623 }
624
625 fn apply_lambda(&self, namespace: &str, lambda: &Lambda, args: &[&MetricValue]) -> MetricValue {
626 fn substitute_all(
627 expressions: &[ExpressionTree],
628 bindings: &HashMap<&str, &MetricValue>,
629 ) -> Vec<ExpressionTree> {
630 expressions.iter().map(|e| substitute(e, bindings)).collect::<Vec<_>>()
631 }
632
633 fn substitute(
634 expression: &ExpressionTree,
635 bindings: &HashMap<&str, &MetricValue>,
636 ) -> ExpressionTree {
637 match expression {
638 ExpressionTree::Function(function, expressions) => ExpressionTree::Function(
639 function.clone(),
640 substitute_all(expressions, bindings),
641 ),
642 ExpressionTree::Vector(expressions) => {
643 ExpressionTree::Vector(substitute_all(expressions, bindings))
644 }
645 ExpressionTree::Variable(name) => {
646 let original_name = name.original_name();
647 if let Some(value) = bindings.get(original_name) {
648 ExpressionTree::Value((*value).clone())
649 } else {
650 ExpressionTree::Variable(name.clone())
651 }
652 }
653 ExpressionTree::Value(value) => ExpressionTree::Value(value.clone()),
654 }
655 }
656
657 let parameters = &lambda.parameters;
658 if parameters.len() != args.len() {
659 return syntax_error(format!(
660 "Function has {} parameters and needs {} arguments, but has {}.",
661 parameters.len(),
662 parameters.len(),
663 args.len()
664 ));
665 }
666 let mut bindings = HashMap::new();
667 for (name, value) in parameters.iter().zip(args.iter()) {
668 bindings.insert(name as &str, *value);
669 }
670 let expression = substitute(&lambda.body, &bindings);
671 self.evaluate(namespace, &expression)
672 }
673
674 fn unpack_lambda(
675 &self,
676 namespace: &str,
677 operands: &'a [ExpressionTree],
678 function_name: &str,
679 ) -> Result<(Box<Lambda>, Vec<MetricValue>), MetricValue> {
680 if operands.is_empty() {
681 return Err(syntax_error(format!(
682 "{function_name} needs a function in its first argument",
683 )));
684 }
685 let lambda = match self.evaluate(namespace, &operands[0]) {
686 MetricValue::Lambda(lambda) => lambda,
687 _ => {
688 return Err(syntax_error(format!(
689 "{function_name} needs a function in its first argument",
690 )))
691 }
692 };
693 let arguments =
694 operands[1..].iter().map(|expr| self.evaluate(namespace, expr)).collect::<Vec<_>>();
695 Ok((lambda, arguments))
696 }
697
698 fn apply(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
700 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Apply") {
701 Ok((lambda, arguments)) => (lambda, arguments),
702 Err(problem) => return problem,
703 };
704 if arguments.is_empty() {
705 return syntax_error("Apply needs a second argument (a vector).");
706 }
707 if arguments.len() > 1 {
708 return syntax_error("Apply only accepts one vector argument.");
709 }
710
711 match &arguments[0] {
712 MetricValue::Vector(apply_args) => {
713 self.apply_lambda(namespace, &lambda, &apply_args.iter().collect::<Vec<_>>())
714 }
715 _ => value_error("Apply only accepts a vector as an argument."),
716 }
717 }
718
719 fn map(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
721 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Map") {
722 Ok((lambda, arguments)) => (lambda, arguments),
723 Err(problem) => return problem,
724 };
725 let vector_args = arguments
727 .iter()
728 .filter(|item| matches!(item, MetricValue::Vector(_)))
729 .collect::<Vec<_>>();
730 let result_length = match vector_args.len() {
731 0 => 0,
732 _ => {
733 let start = match vector_args[0] {
734 MetricValue::Vector(vec) => vec.len(),
735 _ => unreachable!(),
736 };
737 vector_args.iter().fold(start, |accum, item| {
738 min(
739 accum,
740 match item {
741 MetricValue::Vector(items) => items.len(),
742 _ => 0,
743 },
744 )
745 })
746 }
747 };
748 let mut result = Vec::new();
749 for index in 0..result_length {
750 let call_args = arguments
751 .iter()
752 .map(|arg| match arg {
753 MetricValue::Vector(vec) => &vec[index],
754 other => other,
755 })
756 .collect::<Vec<_>>();
757 result.push(self.apply_lambda(namespace, &lambda, &call_args));
758 }
759 MetricValue::Vector(result)
760 }
761
762 fn fold(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
764 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Fold") {
765 Ok((lambda, arguments)) => (lambda, arguments),
766 Err(problem) => return problem,
767 };
768 if arguments.is_empty() {
769 return syntax_error("Fold needs a second argument, a vector");
770 }
771 let vector = match &arguments[0] {
772 MetricValue::Vector(items) => items,
773 _ => return value_error("Second argument of Fold must be a vector"),
774 };
775 let (first, rest) = match arguments.len() {
776 1 => match vector.split_first() {
777 Some(first_rest) => first_rest,
778 None => return value_error("Fold needs at least one value"),
779 },
780 2 => (&arguments[1], &vector[..]),
781 _ => return syntax_error("Fold needs (function, vec) or (function, vec, start)"),
782 };
783 let mut result = first.clone();
784 for item in rest {
785 result = self.apply_lambda(namespace, &lambda, &[&result, item]);
786 }
787 result
788 }
789
790 fn map_fold_bool(&self, args: MapFoldBoolArgs<'_>) -> MetricValue {
791 let (lambda, arguments) =
792 match self.unpack_lambda(args.namespace, args.operands, args.function_name) {
793 Ok((lambda, arguments)) => (lambda, arguments),
794 Err(problem) => return problem,
795 };
796 if arguments.len() != 1 {
797 return syntax_error(format!(
798 "{} needs two arguments (function, vector)",
799 args.function_name
800 ));
801 }
802 let MetricValue::Vector(v) = &arguments[0] else {
803 return syntax_error(format!(
804 "The second argument passed to {} must be a vector",
805 args.function_name
806 ));
807 };
808 if v.is_empty() {
810 return MetricValue::Bool(args.default_when_empty);
811 }
812 let operands: Vec<_> = v
813 .iter()
814 .map(|item| {
815 let metric_value = self.apply_lambda(args.namespace, &lambda, &[item]);
816 ExpressionTree::Value(metric_value)
817 })
818 .collect();
819 self.fold_bool(args.namespace, args.function, &operands, args.short_circuit_behavior)
820 }
821
822 fn filter(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
824 let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Filter") {
825 Ok((lambda, arguments)) => (lambda, arguments),
826 Err(problem) => return problem,
827 };
828 if arguments.len() != 1 {
829 return syntax_error("Filter needs (function, vector)");
830 }
831 let result = match &arguments[0] {
832 MetricValue::Vector(items) => items
833 .iter()
834 .filter_map(|item| match self.apply_lambda(namespace, &lambda, &[item]) {
835 MetricValue::Bool(true) => Some(item.clone()),
836 MetricValue::Bool(false) => None,
837 MetricValue::Problem(problem) => Some(MetricValue::Problem(problem)),
838 bad_type => Some(value_error(format!(
839 "Bad value {bad_type:?} from filter function should be Boolean",
840 ))),
841 })
842 .collect(),
843 _ => return syntax_error("Filter second argument must be a vector"),
844 };
845 MetricValue::Vector(result)
846 }
847
848 fn count_properties(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
850 if operands.len() != 1 {
851 return syntax_error("CountProperties requires one argument, a vector");
852 }
853 match self.evaluate(namespace, &operands[0]) {
855 MetricValue::Vector(items) => {
856 let errors = items
857 .iter()
858 .filter_map(|item| match item {
859 MetricValue::Problem(problem) => Some(problem),
860 _ => None,
861 })
862 .collect::<Vec<_>>();
863 match errors.len() {
864 0 => MetricValue::Int(
865 items.iter().filter(|metric| !matches!(metric, MetricValue::Node)).count()
866 as i64,
867 ),
868 _ => MetricValue::Problem(Self::important_problem(errors)),
869 }
870 }
871 bad => value_error(format!("CountProperties only works on vectors, not {bad}")),
872 }
873 }
874
875 fn count_children(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
877 if operands.len() != 1 {
878 return syntax_error("CountChildren requires one argument, a vector");
879 }
880 match self.evaluate(namespace, &operands[0]) {
882 MetricValue::Vector(items) => {
883 let errors = items
884 .iter()
885 .filter_map(|item| match item {
886 MetricValue::Problem(problem) => Some(problem),
887 _ => None,
888 })
889 .collect::<Vec<_>>();
890 match errors.len() {
891 0 => MetricValue::Int(
892 items.iter().filter(|metric| matches!(metric, MetricValue::Node)).count()
893 as i64,
894 ),
895 _ => MetricValue::Problem(Self::important_problem(errors)),
896 }
897 }
898 bad => value_error(format!("CountChildren only works on vectors, not {bad}")),
899 }
900 }
901
902 fn boolean(&self, operands: &[ExpressionTree], value: bool) -> MetricValue {
903 if !operands.is_empty() {
904 return syntax_error("Boolean functions don't take any arguments");
905 }
906 MetricValue::Bool(value)
907 }
908
909 fn time(&self, namespace: &str, operands: &[ExpressionTree], multiplier: i64) -> MetricValue {
911 if operands.len() != 1 {
912 return syntax_error("Time conversion needs 1 numeric argument");
913 }
914 match self.evaluate(namespace, &operands[0]) {
915 MetricValue::Int(value) => MetricValue::Int(value * multiplier),
916 MetricValue::Float(value) => match safe_float_to_int(value * (multiplier as f64)) {
917 None => value_error(format!(
918 "Time conversion needs 1 numeric argument; couldn't convert {value}",
919 )),
920 Some(value) => MetricValue::Int(value),
921 },
922 MetricValue::Problem(oops) => MetricValue::Problem(oops),
923 bad => value_error(format!("Time conversion needs 1 numeric argument, not {bad}")),
924 }
925 }
926
927 fn evaluate(&self, namespace: &str, e: &ExpressionTree) -> MetricValue {
928 match e {
929 ExpressionTree::Function(f, operands) => self.evaluate_function(namespace, f, operands),
930 ExpressionTree::Variable(name) => self.evaluate_variable(namespace, name),
931 ExpressionTree::Value(value) => value.clone(),
932 ExpressionTree::Vector(values) => MetricValue::Vector(
933 values.iter().map(|value| self.evaluate(namespace, value)).collect(),
934 ),
935 }
936 }
937
938 fn annotation(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
939 if operands.len() != 1 {
940 return syntax_error("Annotation() needs 1 string argument");
941 }
942 match self.evaluate(namespace, &operands[0]) {
943 MetricValue::String(string) => match &self.fetcher {
944 Fetcher::TrialData(fetcher) => fetcher.annotations,
945 Fetcher::FileData(fetcher) => fetcher.annotations,
946 }
947 .fetch(&string),
948 _ => value_error("Annotation() needs a string argument"),
949 }
950 }
951
952 fn log_contains(
953 &self,
954 log_type: &Function,
955 namespace: &str,
956 operands: &[ExpressionTree],
957 ) -> MetricValue {
958 let log_data = match &self.fetcher {
959 Fetcher::TrialData(fetcher) => match log_type {
960 Function::KlogHas => fetcher.klog,
961 Function::SyslogHas => fetcher.syslog,
962 Function::BootlogHas => fetcher.bootlog,
963 _ => return internal_bug("Internal error, log_contains with non-log function"),
964 },
965 Fetcher::FileData(fetcher) => match log_type {
966 Function::KlogHas => fetcher.klog,
967 Function::SyslogHas => fetcher.syslog,
968 Function::BootlogHas => fetcher.bootlog,
969 _ => return internal_bug("Internal error, log_contains with non-log function"),
970 },
971 };
972 if operands.len() != 1 {
973 return syntax_error("Log matcher must use exactly 1 argument, an RE string.");
974 }
975 match self.evaluate(namespace, &operands[0]) {
976 MetricValue::String(re) => MetricValue::Bool(log_data.contains(&re)),
977 _ => value_error("Log matcher needs a string (RE)."),
978 }
979 }
980
981 fn important_problem(problems: Vec<&Problem>) -> Problem {
984 match problems.len() {
985 0 => Problem::InternalBug("Didn't find a Problem".to_string()),
986 len => {
987 let mut p = problems.clone();
988 p.sort_by_cached_key(|p| p.severity());
989 p[len - 1].clone()
990 }
991 }
992 }
993
994 fn apply_boolean_function(
995 &self,
996 namespace: &str,
997 function: &dyn (Fn(&MetricValue, &MetricValue) -> bool),
998 operands: &[ExpressionTree],
999 ) -> MetricValue {
1000 if operands.len() != 2 {
1001 return syntax_error(format!("Bad arg list {operands:?} for binary operator"));
1002 }
1003 let operand_values =
1004 operands.iter().map(|operand| self.evaluate(namespace, operand)).collect::<Vec<_>>();
1005 let args = operand_values.iter().map(unwrap_for_math).collect::<Vec<_>>();
1006 match (args[0], args[1]) {
1007 (MetricValue::Problem(p), MetricValue::Problem(q)) => {
1009 MetricValue::Problem(Self::important_problem(vec![p, q]))
1010 }
1011 (MetricValue::Problem(p), _) => MetricValue::Problem(p.clone()),
1012 (_, MetricValue::Problem(p)) => MetricValue::Problem(p.clone()),
1013 _ => MetricValue::Bool(function(args[0], args[1])),
1014 }
1015 }
1016
1017 fn fold_bool(
1018 &self,
1019 namespace: &str,
1020 function: &dyn (Fn(bool, bool) -> bool),
1021 operands: &[ExpressionTree],
1022 short_circuit_behavior: ShortCircuitBehavior,
1023 ) -> MetricValue {
1024 if operands.is_empty() {
1025 return syntax_error("No operands in boolean expression");
1026 }
1027 let first = self.evaluate(namespace, &operands[0]);
1028 let mut result: bool = match unwrap_for_math(&first) {
1029 MetricValue::Bool(value) => *value,
1030 MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1031 bad => return value_error(format!("{bad:?} is not boolean")),
1032 };
1033 for operand in operands[1..].iter() {
1034 match (result, short_circuit_behavior) {
1035 (true, ShortCircuitBehavior::True) => {
1036 break;
1037 }
1038 (false, ShortCircuitBehavior::False) => {
1039 break;
1040 }
1041 _ => {}
1042 };
1043 let nth = self.evaluate(namespace, operand);
1044 result = match unwrap_for_math(&nth) {
1045 MetricValue::Bool(value) => function(result, *value),
1046 MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1047 bad => return value_error(format!("{bad:?} is not boolean")),
1048 }
1049 }
1050 MetricValue::Bool(result)
1051 }
1052
1053 fn not_bool(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1054 if operands.len() != 1 {
1055 return syntax_error(format!(
1056 "Wrong number of arguments ({}) for unary bool operator",
1057 operands.len()
1058 ));
1059 }
1060 match unwrap_for_math(&self.evaluate(namespace, &operands[0])) {
1061 MetricValue::Bool(true) => MetricValue::Bool(false),
1062 MetricValue::Bool(false) => MetricValue::Bool(true),
1063 MetricValue::Problem(p) => MetricValue::Problem(p.clone()),
1064 bad => value_error(format!("{bad:?} not boolean")),
1065 }
1066 }
1067
1068 fn is_unhandled_type(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1073 if operands.len() != 1 {
1074 return syntax_error(format!(
1075 "Wrong number of operands for UnhandledType(): {}",
1076 operands.len()
1077 ));
1078 }
1079 let value = self.evaluate(namespace, &operands[0]);
1080 MetricValue::Bool(match value {
1081 MetricValue::Problem(Problem::UnhandledType(_)) => true,
1082
1083 MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1088 MetricValue::Problem(Problem::UnhandledType(_)) => true,
1089 MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1090 _ => false,
1091 },
1092 MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1093 _ => false,
1094 })
1095 }
1096
1097 fn is_missing(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1101 if operands.len() != 1 {
1102 return syntax_error(format!(
1103 "Wrong number of operands for Missing(): {}",
1104 operands.len()
1105 ));
1106 }
1107 let value = self.evaluate(namespace, &operands[0]);
1108 MetricValue::Bool(match value {
1109 MetricValue::Problem(Problem::Missing(_)) => true,
1110 MetricValue::Vector(contents) if contents.is_empty() => true,
1112 MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1113 MetricValue::Problem(Problem::Missing(_)) => true,
1114 MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1115 _ => false,
1116 },
1117 MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1118 _ => false,
1119 })
1120 }
1121
1122 fn is_problem(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1124 if operands.len() != 1 {
1125 return syntax_error(format!(
1126 "Wrong number of operands for Problem(): {}",
1127 operands.len()
1128 ));
1129 }
1130 let value = self.evaluate(namespace, &operands[0]);
1131 MetricValue::Bool(matches!(value, MetricValue::Problem(_)))
1132 }
1133}
1134
1135#[cfg(test)]
1142mod test {
1143 use super::*;
1144 use crate::config::{DiagnosticData, Source};
1145 use anyhow::Error;
1146 use std::sync::LazyLock;
1147
1148 #[macro_export]
1149 macro_rules! make_metrics {
1150 ({$($namespace: literal: {
1151 $(eval: {$($ke: literal: $ve: expr),+ $(,)?})?
1152 $(hardcoded: {$($kh: literal: $vh: expr),+ $(,)?})?
1153 $(select: {$($ks: literal: [$($vs: expr),+ $(,)?]),+ $(,)?})?
1154 }),+ $(,)?}) => {{
1155 [$((
1156 $namespace.to_string(),
1157 [
1158 $(
1159 $(
1160 ($ke.to_string(),
1161 ValueSource::try_from_expression_with_namespace($ve, $namespace)
1162 .expect("Unable to parse expression as value source.")),
1163 )+
1164 )?
1165 $(
1166 $(
1167 ($kh.to_string(),
1168 ValueSource::new(Metric::Hardcoded($vh))),
1169 )+
1170 )?
1171 $(
1172 $(
1173 ($ks.to_string(),
1174 ValueSource::new(Metric::Selector(
1175 [$($vs.clone(),)+].to_vec()))),
1176 )+
1177 )?
1178 ]
1179 .into_iter().collect::<HashMap<String, ValueSource>>()
1180 )),+]
1181 .into_iter()
1182 .collect::<HashMap<String, HashMap<String, ValueSource>>>()
1183 }};
1184}
1185
1186 #[macro_export]
1189 macro_rules! assert_problem {
1190 ($missing:expr, $message:expr) => {
1191 match $missing {
1192 MetricValue::Problem(problem) => assert_eq!(format!("{problem:?}"), $message),
1193 oops => {
1194 println!("Non problem type {:?}", oops);
1196 assert!(false, "Non-Problem type");
1197 }
1198 }
1199 };
1200 }
1201
1202 #[macro_export]
1203 macro_rules! assert_not_missing {
1204 ($not_missing:expr) => {
1205 match $not_missing {
1206 MetricValue::Problem(Problem::Missing(message)) => {
1207 assert!(false, "Expected not missing, was: {}", &message)
1208 }
1209 _ => {}
1210 }
1211 };
1212 }
1213
1214 static EMPTY_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1215 let s = r#"[]"#;
1216 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1217 });
1218 static NO_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1219 let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": null}]"#;
1220 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1221 });
1222 static BAD_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1223 let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": ["a", "b"]}]"#;
1224 vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1225 });
1226 static EMPTY_FILE_FETCHER: LazyLock<FileDataFetcher<'static>> =
1227 LazyLock::new(|| FileDataFetcher::new(&EMPTY_F));
1228 static EMPTY_TRIAL_FETCHER: LazyLock<TrialDataFetcher<'static>> =
1229 LazyLock::new(TrialDataFetcher::new_empty);
1230 static NO_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1231 LazyLock::new(|| FileDataFetcher::new(&NO_PAYLOAD_F));
1232 static BAD_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1233 LazyLock::new(|| FileDataFetcher::new(&BAD_PAYLOAD_F));
1234
1235 #[fuchsia::test]
1236 fn focus_on_important_errors() {
1237 let metrics = HashMap::new();
1238 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1239 let major = Problem::SyntaxError("Bad".to_string());
1240 let minor = Problem::Ignore(vec![Problem::Missing("Not a big deal".to_string())]);
1241 let major_arg = ExpressionTree::Value(MetricValue::Problem(major.clone()));
1242 let minor_arg = ExpressionTree::Value(MetricValue::Problem(minor));
1243 assert_problem!(
1244 state.apply_boolean_function(
1245 "",
1246 &|a, b| a == b,
1247 &[minor_arg.clone(), major_arg.clone()]
1248 ),
1249 "SyntaxError: Bad"
1250 );
1251 assert_problem!(
1252 state.apply_boolean_function("", &|a, b| a == b, &[major_arg, minor_arg]),
1253 "SyntaxError: Bad"
1254 );
1255 }
1256
1257 #[fuchsia::test]
1258 fn logs_work() -> Result<(), Error> {
1259 let syslog_text = "line 1\nline 2\nsyslog".to_string();
1260 let klog_text = "first line\nsecond line\nklog\n".to_string();
1261 let bootlog_text = "Yes there's a bootlog with one long line".to_string();
1262 let syslog = DiagnosticData::new("sys".to_string(), Source::Syslog, syslog_text)?;
1263 let klog = DiagnosticData::new("k".to_string(), Source::Klog, klog_text)?;
1264 let bootlog = DiagnosticData::new("boot".to_string(), Source::Bootlog, bootlog_text)?;
1265 let metrics = HashMap::new();
1266 let mut data = vec![klog, syslog, bootlog];
1267 let fetcher = FileDataFetcher::new(&data);
1268 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1269 assert_eq!(state.evaluate_value("", r#"KlogHas("lin")"#), MetricValue::Bool(true));
1270 assert_eq!(state.evaluate_value("", r#"KlogHas("l.ne")"#), MetricValue::Bool(true));
1271 assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*ne")"#), MetricValue::Bool(true));
1272 assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*sec")"#), MetricValue::Bool(false));
1273 assert_eq!(state.evaluate_value("", r#"KlogHas("first line")"#), MetricValue::Bool(true));
1274 assert_eq!(
1276 state.evaluate_value("", r#"KlogHas("f(.)rst \bline")"#),
1277 MetricValue::Bool(true)
1278 );
1279 assert_eq!(
1281 state.evaluate_value("", r#"KlogHas("f(.)rst \bl\1ne")"#),
1282 MetricValue::Bool(false)
1283 );
1284 assert_eq!(state.evaluate_value("", r#"KlogHas("second line")"#), MetricValue::Bool(true));
1285 assert_eq!(
1286 state.evaluate_value("", "KlogHas(\"second line\n\")"),
1287 MetricValue::Bool(false)
1288 );
1289 assert_eq!(state.evaluate_value("", r#"KlogHas("klog")"#), MetricValue::Bool(true));
1290 assert_eq!(state.evaluate_value("", r#"KlogHas("line 2")"#), MetricValue::Bool(false));
1291 assert_eq!(state.evaluate_value("", r#"SyslogHas("line 2")"#), MetricValue::Bool(true));
1292 assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1293 assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(true));
1294 assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1295 data.pop();
1296 let fetcher = FileDataFetcher::new(&data);
1297 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1298 assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1299 assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(false));
1300 assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1301 Ok(())
1302 }
1303
1304 #[fuchsia::test]
1305 fn annotations_work() -> Result<(), Error> {
1306 let annotation_text = r#"{ "build.board": "chromebook-x64", "answer": 42 }"#.to_string();
1307 let annotations =
1308 DiagnosticData::new("a".to_string(), Source::Annotations, annotation_text)?;
1309 let metrics = HashMap::new();
1310 let data = vec![annotations];
1311 let fetcher = FileDataFetcher::new(&data);
1312 let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1313 assert_eq!(
1314 state.evaluate_value("", "Annotation('build.board')"),
1315 MetricValue::String("chromebook-x64".to_string())
1316 );
1317 assert_eq!(state.evaluate_value("", "Annotation('answer')"), MetricValue::Int(42));
1318 assert_problem!(
1319 state.evaluate_value("", "Annotation('bogus')"),
1320 "Missing: Key 'bogus' not found in annotations"
1321 );
1322 assert_problem!(
1323 state.evaluate_value("", "Annotation('bogus', 'Double bogus')"),
1324 "SyntaxError: Annotation() needs 1 string argument"
1325 );
1326 assert_problem!(
1327 state.evaluate_value("", "Annotation(42)"),
1328 "ValueError: Annotation() needs a string argument"
1329 );
1330 Ok(())
1331 }
1332
1333 #[fuchsia::test]
1334 fn test_fetch_errors() {
1335 assert_eq!(0, NO_PAYLOAD_FETCHER.errors().len());
1337 assert_eq!(1, BAD_PAYLOAD_FETCHER.errors().len());
1338 }
1339
1340 #[fuchsia::test]
1345 fn test_evaluation() {
1346 let metrics = make_metrics!({
1347 "root":{
1348 eval: {"is42": "42", "isOk": "'OK'"}
1349 hardcoded: {"unhandled": unhandled_type("Unhandled")}
1350 }
1351 });
1352 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1353
1354 assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1356
1357 assert_eq!(state.evaluate_value("root", "is42 + 1"), MetricValue::Int(43));
1359 assert_eq!(state.evaluate_value("root", "is42 - 1"), MetricValue::Int(41));
1360 assert_eq!(state.evaluate_value("root", "is42 * 2"), MetricValue::Int(84));
1361 assert_eq!(state.evaluate_value("root", "is42 / 4"), MetricValue::Float(10.5));
1363 assert_eq!(state.evaluate_value("root", "is42 // 4"), MetricValue::Int(10));
1364
1365 assert_eq!(
1367 state.evaluate_value("root", "is42 + 10 / 2 * 10 - 2 "),
1368 MetricValue::Float(90.0)
1369 );
1370 assert_eq!(state.evaluate_value("root", "is42 + 10 // 2 * 10 - 2 "), MetricValue::Int(90));
1371
1372 assert_eq!(
1374 state.evaluate_value("root", "And(is42 == 42, is42 < 100)"),
1375 MetricValue::Bool(true)
1376 );
1377 assert_eq!(
1378 state.evaluate_value("root", "And(is42 == 42, is42 > 100)"),
1379 MetricValue::Bool(false)
1380 );
1381 assert_eq!(
1382 state.evaluate_value("root", "Or(is42 == 42, is42 > 100)"),
1383 MetricValue::Bool(true)
1384 );
1385 assert_eq!(
1386 state.evaluate_value("root", "Or(is42 != 42, is42 < 100)"),
1387 MetricValue::Bool(true)
1388 );
1389 assert_eq!(
1390 state.evaluate_value("root", "Or(is42 != 42, is42 > 100)"),
1391 MetricValue::Bool(false)
1392 );
1393 assert_eq!(state.evaluate_value("root", "Not(is42 == 42)"), MetricValue::Bool(false));
1394
1395 assert_eq!(state.evaluate_value("root", "isOk"), MetricValue::String("OK".to_string()));
1397
1398 assert_problem!(
1400 state.evaluate_value("root", "missing"),
1401 "SyntaxError: Metric 'missing' Not Found in 'root'"
1402 );
1403
1404 assert_problem!(
1406 state.evaluate_value("root", "Or(is42 != 42, missing)"),
1407 "SyntaxError: Metric 'missing' Not Found in 'root'"
1408 );
1409 assert_eq!(
1410 state.evaluate_value("root", "Or(is42 == 42, missing)"),
1411 MetricValue::Bool(true)
1412 );
1413 assert_problem!(
1414 state.evaluate_value("root", "And(is42 == 42, missing)"),
1415 "SyntaxError: Metric 'missing' Not Found in 'root'"
1416 );
1417
1418 assert_eq!(
1419 state.evaluate_value("root", "And(is42 != 42, missing)"),
1420 MetricValue::Bool(false)
1421 );
1422
1423 assert_eq!(state.evaluate_value("root", "Missing(is42)"), MetricValue::Bool(false));
1425 assert_problem!(
1427 state.evaluate_value("root", "Missing(not_found)"),
1428 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1429 );
1430 assert_eq!(
1431 state.evaluate_value("root", "And(Not(Missing(is42)), is42 == 42)"),
1432 MetricValue::Bool(true)
1433 );
1434 assert_problem!(
1435 state.evaluate_value("root", "And(Not(Missing(not_found)), not_found == 'Hello')"),
1436 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1437 );
1438 assert_eq!(
1439 state.evaluate_value("root", "Or(Missing(is42), is42 < 42)"),
1440 MetricValue::Bool(false)
1441 );
1442 assert_problem!(
1443 state.evaluate_value("root", "Or(Missing(not_found), not_found == 'Hello')"),
1444 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1445 );
1446 assert_eq!(state.evaluate_value("root", "Missing([])"), MetricValue::Bool(true));
1447
1448 assert_eq!(state.evaluate_value("root", "UnhandledType(is42)"), MetricValue::Bool(false));
1450 assert_problem!(
1452 state.evaluate_value("root", "UnhandledType(not_found)"),
1453 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1454 );
1455 assert_eq!(
1456 state.evaluate_value("root", "And(Not(UnhandledType(is42)), is42 == 42)"),
1457 MetricValue::Bool(true)
1458 );
1459 assert_eq!(
1460 state.evaluate_value("root", "UnhandledType(unhandled)"),
1461 MetricValue::Bool(true)
1462 );
1463 assert_eq!(state.evaluate_value("root", "UnhandledType([])"), MetricValue::Bool(false));
1464
1465 assert_eq!(state.evaluate_value("root", "Problem(is42)"), MetricValue::Bool(false));
1467 assert_eq!(state.evaluate_value("root", "Problem(not_found)"), MetricValue::Bool(true));
1469 assert_eq!(
1470 state.evaluate_value("root", "And(Not(Problem(is42)), is42 == 42)"),
1471 MetricValue::Bool(true)
1472 );
1473 assert_eq!(
1474 state.evaluate_value("root", "And(Not(Problem(not_found)), not_found == 'Hello')"),
1475 MetricValue::Bool(false)
1476 );
1477 assert_eq!(
1478 state.evaluate_value("root", "Or(Problem(is42), is42 < 42)"),
1479 MetricValue::Bool(false)
1480 );
1481 assert_eq!(
1482 state.evaluate_value("root", "Or(Problem(not_found), not_found == 'Hello')"),
1483 MetricValue::Bool(true)
1484 );
1485 assert_problem!(
1486 state.evaluate_value("root", "Or(not_found == 'Hello', Problem(not_found))"),
1487 "SyntaxError: Metric 'not_found' Not Found in 'root'"
1488 );
1489
1490 assert_eq!(
1492 state.evaluate_value("root", "[0==0]"),
1493 MetricValue::Vector(vec![MetricValue::Bool(true)])
1494 );
1495 assert_eq!(
1496 state.eval_action_metric(
1497 "root",
1498 &ValueSource::try_from_expression_with_namespace("[0==0]", "root").unwrap()
1499 ),
1500 MetricValue::Bool(true)
1501 );
1502
1503 assert_eq!(
1504 state.evaluate_value("root", "[0==0, 0==0]"),
1505 MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1506 );
1507 assert_eq!(
1508 state.eval_action_metric(
1509 "root",
1510 &ValueSource::try_from_expression_with_namespace("[0==0, 0==0]", "root").unwrap()
1511 ),
1512 MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1513 );
1514
1515 assert_eq!(
1517 state.eval_action_metric(
1518 "root",
1519 &ValueSource::try_from_expression_with_namespace(
1520 "StringMatches('abcd', '^a.c')",
1521 "root"
1522 )
1523 .unwrap()
1524 ),
1525 MetricValue::Bool(true)
1526 );
1527 assert_eq!(
1528 state.eval_action_metric(
1529 "root",
1530 &ValueSource::try_from_expression_with_namespace(
1531 "StringMatches('abcd', 'a.c$')",
1532 "root"
1533 )
1534 .unwrap()
1535 ),
1536 MetricValue::Bool(false)
1537 );
1538 assert_problem!(
1539 state.eval_action_metric(
1540 "root",
1541 &ValueSource::try_from_expression_with_namespace(
1542 "StringMatches('abcd', '[[')",
1543 "root"
1544 )
1545 .unwrap()
1546 ),
1547 "SyntaxError: Could not parse `[[` as regex"
1548 );
1549 }
1550
1551 #[fuchsia::test]
1553 fn test_caching_after_evaluation() {
1554 let metrics = make_metrics!({
1555 "root":{
1556 eval: {"is42": "42", "is43": "is42 + 1"}
1557 hardcoded: {"unhandled": unhandled_type("Unhandled")}
1558 }
1559 });
1560
1561 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1562 let trial_state =
1563 MetricState::new(&metrics, Fetcher::TrialData(EMPTY_TRIAL_FETCHER.clone()), None);
1564
1565 assert_eq!(
1567 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1568 None
1569 );
1570 assert_eq!(
1571 *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1572 None
1573 );
1574 assert_eq!(
1575 *state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow(),
1576 None
1577 );
1578 assert_eq!(
1579 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1580 None
1581 );
1582 assert_eq!(
1583 *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1584 None
1585 );
1586 assert_eq!(
1587 *trial_state
1588 .metrics
1589 .get("root")
1590 .unwrap()
1591 .get("unhandled")
1592 .unwrap()
1593 .cached_value
1594 .borrow(),
1595 None
1596 );
1597
1598 assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1601 assert_eq!(
1602 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1603 Some(MetricValue::Int(42))
1604 );
1605 assert_eq!(trial_state.evaluate_value("root", "is42"), MetricValue::Int(42));
1606 assert_eq!(
1607 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1608 Some(MetricValue::Int(42))
1609 );
1610
1611 assert_eq!(state.evaluate_value("root", "is43"), MetricValue::Int(43));
1615 assert_eq!(
1616 *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1617 Some(MetricValue::Int(42))
1618 );
1619 assert_eq!(
1620 *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1621 Some(MetricValue::Int(43))
1622 );
1623 assert_eq!(trial_state.evaluate_value("root", "is43"), MetricValue::Int(43));
1624 assert_eq!(
1625 *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1626 Some(MetricValue::Int(42))
1627 );
1628 assert_eq!(
1629 *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1630 Some(MetricValue::Int(43))
1631 );
1632
1633 state.evaluate_value("root", "unhandled");
1635 assert_problem!(
1636 (*state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow())
1637 .as_ref()
1638 .unwrap(),
1639 "UnhandledType: Unhandled"
1640 );
1641 trial_state.evaluate_value("root", "unhandled");
1642 assert_problem!(
1643 (*trial_state
1644 .metrics
1645 .get("root")
1646 .unwrap()
1647 .get("unhandled")
1648 .unwrap()
1649 .cached_value
1650 .borrow())
1651 .as_ref()
1652 .unwrap(),
1653 "UnhandledType: Unhandled"
1654 );
1655 }
1656
1657 macro_rules! eval {
1658 ($e:expr) => {
1659 MetricState::evaluate_math($e)
1660 };
1661 }
1662
1663 #[fuchsia::test]
1665 fn test_missing_hacks() -> Result<(), Error> {
1666 assert_eq!(eval!("Missing(2>'a')"), MetricValue::Bool(true));
1667 assert_eq!(eval!("Missing([])"), MetricValue::Bool(true));
1668 assert_eq!(eval!("Missing([2>'a'])"), MetricValue::Bool(true));
1669 assert_eq!(eval!("Missing([2>'a', 2>'a'])"), MetricValue::Bool(false));
1670 assert_eq!(eval!("Missing([2>1])"), MetricValue::Bool(false));
1671 assert_eq!(eval!("Or(Missing(2>'a'), 2>'a')"), MetricValue::Bool(true));
1672 Ok(())
1673 }
1674
1675 #[fuchsia::test]
1676 fn test_ignores_in_expressions() {
1677 let dbz = "ValueError: Division by zero";
1678 assert_problem!(eval!("CountProperties([1, 2, 3/0])"), dbz);
1679 assert_problem!(eval!("CountProperties([1/0, 2, 3/0])"), dbz);
1680 assert_problem!(eval!("CountProperties([1/?0, 2, 3/?0])"), format!("Ignore: {dbz}"));
1681 assert_problem!(eval!("CountProperties([1/0, 2, 3/?0])"), dbz);
1682 assert_problem!(eval!("And(1 > 0, 3/0 > 0)"), dbz);
1684 assert_problem!(eval!("And(1/0 > 0, 3/0 > 0)"), dbz);
1685 assert_problem!(eval!("And(1/?0 > 0, 3/?0 > 0)"), format!("Ignore: {dbz}"));
1686 assert_problem!(eval!("And(1/?0 > 0, 3/0 > 0)"), format!("Ignore: {dbz}"));
1687 assert_problem!(eval!("1 == 3/0"), dbz);
1688 assert_problem!(eval!("1/0 == 3/0"), dbz);
1689 assert_problem!(eval!("1/?0 == 3/?0"), format!("Ignore: {dbz}"));
1690 assert_problem!(eval!("1/?0 == 3/0"), dbz);
1691 assert_problem!(eval!("1 + 3/0"), dbz);
1692 assert_problem!(eval!("1/0 + 3/0"), dbz);
1693 assert_problem!(eval!("1/?0 + 3/?0"), format!("Ignore: {dbz}"));
1694 assert_problem!(eval!("1/?0 + 3/0"), dbz);
1695 }
1696
1697 #[fuchsia::test]
1699 fn test_checked_divide_preserves_errors() {
1700 let san = "Missing: String(a) not numeric";
1701 assert_problem!(eval!("(1+'a')/1"), san);
1702 assert_problem!(eval!("(1+'a')/?1"), san);
1703 assert_problem!(eval!("1/?(1+'a')"), san);
1704 assert_problem!(eval!("(1+'a')/?(1+'a')"), san);
1705 assert_problem!(eval!("(1+'a')/0"), san);
1708 assert_problem!(eval!("(1+'a')/?0"), san);
1709 assert_problem!(eval!("(1+'a')//1"), san);
1710 assert_problem!(eval!("(1+'a')//?1"), san);
1711 assert_problem!(eval!("1//?(1+'a')"), san);
1712 assert_problem!(eval!("(1+'a')//?(1+'a')"), san);
1713 assert_problem!(eval!("(1+'a')//0"), san);
1714 assert_problem!(eval!("(1+'a')//?0"), san);
1715 }
1716
1717 #[fuchsia::test]
1718 fn test_time() -> Result<(), Error> {
1719 let metrics = Metrics::new();
1720 let files = vec![];
1721 let state_1234 =
1722 MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), Some(1234));
1723 let state_missing =
1724 MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), None);
1725 let now_expression = parse::parse_expression("Now()", "").unwrap();
1726 assert_problem!(MetricState::evaluate_math("Now()"), "Missing: No valid time available");
1727 assert_eq!(state_1234.evaluate_expression(&now_expression), MetricValue::Int(1234));
1728 assert_problem!(
1729 state_missing.evaluate_expression(&now_expression),
1730 "Missing: No valid time available"
1731 );
1732 Ok(())
1733 }
1734
1735 #[fuchsia::test]
1736 fn test_expression_context() {
1737 let selector_expr = "INSPECT:foo:bar:baz";
1739 assert_eq!(
1740 format!(
1741 "{:?}",
1742 ExpressionContext::try_from_expression_with_default_namespace(selector_expr)
1743 .err()
1744 .unwrap()
1745 ),
1746 "Expression Error: \n0: at line 1, in Eof:\nINSPECT:foo:bar:baz\n ^\n\n"
1747 );
1748
1749 let invalid_expr = "1 *";
1751 assert_eq!(
1752 format!(
1753 "{:?}",
1754 ExpressionContext::try_from_expression_with_default_namespace(invalid_expr)
1755 .err()
1756 .unwrap()
1757 ),
1758 concat!("Expression Error: \n0: at line 1, in Eof:\n1 *\n ^\n\n")
1759 );
1760
1761 let valid_expr = "42 + 1";
1763 let parsed_expression = parse::parse_expression(valid_expr, "").unwrap();
1764 assert_eq!(
1765 ExpressionContext::try_from_expression_with_default_namespace(valid_expr).unwrap(),
1766 ExpressionContext { raw_expression: valid_expr.to_string(), parsed_expression }
1767 );
1768 }
1769
1770 #[fuchsia::test]
1771 fn test_not_valid_cycle_same_variable() {
1772 let metrics = make_metrics!({
1773 "root":{
1774 eval: {
1775 "is42": "42",
1776 "shouldBe42": "is42 + 0",
1777 }
1778 },
1779 "n2":{
1780 eval: {
1781 "is42": "root::shouldBe42"
1782 }
1783 }
1784 });
1785 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1786
1787 assert_eq!(state.evaluate_value("n2", "is42"), MetricValue::Int(42));
1789 }
1790
1791 #[fuchsia::test]
1792 fn test_cycle_detected_correctly() {
1793 let metrics = make_metrics!({
1795 "root":{
1796 eval: {
1797 "is42": "42",
1798 "shouldBe62": "is42 + 1 + n2::is19",
1799 "b": "is42 + n2::a"
1800 }
1801 },
1802 "n2":{
1803 eval: {
1804 "is19": "19",
1805 "shouldBe44": "root::is42 + 2",
1806 "a": "is19 + c",
1807 "c": "root::b + root::is42"
1808 }
1809 }
1810 });
1811
1812 let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1813
1814 assert_eq!(state.evaluate_value("root", "shouldBe62 + 1"), MetricValue::Int(63));
1816
1817 assert_problem!(
1819 state.evaluate_value("root", "n2::a"),
1820 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1821 );
1822
1823 assert_eq!(state.evaluate_value("root", "n2::shouldBe44"), MetricValue::Int(44));
1825
1826 assert_problem!(
1828 state.evaluate_value("root", "b"),
1829 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1830 );
1831
1832 assert_problem!(
1834 state.evaluate_value("root", "n2::c"),
1835 "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1836 );
1837
1838 assert_eq!(state.evaluate_value("root", "shouldBe62"), MetricValue::Int(62));
1840 }
1841 }