clitest_lib/
output.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::{Arc, Mutex, OnceLock},
4};
5
6use grok::Grok;
7use serde::Serialize;
8
9use crate::script::{IfCondition, ScriptLocation, ScriptRunContext};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Line {
13    pub number: usize,
14    pub text: String,
15}
16
17#[derive(Clone)]
18pub struct Lines {
19    lines: Arc<Vec<String>>,
20    current_line: usize,
21    ignored_patterns: OutputPatterns,
22    negative_disabled: bool,
23    rejected_patterns: OutputPatterns,
24}
25
26impl std::fmt::Debug for Lines {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        write!(
29            f,
30            "Lines {{ ignored: {} pattern(s), rejected: {} pattern(s) }}",
31            self.ignored_patterns.len(),
32            self.rejected_patterns.len()
33        )
34    }
35}
36
37impl<'s> IntoIterator for &'s Lines {
38    type Item = &'s String;
39    type IntoIter = std::slice::Iter<'s, String>;
40
41    fn into_iter(self) -> Self::IntoIter {
42        self.lines.iter()
43    }
44}
45
46impl std::fmt::Display for Lines {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "{}", self.lines[self.current_line..].join("\n"))
49    }
50}
51
52impl Lines {
53    pub fn new(lines: Vec<String>) -> Self {
54        Self {
55            lines: Arc::new(lines),
56            current_line: 0,
57            ignored_patterns: Default::default(),
58            negative_disabled: false,
59            rejected_patterns: Default::default(),
60        }
61    }
62
63    pub fn is_exhausted(&self) -> bool {
64        self.current_line >= self.lines.len()
65    }
66
67    pub fn next(
68        &self,
69        context: OutputMatchContext,
70    ) -> Result<(Option<Line>, Lines), OutputPatternMatchFailure> {
71        let mut next = self.clone();
72        'outer: while next.current_line < next.lines.len() {
73            if !self.negative_disabled {
74                let ignore_check = next.without_negatives();
75                for ignored_pattern in &*next.ignored_patterns {
76                    if let Ok(next_next) =
77                        ignored_pattern.matches(context.ignore(), ignore_check.clone())
78                    {
79                        next = next_next.with_negatives();
80                        continue 'outer;
81                    }
82                }
83                for rejected_pattern in &*next.rejected_patterns {
84                    if let Ok(_) = rejected_pattern.matches(context.ignore(), ignore_check.clone())
85                    {
86                        return Err(OutputPatternMatchFailure {
87                            location: rejected_pattern.location.clone(),
88                            pattern_type: "reject",
89                            output_line: None,
90                        });
91                    }
92                }
93            }
94            let line = Line {
95                number: next.current_line,
96                text: next.lines[next.current_line].clone(),
97            };
98            next.current_line += 1;
99            return Ok((Some(line), next));
100        }
101        Ok((None, next))
102    }
103
104    pub fn with_ignore(&self, ignore: &OutputPatterns) -> Self {
105        let mut ignored_patterns = self.ignored_patterns.clone();
106        ignored_patterns.extend(&ignore);
107        Self {
108            ignored_patterns,
109            ..self.clone()
110        }
111    }
112
113    pub fn with_reject(&self, reject: &OutputPatterns) -> Self {
114        let mut rejected_patterns = self.rejected_patterns.clone();
115        rejected_patterns.extend(&reject);
116        Self {
117            rejected_patterns,
118            ..self.clone()
119        }
120    }
121
122    fn without_negatives(&self) -> Self {
123        Self {
124            negative_disabled: true,
125            ..self.clone()
126        }
127    }
128
129    fn with_negatives(&self) -> Self {
130        Self {
131            negative_disabled: false,
132            ..self.clone()
133        }
134    }
135
136    pub fn into_inner(self) -> Vec<String> {
137        Arc::unwrap_or_clone(self.lines).split_off(self.current_line)
138    }
139
140    pub fn is_empty(&self) -> bool {
141        self.lines.is_empty()
142    }
143}
144
145#[derive(Clone, Default, Debug, Serialize)]
146
147pub struct OutputPatterns {
148    patterns: Arc<Vec<OutputPattern>>,
149}
150
151impl OutputPatterns {
152    pub fn new(patterns: Vec<OutputPattern>) -> Self {
153        Self {
154            patterns: Arc::new(patterns),
155        }
156    }
157
158    pub fn is_empty(&self) -> bool {
159        self.patterns.is_empty()
160    }
161
162    pub fn len(&self) -> usize {
163        self.patterns.len()
164    }
165
166    pub fn extend(&mut self, patterns: &OutputPatterns) {
167        if self.is_empty() {
168            self.patterns = patterns.patterns.clone();
169            return;
170        }
171        let new_patterns = std::mem::take(&mut self.patterns);
172        let mut new_patterns = Arc::unwrap_or_clone(new_patterns);
173        new_patterns.extend(patterns.patterns.iter().cloned());
174        self.patterns = Arc::new(new_patterns);
175    }
176}
177
178impl std::ops::Deref for OutputPatterns {
179    type Target = Vec<OutputPattern>;
180    fn deref(&self) -> &Self::Target {
181        &*self.patterns
182    }
183}
184
185#[derive(Clone)]
186pub struct OutputPattern {
187    pub location: ScriptLocation,
188    pub pattern: OutputPatternType,
189    pub ignore: OutputPatterns,
190    pub reject: OutputPatterns,
191}
192
193impl Serialize for OutputPattern {
194    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195    where
196        S: serde::Serializer,
197    {
198        self.pattern.serialize(serializer)
199    }
200}
201
202impl std::fmt::Debug for OutputPattern {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "{:?}", self.pattern)
205    }
206}
207
208impl OutputPattern {
209    pub fn new_sequence(location: ScriptLocation, mut patterns: Vec<OutputPattern>) -> Self {
210        if patterns.len() == 1 {
211            patterns.remove(0)
212        } else {
213            Self {
214                pattern: OutputPatternType::Sequence(patterns),
215                ignore: Default::default(),
216                reject: Default::default(),
217                location: location.clone(),
218            }
219        }
220    }
221
222    pub fn prepare(&self, grok: &Grok) -> Result<(), OutputPatternPrepareError> {
223        for pattern in &*self.ignore.patterns {
224            pattern.prepare(grok)?
225        }
226        for pattern in &*self.reject.patterns {
227            pattern.prepare(grok)?
228        }
229        match &self.pattern {
230            OutputPatternType::Pattern(pattern) => {
231                pattern
232                    .prepare(grok)
233                    .map_err(|e| OutputPatternPrepareError {
234                        location: self.location.clone(),
235                        pattern: pattern.pattern.clone(),
236                        error: e,
237                    })?
238            }
239            OutputPatternType::Sequence(patterns) => {
240                for pattern in patterns {
241                    pattern.prepare(grok)?;
242                }
243            }
244            OutputPatternType::Unordered(patterns) => {
245                for pattern in patterns {
246                    pattern.prepare(grok)?;
247                }
248            }
249            OutputPatternType::Choice(patterns) => {
250                for pattern in patterns {
251                    pattern.prepare(grok)?;
252                }
253            }
254            OutputPatternType::If(_, pattern) => pattern.prepare(grok)?,
255            OutputPatternType::Any(pattern) => pattern.prepare(grok)?,
256            OutputPatternType::Repeat(pattern) => pattern.prepare(grok)?,
257            OutputPatternType::Optional(pattern) => pattern.prepare(grok)?,
258            OutputPatternType::Literal(_) => {}
259            OutputPatternType::End | OutputPatternType::None => {}
260        }
261        Ok(())
262    }
263
264    pub fn matches(
265        &self,
266        context: OutputMatchContext,
267        output: Lines,
268    ) -> Result<Lines, OutputPatternMatchFailure> {
269        if self.ignore.is_empty() && self.reject.is_empty() {
270            self.pattern.matches(&self.location, context, output)
271        } else {
272            let output = output.with_ignore(&self.ignore).with_reject(&self.reject);
273            self.pattern.matches(&self.location, context, output)
274        }
275    }
276
277    /// The minimum number of lines this pattern will match.
278    pub fn min_matches(&self) -> usize {
279        self.pattern.min_matches()
280    }
281
282    /// The maximum number of lines this pattern will match (or usize::MAX if unbounded).
283    pub fn max_matches(&self) -> usize {
284        self.pattern.max_matches()
285    }
286}
287
288#[derive(thiserror::Error, Debug)]
289#[error("pattern {pattern} at line {location} failed to compile: {error}")]
290pub struct OutputPatternPrepareError {
291    pub location: ScriptLocation,
292    pub pattern: String,
293    pub error: grok::Error,
294}
295
296#[derive(Clone)]
297pub enum OutputPatternType {
298    /// The end of the output
299    End,
300    /// Matches no lines of output, always succeeds
301    None,
302    /// Any lines, followed by a pattern.
303    Any(Box<OutputPattern>),
304    /// A literal string
305    Literal(String),
306    /// A grok pattern
307    Pattern(Arc<GrokPattern>),
308    /// A pattern that matches one or more of the given pattern
309    Repeat(Box<OutputPattern>),
310    /// A pattern that matches zero or one of the given pattern
311    Optional(Box<OutputPattern>),
312    /// A pattern that all of its subpatterns, but in any order
313    Unordered(Vec<OutputPattern>),
314    /// A pattern that matches one of the given patterns
315    Choice(Vec<OutputPattern>),
316    /// A pattern that matches a sequence of patterns
317    Sequence(Vec<OutputPattern>),
318    /// A pattern that matches a condition
319    If(IfCondition, Box<OutputPattern>),
320}
321
322impl Serialize for OutputPatternType {
323    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
324    where
325        S: serde::Serializer,
326    {
327        match self {
328            OutputPatternType::Literal(literal) => {
329                serializer.serialize_str(&format!("! {literal}"))
330            }
331            OutputPatternType::Pattern(pattern) => {
332                serializer.serialize_str(&format!("? {}", pattern.pattern))
333            }
334            OutputPatternType::Repeat(pattern) => {
335                HashMap::from([("repeat", &pattern)]).serialize(serializer)
336            }
337            OutputPatternType::Optional(pattern) => {
338                HashMap::from([("optional", &pattern)]).serialize(serializer)
339            }
340            OutputPatternType::Unordered(patterns) => {
341                HashMap::from([("unordered", &patterns)]).serialize(serializer)
342            }
343            OutputPatternType::Choice(patterns) => {
344                HashMap::from([("choice", &patterns)]).serialize(serializer)
345            }
346            OutputPatternType::Sequence(patterns) => {
347                HashMap::from([("sequence", &patterns)]).serialize(serializer)
348            }
349            OutputPatternType::Any(pattern) => {
350                HashMap::from([("any", &pattern)]).serialize(serializer)
351            }
352            OutputPatternType::If(condition, pattern) => {
353                #[derive(Serialize)]
354                struct If<'a> {
355                    condition: &'a IfCondition,
356                    pattern: &'a OutputPattern,
357                }
358                If { condition, pattern }.serialize(serializer)
359            }
360            OutputPatternType::End => serializer.serialize_str("end"),
361            OutputPatternType::None => serializer.serialize_str("none"),
362        }
363    }
364}
365
366impl OutputPatternType {
367    /// The minimum number of lines this pattern will match.
368    pub fn min_matches(&self) -> usize {
369        match self {
370            OutputPatternType::None => 0,
371            OutputPatternType::Literal(_) => 1,
372            OutputPatternType::Pattern(_) => 1,
373            OutputPatternType::Repeat(pattern) => pattern.min_matches(),
374            OutputPatternType::Optional(_) => 0,
375            OutputPatternType::Unordered(patterns) => {
376                patterns.iter().map(|p| p.min_matches()).sum()
377            }
378            OutputPatternType::Choice(patterns) => {
379                patterns.iter().map(|p| p.min_matches()).min().unwrap_or(0)
380            }
381            OutputPatternType::Sequence(patterns) => patterns.iter().map(|p| p.min_matches()).sum(),
382            OutputPatternType::Any(pattern) => pattern.min_matches(),
383            OutputPatternType::If(_, _) => 0,
384            OutputPatternType::End => 0,
385        }
386    }
387
388    /// The maximum number of lines this pattern will match (or usize::MAX if unbounded).
389    pub fn max_matches(&self) -> usize {
390        fn saturating_iter_sum<I>(iter: I) -> usize
391        where
392            I: IntoIterator<Item = usize>,
393        {
394            iter.into_iter()
395                .reduce(|n, i| n.saturating_add(i))
396                .unwrap_or(0)
397        }
398
399        match self {
400            OutputPatternType::None => 0,
401            OutputPatternType::Literal(_) => 1,
402            OutputPatternType::Pattern(_) => 1,
403            OutputPatternType::Repeat(pattern) => {
404                if pattern.max_matches() == 0 {
405                    0
406                } else {
407                    usize::MAX
408                }
409            }
410            OutputPatternType::Optional(pattern) => pattern.max_matches(),
411            OutputPatternType::Unordered(patterns) => {
412                saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
413            }
414            OutputPatternType::Choice(patterns) => {
415                patterns.iter().map(|p| p.max_matches()).max().unwrap_or(0)
416            }
417            OutputPatternType::Sequence(patterns) => {
418                saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
419            }
420            OutputPatternType::Any(_) => usize::MAX,
421            OutputPatternType::If(_, pattern) => pattern.max_matches(),
422            OutputPatternType::End => 0,
423        }
424    }
425}
426
427impl Default for OutputPatternType {
428    fn default() -> Self {
429        Self::Sequence(vec![])
430    }
431}
432
433impl std::fmt::Debug for OutputPatternType {
434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435        match self {
436            OutputPatternType::Literal(literal) => write!(f, "Literal({literal})"),
437            OutputPatternType::Pattern(pattern) => write!(f, "Pattern({pattern:?})"),
438            OutputPatternType::Repeat(pattern) => write!(f, "Repeat({pattern:?})"),
439            OutputPatternType::Optional(pattern) => write!(f, "Optional({pattern:?})"),
440            OutputPatternType::Unordered(patterns) => write!(f, "Unordered({patterns:?})"),
441            OutputPatternType::Choice(patterns) => write!(f, "Choice({patterns:?})"),
442            OutputPatternType::Sequence(patterns) => write!(f, "Sequence({patterns:?})"),
443            OutputPatternType::Any(until) => write!(f, "Any({until:?})"),
444            OutputPatternType::If(condition, pattern) => {
445                write!(f, "If({condition:?}, {pattern:?})")
446            }
447            OutputPatternType::End => write!(f, "End"),
448            OutputPatternType::None => write!(f, "None"),
449        }
450    }
451}
452
453#[derive(Serialize, derive_more::Debug)]
454#[debug("/{pattern:?}/")]
455pub struct GrokPattern {
456    pattern: String,
457    aliases: Vec<String>,
458    #[serde(skip)]
459    grok: OnceLock<grok::Pattern>,
460}
461
462impl GrokPattern {
463    pub fn compile(line: &str, escape_non_grok: bool) -> Result<Self, String> {
464        use grok::parser::GrokPatternError;
465        let mut test_pattern = String::new();
466        let mut final_pattern = String::new();
467        let mut aliases = vec![];
468        for bit in grok::parser::grok_split(line) {
469            match bit {
470                grok::parser::GrokComponent::RegularExpression { string, .. } => {
471                    if escape_non_grok {
472                        for char in string.chars() {
473                            if char.is_ascii() && !char.is_alphanumeric() {
474                                test_pattern.push('\\');
475                                test_pattern.push(char);
476                                final_pattern.push('\\');
477                                final_pattern.push(char);
478                            } else {
479                                test_pattern.push(char);
480                                final_pattern.push(char);
481                            }
482                        }
483                    } else {
484                        test_pattern.push_str(string);
485                        final_pattern.push_str(string);
486                    }
487                }
488                grok::parser::GrokComponent::GrokPattern { pattern, alias, .. } => {
489                    test_pattern.push_str(".");
490                    final_pattern.push_str(pattern);
491                    if !alias.is_empty() {
492                        aliases.push(alias.to_string());
493                    }
494                }
495                grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidCharacter(
496                    c,
497                )) => {
498                    return Err(format!("Invalid character in pattern: {:?}", c));
499                }
500                grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidPattern) => {
501                    return Err("Invalid grok pattern".to_string());
502                }
503                grok::parser::GrokComponent::PatternError(
504                    GrokPatternError::InvalidPatternDefinition,
505                ) => {
506                    return Err("Invalid grok pattern definition".to_string());
507                }
508            }
509        }
510
511        test_pattern.push('$');
512        final_pattern.push('$');
513
514        _ = Grok::empty()
515            .compile(&test_pattern, false)
516            .map_err(|e| e.to_string())?;
517
518        Ok(Self {
519            pattern: final_pattern,
520            aliases,
521            grok: OnceLock::new(),
522        })
523    }
524
525    pub fn prepare(&self, grok: &Grok) -> Result<(), grok::Error> {
526        // This could technically suffer from multiple init, but they should
527        // always initialize the same way.
528        if self.grok.get().is_none() {
529            let pattern = grok.compile(&self.pattern, false)?;
530            self.grok.get_or_init(move || pattern);
531        }
532        Ok(())
533    }
534
535    pub fn matches<'a>(&'a self, text: &'a str) -> Option<grok::Matches<'a>> {
536        let pattern_ref = self.grok.get().expect("grok pattern not compiled");
537        return pattern_ref.match_against(text);
538    }
539}
540
541#[derive(Clone, Debug, thiserror::Error, derive_more::Display, PartialEq, Eq)]
542#[display("pattern {pattern_type} at line {location} does not match output line {:?}", output_line.as_ref().map(|l| l.text.clone()).unwrap_or("<eof>".to_string()))]
543pub struct OutputPatternMatchFailure {
544    pub location: ScriptLocation,
545    pub pattern_type: &'static str,
546    pub output_line: Option<Line>,
547}
548
549#[derive(Debug, Clone)]
550pub struct OutputMatchContext<'s> {
551    depth: usize,
552    trace: Arc<Mutex<Vec<String>>>,
553    ignore: bool,
554    expectations: Arc<Mutex<HashMap<String, String>>>,
555    script_context: &'s ScriptRunContext,
556}
557
558impl<'s> OutputMatchContext<'s> {
559    pub fn new(script_context: &'s ScriptRunContext) -> Self {
560        Self {
561            depth: 0,
562            trace: Default::default(),
563            ignore: false,
564            script_context,
565            expectations: Default::default(),
566        }
567    }
568
569    pub fn descend(&self) -> Self {
570        Self {
571            depth: self.depth + 1,
572            trace: self.trace.clone(),
573            ignore: self.ignore,
574            script_context: self.script_context,
575            expectations: self.expectations.clone(),
576        }
577    }
578
579    pub fn ignore(&self) -> Self {
580        Self {
581            depth: self.depth,
582            trace: self.trace.clone(),
583            ignore: true,
584            script_context: self.script_context,
585            expectations: self.expectations.clone(),
586        }
587    }
588
589    pub fn trace(&self, line: &str) {
590        let ignore = if self.ignore { "-" } else { "" };
591        self.trace.lock().unwrap().push(format!(
592            "{:indent$}{ignore}{}",
593            "",
594            line,
595            indent = self.depth * 2
596        ));
597    }
598
599    pub fn traces(&self) -> Vec<String> {
600        std::mem::take(&mut self.trace.lock().unwrap())
601    }
602
603    pub fn expect(&self, key: &str, value: String) {
604        self.expectations
605            .lock()
606            .unwrap()
607            .insert(key.to_string(), value);
608    }
609
610    pub fn expects(&self) -> HashMap<String, String> {
611        self.expectations.lock().unwrap().clone()
612    }
613}
614
615impl OutputPatternType {
616    pub fn matches<'s>(
617        &self,
618        location: &ScriptLocation,
619        context: OutputMatchContext,
620        mut output: Lines,
621    ) -> Result<Lines, OutputPatternMatchFailure> {
622        context.trace(&format!("matching {:?}", self));
623        match self {
624            OutputPatternType::None => Ok(output),
625            OutputPatternType::Literal(literal) => {
626                let (line, next) = output.next(context.clone())?;
627                if let Some(line) = line {
628                    if &line.text.trim_end() == literal {
629                        context.trace(&format!("literal match: {:?} == {literal:?}", line.text));
630                        Ok(next)
631                    } else {
632                        context.trace(&format!(
633                            "literal FAILED match: {:?} == {literal:?}",
634                            line.text
635                        ));
636                        Err(OutputPatternMatchFailure {
637                            location: location.clone(),
638                            pattern_type: "literal",
639                            output_line: Some(line),
640                        })
641                    }
642                } else {
643                    Err(OutputPatternMatchFailure {
644                        location: location.clone(),
645                        pattern_type: "literal",
646                        output_line: None,
647                    })
648                }
649            }
650            OutputPatternType::Pattern(pattern) => {
651                let (line, next) = output.next(context.clone())?;
652                if let Some(line) = line {
653                    let text = line.text.clone();
654                    let res = pattern.matches(&text);
655                    if let Some(matches) = res {
656                        for alias in &pattern.aliases {
657                            if let Some(value) = matches.get(&alias) {
658                                let existing = context
659                                    .expectations
660                                    .lock()
661                                    .unwrap()
662                                    .insert(alias.clone(), value.to_string());
663                                if let Some(existing) = existing {
664                                    if existing != value {
665                                        context.trace(&format!(
666                                            "pattern alias FAILED match: {existing:?} != {value:?}",
667                                        ));
668                                        return Err(OutputPatternMatchFailure {
669                                            location: location.clone(),
670                                            pattern_type: "pattern",
671                                            output_line: Some(line),
672                                        });
673                                    }
674                                }
675                            }
676                        }
677                        context.trace(&format!("pattern match: {:?} =~ {pattern:?}", line.text));
678                        Ok(next)
679                    } else {
680                        context.trace(&format!(
681                            "pattern FAILED match: {:?} =~ {pattern:?}",
682                            line.text
683                        ));
684                        Err(OutputPatternMatchFailure {
685                            location: location.clone(),
686                            pattern_type: "pattern",
687                            output_line: Some(line),
688                        })
689                    }
690                } else {
691                    Err(OutputPatternMatchFailure {
692                        location: location.clone(),
693                        pattern_type: "pattern",
694                        output_line: None,
695                    })
696                }
697            }
698            OutputPatternType::Sequence(patterns) => {
699                for pattern in patterns {
700                    match pattern.matches(context.descend(), output) {
701                        Ok(v) => {
702                            output = v;
703                        }
704                        Err(e) => {
705                            return Err(e);
706                        }
707                    }
708                }
709                Ok(output)
710            }
711            OutputPatternType::Repeat(pattern) => {
712                // Mandatory first match
713                let mut output = pattern.matches(context.descend(), output)?;
714                // Any number of additional matches, greedy
715                loop {
716                    match pattern.matches(context.descend(), output.clone()) {
717                        Ok(new_rest) => {
718                            output = new_rest;
719                        }
720                        Err(_) => break Ok(output),
721                    }
722                }
723            }
724            OutputPatternType::Optional(pattern) => {
725                // Never fails
726                match pattern.matches(context.descend(), output.clone()) {
727                    Ok(v) => Ok(v),
728                    Err(_) => Ok(output),
729                }
730            }
731            OutputPatternType::Unordered(patterns) => {
732                // Found is initialized with 0..patterns.len()
733                let mut not_found = (0..patterns.len()).collect::<HashSet<_>>();
734                'outer: while !not_found.is_empty() {
735                    for pattern in &not_found {
736                        let pattern = *pattern;
737                        match patterns[pattern].matches(context.descend(), output.clone()) {
738                            Ok(v) => {
739                                not_found.remove(&pattern);
740                                output = v;
741                                continue 'outer;
742                            }
743                            Err(_) => {
744                                continue;
745                            }
746                        }
747                    }
748                    return Err(OutputPatternMatchFailure {
749                        location: location.clone(),
750                        pattern_type: "unordered",
751                        output_line: None,
752                    });
753                }
754                Ok(output)
755            }
756            OutputPatternType::Choice(patterns) => {
757                for pattern in patterns {
758                    if let Ok(v) = pattern.matches(context.descend(), output.clone()) {
759                        return Ok(v);
760                    }
761                }
762                Err(OutputPatternMatchFailure {
763                    location: location.clone(),
764                    pattern_type: "choice",
765                    output_line: None,
766                })
767            }
768            OutputPatternType::Any(until) => {
769                loop {
770                    match until.matches(context.descend(), output.clone()) {
771                        Ok(v) => {
772                            output = v;
773                            break Ok(output);
774                        }
775                        Err(e) => {
776                            // Eat one line and try again
777                            let (line, next) = output.next(context.clone())?;
778                            if line.is_some() {
779                                output = next;
780                                continue;
781                            } else {
782                                break Err(e);
783                            }
784                        }
785                    }
786                }
787            }
788            OutputPatternType::If(condition, pattern) => {
789                if condition.matches(context.script_context) {
790                    context.trace(&format!("if match: {:?}", condition));
791                    pattern.matches(context.clone(), output.clone())
792                } else {
793                    context.trace(&format!("if FAILED match: {:?}", condition));
794                    Ok(output)
795                }
796            }
797            OutputPatternType::End => {
798                let (line, next) = output.next(context)?;
799                if let Some(line) = line {
800                    Err(OutputPatternMatchFailure {
801                        location: location.clone(),
802                        pattern_type: "end",
803                        output_line: Some(line),
804                    })
805                } else {
806                    Ok(next)
807                }
808            }
809        }
810    }
811}
812
813#[derive(Debug)]
814pub enum PatternResult {
815    Matches,
816    MatchesFailure,
817    ExpectedFailure,
818    Mismatch(OutputPatternMatchFailure, String),
819}