From ee399e67d40117e3944ebc50419616d64e93bf1f Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Thu, 27 Jul 2023 03:31:46 -0400 Subject: [PATCH] fix: retain header and divider delimiter lengths when updating tests This helps to prevent visual bloat in diffs when updating tests where the delimiter is not 80 chars long --- cli/src/test.rs | 111 +++++++++++++++++++++++++++-------- cli/src/tests/corpus_test.rs | 1 + 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/cli/src/test.rs b/cli/src/test.rs index c531c18a..7c704160 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -16,11 +16,11 @@ use walkdir::WalkDir; lazy_static! { static ref HEADER_REGEX: ByteRegex = - ByteRegexBuilder::new(r"^===+(?P[^=\r\n][^\r\n]*)?\r?\n(?P([^=\r\n][^\r\n]*\r?\n)+)===+(?P[^=\r\n][^\r\n]*)?\r?\n") + ByteRegexBuilder::new(r"^(?P(?:=+){3,})(?P[^=\r\n][^\r\n]*)?\r?\n(?P([^=\r\n][^\r\n]*\r?\n)+)===+(?P[^=\r\n][^\r\n]*)?\r?\n") .multi_line(true) .build() .unwrap(); - static ref DIVIDER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^---+(?P[^-\r\n][^\r\n]*)?\r?\n") + static ref DIVIDER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^(?P(?:-+){3,})(?P[^-\r\n][^\r\n]*)?\r?\n") .multi_line(true) .build() .unwrap(); @@ -40,6 +40,8 @@ pub enum TestEntry { name: String, input: Vec, output: String, + header_delim_len: usize, + divider_delim_len: usize, has_fields: bool, }, } @@ -179,13 +181,15 @@ fn run_tests( mut indent_level: i32, failures: &mut Vec<(String, String, String)>, update: bool, - corrected_entries: &mut Vec<(String, String, String)>, + corrected_entries: &mut Vec<(String, String, String, usize, usize)>, ) -> Result<()> { match test_entry { TestEntry::Example { name, input, output, + header_delim_len, + divider_delim_len, has_fields, } => { if let Some(filter) = filter { @@ -193,7 +197,13 @@ fn run_tests( if update { let input = String::from_utf8(input).unwrap(); let output = format_sexp(&output); - corrected_entries.push((name, input, output)); + corrected_entries.push(( + name, + input, + output, + header_delim_len, + divider_delim_len, + )); } return Ok(()); } @@ -203,21 +213,31 @@ fn run_tests( if !has_fields { actual = strip_sexp_fields(actual); } - for _ in 0..indent_level { - print!(" "); - } + print!("{}", " ".repeat(indent_level as usize)); if actual == output { println!("✓ {}", Colour::Green.paint(&name)); if update { let input = String::from_utf8(input).unwrap(); let output = format_sexp(&output); - corrected_entries.push((name, input, output)); + corrected_entries.push(( + name, + input, + output, + header_delim_len, + divider_delim_len, + )); } } else { if update { let input = String::from_utf8(input).unwrap(); let output = format_sexp(&actual); - corrected_entries.push((name.clone(), input, output)); + corrected_entries.push(( + name.clone(), + input, + output, + header_delim_len, + divider_delim_len, + )); println!("✓ {}", Colour::Blue.paint(&name)); } else { println!("✗ {}", Colour::Red.paint(&name)); @@ -231,9 +251,7 @@ fn run_tests( file_path, } => { if indent_level > 0 { - for _ in 0..indent_level { - print!(" "); - } + print!("{}", " ".repeat(indent_level as usize)); println!("{}:", name); } @@ -314,27 +332,32 @@ fn format_sexp_indented(sexp: &String, initial_indent_level: u32) -> String { formatted } -fn write_tests(file_path: &Path, corrected_entries: &Vec<(String, String, String)>) -> Result<()> { +fn write_tests( + file_path: &Path, + corrected_entries: &Vec<(String, String, String, usize, usize)>, +) -> Result<()> { let mut buffer = fs::File::create(file_path)?; write_tests_to_buffer(&mut buffer, corrected_entries) } fn write_tests_to_buffer( buffer: &mut impl Write, - corrected_entries: &Vec<(String, String, String)>, + corrected_entries: &Vec<(String, String, String, usize, usize)>, ) -> Result<()> { - for (i, (name, input, output)) in corrected_entries.iter().enumerate() { + for (i, (name, input, output, header_delim_len, divider_delim_len)) in + corrected_entries.iter().enumerate() + { if i > 0 { write!(buffer, "\n")?; } write!( buffer, "{}\n{}\n{}\n{}\n{}\n\n{}\n", - "=".repeat(80), + "=".repeat(*header_delim_len), name, - "=".repeat(80), + "=".repeat(*header_delim_len), input, - "-".repeat(80), + "-".repeat(*divider_delim_len), output.trim() )?; } @@ -398,6 +421,7 @@ fn parse_test_content(name: String, content: String, file_path: Option) // Ignore any matches whose suffix does not match the first header // suffix in the file. let header_matches = HEADER_REGEX.captures_iter(&bytes).filter_map(|c| { + let header_delim_len = c.name("equals").map(|n| n.as_bytes().len()).unwrap_or(80); let suffix1 = c .name("suffix1") .map(|m| String::from_utf8_lossy(m.as_bytes())); @@ -409,13 +433,17 @@ fn parse_test_content(name: String, content: String, file_path: Option) let test_name = c .name("test_name") .map(|c| String::from_utf8_lossy(c.as_bytes()).trim_end().to_string()); - Some((header_range, test_name)) + let res = Some((header_delim_len, header_range, test_name)); + res } else { None } }); - for (header_range, test_name) in header_matches.chain(Some((bytes.len()..bytes.len(), None))) { + let mut prev_header_len = 80; + for (header_delim_len, header_range, test_name) in + header_matches.chain(Some((80, bytes.len()..bytes.len(), None))) + { // Find the longest line of dashes following each test description. That line // separates the input from the expected output. Ignore any matches whose suffix // does not match the first suffix in the file. @@ -423,19 +451,25 @@ fn parse_test_content(name: String, content: String, file_path: Option) let divider_range = DIVIDER_REGEX .captures_iter(&bytes[prev_header_end..header_range.start]) .filter_map(|m| { + let divider_delim_len = + m.name("hyphens").map(|m| m.as_bytes().len()).unwrap_or(80); let suffix = m .name("suffix") .map(|m| String::from_utf8_lossy(m.as_bytes())); if suffix == first_suffix { let range = m.get(0).unwrap().range(); - Some((prev_header_end + range.start)..(prev_header_end + range.end)) + let res = Some(( + divider_delim_len, + (prev_header_end + range.start)..(prev_header_end + range.end), + )); + res } else { None } }) - .max_by_key(|range| range.len()); + .max_by_key(|(_, range)| range.len()); - if let Some(divider_range) = divider_range { + if let Some((divider_delim_len, divider_range)) = divider_range { if let Ok(output) = str::from_utf8(&bytes[divider_range.end..header_range.start]) { let mut input = bytes[prev_header_end..divider_range.start].to_vec(); @@ -460,12 +494,15 @@ fn parse_test_content(name: String, content: String, file_path: Option) name: prev_name, input, output, + header_delim_len: prev_header_len, + divider_delim_len, has_fields, }); } } } prev_name = test_name.unwrap_or(String::new()); + prev_header_len = header_delim_len; prev_header_end = header_range.end; } TestEntry::Group { @@ -516,12 +553,16 @@ d name: "The first test".to_string(), input: "\na b c\n".as_bytes().to_vec(), output: "(a (b c))".to_string(), + header_delim_len: 15, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "The second test".to_string(), input: "d".as_bytes().to_vec(), output: "(d)".to_string(), + header_delim_len: 16, + divider_delim_len: 3, has_fields: false, }, ], @@ -570,12 +611,16 @@ abc name: "Code with dashes".to_string(), input: "abc\n---\ndefg\n----\nhijkl".as_bytes().to_vec(), output: "(a (b))".to_string(), + header_delim_len: 18, + divider_delim_len: 7, has_fields: false, }, TestEntry::Example { name: "Code ending with dashes".to_string(), input: "abc\n-----------".as_bytes().to_vec(), output: "(c (d))".to_string(), + header_delim_len: 25, + divider_delim_len: 19, has_fields: false, }, ], @@ -619,11 +664,15 @@ abc "title 1".to_string(), "input 1".to_string(), "output 1".to_string(), + 80, + 80, ), ( "title 2".to_string(), "input 2".to_string(), "output 2".to_string(), + 80, + 80, ), ]; write_tests_to_buffer(&mut buffer, &corrected_entries).unwrap(); @@ -700,18 +749,24 @@ code name: "sexp with comment".to_string(), input: "code".as_bytes().to_vec(), output: "(a (b))".to_string(), + header_delim_len: 18, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "sexp with comment between".to_string(), input: "code".as_bytes().to_vec(), output: "(a (b))".to_string(), + header_delim_len: 18, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "sexp with ';'".to_string(), input: "code".as_bytes().to_vec(), output: "(MISSING \";\")".to_string(), + header_delim_len: 25, + divider_delim_len: 3, has_fields: false, } ], @@ -784,18 +839,24 @@ NOT A TEST HEADER name: "First test".to_string(), input: expected_input.clone(), output: "(a)".to_string(), + header_delim_len: 18, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "Second test".to_string(), input: expected_input.clone(), output: "(a)".to_string(), + header_delim_len: 18, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "Test name with = symbol".to_string(), input: expected_input.clone(), output: "(a)".to_string(), + header_delim_len: 25, + divider_delim_len: 3, has_fields: false, } ], @@ -839,12 +900,16 @@ code with ---- name: "name\nwith\nnewlines".to_string(), input: b"a".to_vec(), output: "(b)".to_string(), + header_delim_len: 15, + divider_delim_len: 3, has_fields: false, }, TestEntry::Example { name: "name with === signs".to_string(), input: b"code with ----".to_vec(), output: "(d)".to_string(), + header_delim_len: 20, + divider_delim_len: 3, has_fields: false, } ] diff --git a/cli/src/tests/corpus_test.rs b/cli/src/tests/corpus_test.rs index 7166d249..24e72398 100644 --- a/cli/src/tests/corpus_test.rs +++ b/cli/src/tests/corpus_test.rs @@ -502,6 +502,7 @@ fn flatten_tests(test: TestEntry) -> Vec { input, output, has_fields, + .. } => { if !prefix.is_empty() { name.insert_str(0, " - ");