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 {} not in test values and refers outside the file",
364                name
365            ));
366        }
367        match self.metrics.get(namespace) {
368            None => internal_bug(format!("BUG! Bad namespace '{}'", namespace)),
369            Some(metric_map) => match metric_map.get(name) {
370                None => syntax_error(format!("Metric '{}' Not Found in '{}'", name, namespace)),
371                Some(value_source) => {
372                    let resolved_value: MetricValue;
373                    {
374                        let cached_value_cell = value_source.cached_value.borrow();
375                        match &*cached_value_cell {
376                            None => {
377                                resolved_value = match &value_source.metric {
378                                    Metric::Selector(_) => missing(format!(
379                                        "Selector {} can't be used in tests; please supply a value",
380                                        name
381                                    )),
382                                    Metric::Eval(expression) => {
383                                        self.evaluate(namespace, &expression.parsed_expression)
384                                    }
385                                    #[cfg(test)]
386                                    Metric::Hardcoded(value) => value.clone(),
387                                };
388                            }
389                            Some(cached_value) => return cached_value.clone(),
390                        }
391                    }
392                    let mut cached_value_cell = value_source.cached_value.borrow_mut();
393                    *cached_value_cell = Some(resolved_value.clone());
394                    resolved_value
395                }
396            },
397        }
398    }
399
400    /// If [name] is of the form "namespace::name" then [namespace] is ignored.
401    /// If [name] is just "name" then [namespace] is used to look up the Metric.
402    fn metric_value_for_file(
403        &self,
404        fetcher: &FileDataFetcher<'_>,
405        namespace: &str,
406        name: &VariableName,
407    ) -> MetricValue {
408        if let Some((real_namespace, real_name)) = name.name_parts(namespace) {
409            match self.metrics.get(real_namespace) {
410                None => syntax_error(format!("Bad namespace '{}'", real_namespace)),
411                Some(metric_map) => match metric_map.get(real_name) {
412                    None => syntax_error(format!(
413                        "Metric '{}' Not Found in '{}'",
414                        real_name, real_namespace
415                    )),
416                    Some(value_source) => {
417                        if let Some(cached_value) = value_source.cached_value.borrow().as_ref() {
418                            return cached_value.clone();
419                        }
420                        let resolved_value = match &value_source.metric {
421                            Metric::Selector(selectors) => first_usable_value(
422                                selectors.iter().map(|selector| fetcher.fetch(selector)),
423                            ),
424                            Metric::Eval(expression) => {
425                                self.evaluate(real_namespace, &expression.parsed_expression)
426                            }
427                            #[cfg(test)]
428                            Metric::Hardcoded(value) => value.clone(),
429                        };
430                        let mut cached_value_cell = value_source.cached_value.borrow_mut();
431                        *cached_value_cell = Some(resolved_value.clone());
432                        resolved_value
433                    }
434                },
435            }
436        } else {
437            syntax_error(format!("Bad name '{}'", name.original_name()))
438        }
439    }
440
441    /// Calculate the value of a Metric specified by name and namespace.
442    fn evaluate_variable(&self, namespace: &str, name: &VariableName) -> MetricValue {
443        // TODO(cphoenix): When historical metrics are added, change semantics to refresh()
444        // TODO(cphoenix): Improve the data structure on Metric names. Probably fill in
445        //  namespace during parse.
446        if self.stack.borrow().contains(&name.full_name(namespace)) {
447            // Clear the stack for future reuse
448            let _ = self.stack.replace(HashSet::new());
449            return evaluation_error(format!(
450                "Cycle encountered while evaluating variable {} in the expression",
451                name.name
452            ));
453        }
454        // Insert the full name including namespace.
455        self.stack.borrow_mut().insert(name.full_name(namespace));
456
457        let value = match &self.fetcher {
458            Fetcher::FileData(fetcher) => self.metric_value_for_file(fetcher, namespace, name),
459            Fetcher::TrialData(fetcher) => self.metric_value_for_trial(fetcher, namespace, name),
460        };
461
462        self.stack.borrow_mut().remove(&name.full_name(namespace));
463        value
464    }
465
466    /// Fetch or compute the value of a Metric expression from an action.
467    pub(crate) fn eval_action_metric(
468        &self,
469        namespace: &str,
470        value_source: &ValueSource,
471    ) -> MetricValue {
472        if let Some(cached_value) = &*value_source.cached_value.borrow() {
473            return cached_value.clone();
474        }
475
476        let resolved_value = match &value_source.metric {
477            Metric::Selector(_) => syntax_error("Selectors aren't allowed in action triggers"),
478            Metric::Eval(expression) => {
479                unwrap_for_math(&self.evaluate(namespace, &expression.parsed_expression)).clone()
480            }
481            #[cfg(test)]
482            Metric::Hardcoded(value) => value.clone(),
483        };
484
485        let mut cached_value_cell = value_source.cached_value.borrow_mut();
486        *cached_value_cell = Some(resolved_value.clone());
487        resolved_value
488    }
489
490    #[cfg(test)]
491    fn evaluate_value(&self, namespace: &str, expression: &str) -> MetricValue {
492        match parse::parse_expression(expression, namespace) {
493            Ok(expr) => self.evaluate(namespace, &expr),
494            Err(e) => syntax_error(format!("Expression parse error\n{}", e)),
495        }
496    }
497
498    /// Evaluate an Expression which contains only base values, not referring to other Metrics.
499    pub(crate) fn evaluate_math(expr: &str) -> MetricValue {
500        let tree = match ExpressionContext::try_from_expression_with_default_namespace(expr) {
501            Ok(expr_context) => expr_context.parsed_expression,
502            Err(err) => return syntax_error(format!("Failed to parse '{}': {}", expr, err)),
503        };
504        Self::evaluate_const_expression(&tree)
505    }
506
507    pub(crate) fn evaluate_const_expression(tree: &ExpressionTree) -> MetricValue {
508        let values = HashMap::new();
509        let fetcher = Fetcher::TrialData(TrialDataFetcher::new(&values));
510        let files = HashMap::new();
511        let metric_state = MetricState::new(&files, fetcher, None);
512        metric_state.evaluate("", tree)
513    }
514
515    #[cfg(test)]
516    pub(crate) fn evaluate_expression(&self, e: &ExpressionTree) -> MetricValue {
517        self.evaluate("", e)
518    }
519
520    fn evaluate_function(
521        &self,
522        namespace: &str,
523        function: &Function,
524        operands: &[ExpressionTree],
525    ) -> MetricValue {
526        match function {
527            Function::Math(operation) => arithmetic::calculate(
528                operation,
529                &operands.iter().map(|o| self.evaluate(namespace, o)).collect::<Vec<MetricValue>>(),
530            ),
531            Function::Equals => self.apply_boolean_function(namespace, &|a, b| a == b, operands),
532            Function::NotEq => self.apply_boolean_function(namespace, &|a, b| a != b, operands),
533            Function::And => {
534                self.fold_bool(namespace, &|a, b| a && b, operands, ShortCircuitBehavior::False)
535            }
536            Function::Or => {
537                self.fold_bool(namespace, &|a, b| a || b, operands, ShortCircuitBehavior::True)
538            }
539            Function::Not => self.not_bool(namespace, operands),
540            Function::KlogHas | Function::SyslogHas | Function::BootlogHas => {
541                self.log_contains(function, namespace, operands)
542            }
543            Function::Missing => self.is_missing(namespace, operands),
544            Function::UnhandledType => self.is_unhandled_type(namespace, operands),
545            Function::Problem => self.is_problem(namespace, operands),
546            Function::Annotation => self.annotation(namespace, operands),
547            Function::Lambda => Lambda::as_metric_value(operands),
548            Function::Apply => self.apply(namespace, operands),
549            Function::Map => self.map(namespace, operands),
550            Function::Fold => self.fold(namespace, operands),
551            Function::All => self.map_fold_bool(MapFoldBoolArgs {
552                namespace,
553                operands,
554                function_name: "All",
555                default_when_empty: true,
556                function: &|a, b| a && b,
557                short_circuit_behavior: ShortCircuitBehavior::False,
558            }),
559            Function::Any => self.map_fold_bool(MapFoldBoolArgs {
560                namespace,
561                operands,
562                function_name: "Any",
563                default_when_empty: false,
564                function: &|a, b| a || b,
565                short_circuit_behavior: ShortCircuitBehavior::True,
566            }),
567            Function::Filter => self.filter(namespace, operands),
568            Function::Count | Function::CountProperties => {
569                self.count_properties(namespace, operands)
570            }
571            Function::CountChildren => self.count_children(namespace, operands),
572            Function::Nanos => self.time(namespace, operands, 1),
573            Function::Micros => self.time(namespace, operands, 1_000),
574            Function::Millis => self.time(namespace, operands, 1_000_000),
575            Function::Seconds => self.time(namespace, operands, 1_000_000_000),
576            Function::Minutes => self.time(namespace, operands, 1_000_000_000 * 60),
577            Function::Hours => self.time(namespace, operands, 1_000_000_000 * 60 * 60),
578            Function::Days => self.time(namespace, operands, 1_000_000_000 * 60 * 60 * 24),
579            Function::Now => self.now(operands),
580            Function::OptionF => self.option(namespace, operands),
581            Function::StringMatches => self.regex(namespace, operands),
582            Function::True => self.boolean(operands, true),
583            Function::False => self.boolean(operands, false),
584        }
585    }
586
587    fn regex(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
588        if operands.len() != 2 {
589            return syntax_error(
590                "StringMatches(metric, regex) needs one string metric and one string regex",
591            );
592        }
593
594        let (value, regex) = match (
595            self.evaluate(namespace, &operands[0]),
596            self.evaluate(namespace, &operands[1]),
597        ) {
598            (MetricValue::String(value), MetricValue::String(regex)) => (value, regex),
599            _ => {
600                return syntax_error("Arguments to StringMatches must be strings");
601            }
602        };
603
604        let regex = match Regex::new(&regex) {
605            Ok(v) => v,
606            Err(_) => {
607                return syntax_error(format!("Could not parse `{}` as regex", regex));
608            }
609        };
610
611        MetricValue::Bool(regex.is_match(&value))
612    }
613
614    fn option(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
615        first_usable_value(operands.iter().map(|expression| self.evaluate(namespace, expression)))
616    }
617
618    fn now(&self, operands: &'a [ExpressionTree]) -> MetricValue {
619        if !operands.is_empty() {
620            return syntax_error("Now() requires no operands.");
621        }
622        match self.now {
623            Some(time) => MetricValue::Int(time),
624            None => missing("No valid time available"),
625        }
626    }
627
628    fn apply_lambda(&self, namespace: &str, lambda: &Lambda, args: &[&MetricValue]) -> MetricValue {
629        fn substitute_all(
630            expressions: &[ExpressionTree],
631            bindings: &HashMap<&str, &MetricValue>,
632        ) -> Vec<ExpressionTree> {
633            expressions.iter().map(|e| substitute(e, bindings)).collect::<Vec<_>>()
634        }
635
636        fn substitute(
637            expression: &ExpressionTree,
638            bindings: &HashMap<&str, &MetricValue>,
639        ) -> ExpressionTree {
640            match expression {
641                ExpressionTree::Function(function, expressions) => ExpressionTree::Function(
642                    function.clone(),
643                    substitute_all(expressions, bindings),
644                ),
645                ExpressionTree::Vector(expressions) => {
646                    ExpressionTree::Vector(substitute_all(expressions, bindings))
647                }
648                ExpressionTree::Variable(name) => {
649                    let original_name = name.original_name();
650                    if let Some(value) = bindings.get(original_name) {
651                        ExpressionTree::Value((*value).clone())
652                    } else {
653                        ExpressionTree::Variable(name.clone())
654                    }
655                }
656                ExpressionTree::Value(value) => ExpressionTree::Value(value.clone()),
657            }
658        }
659
660        let parameters = &lambda.parameters;
661        if parameters.len() != args.len() {
662            return syntax_error(format!(
663                "Function has {} parameters and needs {} arguments, but has {}.",
664                parameters.len(),
665                parameters.len(),
666                args.len()
667            ));
668        }
669        let mut bindings = HashMap::new();
670        for (name, value) in parameters.iter().zip(args.iter()) {
671            bindings.insert(name as &str, *value);
672        }
673        let expression = substitute(&lambda.body, &bindings);
674        self.evaluate(namespace, &expression)
675    }
676
677    fn unpack_lambda(
678        &self,
679        namespace: &str,
680        operands: &'a [ExpressionTree],
681        function_name: &str,
682    ) -> Result<(Box<Lambda>, Vec<MetricValue>), MetricValue> {
683        if operands.is_empty() {
684            return Err(syntax_error(format!(
685                "{} needs a function in its first argument",
686                function_name
687            )));
688        }
689        let lambda = match self.evaluate(namespace, &operands[0]) {
690            MetricValue::Lambda(lambda) => lambda,
691            _ => {
692                return Err(syntax_error(format!(
693                    "{} needs a function in its first argument",
694                    function_name
695                )))
696            }
697        };
698        let arguments =
699            operands[1..].iter().map(|expr| self.evaluate(namespace, expr)).collect::<Vec<_>>();
700        Ok((lambda, arguments))
701    }
702
703    /// This implements the Apply() function.
704    fn apply(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
705        let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Apply") {
706            Ok((lambda, arguments)) => (lambda, arguments),
707            Err(problem) => return problem,
708        };
709        if arguments.is_empty() {
710            return syntax_error("Apply needs a second argument (a vector).");
711        }
712        if arguments.len() > 1 {
713            return syntax_error("Apply only accepts one vector argument.");
714        }
715
716        match &arguments[0] {
717            MetricValue::Vector(apply_args) => {
718                self.apply_lambda(namespace, &lambda, &apply_args.iter().collect::<Vec<_>>())
719            }
720            _ => value_error("Apply only accepts a vector as an argument."),
721        }
722    }
723
724    /// This implements the Map() function.
725    fn map(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
726        let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Map") {
727            Ok((lambda, arguments)) => (lambda, arguments),
728            Err(problem) => return problem,
729        };
730        // TODO(cphoenix): Clean this up - replace the next 25 lines with a for loop
731        let vector_args = arguments
732            .iter()
733            .filter(|item| matches!(item, MetricValue::Vector(_)))
734            .collect::<Vec<_>>();
735        let result_length = match vector_args.len() {
736            0 => 0,
737            _ => {
738                let start = match vector_args[0] {
739                    MetricValue::Vector(vec) => vec.len(),
740                    _ => unreachable!(),
741                };
742                vector_args.iter().fold(start, |accum, item| {
743                    min(
744                        accum,
745                        match item {
746                            MetricValue::Vector(items) => items.len(),
747                            _ => 0,
748                        },
749                    )
750                })
751            }
752        };
753        let mut result = Vec::new();
754        for index in 0..result_length {
755            let call_args = arguments
756                .iter()
757                .map(|arg| match arg {
758                    MetricValue::Vector(vec) => &vec[index],
759                    other => other,
760                })
761                .collect::<Vec<_>>();
762            result.push(self.apply_lambda(namespace, &lambda, &call_args));
763        }
764        MetricValue::Vector(result)
765    }
766
767    /// This implements the Fold() function.
768    fn fold(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
769        let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Fold") {
770            Ok((lambda, arguments)) => (lambda, arguments),
771            Err(problem) => return problem,
772        };
773        if arguments.is_empty() {
774            return syntax_error("Fold needs a second argument, a vector");
775        }
776        let vector = match &arguments[0] {
777            MetricValue::Vector(items) => items,
778            _ => return value_error("Second argument of Fold must be a vector"),
779        };
780        let (first, rest) = match arguments.len() {
781            1 => match vector.split_first() {
782                Some(first_rest) => first_rest,
783                None => return value_error("Fold needs at least one value"),
784            },
785            2 => (&arguments[1], &vector[..]),
786            _ => return syntax_error("Fold needs (function, vec) or (function, vec, start)"),
787        };
788        let mut result = first.clone();
789        for item in rest {
790            result = self.apply_lambda(namespace, &lambda, &[&result, item]);
791        }
792        result
793    }
794
795    fn map_fold_bool(&self, args: MapFoldBoolArgs<'_>) -> MetricValue {
796        let (lambda, arguments) =
797            match self.unpack_lambda(args.namespace, args.operands, args.function_name) {
798                Ok((lambda, arguments)) => (lambda, arguments),
799                Err(problem) => return problem,
800            };
801        if arguments.len() != 1 {
802            return syntax_error(format!(
803                "{} needs two arguments (function, vector)",
804                args.function_name
805            ));
806        }
807        let MetricValue::Vector(v) = &arguments[0] else {
808            return syntax_error(format!(
809                "The second argument passed to {} must be a vector",
810                args.function_name
811            ));
812        };
813        // Return default_when_empty in case of empty args
814        if v.is_empty() {
815            return MetricValue::Bool(args.default_when_empty);
816        }
817        let operands: Vec<_> = v
818            .iter()
819            .map(|item| {
820                let metric_value = self.apply_lambda(args.namespace, &lambda, &[item]);
821                ExpressionTree::Value(metric_value)
822            })
823            .collect();
824        self.fold_bool(args.namespace, args.function, &operands, args.short_circuit_behavior)
825    }
826
827    /// This implements the Filter() function.
828    fn filter(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
829        let (lambda, arguments) = match self.unpack_lambda(namespace, operands, "Filter") {
830            Ok((lambda, arguments)) => (lambda, arguments),
831            Err(problem) => return problem,
832        };
833        if arguments.len() != 1 {
834            return syntax_error("Filter needs (function, vector)");
835        }
836        let result = match &arguments[0] {
837            MetricValue::Vector(items) => items
838                .iter()
839                .filter_map(|item| match self.apply_lambda(namespace, &lambda, &[item]) {
840                    MetricValue::Bool(true) => Some(item.clone()),
841                    MetricValue::Bool(false) => None,
842                    MetricValue::Problem(problem) => Some(MetricValue::Problem(problem)),
843                    bad_type => Some(value_error(format!(
844                        "Bad value {:?} from filter function should be Boolean",
845                        bad_type
846                    ))),
847                })
848                .collect(),
849            _ => return syntax_error("Filter second argument must be a vector"),
850        };
851        MetricValue::Vector(result)
852    }
853
854    /// This implements the CountProperties() function.
855    fn count_properties(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
856        if operands.len() != 1 {
857            return syntax_error("CountProperties requires one argument, a vector");
858        }
859        // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
860        match self.evaluate(namespace, &operands[0]) {
861            MetricValue::Vector(items) => {
862                let errors = items
863                    .iter()
864                    .filter_map(|item| match item {
865                        MetricValue::Problem(problem) => Some(problem),
866                        _ => None,
867                    })
868                    .collect::<Vec<_>>();
869                match errors.len() {
870                    0 => MetricValue::Int(
871                        items.iter().filter(|metric| !matches!(metric, MetricValue::Node)).count()
872                            as i64,
873                    ),
874                    _ => MetricValue::Problem(Self::important_problem(errors)),
875                }
876            }
877            bad => value_error(format!("CountProperties only works on vectors, not {}", bad)),
878        }
879    }
880
881    /// This implements the CountChildren() function.
882    fn count_children(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
883        if operands.len() != 1 {
884            return syntax_error("CountChildren requires one argument, a vector");
885        }
886        // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
887        match self.evaluate(namespace, &operands[0]) {
888            MetricValue::Vector(items) => {
889                let errors = items
890                    .iter()
891                    .filter_map(|item| match item {
892                        MetricValue::Problem(problem) => Some(problem),
893                        _ => None,
894                    })
895                    .collect::<Vec<_>>();
896                match errors.len() {
897                    0 => MetricValue::Int(
898                        items.iter().filter(|metric| matches!(metric, MetricValue::Node)).count()
899                            as i64,
900                    ),
901                    _ => MetricValue::Problem(Self::important_problem(errors)),
902                }
903            }
904            bad => value_error(format!("CountChildren only works on vectors, not {}", bad)),
905        }
906    }
907
908    fn boolean(&self, operands: &[ExpressionTree], value: bool) -> MetricValue {
909        if !operands.is_empty() {
910            return syntax_error("Boolean functions don't take any arguments");
911        }
912        MetricValue::Bool(value)
913    }
914
915    /// This implements the time-conversion functions.
916    fn time(&self, namespace: &str, operands: &[ExpressionTree], multiplier: i64) -> MetricValue {
917        if operands.len() != 1 {
918            return syntax_error("Time conversion needs 1 numeric argument");
919        }
920        match self.evaluate(namespace, &operands[0]) {
921            MetricValue::Int(value) => MetricValue::Int(value * multiplier),
922            MetricValue::Float(value) => match safe_float_to_int(value * (multiplier as f64)) {
923                None => value_error(format!(
924                    "Time conversion needs 1 numeric argument; couldn't convert {}",
925                    value
926                )),
927                Some(value) => MetricValue::Int(value),
928            },
929            MetricValue::Problem(oops) => MetricValue::Problem(oops),
930            bad => value_error(format!("Time conversion needs 1 numeric argument, not {}", bad)),
931        }
932    }
933
934    fn evaluate(&self, namespace: &str, e: &ExpressionTree) -> MetricValue {
935        match e {
936            ExpressionTree::Function(f, operands) => self.evaluate_function(namespace, f, operands),
937            ExpressionTree::Variable(name) => self.evaluate_variable(namespace, name),
938            ExpressionTree::Value(value) => value.clone(),
939            ExpressionTree::Vector(values) => MetricValue::Vector(
940                values.iter().map(|value| self.evaluate(namespace, value)).collect(),
941            ),
942        }
943    }
944
945    fn annotation(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
946        if operands.len() != 1 {
947            return syntax_error("Annotation() needs 1 string argument");
948        }
949        match self.evaluate(namespace, &operands[0]) {
950            MetricValue::String(string) => match &self.fetcher {
951                Fetcher::TrialData(fetcher) => fetcher.annotations,
952                Fetcher::FileData(fetcher) => fetcher.annotations,
953            }
954            .fetch(&string),
955            _ => value_error("Annotation() needs a string argument"),
956        }
957    }
958
959    fn log_contains(
960        &self,
961        log_type: &Function,
962        namespace: &str,
963        operands: &[ExpressionTree],
964    ) -> MetricValue {
965        let log_data = match &self.fetcher {
966            Fetcher::TrialData(fetcher) => match log_type {
967                Function::KlogHas => fetcher.klog,
968                Function::SyslogHas => fetcher.syslog,
969                Function::BootlogHas => fetcher.bootlog,
970                _ => return internal_bug("Internal error, log_contains with non-log function"),
971            },
972            Fetcher::FileData(fetcher) => match log_type {
973                Function::KlogHas => fetcher.klog,
974                Function::SyslogHas => fetcher.syslog,
975                Function::BootlogHas => fetcher.bootlog,
976                _ => return internal_bug("Internal error, log_contains with non-log function"),
977            },
978        };
979        if operands.len() != 1 {
980            return syntax_error("Log matcher must use exactly 1 argument, an RE string.");
981        }
982        match self.evaluate(namespace, &operands[0]) {
983            MetricValue::String(re) => MetricValue::Bool(log_data.contains(&re)),
984            _ => value_error("Log matcher needs a string (RE)."),
985        }
986    }
987
988    // Prioritizes significant problems over Unhandled and Missing, and Missing over Unhandled.
989    // Don't call with a vector that might be empty.
990    fn important_problem(problems: Vec<&Problem>) -> Problem {
991        match problems.len() {
992            0 => Problem::InternalBug("Didn't find a Problem".to_string()),
993            len => {
994                let mut p = problems.clone();
995                p.sort_by_cached_key(|p| p.severity());
996                p[len - 1].clone()
997            }
998        }
999    }
1000
1001    fn apply_boolean_function(
1002        &self,
1003        namespace: &str,
1004        function: &dyn (Fn(&MetricValue, &MetricValue) -> bool),
1005        operands: &[ExpressionTree],
1006    ) -> MetricValue {
1007        if operands.len() != 2 {
1008            return syntax_error(format!("Bad arg list {:?} for binary operator", operands));
1009        }
1010        let operand_values =
1011            operands.iter().map(|operand| self.evaluate(namespace, operand)).collect::<Vec<_>>();
1012        let args = operand_values.iter().map(unwrap_for_math).collect::<Vec<_>>();
1013        match (args[0], args[1]) {
1014            // TODO(https://fxbug.dev/42136933): Refactor all the arg-sanitizing boilerplate into one function
1015            (MetricValue::Problem(p), MetricValue::Problem(q)) => {
1016                MetricValue::Problem(Self::important_problem(vec![p, q]))
1017            }
1018            (MetricValue::Problem(p), _) => MetricValue::Problem(p.clone()),
1019            (_, MetricValue::Problem(p)) => MetricValue::Problem(p.clone()),
1020            _ => MetricValue::Bool(function(args[0], args[1])),
1021        }
1022    }
1023
1024    fn fold_bool(
1025        &self,
1026        namespace: &str,
1027        function: &dyn (Fn(bool, bool) -> bool),
1028        operands: &[ExpressionTree],
1029        short_circuit_behavior: ShortCircuitBehavior,
1030    ) -> MetricValue {
1031        if operands.is_empty() {
1032            return syntax_error("No operands in boolean expression");
1033        }
1034        let first = self.evaluate(namespace, &operands[0]);
1035        let mut result: bool = match unwrap_for_math(&first) {
1036            MetricValue::Bool(value) => *value,
1037            MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1038            bad => return value_error(format!("{:?} is not boolean", bad)),
1039        };
1040        for operand in operands[1..].iter() {
1041            match (result, short_circuit_behavior) {
1042                (true, ShortCircuitBehavior::True) => {
1043                    break;
1044                }
1045                (false, ShortCircuitBehavior::False) => {
1046                    break;
1047                }
1048                _ => {}
1049            };
1050            let nth = self.evaluate(namespace, operand);
1051            result = match unwrap_for_math(&nth) {
1052                MetricValue::Bool(value) => function(result, *value),
1053                MetricValue::Problem(p) => return MetricValue::Problem(p.clone()),
1054                bad => return value_error(format!("{:?} is not boolean", bad)),
1055            }
1056        }
1057        MetricValue::Bool(result)
1058    }
1059
1060    fn not_bool(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1061        if operands.len() != 1 {
1062            return syntax_error(format!(
1063                "Wrong number of arguments ({}) for unary bool operator",
1064                operands.len()
1065            ));
1066        }
1067        match unwrap_for_math(&self.evaluate(namespace, &operands[0])) {
1068            MetricValue::Bool(true) => MetricValue::Bool(false),
1069            MetricValue::Bool(false) => MetricValue::Bool(true),
1070            MetricValue::Problem(p) => MetricValue::Problem(p.clone()),
1071            bad => value_error(format!("{:?} not boolean", bad)),
1072        }
1073    }
1074
1075    // Returns Bool true if the given metric is Unhandled, or a vector with one Unhandled (which
1076    // may be returned by fetch()).
1077    // Returns false if the metric is a non-Problem value.
1078    // Propagates non-Unhandled Problems.
1079    fn is_unhandled_type(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1080        if operands.len() != 1 {
1081            return syntax_error(format!(
1082                "Wrong number of operands for UnhandledType(): {}",
1083                operands.len()
1084            ));
1085        }
1086        let value = self.evaluate(namespace, &operands[0]);
1087        MetricValue::Bool(match value {
1088            MetricValue::Problem(Problem::UnhandledType(_)) => true,
1089
1090            // TODO(https://fxbug.dev/42136933): Well-designed errors and special cases, not hacks
1091            // is_unhandled_type() returns false on an empty vector, while is_missing() returns
1092            // true as a special case. So these functions can't easily be refactored. When 58922 is
1093            // completed this logic will be a lot simpler and more consistent.
1094            MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1095                MetricValue::Problem(Problem::UnhandledType(_)) => true,
1096                MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1097                _ => false,
1098            },
1099            MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1100            _ => false,
1101        })
1102    }
1103
1104    // Returns Bool true if the given metric is Missing, false if the metric has a value. Propagates
1105    // non-Missing errors. Special case: An empty vector, or a vector containing one Missing,
1106    // counts as Missing.
1107    fn is_missing(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1108        if operands.len() != 1 {
1109            return syntax_error(format!(
1110                "Wrong number of operands for Missing(): {}",
1111                operands.len()
1112            ));
1113        }
1114        let value = self.evaluate(namespace, &operands[0]);
1115        MetricValue::Bool(match value {
1116            MetricValue::Problem(Problem::Missing(_)) => true,
1117            // TODO(https://fxbug.dev/42136933): Well-designed errors and special cases, not hacks
1118            MetricValue::Vector(contents) if contents.is_empty() => true,
1119            MetricValue::Vector(contents) if contents.len() == 1 => match contents[0] {
1120                MetricValue::Problem(Problem::Missing(_)) => true,
1121                MetricValue::Problem(ref problem) => return MetricValue::Problem(problem.clone()),
1122                _ => false,
1123            },
1124            MetricValue::Problem(problem) => return MetricValue::Problem(problem),
1125            _ => false,
1126        })
1127    }
1128
1129    // Returns Bool true if and only if the value for the given metric is any sort of Problem.
1130    fn is_problem(&self, namespace: &str, operands: &[ExpressionTree]) -> MetricValue {
1131        if operands.len() != 1 {
1132            return syntax_error(format!(
1133                "Wrong number of operands for Problem(): {}",
1134                operands.len()
1135            ));
1136        }
1137        let value = self.evaluate(namespace, &operands[0]);
1138        MetricValue::Bool(matches!(value, MetricValue::Problem(_)))
1139    }
1140}
1141
1142// The evaluation of math expressions is tested pretty exhaustively in parse.rs unit tests.
1143
1144// The use of metric names in expressions and actions, with and without namespaces, is tested in
1145// the integration test.
1146//   $ fx test triage_lib_test
1147
1148#[cfg(test)]
1149mod test {
1150    use super::*;
1151    use crate::config::{DiagnosticData, Source};
1152    use anyhow::Error;
1153    use std::sync::LazyLock;
1154
1155    #[macro_export]
1156    macro_rules! make_metrics {
1157    ({$($namespace: literal: {
1158            $(eval:  {$($ke: literal: $ve: expr),+ $(,)?})?
1159            $(hardcoded: {$($kh: literal: $vh: expr),+ $(,)?})?
1160            $(select: {$($ks: literal: [$($vs: expr),+ $(,)?]),+ $(,)?})?
1161        }),+ $(,)?}) => {{
1162        [$((
1163            $namespace.to_string(),
1164            [
1165                $(
1166                    $(
1167                        ($ke.to_string(),
1168                        ValueSource::try_from_expression_with_namespace($ve, $namespace)
1169                            .expect("Unable to parse expression as value source.")),
1170                    )+
1171                )?
1172                $(
1173                    $(
1174                        ($kh.to_string(),
1175                        ValueSource::new(Metric::Hardcoded($vh))),
1176                    )+
1177                )?
1178                $(
1179                    $(
1180                        ($ks.to_string(),
1181                        ValueSource::new(Metric::Selector(
1182                            [$($vs.clone(),)+].to_vec()))),
1183                    )+
1184                )?
1185            ]
1186            .into_iter().collect::<HashMap<String, ValueSource>>()
1187        )),+]
1188        .into_iter()
1189        .collect::<HashMap<String, HashMap<String, ValueSource>>>()
1190    }};
1191}
1192
1193    /// Problem should never equal anything, even an identical Problem. Code (tests) can use
1194    /// assert_problem!(MetricValue::Problem(_), "foo") to test error messages.
1195    #[macro_export]
1196    macro_rules! assert_problem {
1197        ($missing:expr, $message:expr) => {
1198            match $missing {
1199                MetricValue::Problem(problem) => assert_eq!(format!("{:?}", problem), $message),
1200                oops => {
1201                    // TODO: Add $missing to println, it currently returns a generics error.
1202                    println!("Non problem type {:?}", oops);
1203                    assert!(false, "Non-Problem type");
1204                }
1205            }
1206        };
1207    }
1208
1209    #[macro_export]
1210    macro_rules! assert_not_missing {
1211        ($not_missing:expr) => {
1212            match $not_missing {
1213                MetricValue::Problem(Problem::Missing(message)) => {
1214                    assert!(false, "Expected not missing, was: {}", &message)
1215                }
1216                _ => {}
1217            }
1218        };
1219    }
1220
1221    static EMPTY_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1222        let s = r#"[]"#;
1223        vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1224    });
1225    static NO_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1226        let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": null}]"#;
1227        vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1228    });
1229    static BAD_PAYLOAD_F: LazyLock<Vec<DiagnosticData>> = LazyLock::new(|| {
1230        let s = r#"[{"moniker": "abcd", "metadata": {}, "payload": ["a", "b"]}]"#;
1231        vec![DiagnosticData::new("i".to_string(), Source::Inspect, s.to_string()).unwrap()]
1232    });
1233    static EMPTY_FILE_FETCHER: LazyLock<FileDataFetcher<'static>> =
1234        LazyLock::new(|| FileDataFetcher::new(&EMPTY_F));
1235    static EMPTY_TRIAL_FETCHER: LazyLock<TrialDataFetcher<'static>> =
1236        LazyLock::new(TrialDataFetcher::new_empty);
1237    static NO_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1238        LazyLock::new(|| FileDataFetcher::new(&NO_PAYLOAD_F));
1239    static BAD_PAYLOAD_FETCHER: LazyLock<FileDataFetcher<'static>> =
1240        LazyLock::new(|| FileDataFetcher::new(&BAD_PAYLOAD_F));
1241
1242    #[fuchsia::test]
1243    fn focus_on_important_errors() {
1244        let metrics = HashMap::new();
1245        let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1246        let major = Problem::SyntaxError("Bad".to_string());
1247        let minor = Problem::Ignore(vec![Problem::Missing("Not a big deal".to_string())]);
1248        let major_arg = ExpressionTree::Value(MetricValue::Problem(major.clone()));
1249        let minor_arg = ExpressionTree::Value(MetricValue::Problem(minor));
1250        assert_problem!(
1251            state.apply_boolean_function(
1252                "",
1253                &|a, b| a == b,
1254                &[minor_arg.clone(), major_arg.clone()]
1255            ),
1256            "SyntaxError: Bad"
1257        );
1258        assert_problem!(
1259            state.apply_boolean_function("", &|a, b| a == b, &[major_arg, minor_arg]),
1260            "SyntaxError: Bad"
1261        );
1262    }
1263
1264    #[fuchsia::test]
1265    fn logs_work() -> Result<(), Error> {
1266        let syslog_text = "line 1\nline 2\nsyslog".to_string();
1267        let klog_text = "first line\nsecond line\nklog\n".to_string();
1268        let bootlog_text = "Yes there's a bootlog with one long line".to_string();
1269        let syslog = DiagnosticData::new("sys".to_string(), Source::Syslog, syslog_text)?;
1270        let klog = DiagnosticData::new("k".to_string(), Source::Klog, klog_text)?;
1271        let bootlog = DiagnosticData::new("boot".to_string(), Source::Bootlog, bootlog_text)?;
1272        let metrics = HashMap::new();
1273        let mut data = vec![klog, syslog, bootlog];
1274        let fetcher = FileDataFetcher::new(&data);
1275        let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1276        assert_eq!(state.evaluate_value("", r#"KlogHas("lin")"#), MetricValue::Bool(true));
1277        assert_eq!(state.evaluate_value("", r#"KlogHas("l.ne")"#), MetricValue::Bool(true));
1278        assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*ne")"#), MetricValue::Bool(true));
1279        assert_eq!(state.evaluate_value("", r#"KlogHas("fi.*sec")"#), MetricValue::Bool(false));
1280        assert_eq!(state.evaluate_value("", r#"KlogHas("first line")"#), MetricValue::Bool(true));
1281        // Full regex; even capture groups are allowed but the values can't be extracted.
1282        assert_eq!(
1283            state.evaluate_value("", r#"KlogHas("f(.)rst \bline")"#),
1284            MetricValue::Bool(true)
1285        );
1286        // Backreferences don't work; this is regex, not fancy_regex.
1287        assert_eq!(
1288            state.evaluate_value("", r#"KlogHas("f(.)rst \bl\1ne")"#),
1289            MetricValue::Bool(false)
1290        );
1291        assert_eq!(state.evaluate_value("", r#"KlogHas("second line")"#), MetricValue::Bool(true));
1292        assert_eq!(
1293            state.evaluate_value("", "KlogHas(\"second line\n\")"),
1294            MetricValue::Bool(false)
1295        );
1296        assert_eq!(state.evaluate_value("", r#"KlogHas("klog")"#), MetricValue::Bool(true));
1297        assert_eq!(state.evaluate_value("", r#"KlogHas("line 2")"#), MetricValue::Bool(false));
1298        assert_eq!(state.evaluate_value("", r#"SyslogHas("line 2")"#), MetricValue::Bool(true));
1299        assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1300        assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(true));
1301        assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1302        data.pop();
1303        let fetcher = FileDataFetcher::new(&data);
1304        let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1305        assert_eq!(state.evaluate_value("", r#"SyslogHas("syslog")"#), MetricValue::Bool(true));
1306        assert_eq!(state.evaluate_value("", r#"BootlogHas("bootlog")"#), MetricValue::Bool(false));
1307        assert_eq!(state.evaluate_value("", r#"BootlogHas("syslog")"#), MetricValue::Bool(false));
1308        Ok(())
1309    }
1310
1311    #[fuchsia::test]
1312    fn annotations_work() -> Result<(), Error> {
1313        let annotation_text = r#"{ "build.board": "chromebook-x64", "answer": 42 }"#.to_string();
1314        let annotations =
1315            DiagnosticData::new("a".to_string(), Source::Annotations, annotation_text)?;
1316        let metrics = HashMap::new();
1317        let data = vec![annotations];
1318        let fetcher = FileDataFetcher::new(&data);
1319        let state = MetricState::new(&metrics, Fetcher::FileData(fetcher), None);
1320        assert_eq!(
1321            state.evaluate_value("", "Annotation('build.board')"),
1322            MetricValue::String("chromebook-x64".to_string())
1323        );
1324        assert_eq!(state.evaluate_value("", "Annotation('answer')"), MetricValue::Int(42));
1325        assert_problem!(
1326            state.evaluate_value("", "Annotation('bogus')"),
1327            "Missing: Key 'bogus' not found in annotations"
1328        );
1329        assert_problem!(
1330            state.evaluate_value("", "Annotation('bogus', 'Double bogus')"),
1331            "SyntaxError: Annotation() needs 1 string argument"
1332        );
1333        assert_problem!(
1334            state.evaluate_value("", "Annotation(42)"),
1335            "ValueError: Annotation() needs a string argument"
1336        );
1337        Ok(())
1338    }
1339
1340    #[fuchsia::test]
1341    fn test_fetch_errors() {
1342        // Do not show errors when there is simply no payload.
1343        assert_eq!(0, NO_PAYLOAD_FETCHER.errors().len());
1344        assert_eq!(1, BAD_PAYLOAD_FETCHER.errors().len());
1345    }
1346
1347    // Correct operation of the klog, syslog, and bootlog fields of TrialDataFetcher are tested
1348    // in the integration test via log_tests.triage.
1349
1350    // Test evaluation on static values.
1351    #[fuchsia::test]
1352    fn test_evaluation() {
1353        let metrics = make_metrics!({
1354            "root":{
1355                eval: {"is42": "42", "isOk": "'OK'"}
1356                hardcoded: {"unhandled": unhandled_type("Unhandled")}
1357            }
1358        });
1359        let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1360
1361        // Can read a value.
1362        assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1363
1364        // Basic arithmetic
1365        assert_eq!(state.evaluate_value("root", "is42 + 1"), MetricValue::Int(43));
1366        assert_eq!(state.evaluate_value("root", "is42 - 1"), MetricValue::Int(41));
1367        assert_eq!(state.evaluate_value("root", "is42 * 2"), MetricValue::Int(84));
1368        // Automatic float conversion and truncating divide.
1369        assert_eq!(state.evaluate_value("root", "is42 / 4"), MetricValue::Float(10.5));
1370        assert_eq!(state.evaluate_value("root", "is42 // 4"), MetricValue::Int(10));
1371
1372        // Order of operations
1373        assert_eq!(
1374            state.evaluate_value("root", "is42 + 10 / 2 * 10 - 2 "),
1375            MetricValue::Float(90.0)
1376        );
1377        assert_eq!(state.evaluate_value("root", "is42 + 10 // 2 * 10 - 2 "), MetricValue::Int(90));
1378
1379        // Boolean
1380        assert_eq!(
1381            state.evaluate_value("root", "And(is42 == 42, is42 < 100)"),
1382            MetricValue::Bool(true)
1383        );
1384        assert_eq!(
1385            state.evaluate_value("root", "And(is42 == 42, is42 > 100)"),
1386            MetricValue::Bool(false)
1387        );
1388        assert_eq!(
1389            state.evaluate_value("root", "Or(is42 == 42, is42 > 100)"),
1390            MetricValue::Bool(true)
1391        );
1392        assert_eq!(
1393            state.evaluate_value("root", "Or(is42 != 42, is42 < 100)"),
1394            MetricValue::Bool(true)
1395        );
1396        assert_eq!(
1397            state.evaluate_value("root", "Or(is42 != 42, is42 > 100)"),
1398            MetricValue::Bool(false)
1399        );
1400        assert_eq!(state.evaluate_value("root", "Not(is42 == 42)"), MetricValue::Bool(false));
1401
1402        // Read strings
1403        assert_eq!(state.evaluate_value("root", "isOk"), MetricValue::String("OK".to_string()));
1404
1405        // Missing value
1406        assert_problem!(
1407            state.evaluate_value("root", "missing"),
1408            "SyntaxError: Metric 'missing' Not Found in 'root'"
1409        );
1410
1411        // Booleans short circuit
1412        assert_problem!(
1413            state.evaluate_value("root", "Or(is42 != 42, missing)"),
1414            "SyntaxError: Metric 'missing' Not Found in 'root'"
1415        );
1416        assert_eq!(
1417            state.evaluate_value("root", "Or(is42 == 42, missing)"),
1418            MetricValue::Bool(true)
1419        );
1420        assert_problem!(
1421            state.evaluate_value("root", "And(is42 == 42, missing)"),
1422            "SyntaxError: Metric 'missing' Not Found in 'root'"
1423        );
1424
1425        assert_eq!(
1426            state.evaluate_value("root", "And(is42 != 42, missing)"),
1427            MetricValue::Bool(false)
1428        );
1429
1430        // Missing() checks
1431        assert_eq!(state.evaluate_value("root", "Missing(is42)"), MetricValue::Bool(false));
1432        // An unknown variable is a SyntaxError, not a Missing(), and Missing() won't catch it.
1433        assert_problem!(
1434            state.evaluate_value("root", "Missing(not_found)"),
1435            "SyntaxError: Metric 'not_found' Not Found in 'root'"
1436        );
1437        assert_eq!(
1438            state.evaluate_value("root", "And(Not(Missing(is42)), is42 == 42)"),
1439            MetricValue::Bool(true)
1440        );
1441        assert_problem!(
1442            state.evaluate_value("root", "And(Not(Missing(not_found)), not_found == 'Hello')"),
1443            "SyntaxError: Metric 'not_found' Not Found in 'root'"
1444        );
1445        assert_eq!(
1446            state.evaluate_value("root", "Or(Missing(is42), is42 < 42)"),
1447            MetricValue::Bool(false)
1448        );
1449        assert_problem!(
1450            state.evaluate_value("root", "Or(Missing(not_found), not_found == 'Hello')"),
1451            "SyntaxError: Metric 'not_found' Not Found in 'root'"
1452        );
1453        assert_eq!(state.evaluate_value("root", "Missing([])"), MetricValue::Bool(true));
1454
1455        // UnhandledType() checks
1456        assert_eq!(state.evaluate_value("root", "UnhandledType(is42)"), MetricValue::Bool(false));
1457        // An unknown variable is a SyntaxError, not a Missing(), and Missing() won't catch it.
1458        assert_problem!(
1459            state.evaluate_value("root", "UnhandledType(not_found)"),
1460            "SyntaxError: Metric 'not_found' Not Found in 'root'"
1461        );
1462        assert_eq!(
1463            state.evaluate_value("root", "And(Not(UnhandledType(is42)), is42 == 42)"),
1464            MetricValue::Bool(true)
1465        );
1466        assert_eq!(
1467            state.evaluate_value("root", "UnhandledType(unhandled)"),
1468            MetricValue::Bool(true)
1469        );
1470        assert_eq!(state.evaluate_value("root", "UnhandledType([])"), MetricValue::Bool(false));
1471
1472        // Problem() checks
1473        assert_eq!(state.evaluate_value("root", "Problem(is42)"), MetricValue::Bool(false));
1474        // An unknown variable is a SyntaxError, not a Missing()
1475        assert_eq!(state.evaluate_value("root", "Problem(not_found)"), MetricValue::Bool(true));
1476        assert_eq!(
1477            state.evaluate_value("root", "And(Not(Problem(is42)), is42 == 42)"),
1478            MetricValue::Bool(true)
1479        );
1480        assert_eq!(
1481            state.evaluate_value("root", "And(Not(Problem(not_found)), not_found == 'Hello')"),
1482            MetricValue::Bool(false)
1483        );
1484        assert_eq!(
1485            state.evaluate_value("root", "Or(Problem(is42), is42 < 42)"),
1486            MetricValue::Bool(false)
1487        );
1488        assert_eq!(
1489            state.evaluate_value("root", "Or(Problem(not_found), not_found == 'Hello')"),
1490            MetricValue::Bool(true)
1491        );
1492        assert_problem!(
1493            state.evaluate_value("root", "Or(not_found == 'Hello', Problem(not_found))"),
1494            "SyntaxError: Metric 'not_found' Not Found in 'root'"
1495        );
1496
1497        // Ensure evaluation for action converts vector values.
1498        assert_eq!(
1499            state.evaluate_value("root", "[0==0]"),
1500            MetricValue::Vector(vec![MetricValue::Bool(true)])
1501        );
1502        assert_eq!(
1503            state.eval_action_metric(
1504                "root",
1505                &ValueSource::try_from_expression_with_namespace("[0==0]", "root").unwrap()
1506            ),
1507            MetricValue::Bool(true)
1508        );
1509
1510        assert_eq!(
1511            state.evaluate_value("root", "[0==0, 0==0]"),
1512            MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1513        );
1514        assert_eq!(
1515            state.eval_action_metric(
1516                "root",
1517                &ValueSource::try_from_expression_with_namespace("[0==0, 0==0]", "root").unwrap()
1518            ),
1519            MetricValue::Vector(vec![MetricValue::Bool(true), MetricValue::Bool(true)])
1520        );
1521
1522        // Test regex operations
1523        assert_eq!(
1524            state.eval_action_metric(
1525                "root",
1526                &ValueSource::try_from_expression_with_namespace(
1527                    "StringMatches('abcd', '^a.c')",
1528                    "root"
1529                )
1530                .unwrap()
1531            ),
1532            MetricValue::Bool(true)
1533        );
1534        assert_eq!(
1535            state.eval_action_metric(
1536                "root",
1537                &ValueSource::try_from_expression_with_namespace(
1538                    "StringMatches('abcd', 'a.c$')",
1539                    "root"
1540                )
1541                .unwrap()
1542            ),
1543            MetricValue::Bool(false)
1544        );
1545        assert_problem!(
1546            state.eval_action_metric(
1547                "root",
1548                &ValueSource::try_from_expression_with_namespace(
1549                    "StringMatches('abcd', '[[')",
1550                    "root"
1551                )
1552                .unwrap()
1553            ),
1554            "SyntaxError: Could not parse `[[` as regex"
1555        );
1556    }
1557
1558    // Test caching after evaluating static values
1559    #[fuchsia::test]
1560    fn test_caching_after_evaluation() {
1561        let metrics = make_metrics!({
1562            "root":{
1563                eval: {"is42": "42", "is43": "is42 + 1"}
1564                hardcoded: {"unhandled": unhandled_type("Unhandled")}
1565            }
1566        });
1567
1568        let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1569        let trial_state =
1570            MetricState::new(&metrics, Fetcher::TrialData(EMPTY_TRIAL_FETCHER.clone()), None);
1571
1572        // Test correct initialization of RefCells
1573        assert_eq!(
1574            *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1575            None
1576        );
1577        assert_eq!(
1578            *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1579            None
1580        );
1581        assert_eq!(
1582            *state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow(),
1583            None
1584        );
1585        assert_eq!(
1586            *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1587            None
1588        );
1589        assert_eq!(
1590            *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1591            None
1592        );
1593        assert_eq!(
1594            *trial_state
1595                .metrics
1596                .get("root")
1597                .unwrap()
1598                .get("unhandled")
1599                .unwrap()
1600                .cached_value
1601                .borrow(),
1602            None
1603        );
1604
1605        // Test correct caching of values after evaluation
1606        // Evaluating single metric
1607        assert_eq!(state.evaluate_value("root", "is42"), MetricValue::Int(42));
1608        assert_eq!(
1609            *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1610            Some(MetricValue::Int(42))
1611        );
1612        assert_eq!(trial_state.evaluate_value("root", "is42"), MetricValue::Int(42));
1613        assert_eq!(
1614            *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1615            Some(MetricValue::Int(42))
1616        );
1617
1618        // Evaluating metric with a nested metric
1619        // Ensure the previous metric cached value is not modified and new metric is
1620        // correctly stored
1621        assert_eq!(state.evaluate_value("root", "is43"), MetricValue::Int(43));
1622        assert_eq!(
1623            *state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1624            Some(MetricValue::Int(42))
1625        );
1626        assert_eq!(
1627            *state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1628            Some(MetricValue::Int(43))
1629        );
1630        assert_eq!(trial_state.evaluate_value("root", "is43"), MetricValue::Int(43));
1631        assert_eq!(
1632            *trial_state.metrics.get("root").unwrap().get("is42").unwrap().cached_value.borrow(),
1633            Some(MetricValue::Int(42))
1634        );
1635        assert_eq!(
1636            *trial_state.metrics.get("root").unwrap().get("is43").unwrap().cached_value.borrow(),
1637            Some(MetricValue::Int(43))
1638        );
1639
1640        // Evaluating Hardcoded Metric and ensuring correct caching behavior
1641        state.evaluate_value("root", "unhandled");
1642        assert_problem!(
1643            (*state.metrics.get("root").unwrap().get("unhandled").unwrap().cached_value.borrow())
1644                .as_ref()
1645                .unwrap(),
1646            "UnhandledType: Unhandled"
1647        );
1648        trial_state.evaluate_value("root", "unhandled");
1649        assert_problem!(
1650            (*trial_state
1651                .metrics
1652                .get("root")
1653                .unwrap()
1654                .get("unhandled")
1655                .unwrap()
1656                .cached_value
1657                .borrow())
1658            .as_ref()
1659            .unwrap(),
1660            "UnhandledType: Unhandled"
1661        );
1662    }
1663
1664    macro_rules! eval {
1665        ($e:expr) => {
1666            MetricState::evaluate_math($e)
1667        };
1668    }
1669
1670    // TODO(https://fxbug.dev/42136933): Modify or probably delete this function after better error design.
1671    #[fuchsia::test]
1672    fn test_missing_hacks() -> Result<(), Error> {
1673        assert_eq!(eval!("Missing(2>'a')"), MetricValue::Bool(true));
1674        assert_eq!(eval!("Missing([])"), MetricValue::Bool(true));
1675        assert_eq!(eval!("Missing([2>'a'])"), MetricValue::Bool(true));
1676        assert_eq!(eval!("Missing([2>'a', 2>'a'])"), MetricValue::Bool(false));
1677        assert_eq!(eval!("Missing([2>1])"), MetricValue::Bool(false));
1678        assert_eq!(eval!("Or(Missing(2>'a'), 2>'a')"), MetricValue::Bool(true));
1679        Ok(())
1680    }
1681
1682    #[fuchsia::test]
1683    fn test_ignores_in_expressions() {
1684        let dbz = "ValueError: Division by zero";
1685        assert_problem!(eval!("CountProperties([1, 2, 3/0])"), dbz);
1686        assert_problem!(eval!("CountProperties([1/0, 2, 3/0])"), dbz);
1687        assert_problem!(eval!("CountProperties([1/?0, 2, 3/?0])"), format!("Ignore: {}", dbz));
1688        assert_problem!(eval!("CountProperties([1/0, 2, 3/?0])"), dbz);
1689        // And() short-circuits so it will only see the first error
1690        assert_problem!(eval!("And(1 > 0, 3/0 > 0)"), dbz);
1691        assert_problem!(eval!("And(1/0 > 0, 3/0 > 0)"), dbz);
1692        assert_problem!(eval!("And(1/?0 > 0, 3/?0 > 0)"), format!("Ignore: {}", dbz));
1693        assert_problem!(eval!("And(1/?0 > 0, 3/0 > 0)"), format!("Ignore: {}", dbz));
1694        assert_problem!(eval!("1 == 3/0"), dbz);
1695        assert_problem!(eval!("1/0 == 3/0"), dbz);
1696        assert_problem!(eval!("1/?0 == 3/?0"), format!("Ignore: {}", dbz));
1697        assert_problem!(eval!("1/?0 == 3/0"), dbz);
1698        assert_problem!(eval!("1 + 3/0"), dbz);
1699        assert_problem!(eval!("1/0 + 3/0"), dbz);
1700        assert_problem!(eval!("1/?0 + 3/?0"), format!("Ignore: {}", dbz));
1701        assert_problem!(eval!("1/?0 + 3/0"), dbz);
1702    }
1703
1704    /// Make sure that checked divide doesn't hide worse errors in its arguments
1705    #[fuchsia::test]
1706    fn test_checked_divide_preserves_errors() {
1707        let san = "Missing: String(a) not numeric";
1708        assert_problem!(eval!("(1+'a')/1"), san);
1709        assert_problem!(eval!("(1+'a')/?1"), san);
1710        assert_problem!(eval!("1/?(1+'a')"), san);
1711        assert_problem!(eval!("(1+'a')/?(1+'a')"), san);
1712        // If the numerator has a Problem and the denominator doesn't, it won't get to the divide
1713        // logic, so both / and /? won't observe the "division by zero"
1714        assert_problem!(eval!("(1+'a')/0"), san);
1715        assert_problem!(eval!("(1+'a')/?0"), san);
1716        assert_problem!(eval!("(1+'a')//1"), san);
1717        assert_problem!(eval!("(1+'a')//?1"), san);
1718        assert_problem!(eval!("1//?(1+'a')"), san);
1719        assert_problem!(eval!("(1+'a')//?(1+'a')"), san);
1720        assert_problem!(eval!("(1+'a')//0"), san);
1721        assert_problem!(eval!("(1+'a')//?0"), san);
1722    }
1723
1724    #[fuchsia::test]
1725    fn test_time() -> Result<(), Error> {
1726        let metrics = Metrics::new();
1727        let files = vec![];
1728        let state_1234 =
1729            MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), Some(1234));
1730        let state_missing =
1731            MetricState::new(&metrics, Fetcher::FileData(FileDataFetcher::new(&files)), None);
1732        let now_expression = parse::parse_expression("Now()", /*namespace= */ "").unwrap();
1733        assert_problem!(MetricState::evaluate_math("Now()"), "Missing: No valid time available");
1734        assert_eq!(state_1234.evaluate_expression(&now_expression), MetricValue::Int(1234));
1735        assert_problem!(
1736            state_missing.evaluate_expression(&now_expression),
1737            "Missing: No valid time available"
1738        );
1739        Ok(())
1740    }
1741
1742    #[fuchsia::test]
1743    fn test_expression_context() {
1744        // Check correct error behavior when building expression from selector string
1745        let selector_expr = "INSPECT:foo:bar:baz";
1746        assert_eq!(
1747            format!(
1748                "{:?}",
1749                ExpressionContext::try_from_expression_with_default_namespace(selector_expr)
1750                    .err()
1751                    .unwrap()
1752            ),
1753            "Expression Error: \n0: at line 1, in Eof:\nINSPECT:foo:bar:baz\n       ^\n\n"
1754        );
1755
1756        // Check correct error behavior when building expression from invalid expression string
1757        let invalid_expr = "1 *";
1758        assert_eq!(
1759            format!(
1760                "{:?}",
1761                ExpressionContext::try_from_expression_with_default_namespace(invalid_expr)
1762                    .err()
1763                    .unwrap()
1764            ),
1765            concat!("Expression Error: \n0: at line 1, in Eof:\n1 *\n  ^\n\n")
1766        );
1767
1768        // Check expression correctly built from valid expression
1769        let valid_expr = "42 + 1";
1770        let parsed_expression = parse::parse_expression(valid_expr, "").unwrap();
1771        assert_eq!(
1772            ExpressionContext::try_from_expression_with_default_namespace(valid_expr).unwrap(),
1773            ExpressionContext { raw_expression: valid_expr.to_string(), parsed_expression }
1774        );
1775    }
1776
1777    #[fuchsia::test]
1778    fn test_not_valid_cycle_same_variable() {
1779        let metrics = make_metrics!({
1780            "root":{
1781                eval: {
1782                    "is42": "42",
1783                    "shouldBe42": "is42 + 0",
1784                }
1785            },
1786            "n2":{
1787                eval: {
1788                    "is42": "root::shouldBe42"
1789                }
1790            }
1791        });
1792        let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1793
1794        // Evaluation with the same variable name but different namespace should be successful.
1795        assert_eq!(state.evaluate_value("n2", "is42"), MetricValue::Int(42));
1796    }
1797
1798    #[fuchsia::test]
1799    fn test_cycle_detected_correctly() {
1800        // Cycle is between n2::a -> n2::c -> root::b -> n2::a
1801        let metrics = make_metrics!({
1802            "root":{
1803                eval: {
1804                    "is42": "42",
1805                    "shouldBe62": "is42 + 1 + n2::is19",
1806                    "b": "is42 + n2::a"
1807                }
1808            },
1809            "n2":{
1810                eval: {
1811                    "is19": "19",
1812                    "shouldBe44": "root::is42 + 2",
1813                    "a": "is19 + c",
1814                    "c": "root::b + root::is42"
1815                }
1816            }
1817        });
1818
1819        let state = MetricState::new(&metrics, Fetcher::FileData(EMPTY_FILE_FETCHER.clone()), None);
1820
1821        // Evaluation when a cycle is not encountered should be successful.
1822        assert_eq!(state.evaluate_value("root", "shouldBe62 + 1"), MetricValue::Int(63));
1823
1824        // Check evaluation with a cycle leads to an 'EvaluationError'.
1825        assert_problem!(
1826            state.evaluate_value("root", "n2::a"),
1827            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1828        );
1829
1830        // Check if the stack was reset properly after problem
1831        assert_eq!(state.evaluate_value("root", "n2::shouldBe44"), MetricValue::Int(44));
1832
1833        // Check evaluation with a cycle leads to an 'EvaluationError'.
1834        assert_problem!(
1835            state.evaluate_value("root", "b"),
1836            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1837        );
1838
1839        // Check evaluation with a cycle leads to an 'EvaluationError'.
1840        assert_problem!(
1841            state.evaluate_value("root", "n2::c"),
1842            "EvaluationError: Cycle encountered while evaluating variable n2::a in the expression"
1843        );
1844
1845        // Check if the stack was reset properly after problem
1846        assert_eq!(state.evaluate_value("root", "shouldBe62"), MetricValue::Int(62));
1847    }
1848    // Correct operation of annotations is tested via annotation_tests.triage.
1849}