summaryrefslogtreecommitdiff
path: root/test_util/src
diff options
context:
space:
mode:
Diffstat (limited to 'test_util/src')
-rw-r--r--test_util/src/assertions.rs41
-rw-r--r--test_util/src/lib.rs403
2 files changed, 387 insertions, 57 deletions
diff --git a/test_util/src/assertions.rs b/test_util/src/assertions.rs
index 239e0e99c..91ac9dd1c 100644
--- a/test_util/src/assertions.rs
+++ b/test_util/src/assertions.rs
@@ -1,5 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use crate::colors;
+
#[macro_export]
macro_rules! assert_starts_with {
($string:expr, $($test:expr),+) => {
@@ -52,11 +54,40 @@ macro_rules! assert_not_contains {
#[track_caller]
pub fn assert_wildcard_match(actual: &str, expected: &str) {
- if !expected.contains("[WILDCARD]") {
+ if !expected.contains("[WILDCARD]") && !expected.contains("[IGNORE_START]") {
pretty_assertions::assert_eq!(actual, expected);
- } else if !crate::wildcard_match(expected, actual) {
- println!("OUTPUT START\n{actual}\nOUTPUT END");
- println!("EXPECTED START\n{expected}\nEXPECTED END");
- panic!("pattern match failed");
+ } else {
+ match crate::wildcard_match_detailed(expected, actual) {
+ crate::WildcardMatchResult::Success => {
+ // ignore
+ }
+ crate::WildcardMatchResult::Fail(debug_output) => {
+ println!(
+ "{}{}{}",
+ colors::bold("-- "),
+ colors::bold_red("OUTPUT"),
+ colors::bold(" START --"),
+ );
+ println!("{}", actual);
+ println!("{}", colors::bold("-- OUTPUT END --"));
+ println!(
+ "{}{}{}",
+ colors::bold("-- "),
+ colors::bold_green("EXPECTED"),
+ colors::bold(" START --"),
+ );
+ println!("{}", expected);
+ println!("{}", colors::bold("-- EXPECTED END --"));
+ println!(
+ "{}{}{}",
+ colors::bold("-- "),
+ colors::bold_blue("DEBUG"),
+ colors::bold(" START --"),
+ );
+ println!("{debug_output}");
+ println!("{}", colors::bold("-- DEBUG END --"));
+ panic!("pattern match failed");
+ }
+ }
}
}
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs
index fa0cb8a84..0600867d7 100644
--- a/test_util/src/lib.rs
+++ b/test_util/src/lib.rs
@@ -2410,57 +2410,266 @@ impl<'a> CheckOutputIntegrationTest<'a> {
}
}
-pub fn wildcard_match(pattern: &str, s: &str) -> bool {
- pattern_match(pattern, s, "[WILDCARD]")
+pub fn wildcard_match(pattern: &str, text: &str) -> bool {
+ match wildcard_match_detailed(pattern, text) {
+ WildcardMatchResult::Success => true,
+ WildcardMatchResult::Fail(debug_output) => {
+ eprintln!("{}", debug_output);
+ false
+ }
+ }
}
-pub fn pattern_match(pattern: &str, s: &str, wildcard: &str) -> bool {
- // Normalize line endings
- let mut s = s.replace("\r\n", "\n");
- let pattern = pattern.replace("\r\n", "\n");
+pub enum WildcardMatchResult {
+ Success,
+ Fail(String),
+}
- if pattern == wildcard {
- return true;
+pub fn wildcard_match_detailed(
+ pattern: &str,
+ text: &str,
+) -> WildcardMatchResult {
+ fn annotate_whitespace(text: &str) -> String {
+ text.replace('\t', "\u{2192}").replace(' ', "\u{00B7}")
}
- let parts = pattern.split(wildcard).collect::<Vec<&str>>();
- if parts.len() == 1 {
- return pattern == s;
- }
+ // Normalize line endings
+ let original_text = text.replace("\r\n", "\n");
+ let mut current_text = original_text.as_str();
+ let pattern = pattern.replace("\r\n", "\n");
+ let mut output_lines = Vec::new();
- if !s.starts_with(parts[0]) {
- return false;
- }
+ let parts = parse_wildcard_pattern_text(&pattern).unwrap();
- // If the first line of the pattern is just a wildcard the newline character
- // needs to be pre-pended so it can safely match anything or nothing and
- // continue matching.
- if pattern.lines().next() == Some(wildcard) {
- s.insert(0, '\n');
+ let mut was_last_wildcard = false;
+ for (i, part) in parts.iter().enumerate() {
+ match part {
+ WildcardPatternPart::Wildcard => {
+ output_lines.push("<WILDCARD />".to_string());
+ }
+ WildcardPatternPart::Text(search_text) => {
+ let is_last = i + 1 == parts.len();
+ let search_index = if is_last && was_last_wildcard {
+ // search from the end of the file
+ current_text.rfind(search_text)
+ } else {
+ current_text.find(search_text)
+ };
+ match search_index {
+ Some(found_index) if was_last_wildcard || found_index == 0 => {
+ output_lines.push(format!(
+ "<FOUND>{}</FOUND>",
+ colors::gray(annotate_whitespace(search_text))
+ ));
+ current_text = &current_text[found_index + search_text.len()..];
+ }
+ Some(index) => {
+ output_lines.push(
+ "==== FOUND SEARCH TEXT IN WRONG POSITION ====".to_string(),
+ );
+ output_lines.push(colors::gray(annotate_whitespace(search_text)));
+ output_lines
+ .push("==== HAD UNKNOWN PRECEEDING TEXT ====".to_string());
+ output_lines
+ .push(colors::red(annotate_whitespace(&current_text[..index])));
+ return WildcardMatchResult::Fail(output_lines.join("\n"));
+ }
+ None => {
+ let mut max_found_index = 0;
+ for (index, _) in search_text.char_indices() {
+ let sub_string = &search_text[..index];
+ if let Some(found_index) = current_text.find(sub_string) {
+ if was_last_wildcard || found_index == 0 {
+ max_found_index = index;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if !was_last_wildcard && max_found_index > 0 {
+ output_lines.push(format!(
+ "<FOUND>{}</FOUND>",
+ colors::gray(annotate_whitespace(
+ &search_text[..max_found_index]
+ ))
+ ));
+ }
+ output_lines
+ .push("==== COULD NOT FIND SEARCH TEXT ====".to_string());
+ output_lines.push(colors::green(annotate_whitespace(
+ if was_last_wildcard {
+ search_text
+ } else {
+ &search_text[max_found_index..]
+ },
+ )));
+ if was_last_wildcard && max_found_index > 0 {
+ output_lines.push(format!(
+ "==== MAX FOUND ====\n{}",
+ colors::red(annotate_whitespace(
+ &search_text[..max_found_index]
+ ))
+ ));
+ }
+ return WildcardMatchResult::Fail(output_lines.join("\n"));
+ }
+ }
+ }
+ WildcardPatternPart::UnorderedLines(expected_lines) => {
+ assert!(!was_last_wildcard, "unsupported");
+ let mut actual_lines = Vec::with_capacity(expected_lines.len());
+ for _ in 0..expected_lines.len() {
+ match current_text.find('\n') {
+ Some(end_line_index) => {
+ actual_lines.push(&current_text[..end_line_index]);
+ current_text = &current_text[end_line_index + 1..];
+ }
+ None => {
+ break;
+ }
+ }
+ }
+ actual_lines.sort_unstable();
+ let mut expected_lines = expected_lines.clone();
+ expected_lines.sort_unstable();
+
+ if actual_lines.len() != expected_lines.len() {
+ output_lines
+ .push("==== HAD WRONG NUMBER OF UNORDERED LINES ====".to_string());
+ output_lines.push("# ACTUAL".to_string());
+ output_lines.extend(
+ actual_lines
+ .iter()
+ .map(|l| colors::green(annotate_whitespace(l))),
+ );
+ output_lines.push("# EXPECTED".to_string());
+ output_lines.extend(
+ expected_lines
+ .iter()
+ .map(|l| colors::green(annotate_whitespace(l))),
+ );
+ return WildcardMatchResult::Fail(output_lines.join("\n"));
+ }
+ for (actual, expected) in actual_lines.iter().zip(expected_lines.iter())
+ {
+ if actual != expected {
+ output_lines
+ .push("==== UNORDERED LINE DID NOT MATCH ====".to_string());
+ output_lines.push(format!(
+ " ACTUAL: {}",
+ colors::red(annotate_whitespace(actual))
+ ));
+ output_lines.push(format!(
+ "EXPECTED: {}",
+ colors::green(annotate_whitespace(expected))
+ ));
+ return WildcardMatchResult::Fail(output_lines.join("\n"));
+ }
+ }
+ output_lines.push("# Found matching unordered lines".to_string());
+ }
+ }
+ was_last_wildcard = matches!(part, WildcardPatternPart::Wildcard);
}
- let mut t = s.split_at(parts[0].len());
+ if was_last_wildcard || current_text.is_empty() {
+ WildcardMatchResult::Success
+ } else {
+ output_lines.push("==== HAD TEXT AT END OF FILE ====".to_string());
+ output_lines.push(colors::red(annotate_whitespace(current_text)));
+ WildcardMatchResult::Fail(output_lines.join("\n"))
+ }
+}
+
+#[derive(Debug)]
+enum WildcardPatternPart<'a> {
+ Wildcard,
+ Text(&'a str),
+ UnorderedLines(Vec<&'a str>),
+}
+
+fn parse_wildcard_pattern_text(
+ text: &str,
+) -> Result<Vec<WildcardPatternPart>, monch::ParseErrorFailureError> {
+ use monch::*;
+
+ fn parse_unordered_lines(input: &str) -> ParseResult<Vec<&str>> {
+ const END_TEXT: &str = "\n[UNORDERED_END]\n";
+ let (input, _) = tag("[UNORDERED_START]\n")(input)?;
+ match input.find(END_TEXT) {
+ Some(end_index) => ParseResult::Ok((
+ &input[end_index + END_TEXT.len()..],
+ input[..end_index].lines().collect::<Vec<_>>(),
+ )),
+ None => ParseError::fail(input, "Could not find [UNORDERED_END]"),
+ }
+ }
+
+ enum InnerPart<'a> {
+ Wildcard,
+ UnorderedLines(Vec<&'a str>),
+ Char,
+ }
+
+ struct Parser<'a> {
+ current_input: &'a str,
+ last_text_input: &'a str,
+ parts: Vec<WildcardPatternPart<'a>>,
+ }
+
+ impl<'a> Parser<'a> {
+ fn parse(mut self) -> ParseResult<'a, Vec<WildcardPatternPart<'a>>> {
+ while !self.current_input.is_empty() {
+ let (next_input, inner_part) = or3(
+ map(tag("[WILDCARD]"), |_| InnerPart::Wildcard),
+ map(parse_unordered_lines, |lines| {
+ InnerPart::UnorderedLines(lines)
+ }),
+ map(next_char, |_| InnerPart::Char),
+ )(self.current_input)?;
+ match inner_part {
+ InnerPart::Wildcard => {
+ self.queue_previous_text(next_input);
+ self.parts.push(WildcardPatternPart::Wildcard);
+ }
+ InnerPart::UnorderedLines(expected_lines) => {
+ self.queue_previous_text(next_input);
+ self
+ .parts
+ .push(WildcardPatternPart::UnorderedLines(expected_lines));
+ }
+ InnerPart::Char => {
+ // ignore
+ }
+ }
+ self.current_input = next_input;
+ }
- for (i, part) in parts.iter().enumerate() {
- if i == 0 {
- continue;
- }
- dbg!(part, i);
- if i == parts.len() - 1 && (part.is_empty() || *part == "\n") {
- dbg!("exit 1 true", i);
- return true;
+ self.queue_previous_text("");
+
+ ParseResult::Ok(("", self.parts))
}
- if let Some(found) = t.1.find(*part) {
- dbg!("found ", found);
- t = t.1.split_at(found + part.len());
- } else {
- dbg!("exit false ", i);
- return false;
+
+ fn queue_previous_text(&mut self, next_input: &'a str) {
+ let previous_text = &self.last_text_input
+ [..self.last_text_input.len() - self.current_input.len()];
+ if !previous_text.is_empty() {
+ self.parts.push(WildcardPatternPart::Text(previous_text));
+ }
+ self.last_text_input = next_input;
}
}
- dbg!("end ", t.1.len());
- t.1.is_empty()
+ with_failure_handling(|input| {
+ Parser {
+ current_input: input,
+ last_text_input: input,
+ parts: Vec::new(),
+ }
+ .parse()
+ })(text)
}
pub fn with_pty(deno_args: &[&str], action: impl FnMut(Pty)) {
@@ -2604,6 +2813,67 @@ pub fn parse_max_mem(output: &str) -> Option<u64> {
None
}
+pub(crate) mod colors {
+ use std::io::Write;
+
+ use termcolor::Ansi;
+ use termcolor::Color;
+ use termcolor::ColorSpec;
+ use termcolor::WriteColor;
+
+ pub fn bold<S: AsRef<str>>(s: S) -> String {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bold(true);
+ style(s, style_spec)
+ }
+
+ pub fn red<S: AsRef<str>>(s: S) -> String {
+ fg_color(s, Color::Red)
+ }
+
+ pub fn bold_red<S: AsRef<str>>(s: S) -> String {
+ bold_fg_color(s, Color::Red)
+ }
+
+ pub fn green<S: AsRef<str>>(s: S) -> String {
+ fg_color(s, Color::Green)
+ }
+
+ pub fn bold_green<S: AsRef<str>>(s: S) -> String {
+ bold_fg_color(s, Color::Green)
+ }
+
+ pub fn bold_blue<S: AsRef<str>>(s: S) -> String {
+ bold_fg_color(s, Color::Blue)
+ }
+
+ pub fn gray<S: AsRef<str>>(s: S) -> String {
+ fg_color(s, Color::Ansi256(245))
+ }
+
+ fn bold_fg_color<S: AsRef<str>>(s: S, color: Color) -> String {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_bold(true);
+ style_spec.set_fg(Some(color));
+ style(s, style_spec)
+ }
+
+ fn fg_color<S: AsRef<str>>(s: S, color: Color) -> String {
+ let mut style_spec = ColorSpec::new();
+ style_spec.set_fg(Some(color));
+ style(s, style_spec)
+ }
+
+ fn style<S: AsRef<str>>(s: S, colorspec: ColorSpec) -> String {
+ let mut v = Vec::new();
+ let mut ansi_writer = Ansi::new(&mut v);
+ ansi_writer.set_color(&colorspec).unwrap();
+ ansi_writer.write_all(s.as_ref().as_bytes()).unwrap();
+ ansi_writer.reset().unwrap();
+ String::from_utf8_lossy(&v).into_owned()
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -2690,6 +2960,15 @@ mod tests {
}
#[test]
+ fn parse_parse_wildcard_match_text() {
+ let result =
+ parse_wildcard_pattern_text("[UNORDERED_START]\ntesting\ntesting")
+ .err()
+ .unwrap();
+ assert_contains!(result.to_string(), "Could not find [UNORDERED_END]");
+ }
+
+ #[test]
fn test_wildcard_match() {
let fixtures = vec![
("foobarbaz", "foobarbaz", true),
@@ -2733,16 +3012,15 @@ mod tests {
}
#[test]
- fn test_pattern_match() {
+ fn test_wildcard_match2() {
// foo, bar, baz, qux, quux, quuz, corge, grault, garply, waldo, fred, plugh, xyzzy
- let wildcard = "[BAR]";
- assert!(pattern_match("foo[BAR]baz", "foobarbaz", wildcard));
- assert!(!pattern_match("foo[BAR]baz", "foobazbar", wildcard));
+ assert!(wildcard_match("foo[WILDCARD]baz", "foobarbaz"));
+ assert!(!wildcard_match("foo[WILDCARD]baz", "foobazbar"));
- let multiline_pattern = "[BAR]
+ let multiline_pattern = "[WILDCARD]
foo:
-[BAR]baz[BAR]";
+[WILDCARD]baz[WILDCARD]";
fn multi_line_builder(input: &str, leading_text: Option<&str>) -> String {
// If there is leading text add a newline so it's on it's own line
@@ -2767,31 +3045,52 @@ grault",
);
// Correct input & leading line
- assert!(pattern_match(
+ assert!(wildcard_match(
multiline_pattern,
&multi_line_builder("baz", Some("QUX=quux")),
- wildcard
));
- // Correct input & no leading line
- assert!(pattern_match(
+ // Should fail when leading line
+ assert!(!wildcard_match(
multiline_pattern,
&multi_line_builder("baz", None),
- wildcard
));
// Incorrect input & leading line
- assert!(!pattern_match(
+ assert!(!wildcard_match(
multiline_pattern,
&multi_line_builder("garply", Some("QUX=quux")),
- wildcard
));
// Incorrect input & no leading line
- assert!(!pattern_match(
+ assert!(!wildcard_match(
multiline_pattern,
&multi_line_builder("garply", None),
- wildcard
+ ));
+ }
+
+ #[test]
+ fn test_wildcard_match_unordered_lines() {
+ // matching
+ assert!(wildcard_match(
+ concat!("[UNORDERED_START]\n", "B\n", "A\n", "[UNORDERED_END]\n"),
+ concat!("A\n", "B\n",)
+ ));
+ // different line
+ assert!(!wildcard_match(
+ concat!("[UNORDERED_START]\n", "Ba\n", "A\n", "[UNORDERED_END]\n"),
+ concat!("A\n", "B\n",)
+ ));
+ // different number of lines
+ assert!(!wildcard_match(
+ concat!(
+ "[UNORDERED_START]\n",
+ "B\n",
+ "A\n",
+ "C\n",
+ "[UNORDERED_END]\n"
+ ),
+ concat!("A\n", "B\n",)
));
}