aboutsummaryrefslogtreecommitdiff
path: root/crates/expect
diff options
context:
space:
mode:
Diffstat (limited to 'crates/expect')
-rw-r--r--crates/expect/src/lib.rs97
1 files changed, 76 insertions, 21 deletions
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs
index dd7b96aab..a5e26fade 100644
--- a/crates/expect/src/lib.rs
+++ b/crates/expect/src/lib.rs
@@ -2,7 +2,7 @@
2//! https://github.com/rust-analyzer/rust-analyzer/pull/5101 2//! https://github.com/rust-analyzer/rust-analyzer/pull/5101
3use std::{ 3use std::{
4 collections::HashMap, 4 collections::HashMap,
5 env, fmt, fs, 5 env, fmt, fs, mem,
6 ops::Range, 6 ops::Range,
7 panic, 7 panic,
8 path::{Path, PathBuf}, 8 path::{Path, PathBuf},
@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
14use stdx::{lines_with_ends, trim_indent}; 14use stdx::{lines_with_ends, trim_indent};
15 15
16const HELP: &str = " 16const HELP: &str = "
17You can update all `expect![[]]` tests by: 17You can update all `expect![[]]` tests by running:
18 18
19 env UPDATE_EXPECT=1 cargo test 19 env UPDATE_EXPECT=1 cargo test
20 20
@@ -25,24 +25,48 @@ fn update_expect() -> bool {
25 env::var("UPDATE_EXPECT").is_ok() 25 env::var("UPDATE_EXPECT").is_ok()
26} 26}
27 27
28/// expect![[""]] 28/// expect![[r#"inline snapshot"#]]
29#[macro_export] 29#[macro_export]
30macro_rules! expect { 30macro_rules! expect {
31 [[$lit:literal]] => {$crate::Expect { 31 [[$data:literal]] => {$crate::Expect {
32 file: file!(), 32 position: $crate::Position {
33 line: line!(), 33 file: file!(),
34 column: column!(), 34 line: line!(),
35 data: $lit, 35 column: column!(),
36 },
37 data: $data,
36 }}; 38 }};
37 [[]] => { $crate::expect![[""]] }; 39 [[]] => { $crate::expect![[""]] };
38} 40}
39 41
42/// expect_file!["/crates/foo/test_data/bar.html"]
43#[macro_export]
44macro_rules! expect_file {
45 [$path:literal] => {$crate::ExpectFile { path: $path }};
46}
47
40#[derive(Debug)] 48#[derive(Debug)]
41pub struct Expect { 49pub struct Expect {
50 pub position: Position,
51 pub data: &'static str,
52}
53
54#[derive(Debug)]
55pub struct ExpectFile {
56 pub path: &'static str,
57}
58
59#[derive(Debug)]
60pub struct Position {
42 pub file: &'static str, 61 pub file: &'static str,
43 pub line: u32, 62 pub line: u32,
44 pub column: u32, 63 pub column: u32,
45 pub data: &'static str, 64}
65
66impl fmt::Display for Position {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "{}:{}:{}", self.file, self.line, self.column)
69 }
46} 70}
47 71
48impl Expect { 72impl Expect {
@@ -51,7 +75,7 @@ impl Expect {
51 if &trimmed == actual { 75 if &trimmed == actual {
52 return; 76 return;
53 } 77 }
54 Runtime::fail(self, &trimmed, actual); 78 Runtime::fail_expect(self, &trimmed, actual);
55 } 79 }
56 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { 80 pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) {
57 let actual = format!("{:#?}\n", actual); 81 let actual = format!("{:#?}\n", actual);
@@ -69,7 +93,7 @@ impl Expect {
69 let mut target_line = None; 93 let mut target_line = None;
70 let mut line_start = 0; 94 let mut line_start = 0;
71 for (i, line) in lines_with_ends(file).enumerate() { 95 for (i, line) in lines_with_ends(file).enumerate() {
72 if i == self.line as usize - 1 { 96 if i == self.position.line as usize - 1 {
73 let pat = "expect![["; 97 let pat = "expect![[";
74 let offset = line.find(pat).unwrap(); 98 let offset = line.find(pat).unwrap();
75 let literal_start = line_start + offset + pat.len(); 99 let literal_start = line_start + offset + pat.len();
@@ -87,6 +111,25 @@ impl Expect {
87 } 111 }
88} 112}
89 113
114impl ExpectFile {
115 pub fn assert_eq(&self, actual: &str) {
116 let expected = self.read();
117 if actual == expected {
118 return;
119 }
120 Runtime::fail_file(self, &expected, actual);
121 }
122 fn read(&self) -> String {
123 fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n")
124 }
125 fn write(&self, contents: &str) {
126 fs::write(self.abs_path(), contents).unwrap()
127 }
128 fn abs_path(&self) -> PathBuf {
129 workspace_root().join(self.path)
130 }
131}
132
90#[derive(Default)] 133#[derive(Default)]
91struct Runtime { 134struct Runtime {
92 help_printed: bool, 135 help_printed: bool,
@@ -95,27 +138,39 @@ struct Runtime {
95static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default); 138static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default);
96 139
97impl Runtime { 140impl Runtime {
98 fn fail(expect: &Expect, expected: &str, actual: &str) { 141 fn fail_expect(expect: &Expect, expected: &str, actual: &str) {
99 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); 142 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
100 let mut updated = "";
101 if update_expect() { 143 if update_expect() {
102 updated = " (updated)"; 144 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position);
103 rt.per_file 145 rt.per_file
104 .entry(expect.file) 146 .entry(expect.position.file)
105 .or_insert_with(|| FileRuntime::new(expect)) 147 .or_insert_with(|| FileRuntime::new(expect))
106 .update(expect, actual); 148 .update(expect, actual);
149 return;
107 } 150 }
108 let print_help = !rt.help_printed && !update_expect(); 151 rt.panic(expect.position.to_string(), expected, actual);
109 rt.help_printed = true; 152 }
153
154 fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) {
155 let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
156 if update_expect() {
157 println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path);
158 expect.write(actual);
159 return;
160 }
161 rt.panic(expect.path.to_string(), expected, actual);
162 }
110 163
164 fn panic(&mut self, position: String, expected: &str, actual: &str) {
165 let print_help = !mem::replace(&mut self.help_printed, true);
111 let help = if print_help { HELP } else { "" }; 166 let help = if print_help { HELP } else { "" };
112 167
113 let diff = Changeset::new(actual, expected, "\n"); 168 let diff = Changeset::new(actual, expected, "\n");
114 169
115 println!( 170 println!(
116 "\n 171 "\n
117\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{} 172\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m
118 \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} 173 \x1b[1m\x1b[34m-->\x1b[0m {}
119{} 174{}
120\x1b[1mExpect\x1b[0m: 175\x1b[1mExpect\x1b[0m:
121---- 176----
@@ -132,7 +187,7 @@ impl Runtime {
132{} 187{}
133---- 188----
134", 189",
135 updated, expect.file, expect.line, expect.column, help, expected, actual, diff 190 position, help, expected, actual, diff
136 ); 191 );
137 // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. 192 // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise.
138 panic::resume_unwind(Box::new(())); 193 panic::resume_unwind(Box::new(()));
@@ -147,7 +202,7 @@ struct FileRuntime {
147 202
148impl FileRuntime { 203impl FileRuntime {
149 fn new(expect: &Expect) -> FileRuntime { 204 fn new(expect: &Expect) -> FileRuntime {
150 let path = workspace_root().join(expect.file); 205 let path = workspace_root().join(expect.position.file);
151 let original_text = fs::read_to_string(&path).unwrap(); 206 let original_text = fs::read_to_string(&path).unwrap();
152 let patchwork = Patchwork::new(original_text.clone()); 207 let patchwork = Patchwork::new(original_text.clone());
153 FileRuntime { path, original_text, patchwork } 208 FileRuntime { path, original_text, patchwork }