fuchsia_triage/
metrics.rs

1// Copyright 2019 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
5pub(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/// The contents of a single Metric. Metrics produce a value for use in Actions or other Metrics.
23#[derive(Clone, Debug, PartialEq, Serialize)]
24pub(crate) enum Metric {
25    /// Selector tells where to find a value in the Inspect data. The
26    /// first non-empty option is returned.
27    // Note: This can't be a fidl_fuchsia_diagnostics::Selector because it's not deserializable or
28    // cloneable.
29    Selector(Vec<SelectorString>),
30    /// Eval contains an arithmetic expression,
31    Eval(ExpressionContext),
32    // Directly specify a value that's hard to generate, for example Problem::UnhandledType.
33    #[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/// Contains a Metric and the resulting MetricValue if the Metric has been evaluated at least once.
49/// If the Metric has not been evaluated at least once the metric_value contains None.
50#[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
84/// [Metrics] are a map from namespaces to the named [ValueSource]s stored within that namespace.
85pub type Metrics = HashMap<String, HashMap<String, ValueSource>>;
86
87/// Contains all the information needed to look up and evaluate a Metric - other
88/// [Metric]s that may be referred to, and a source of input values to calculate on.
89///
90/// Note: MetricState uses a single Now() value for all evaluations. If a MetricState is
91/// retained and used for multiple evaluations at different times, provide a way to update
92/// the `now` field.
93pub 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 and NotEq can apply to bools and strings, and handle int/float without needing
122    // the mechanisms in mod arithmetic.
123    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/// Lambda stores a function; its parameters and body are evaluated lazily.
160/// Lambda's are created by evaluating the "Fn()" expression.
161#[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// Behavior for short circuiting execution when applying operands.
201#[derive(Copy, Clone, Debug)]
202enum ShortCircuitBehavior {
203    // Short circuit when the first true value is found.
204    True,
205    // Short circuit when the first false value is found.
206    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/// ExpressionTree represents the parsed body of an Eval Metric. It applies
219/// a function to sub-expressions, or holds a Problem, the name of a
220/// Metric, a vector of expressions, or a basic Value.
221#[derive(Deserialize, Debug, Clone, PartialEq, Serialize)]
222pub(crate) enum ExpressionTree {
223    // Some operators have arity 1 or 2, some have arity N.
224    // For symmetry/readability, I use the same operand-spec Vec<Expression> for all.
225    // TODO(cphoenix): Check on load that all operators have a legal number of operands.
226    Function(Function, Vec<ExpressionTree>),
227    Vector(Vec<ExpressionTree>),
228    Variable(VariableName),
229    Value(MetricValue),
230}
231
232/// ExpressionContext represents a wrapper class which contains a DSL string
233/// representing an expression and the resulting parsed ExpressionTree
234#[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            /*namespace= */ "",
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
265// Selectors return a vec of values. Typically they will select a single
266// value which we want to use for math without lots of boilerplate.
267// So we "promote" a 1-entry vector into the value it contains.
268// Other vectors will be passed through unchanged (and cause an error later).
269fn 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
276// Construct Problem() metric from a message
277fn 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    // TODO(https://fxbug.dev/42136933): Improve and simplify the error semantics
329    missing("Every value was missing")
330}
331
332impl<'a> MetricState<'a> {
333    /// Create an initialized MetricState.
334    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    /// Forcefully evaluate all [Metric]s to populate the cached values.
339    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    /// Any [name] found in the trial's "values" uses the corresponding value, regardless of
348    /// whether it is a Selector or Eval Metric, and regardless of whether it includes
349    /// a namespace; the string match must be exact.
350    /// If not found in "values" the name must be an Eval metric from the current file.
351    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    /// If [name] is of the form "namespace::name" then [namespace] is ignored.
399    /// If [name] is just "name" then [namespace] is used to look up the Metric.
400    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    /// Calculate the value of a Metric specified by name and namespace.
439    fn evaluate_variable(&self, namespace: &str, name: &VariableName) -> MetricValue {
440        // TODO(cphoenix): When historical metrics are added, change semantics to refresh()
441        // TODO(cphoenix): Improve the data structure on Metric names. Probably fill in
442        //  namespace during parse.
443        if self.stack.borrow().contains(&name.full_name(namespace)) {
444            // Clear the stack for future reuse
445            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        // Insert the full name including namespace.
452        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    /// Fetch or compute the value of a Metric expression from an action.
464    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    /// Evaluate an Expression which contains only base values, not referring to other Metrics.
496    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(&regex) {
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    /// This implements the Apply() function.
699    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    /// This implements the Map() function.
720    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        // TODO(cphoenix): Clean this up - replace the next 25 lines with a for loop
726        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    /// This implements the Fold() function.
763    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        // Return default_when_empty in case of empty args
809        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    /// This implements the Filter() function.
823    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    /// This implements the CountProperties() function.
849    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        // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
854        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    /// This implements the CountChildren() function.
876    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        // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
881        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    /// This implements the time-conversion functions.
910    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    // Prioritizes significant problems over Unhandled and Missing, and Missing over Unhandled.
982    // Don't call with a vector that might be empty.
983    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            // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
1008            (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    // Returns Bool true if the given metric is Unhandled, or a vector with one Unhandled (which
1069    // may be returned by fetch()).
1070    // Returns false if the metric is a non-Problem value.
1071    // Propagates non-Unhandled Problems.
1072    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            // TODO(https://fxbug.dev/42136933): Well-designed errors and special cases, not hacks
1084            // is_unhandled_type() returns false on an empty vector, while is_missing() returns
1085            // true as a special case. So these functions can't easily be refactored. When 58922 is
1086            // completed this logic will be a lot simpler and more consistent.
1087            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    // Returns Bool true if the given metric is Missing, false if the metric has a value. Propagates
1098    // non-Missing errors. Special case: An empty vector, or a vector containing one Missing,
1099    // counts as Missing.
1100    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            // TODO(https://fxbug.dev/42136933): Well-designed errors and special cases, not hacks
1111            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    // Returns Bool true if and only if the value for the given metric is any sort of Problem.
1123    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// The evaluation of math expressions is tested pretty exhaustively in parse.rs unit tests.
1136
1137// The use of metric names in expressions and actions, with and without namespaces, is tested in
1138// the integration test.
1139//   $ fx test triage_lib_test
1140
1141#[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    /// Problem should never equal anything, even an identical Problem. Code (tests) can use
1187    /// assert_problem!(MetricValue::Problem(_), "foo") to test error messages.
1188    #[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                    // TODO: Add $missing to println, it currently returns a generics error.
1195                    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        // Full regex; even capture groups are allowed but the values can't be extracted.
1275        assert_eq!(
1276            state.evaluate_value("", r#"KlogHas("f(.)rst \bline")"#),
1277            MetricValue::Bool(true)
1278        );
1279        // Backreferences don't work; this is regex, not fancy_regex.
1280        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        // Do not show errors when there is simply no payload.
1336        assert_eq!(0, NO_PAYLOAD_FETCHER.errors().len());
1337        assert_eq!(1, BAD_PAYLOAD_FETCHER.errors().len());
1338    }
1339
1340    // Correct operation of the klog, syslog, and bootlog fields of TrialDataFetcher are tested
1341    // in the integration test via log_tests.triage.
1342
1343    // Test evaluation on static values.
1344    #[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        // Can read a value.
1355        assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1356
1357        // Basic arithmetic
1358        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        // Automatic float conversion and truncating divide.
1362        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        // Order of operations
1366        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        // Boolean
1373        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        // Read strings
1396        assert_eq!(state.evaluate_value("root", "isOk"), MetricValue::String("OK".to_string()));
1397
1398        // Missing value
1399        assert_problem!(
1400            state.evaluate_value("root", "missing"),
1401            "SyntaxError: Metric 'missing' Not Found in 'root'"
1402        );
1403
1404        // Booleans short circuit
1405        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        // Missing() checks
1424        assert_eq!(state.evaluate_value("root", "Missing(is42)"), MetricValue::Bool(false));
1425        // An unknown variable is a SyntaxError, not a Missing(), and Missing() won't catch it.
1426        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        // UnhandledType() checks
1449        assert_eq!(state.evaluate_value("root", "UnhandledType(is42)"), MetricValue::Bool(false));
1450        // An unknown variable is a SyntaxError, not a Missing(), and Missing() won't catch it.
1451        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        // Problem() checks
1466        assert_eq!(state.evaluate_value("root", "Problem(is42)"), MetricValue::Bool(false));
1467        // An unknown variable is a SyntaxError, not a Missing()
1468        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        // Ensure evaluation for action converts vector values.
1491        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        // Test regex operations
1516        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    // Test caching after evaluating static values
1552    #[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        // Test correct initialization of RefCells
1566        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        // Test correct caching of values after evaluation
1599        // Evaluating single metric
1600        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        // Evaluating metric with a nested metric
1612        // Ensure the previous metric cached value is not modified and new metric is
1613        // correctly stored
1614        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        // Evaluating Hardcoded Metric and ensuring correct caching behavior
1634        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    // TODO(https://fxbug.dev/42136933): Modify or probably delete this function after better error design.
1664    #[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        // And() short-circuits so it will only see the first error
1683        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    /// Make sure that checked divide doesn't hide worse errors in its arguments
1698    #[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        // If the numerator has a Problem and the denominator doesn't, it won't get to the divide
1706        // logic, so both / and /? won't observe the "division by zero"
1707        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()", /*namespace= */ "").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        // Check correct error behavior when building expression from selector string
1738        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        // Check correct error behavior when building expression from invalid expression string
1750        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        // Check expression correctly built from valid expression
1762        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        // Evaluation with the same variable name but different namespace should be successful.
1788        assert_eq!(state.evaluate_value("n2", "is42"), MetricValue::Int(42));
1789    }
1790
1791    #[fuchsia::test]
1792    fn test_cycle_detected_correctly() {
1793        // Cycle is between n2::a -> n2::c -> root::b -> n2::a
1794        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        // Evaluation when a cycle is not encountered should be successful.
1815        assert_eq!(state.evaluate_value("root", "shouldBe62 + 1"), MetricValue::Int(63));
1816
1817        // Check evaluation with a cycle leads to an 'EvaluationError'.
1818        assert_problem!(
1819            state.evaluate_value("root", "n2::a"),
1820            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1821        );
1822
1823        // Check if the stack was reset properly after problem
1824        assert_eq!(state.evaluate_value("root", "n2::shouldBe44"), MetricValue::Int(44));
1825
1826        // Check evaluation with a cycle leads to an 'EvaluationError'.
1827        assert_problem!(
1828            state.evaluate_value("root", "b"),
1829            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1830        );
1831
1832        // Check evaluation with a cycle leads to an 'EvaluationError'.
1833        assert_problem!(
1834            state.evaluate_value("root", "n2::c"),
1835            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1836        );
1837
1838        // Check if the stack was reset properly after problem
1839        assert_eq!(state.evaluate_value("root", "shouldBe62"), MetricValue::Int(62));
1840    }
1841    // Correct operation of annotations is tested via annotation_tests.triage.
1842}