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