From 03c5a6690d943e48ac5b5464c2ac2fd054ea6251 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 17:53:50 +0200 Subject: Add light-weight snapshot testing library with editor integration --- crates/expect/src/lib.rs | 308 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 crates/expect/src/lib.rs (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs new file mode 100644 index 000000000..08d0eafdf --- /dev/null +++ b/crates/expect/src/lib.rs @@ -0,0 +1,308 @@ +//! Snapshot testing library, see +//! https://github.com/rust-analyzer/rust-analyzer/pull/5101 +use std::{ + collections::HashMap, + env, fmt, fs, + ops::Range, + path::{Path, PathBuf}, + sync::Mutex, +}; + +use once_cell::sync::Lazy; +use stdx::{lines_with_ends, trim_indent}; + +const HELP: &str = " +You can update all `expect![[]]` tests by: + + env UPDATE_EXPECT=1 cargo test + +To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer. +"; + +fn update_expect() -> bool { + env::var("UPDATE_EXPECT").is_ok() +} + +/// expect![[""]] +#[macro_export] +macro_rules! expect { + [[$lit:literal]] => {$crate::Expect { + file: file!(), + line: line!(), + column: column!(), + data: $lit, + }}; + [[]] => { $crate::expect![[""]] }; +} + +#[derive(Debug)] +pub struct Expect { + pub file: &'static str, + pub line: u32, + pub column: u32, + pub data: &'static str, +} + +impl Expect { + pub fn assert_eq(&self, actual: &str) { + let trimmed = self.trimmed(); + if &trimmed == actual { + return; + } + Runtime::fail(self, &trimmed, actual); + } + pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { + let actual = format!("{:#?}\n", actual); + self.assert_eq(&actual) + } + + fn trimmed(&self) -> String { + if !self.data.contains('\n') { + return self.data.to_string(); + } + trim_indent(self.data) + } + + fn locate(&self, file: &str) -> Location { + let mut target_line = None; + let mut line_start = 0; + for (i, line) in lines_with_ends(file).enumerate() { + if i == self.line as usize - 1 { + let pat = "expect![["; + let offset = line.find(pat).unwrap(); + let literal_start = line_start + offset + pat.len(); + let indent = line.chars().take_while(|&it| it == ' ').count(); + target_line = Some((literal_start, indent)); + break; + } + line_start += line.len(); + } + let (literal_start, line_indent) = target_line.unwrap(); + let literal_length = file[literal_start..].find("]]").unwrap(); + let literal_range = literal_start..literal_start + literal_length; + Location { line_indent, literal_range } + } +} + +#[derive(Default)] +struct Runtime { + help_printed: bool, + per_file: HashMap<&'static str, FileRuntime>, +} +static RT: Lazy> = Lazy::new(Default::default); + +impl Runtime { + fn fail(expect: &Expect, expected: &str, actual: &str) { + let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + let mut updated = ""; + if update_expect() { + updated = " (updated)"; + rt.per_file + .entry(expect.file) + .or_insert_with(|| FileRuntime::new(expect)) + .update(expect, actual); + } + let print_help = !rt.help_printed && !update_expect(); + rt.help_printed = true; + + let help = if print_help { HELP } else { "" }; + panic!( + "\n +error: expect test failed{} + --> {}:{}:{} +{} +Expect: +---- +{} +---- + +Actual: +---- +{} +---- +", + updated, expect.file, expect.line, expect.column, help, expected, actual + ) + } +} + +struct FileRuntime { + path: PathBuf, + original_text: String, + patchwork: Patchwork, +} + +impl FileRuntime { + fn new(expect: &Expect) -> FileRuntime { + let path = workspace_root().join(expect.file); + let original_text = fs::read_to_string(&path).unwrap(); + let patchwork = Patchwork::new(original_text.clone()); + FileRuntime { path, original_text, patchwork } + } + fn update(&mut self, expect: &Expect, actual: &str) { + let loc = expect.locate(&self.original_text); + let patch = format_patch(loc.line_indent.clone(), actual); + self.patchwork.patch(loc.literal_range, &patch); + fs::write(&self.path, &self.patchwork.text).unwrap() + } +} + +#[derive(Debug)] +struct Location { + line_indent: usize, + literal_range: Range, +} + +#[derive(Debug)] +struct Patchwork { + text: String, + indels: Vec<(Range, usize)>, +} + +impl Patchwork { + fn new(text: String) -> Patchwork { + Patchwork { text, indels: Vec::new() } + } + fn patch(&mut self, mut range: Range, patch: &str) { + self.indels.push((range.clone(), patch.len())); + self.indels.sort_by_key(|(delete, _insert)| delete.start); + + let (delete, insert) = self + .indels + .iter() + .take_while(|(delete, _)| delete.start < range.start) + .map(|(delete, insert)| (delete.end - delete.start, insert)) + .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2)); + + for pos in &mut [&mut range.start, &mut range.end] { + **pos += insert; + **pos -= delete + } + + self.text.replace_range(range, &patch); + } +} + +fn format_patch(line_indent: usize, patch: &str) -> String { + let mut max_hashes = 0; + let mut cur_hashes = 0; + for byte in patch.bytes() { + if byte != b'#' { + cur_hashes = 0; + continue; + } + cur_hashes += 1; + max_hashes = max_hashes.max(cur_hashes); + } + let hashes = &"#".repeat(max_hashes + 1); + let indent = &" ".repeat(line_indent); + let is_multiline = patch.contains('\n'); + + let mut buf = String::new(); + buf.push('r'); + buf.push_str(hashes); + buf.push('"'); + if is_multiline { + buf.push('\n'); + } + let mut final_newline = false; + for line in lines_with_ends(patch) { + if is_multiline { + buf.push_str(indent); + buf.push_str(" "); + } + buf.push_str(line); + final_newline = line.ends_with('\n'); + } + if final_newline { + buf.push_str(indent); + } + buf.push('"'); + buf.push_str(hashes); + buf +} + +fn workspace_root() -> PathBuf { + Path::new( + &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), + ) + .ancestors() + .nth(2) + .unwrap() + .to_path_buf() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_expect_macro() { + let empty = expect![[]]; + expect![[r#" + Expect { + file: "crates/expect/src/lib.rs", + line: 241, + column: 21, + data: "", + } + "#]] + .assert_debug_eq(&empty); + + let expect = expect![[" + hello + world + "]]; + expect![[r#" + Expect { + file: "crates/expect/src/lib.rs", + line: 252, + column: 22, + data: "\n hello\n world\n ", + } + "#]] + .assert_debug_eq(&expect); + } + + #[test] + fn test_format_patch() { + let patch = format_patch(0, "hello\nworld\n"); + expect![[r##" + r#" + hello + world + "#"##]] + .assert_eq(&patch); + + let patch = format_patch(4, "single line"); + expect![[r##"r#"single line"#"##]].assert_eq(&patch); + } + + #[test] + fn test_patchwork() { + let mut patchwork = Patchwork::new("one two three".to_string()); + patchwork.patch(4..7, "zwei"); + patchwork.patch(0..3, "один"); + patchwork.patch(8..13, "3"); + expect![[r#" + Patchwork { + text: "один zwei 3", + indels: [ + ( + 0..3, + 8, + ), + ( + 4..7, + 4, + ), + ( + 8..13, + 1, + ), + ], + } + "#]] + .assert_debug_eq(&patchwork); + } +} -- cgit v1.2.3 From d21dae738b4440f699356865c71e4235f2911de6 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 19:55:54 +0200 Subject: Update crates/expect/src/lib.rs Co-authored-by: bjorn3 --- crates/expect/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 08d0eafdf..1a1302cec 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -106,7 +106,7 @@ impl Runtime { rt.help_printed = true; let help = if print_help { HELP } else { "" }; - panic!( + println!( "\n error: expect test failed{} --> {}:{}:{} @@ -122,7 +122,9 @@ Actual: ---- ", updated, expect.file, expect.line, expect.column, help, expected, actual - ) + ); + // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. + std::panic::resume_unwind(Box::new(())); } } -- cgit v1.2.3 From 18e4e9fb0ba2fa4e1f018b499d9b3c025cb2a51c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 20:45:04 +0200 Subject: Update crates/expect/src/lib.rs Co-authored-by: bjorn3 --- crates/expect/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 1a1302cec..92364bfa7 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -78,7 +78,7 @@ impl Expect { line_start += line.len(); } let (literal_start, line_indent) = target_line.unwrap(); - let literal_length = file[literal_start..].find("]]").unwrap(); + let literal_length = file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`."); let literal_range = literal_start..literal_start + literal_length; Location { line_indent, literal_range } } -- cgit v1.2.3 From 175e48e5be46faf6338d36907c6caf10c2d056f1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 20:45:59 +0200 Subject: Remove fragile test This test needs to be updated after every change (it contains line number), which is annoying. It also fails on windows due to \, so it's easier to remove it. --- crates/expect/src/lib.rs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 92364bfa7..dc4a4223e 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -78,7 +78,8 @@ impl Expect { line_start += line.len(); } let (literal_start, line_indent) = target_line.unwrap(); - let literal_length = file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`."); + let literal_length = + file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`."); let literal_range = literal_start..literal_start + literal_length; Location { line_indent, literal_range } } @@ -238,34 +239,6 @@ fn workspace_root() -> PathBuf { mod tests { use super::*; - #[test] - fn test_expect_macro() { - let empty = expect![[]]; - expect![[r#" - Expect { - file: "crates/expect/src/lib.rs", - line: 241, - column: 21, - data: "", - } - "#]] - .assert_debug_eq(&empty); - - let expect = expect![[" - hello - world - "]]; - expect![[r#" - Expect { - file: "crates/expect/src/lib.rs", - line: 252, - column: 22, - data: "\n hello\n world\n ", - } - "#]] - .assert_debug_eq(&expect); - } - #[test] fn test_format_patch() { let patch = format_patch(0, "hello\nworld\n"); -- cgit v1.2.3 From a9b4fb034bab194bef80c75f146288e55ae8aa2d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 21:13:49 +0200 Subject: Add colors --- crates/expect/src/lib.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index dc4a4223e..18f361ec2 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -107,22 +107,30 @@ impl Runtime { rt.help_printed = true; let help = if print_help { HELP } else { "" }; + + let diff = difference::Changeset::new(actual, expected, "\n"); + println!( "\n -error: expect test failed{} - --> {}:{}:{} +\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{} + \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} +{} +\x1b[1mExpect\x1b[0m: +---- {} -Expect: +---- + +\x1b[1mActual\x1b[0m: ---- {} ---- -Actual: +\x1b[1mDiff\x1b[0m: ---- {} ---- ", - updated, expect.file, expect.line, expect.column, help, expected, actual + updated, expect.file, expect.line, expect.column, help, expected, actual, diff ); // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. std::panic::resume_unwind(Box::new(())); -- cgit v1.2.3 From 53787c7eba71d91811c6519a1186755787dcd204 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 21:33:14 +0200 Subject: style --- crates/expect/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 18f361ec2..aa95a88c5 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -4,10 +4,12 @@ use std::{ collections::HashMap, env, fmt, fs, ops::Range, + panic, path::{Path, PathBuf}, sync::Mutex, }; +use difference::Changeset; use once_cell::sync::Lazy; use stdx::{lines_with_ends, trim_indent}; @@ -108,7 +110,7 @@ impl Runtime { let help = if print_help { HELP } else { "" }; - let diff = difference::Changeset::new(actual, expected, "\n"); + let diff = Changeset::new(actual, expected, "\n"); println!( "\n @@ -133,7 +135,7 @@ impl Runtime { updated, expect.file, expect.line, expect.column, help, expected, actual, diff ); // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. - std::panic::resume_unwind(Box::new(())); + panic::resume_unwind(Box::new(())); } } -- cgit v1.2.3 From 3c1714d76d01b513a2e31fbeae14feca438515fa Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 27 Jun 2020 21:35:52 +0200 Subject: Fix potential overflow --- crates/expect/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index aa95a88c5..dd7b96aab 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -188,8 +188,8 @@ impl Patchwork { .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2)); for pos in &mut [&mut range.start, &mut range.end] { + **pos -= delete; **pos += insert; - **pos -= delete } self.text.replace_range(range, &patch); -- cgit v1.2.3 From 977b688144fb997fdf5dd5bdb587e4f357d853f2 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 10:37:55 +0200 Subject: Don't fail tests when updating snapshot --- crates/expect/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index dd7b96aab..d678b1817 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -2,7 +2,7 @@ //! https://github.com/rust-analyzer/rust-analyzer/pull/5101 use std::{ collections::HashMap, - env, fmt, fs, + env, fmt, fs, mem, ops::Range, panic, path::{Path, PathBuf}, @@ -97,24 +97,25 @@ static RT: Lazy> = Lazy::new(Default::default); impl Runtime { fn fail(expect: &Expect, expected: &str, actual: &str) { let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); - let mut updated = ""; if update_expect() { - updated = " (updated)"; + println!( + "\x1b[1m\x1b[92mupdating\x1b[0m: {}:{}:{}", + expect.file, expect.line, expect.column + ); rt.per_file .entry(expect.file) .or_insert_with(|| FileRuntime::new(expect)) .update(expect, actual); + return; } - let print_help = !rt.help_printed && !update_expect(); - rt.help_printed = true; - + let print_help = !mem::replace(&mut rt.help_printed, true); let help = if print_help { HELP } else { "" }; let diff = Changeset::new(actual, expected, "\n"); println!( "\n -\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{} +\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} {} \x1b[1mExpect\x1b[0m: @@ -132,7 +133,7 @@ impl Runtime { {} ---- ", - updated, expect.file, expect.line, expect.column, help, expected, actual, diff + expect.file, expect.line, expect.column, help, expected, actual, diff ); // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. panic::resume_unwind(Box::new(())); -- cgit v1.2.3 From 82838f5eda6ee98cebb9574ceef36544f1a45a4d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 10:43:11 +0200 Subject: Cleanup --- crates/expect/src/lib.rs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index d678b1817..3a92b36e2 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -29,9 +29,11 @@ fn update_expect() -> bool { #[macro_export] macro_rules! expect { [[$lit:literal]] => {$crate::Expect { - file: file!(), - line: line!(), - column: column!(), + position: $crate::Position { + file: file!(), + line: line!(), + column: column!(), + }, data: $lit, }}; [[]] => { $crate::expect![[""]] }; @@ -39,10 +41,21 @@ macro_rules! expect { #[derive(Debug)] pub struct Expect { + pub position: Position, + pub data: &'static str, +} + +#[derive(Debug)] +pub struct Position { pub file: &'static str, pub line: u32, pub column: u32, - pub data: &'static str, +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", self.file, self.line, self.column) + } } impl Expect { @@ -69,7 +82,7 @@ impl Expect { let mut target_line = None; let mut line_start = 0; for (i, line) in lines_with_ends(file).enumerate() { - if i == self.line as usize - 1 { + if i == self.position.line as usize - 1 { let pat = "expect![["; let offset = line.find(pat).unwrap(); let literal_start = line_start + offset + pat.len(); @@ -98,12 +111,9 @@ impl Runtime { fn fail(expect: &Expect, expected: &str, actual: &str) { let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); if update_expect() { - println!( - "\x1b[1m\x1b[92mupdating\x1b[0m: {}:{}:{}", - expect.file, expect.line, expect.column - ); + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position); rt.per_file - .entry(expect.file) + .entry(expect.position.file) .or_insert_with(|| FileRuntime::new(expect)) .update(expect, actual); return; @@ -116,7 +126,7 @@ impl Runtime { println!( "\n \x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m - \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} + \x1b[1m\x1b[34m-->\x1b[0m {} {} \x1b[1mExpect\x1b[0m: ---- @@ -133,7 +143,7 @@ impl Runtime { {} ---- ", - expect.file, expect.line, expect.column, help, expected, actual, diff + expect.position, help, expected, actual, diff ); // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. panic::resume_unwind(Box::new(())); @@ -148,7 +158,7 @@ struct FileRuntime { impl FileRuntime { fn new(expect: &Expect) -> FileRuntime { - let path = workspace_root().join(expect.file); + let path = workspace_root().join(expect.position.file); let original_text = fs::read_to_string(&path).unwrap(); let patchwork = Patchwork::new(original_text.clone()); FileRuntime { path, original_text, patchwork } -- cgit v1.2.3 From adf624b433277a0106f5354bb7d62ab1a04f216b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 11:19:40 +0200 Subject: Add file support to expect --- crates/expect/src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 8 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 3a92b36e2..3f293f5d5 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -14,7 +14,7 @@ use once_cell::sync::Lazy; use stdx::{lines_with_ends, trim_indent}; const HELP: &str = " -You can update all `expect![[]]` tests by: +You can update all `expect![[]]` tests by running: env UPDATE_EXPECT=1 cargo test @@ -25,26 +25,37 @@ fn update_expect() -> bool { env::var("UPDATE_EXPECT").is_ok() } -/// expect![[""]] +/// expect![[r#"inline snapshot"#]] #[macro_export] macro_rules! expect { - [[$lit:literal]] => {$crate::Expect { + [[$data:literal]] => {$crate::Expect { position: $crate::Position { file: file!(), line: line!(), column: column!(), }, - data: $lit, + data: $data, }}; [[]] => { $crate::expect![[""]] }; } +/// expect_file!["/crates/foo/test_data/foo.rs"] +#[macro_export] +macro_rules! expect_file { + [$path:literal] => {$crate::ExpectFile { path: $path }}; +} + #[derive(Debug)] pub struct Expect { pub position: Position, pub data: &'static str, } +#[derive(Debug)] +pub struct ExpectFile { + pub path: &'static str, +} + #[derive(Debug)] pub struct Position { pub file: &'static str, @@ -64,7 +75,7 @@ impl Expect { if &trimmed == actual { return; } - Runtime::fail(self, &trimmed, actual); + Runtime::fail_expect(self, &trimmed, actual); } pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { let actual = format!("{:#?}\n", actual); @@ -100,6 +111,25 @@ impl Expect { } } +impl ExpectFile { + pub fn assert_eq(&self, actual: &str) { + let expected = self.read(); + if actual == expected { + return; + } + Runtime::fail_file(self, &expected, actual); + } + fn read(&self) -> String { + fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n") + } + fn write(&self, contents: &str) { + fs::write(self.abs_path(), contents).unwrap() + } + fn abs_path(&self) -> PathBuf { + workspace_root().join(self.path) + } +} + #[derive(Default)] struct Runtime { help_printed: bool, @@ -108,7 +138,7 @@ struct Runtime { static RT: Lazy> = Lazy::new(Default::default); impl Runtime { - fn fail(expect: &Expect, expected: &str, actual: &str) { + fn fail_expect(expect: &Expect, expected: &str, actual: &str) { let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); if update_expect() { println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position); @@ -118,7 +148,21 @@ impl Runtime { .update(expect, actual); return; } - let print_help = !mem::replace(&mut rt.help_printed, true); + rt.panic(expect.position.to_string(), expected, actual); + } + + fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) { + let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + if update_expect() { + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path); + expect.write(actual); + return; + } + rt.panic(expect.path.to_string(), expected, actual); + } + + fn panic(&mut self, position: String, expected: &str, actual: &str) { + let print_help = !mem::replace(&mut self.help_printed, true); let help = if print_help { HELP } else { "" }; let diff = Changeset::new(actual, expected, "\n"); @@ -143,7 +187,7 @@ impl Runtime { {} ---- ", - expect.position, help, expected, actual, diff + position, help, expected, actual, diff ); // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. panic::resume_unwind(Box::new(())); -- cgit v1.2.3 From 05d67a9a0efafb3dd5087aad17d75aa88aa85178 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 1 Jul 2020 11:25:22 +0200 Subject: Move test data to test_data directory --- crates/expect/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 3f293f5d5..a5e26fade 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -39,7 +39,7 @@ macro_rules! expect { [[]] => { $crate::expect![[""]] }; } -/// expect_file!["/crates/foo/test_data/foo.rs"] +/// expect_file!["/crates/foo/test_data/bar.html"] #[macro_export] macro_rules! expect_file { [$path:literal] => {$crate::ExpectFile { path: $path }}; -- cgit v1.2.3 From b9aab22d569c4ffe4d4f544a778bf07441ccf118 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 9 Jul 2020 10:33:32 +0200 Subject: No blank indent --- crates/expect/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index a5e26fade..7579a5027 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -275,7 +275,7 @@ fn format_patch(line_indent: usize, patch: &str) -> String { } let mut final_newline = false; for line in lines_with_ends(patch) { - if is_multiline { + if is_multiline && !line.trim().is_empty() { buf.push_str(indent); buf.push_str(" "); } -- cgit v1.2.3 From b660681a6becdcd33d4ed8cbb167c2a3dc170990 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 9 Jul 2020 10:58:56 +0200 Subject: Unify tests --- crates/expect/src/lib.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 7579a5027..408448eed 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -42,7 +42,9 @@ macro_rules! expect { /// expect_file!["/crates/foo/test_data/bar.html"] #[macro_export] macro_rules! expect_file { - [$path:literal] => {$crate::ExpectFile { path: $path }}; + [$path:literal] => {$crate::ExpectFile { + path: $crate::ExpectFilePath::Static($path) + }}; } #[derive(Debug)] @@ -53,7 +55,13 @@ pub struct Expect { #[derive(Debug)] pub struct ExpectFile { - pub path: &'static str, + pub path: ExpectFilePath, +} + +#[derive(Debug)] +pub enum ExpectFilePath { + Static(&'static str), + Dynamic(PathBuf), } #[derive(Debug)] @@ -112,6 +120,9 @@ impl Expect { } impl ExpectFile { + pub fn new(path: PathBuf) -> ExpectFile { + ExpectFile { path: ExpectFilePath::Dynamic(path) } + } pub fn assert_eq(&self, actual: &str) { let expected = self.read(); if actual == expected { @@ -125,8 +136,14 @@ impl ExpectFile { fn write(&self, contents: &str) { fs::write(self.abs_path(), contents).unwrap() } + fn path(&self) -> &Path { + match &self.path { + ExpectFilePath::Static(it) => it.as_ref(), + ExpectFilePath::Dynamic(it) => it.as_path(), + } + } fn abs_path(&self) -> PathBuf { - workspace_root().join(self.path) + workspace_root().join(self.path()) } } @@ -154,11 +171,11 @@ impl Runtime { fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) { let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); if update_expect() { - println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path); + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path().display()); expect.write(actual); return; } - rt.panic(expect.path.to_string(), expected, actual); + rt.panic(expect.path().display().to_string(), expected, actual); } fn panic(&mut self, position: String, expected: &str, actual: &str) { -- cgit v1.2.3 From 8e1ebbcc136d1bb8f120d0db2625b70a37b756f8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 9 Jul 2020 11:47:27 +0200 Subject: Simplify --- crates/expect/src/lib.rs | 49 ++++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 31 deletions(-) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index 408448eed..c54e99203 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -42,8 +42,8 @@ macro_rules! expect { /// expect_file!["/crates/foo/test_data/bar.html"] #[macro_export] macro_rules! expect_file { - [$path:literal] => {$crate::ExpectFile { - path: $crate::ExpectFilePath::Static($path) + [$path:expr] => {$crate::ExpectFile { + path: std::path::PathBuf::from($path) }}; } @@ -55,13 +55,7 @@ pub struct Expect { #[derive(Debug)] pub struct ExpectFile { - pub path: ExpectFilePath, -} - -#[derive(Debug)] -pub enum ExpectFilePath { - Static(&'static str), - Dynamic(PathBuf), + pub path: PathBuf, } #[derive(Debug)] @@ -120,9 +114,6 @@ impl Expect { } impl ExpectFile { - pub fn new(path: PathBuf) -> ExpectFile { - ExpectFile { path: ExpectFilePath::Dynamic(path) } - } pub fn assert_eq(&self, actual: &str) { let expected = self.read(); if actual == expected { @@ -136,14 +127,8 @@ impl ExpectFile { fn write(&self, contents: &str) { fs::write(self.abs_path(), contents).unwrap() } - fn path(&self) -> &Path { - match &self.path { - ExpectFilePath::Static(it) => it.as_ref(), - ExpectFilePath::Dynamic(it) => it.as_path(), - } - } fn abs_path(&self) -> PathBuf { - workspace_root().join(self.path()) + WORKSPACE_ROOT.join(&self.path) } } @@ -171,11 +156,11 @@ impl Runtime { fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) { let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); if update_expect() { - println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path().display()); + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path.display()); expect.write(actual); return; } - rt.panic(expect.path().display().to_string(), expected, actual); + rt.panic(expect.path.display().to_string(), expected, actual); } fn panic(&mut self, position: String, expected: &str, actual: &str) { @@ -219,7 +204,7 @@ struct FileRuntime { impl FileRuntime { fn new(expect: &Expect) -> FileRuntime { - let path = workspace_root().join(expect.position.file); + let path = WORKSPACE_ROOT.join(expect.position.file); let original_text = fs::read_to_string(&path).unwrap(); let patchwork = Patchwork::new(original_text.clone()); FileRuntime { path, original_text, patchwork } @@ -307,15 +292,17 @@ fn format_patch(line_indent: usize, patch: &str) -> String { buf } -fn workspace_root() -> PathBuf { - Path::new( - &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), - ) - .ancestors() - .nth(2) - .unwrap() - .to_path_buf() -} +static WORKSPACE_ROOT: Lazy = Lazy::new(|| { + let my_manifest = + env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()); + // Heuristic, see https://github.com/rust-lang/cargo/issues/3946 + Path::new(&my_manifest) + .ancestors() + .filter(|it| it.join("Cargo.toml").exists()) + .last() + .unwrap() + .to_path_buf() +}); #[cfg(test)] mod tests { -- cgit v1.2.3 From e075e6eef2c275d9b9b511b37dad478285aecb48 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 9 Jul 2020 16:04:29 +0200 Subject: Move diagnostics tests to expect --- crates/expect/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'crates/expect/src') diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs index c54e99203..21a458d47 100644 --- a/crates/expect/src/lib.rs +++ b/crates/expect/src/lib.rs @@ -121,6 +121,10 @@ impl ExpectFile { } Runtime::fail_file(self, &expected, actual); } + pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { + let actual = format!("{:#?}\n", actual); + self.assert_eq(&actual) + } fn read(&self) -> String { fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n") } -- cgit v1.2.3