diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-06-30 09:34:08 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-06-30 09:34:08 +0100 |
commit | d13ded6cbc8b835807f08606db90bedf18643154 (patch) | |
tree | d77c573d3ed0cadc5d318b2cfe57b8e8d4426fa7 /crates | |
parent | 2bd717139918e15e537dcd833bb003e85d24b3d1 (diff) | |
parent | a4f934efa8e37d3bc822575109d103998ecd8fe1 (diff) |
Merge #5101
5101: Add expect -- a light-weight alternative to insta r=matklad a=matklad
This PR implements a small snapshot-testing library. Snapshot updating is done by setting an env var, or by using editor feature (which runs a test with env-var set).
Here's workflow for updating a failing test:
![expect](https://user-images.githubusercontent.com/1711539/85926956-28afa080-b8a3-11ea-9260-c6d0d8914d0b.gif)
Here's workflow for adding a new test:
![expect-fresh](https://user-images.githubusercontent.com/1711539/85926961-306f4500-b8a3-11ea-9369-f2373e327a3f.gif)
Note that colorized diffs are not implemented in this PR, but should be easy to add (we already use them in test_utils).
Main differences from insta (which is essential for rust-analyzer development, thanks @mitsuhiko!):
* self-updating tests, no need for a separate tool
* fewer features (only inline snapshots, no redactions)
* fewer deps (no yaml, no persistence)
* tighter integration with editor
* first-class snapshot object, which can be used to write test functions (as opposed to testing macros)
* trivial to tweak for rust-analyzer needs, by virtue of being a workspace member.
I think eventually we should converge to a single snapshot testing library, but I am not sure that `expect` is exactly right, so I suggest rolling with both insta and expect for some time (if folks agree that expect might be better in the first place!).
# Editor Integration Implementation
The thing I am most excited about is the ability to update a specific snapshot from the editor. I want this to be available to other snapshot-testing libraries (cc @mitsuhiko, @aaronabramov), so I want to document how this works.
The ideal UI here would be a code action (:bulb:). Unfortunately, it seems like it is impossible to implement without some kind of persistence (if you save test failures into some kind of a database, like insta does, than you can read the database from the editor plugin). Note that it is possible to highlight error by outputing error message in rustc's format. Unfortunately, one can't use the same trick to implement a quick fix.
For this reason, expect makes use of another rust-analyzer feature -- ability to run a single test at the cursor position. This does need some expect-specific code in rust-analyzer unfortunately. Specifically, if rust-analyzer notices that the cursor is on `expect!` macro, it adds a special flag to runnable's JSON. However, given #5017 it is possible to approximate this well-enough without rust-analyzer integration. Specifically, an extension can register a special runner which checks (using regexes) if rust-anlyzer runnable covers text with specific macro invocation and do special magic in that case.
closes #3835
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r-- | crates/expect/Cargo.toml | 10 | ||||
-rw-r--r-- | crates/expect/src/lib.rs | 293 | ||||
-rw-r--r-- | crates/ra_ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 41 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 25 | ||||
-rw-r--r-- | crates/rust-analyzer/src/lsp_ext.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 1 |
7 files changed, 361 insertions, 12 deletions
diff --git a/crates/expect/Cargo.toml b/crates/expect/Cargo.toml new file mode 100644 index 000000000..caee43106 --- /dev/null +++ b/crates/expect/Cargo.toml | |||
@@ -0,0 +1,10 @@ | |||
1 | [package] | ||
2 | name = "expect" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | |||
7 | [dependencies] | ||
8 | once_cell = "1" | ||
9 | difference = "2" | ||
10 | stdx = { path = "../stdx" } | ||
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs new file mode 100644 index 000000000..dd7b96aab --- /dev/null +++ b/crates/expect/src/lib.rs | |||
@@ -0,0 +1,293 @@ | |||
1 | //! Snapshot testing library, see | ||
2 | //! https://github.com/rust-analyzer/rust-analyzer/pull/5101 | ||
3 | use std::{ | ||
4 | collections::HashMap, | ||
5 | env, fmt, fs, | ||
6 | ops::Range, | ||
7 | panic, | ||
8 | path::{Path, PathBuf}, | ||
9 | sync::Mutex, | ||
10 | }; | ||
11 | |||
12 | use difference::Changeset; | ||
13 | use once_cell::sync::Lazy; | ||
14 | use stdx::{lines_with_ends, trim_indent}; | ||
15 | |||
16 | const HELP: &str = " | ||
17 | You can update all `expect![[]]` tests by: | ||
18 | |||
19 | env UPDATE_EXPECT=1 cargo test | ||
20 | |||
21 | To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer. | ||
22 | "; | ||
23 | |||
24 | fn update_expect() -> bool { | ||
25 | env::var("UPDATE_EXPECT").is_ok() | ||
26 | } | ||
27 | |||
28 | /// expect![[""]] | ||
29 | #[macro_export] | ||
30 | macro_rules! expect { | ||
31 | [[$lit:literal]] => {$crate::Expect { | ||
32 | file: file!(), | ||
33 | line: line!(), | ||
34 | column: column!(), | ||
35 | data: $lit, | ||
36 | }}; | ||
37 | [[]] => { $crate::expect![[""]] }; | ||
38 | } | ||
39 | |||
40 | #[derive(Debug)] | ||
41 | pub struct Expect { | ||
42 | pub file: &'static str, | ||
43 | pub line: u32, | ||
44 | pub column: u32, | ||
45 | pub data: &'static str, | ||
46 | } | ||
47 | |||
48 | impl Expect { | ||
49 | pub fn assert_eq(&self, actual: &str) { | ||
50 | let trimmed = self.trimmed(); | ||
51 | if &trimmed == actual { | ||
52 | return; | ||
53 | } | ||
54 | Runtime::fail(self, &trimmed, actual); | ||
55 | } | ||
56 | pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { | ||
57 | let actual = format!("{:#?}\n", actual); | ||
58 | self.assert_eq(&actual) | ||
59 | } | ||
60 | |||
61 | fn trimmed(&self) -> String { | ||
62 | if !self.data.contains('\n') { | ||
63 | return self.data.to_string(); | ||
64 | } | ||
65 | trim_indent(self.data) | ||
66 | } | ||
67 | |||
68 | fn locate(&self, file: &str) -> Location { | ||
69 | let mut target_line = None; | ||
70 | let mut line_start = 0; | ||
71 | for (i, line) in lines_with_ends(file).enumerate() { | ||
72 | if i == self.line as usize - 1 { | ||
73 | let pat = "expect![["; | ||
74 | let offset = line.find(pat).unwrap(); | ||
75 | let literal_start = line_start + offset + pat.len(); | ||
76 | let indent = line.chars().take_while(|&it| it == ' ').count(); | ||
77 | target_line = Some((literal_start, indent)); | ||
78 | break; | ||
79 | } | ||
80 | line_start += line.len(); | ||
81 | } | ||
82 | let (literal_start, line_indent) = target_line.unwrap(); | ||
83 | let literal_length = | ||
84 | file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`."); | ||
85 | let literal_range = literal_start..literal_start + literal_length; | ||
86 | Location { line_indent, literal_range } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | #[derive(Default)] | ||
91 | struct Runtime { | ||
92 | help_printed: bool, | ||
93 | per_file: HashMap<&'static str, FileRuntime>, | ||
94 | } | ||
95 | static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default); | ||
96 | |||
97 | impl Runtime { | ||
98 | fn fail(expect: &Expect, expected: &str, actual: &str) { | ||
99 | let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); | ||
100 | let mut updated = ""; | ||
101 | if update_expect() { | ||
102 | updated = " (updated)"; | ||
103 | rt.per_file | ||
104 | .entry(expect.file) | ||
105 | .or_insert_with(|| FileRuntime::new(expect)) | ||
106 | .update(expect, actual); | ||
107 | } | ||
108 | let print_help = !rt.help_printed && !update_expect(); | ||
109 | rt.help_printed = true; | ||
110 | |||
111 | let help = if print_help { HELP } else { "" }; | ||
112 | |||
113 | let diff = Changeset::new(actual, expected, "\n"); | ||
114 | |||
115 | println!( | ||
116 | "\n | ||
117 | \x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m{} | ||
118 | \x1b[1m\x1b[34m-->\x1b[0m {}:{}:{} | ||
119 | {} | ||
120 | \x1b[1mExpect\x1b[0m: | ||
121 | ---- | ||
122 | {} | ||
123 | ---- | ||
124 | |||
125 | \x1b[1mActual\x1b[0m: | ||
126 | ---- | ||
127 | {} | ||
128 | ---- | ||
129 | |||
130 | \x1b[1mDiff\x1b[0m: | ||
131 | ---- | ||
132 | {} | ||
133 | ---- | ||
134 | ", | ||
135 | updated, expect.file, expect.line, expect.column, help, expected, actual, diff | ||
136 | ); | ||
137 | // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. | ||
138 | panic::resume_unwind(Box::new(())); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | struct FileRuntime { | ||
143 | path: PathBuf, | ||
144 | original_text: String, | ||
145 | patchwork: Patchwork, | ||
146 | } | ||
147 | |||
148 | impl FileRuntime { | ||
149 | fn new(expect: &Expect) -> FileRuntime { | ||
150 | let path = workspace_root().join(expect.file); | ||
151 | let original_text = fs::read_to_string(&path).unwrap(); | ||
152 | let patchwork = Patchwork::new(original_text.clone()); | ||
153 | FileRuntime { path, original_text, patchwork } | ||
154 | } | ||
155 | fn update(&mut self, expect: &Expect, actual: &str) { | ||
156 | let loc = expect.locate(&self.original_text); | ||
157 | let patch = format_patch(loc.line_indent.clone(), actual); | ||
158 | self.patchwork.patch(loc.literal_range, &patch); | ||
159 | fs::write(&self.path, &self.patchwork.text).unwrap() | ||
160 | } | ||
161 | } | ||
162 | |||
163 | #[derive(Debug)] | ||
164 | struct Location { | ||
165 | line_indent: usize, | ||
166 | literal_range: Range<usize>, | ||
167 | } | ||
168 | |||
169 | #[derive(Debug)] | ||
170 | struct Patchwork { | ||
171 | text: String, | ||
172 | indels: Vec<(Range<usize>, usize)>, | ||
173 | } | ||
174 | |||
175 | impl Patchwork { | ||
176 | fn new(text: String) -> Patchwork { | ||
177 | Patchwork { text, indels: Vec::new() } | ||
178 | } | ||
179 | fn patch(&mut self, mut range: Range<usize>, patch: &str) { | ||
180 | self.indels.push((range.clone(), patch.len())); | ||
181 | self.indels.sort_by_key(|(delete, _insert)| delete.start); | ||
182 | |||
183 | let (delete, insert) = self | ||
184 | .indels | ||
185 | .iter() | ||
186 | .take_while(|(delete, _)| delete.start < range.start) | ||
187 | .map(|(delete, insert)| (delete.end - delete.start, insert)) | ||
188 | .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2)); | ||
189 | |||
190 | for pos in &mut [&mut range.start, &mut range.end] { | ||
191 | **pos -= delete; | ||
192 | **pos += insert; | ||
193 | } | ||
194 | |||
195 | self.text.replace_range(range, &patch); | ||
196 | } | ||
197 | } | ||
198 | |||
199 | fn format_patch(line_indent: usize, patch: &str) -> String { | ||
200 | let mut max_hashes = 0; | ||
201 | let mut cur_hashes = 0; | ||
202 | for byte in patch.bytes() { | ||
203 | if byte != b'#' { | ||
204 | cur_hashes = 0; | ||
205 | continue; | ||
206 | } | ||
207 | cur_hashes += 1; | ||
208 | max_hashes = max_hashes.max(cur_hashes); | ||
209 | } | ||
210 | let hashes = &"#".repeat(max_hashes + 1); | ||
211 | let indent = &" ".repeat(line_indent); | ||
212 | let is_multiline = patch.contains('\n'); | ||
213 | |||
214 | let mut buf = String::new(); | ||
215 | buf.push('r'); | ||
216 | buf.push_str(hashes); | ||
217 | buf.push('"'); | ||
218 | if is_multiline { | ||
219 | buf.push('\n'); | ||
220 | } | ||
221 | let mut final_newline = false; | ||
222 | for line in lines_with_ends(patch) { | ||
223 | if is_multiline { | ||
224 | buf.push_str(indent); | ||
225 | buf.push_str(" "); | ||
226 | } | ||
227 | buf.push_str(line); | ||
228 | final_newline = line.ends_with('\n'); | ||
229 | } | ||
230 | if final_newline { | ||
231 | buf.push_str(indent); | ||
232 | } | ||
233 | buf.push('"'); | ||
234 | buf.push_str(hashes); | ||
235 | buf | ||
236 | } | ||
237 | |||
238 | fn workspace_root() -> PathBuf { | ||
239 | Path::new( | ||
240 | &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), | ||
241 | ) | ||
242 | .ancestors() | ||
243 | .nth(2) | ||
244 | .unwrap() | ||
245 | .to_path_buf() | ||
246 | } | ||
247 | |||
248 | #[cfg(test)] | ||
249 | mod tests { | ||
250 | use super::*; | ||
251 | |||
252 | #[test] | ||
253 | fn test_format_patch() { | ||
254 | let patch = format_patch(0, "hello\nworld\n"); | ||
255 | expect![[r##" | ||
256 | r#" | ||
257 | hello | ||
258 | world | ||
259 | "#"##]] | ||
260 | .assert_eq(&patch); | ||
261 | |||
262 | let patch = format_patch(4, "single line"); | ||
263 | expect![[r##"r#"single line"#"##]].assert_eq(&patch); | ||
264 | } | ||
265 | |||
266 | #[test] | ||
267 | fn test_patchwork() { | ||
268 | let mut patchwork = Patchwork::new("one two three".to_string()); | ||
269 | patchwork.patch(4..7, "zwei"); | ||
270 | patchwork.patch(0..3, "один"); | ||
271 | patchwork.patch(8..13, "3"); | ||
272 | expect![[r#" | ||
273 | Patchwork { | ||
274 | text: "один zwei 3", | ||
275 | indels: [ | ||
276 | ( | ||
277 | 0..3, | ||
278 | 8, | ||
279 | ), | ||
280 | ( | ||
281 | 4..7, | ||
282 | 4, | ||
283 | ), | ||
284 | ( | ||
285 | 8..13, | ||
286 | 1, | ||
287 | ), | ||
288 | ], | ||
289 | } | ||
290 | "#]] | ||
291 | .assert_debug_eq(&patchwork); | ||
292 | } | ||
293 | } | ||
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index bbc6a5c9b..8e8892309 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml | |||
@@ -28,6 +28,7 @@ ra_cfg = { path = "../ra_cfg" } | |||
28 | ra_fmt = { path = "../ra_fmt" } | 28 | ra_fmt = { path = "../ra_fmt" } |
29 | ra_prof = { path = "../ra_prof" } | 29 | ra_prof = { path = "../ra_prof" } |
30 | test_utils = { path = "../test_utils" } | 30 | test_utils = { path = "../test_utils" } |
31 | expect = { path = "../expect" } | ||
31 | ra_assists = { path = "../ra_assists" } | 32 | ra_assists = { path = "../ra_assists" } |
32 | ra_ssr = { path = "../ra_ssr" } | 33 | ra_ssr = { path = "../ra_ssr" } |
33 | 34 | ||
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index bea7fbfa7..969d5e0ff 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -103,6 +103,7 @@ pub(crate) fn reference_definition( | |||
103 | 103 | ||
104 | #[cfg(test)] | 104 | #[cfg(test)] |
105 | mod tests { | 105 | mod tests { |
106 | use expect::{expect, Expect}; | ||
106 | use test_utils::assert_eq_text; | 107 | use test_utils::assert_eq_text; |
107 | 108 | ||
108 | use crate::mock_analysis::analysis_and_position; | 109 | use crate::mock_analysis::analysis_and_position; |
@@ -142,16 +143,40 @@ mod tests { | |||
142 | nav.assert_match(expected); | 143 | nav.assert_match(expected); |
143 | } | 144 | } |
144 | 145 | ||
146 | fn check(ra_fixture: &str, expect: Expect) { | ||
147 | let (analysis, pos) = analysis_and_position(ra_fixture); | ||
148 | |||
149 | let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; | ||
150 | if navs.len() == 0 { | ||
151 | panic!("unresolved reference") | ||
152 | } | ||
153 | assert_eq!(navs.len(), 1); | ||
154 | |||
155 | let nav = navs.pop().unwrap(); | ||
156 | let file_text = analysis.file_text(nav.file_id()).unwrap(); | ||
157 | |||
158 | let mut actual = nav.debug_render(); | ||
159 | actual += "\n"; | ||
160 | actual += &file_text[nav.full_range()].to_string(); | ||
161 | if let Some(focus) = nav.focus_range() { | ||
162 | actual += "|"; | ||
163 | actual += &file_text[focus]; | ||
164 | actual += "\n"; | ||
165 | } | ||
166 | expect.assert_eq(&actual); | ||
167 | } | ||
168 | |||
145 | #[test] | 169 | #[test] |
146 | fn goto_def_in_items() { | 170 | fn goto_def_in_items() { |
147 | check_goto( | 171 | check( |
148 | " | 172 | r#" |
149 | //- /lib.rs | 173 | struct Foo; |
150 | struct Foo; | 174 | enum E { X(Foo<|>) } |
151 | enum E { X(Foo<|>) } | 175 | "#, |
152 | ", | 176 | expect![[r#" |
153 | "Foo STRUCT_DEF FileId(1) 0..11 7..10", | 177 | Foo STRUCT_DEF FileId(1) 0..11 7..10 |
154 | "struct Foo;|Foo", | 178 | struct Foo;|Foo |
179 | "#]], | ||
155 | ); | 180 | ); |
156 | } | 181 | } |
157 | 182 | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index e35a5e846..0940fcc28 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -23,7 +23,7 @@ use ra_ide::{ | |||
23 | }; | 23 | }; |
24 | use ra_prof::profile; | 24 | use ra_prof::profile; |
25 | use ra_project_model::TargetKind; | 25 | use ra_project_model::TargetKind; |
26 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize}; | 26 | use ra_syntax::{algo, ast, AstNode, SyntaxKind, TextRange, TextSize}; |
27 | use serde::{Deserialize, Serialize}; | 27 | use serde::{Deserialize, Serialize}; |
28 | use serde_json::to_value; | 28 | use serde_json::to_value; |
29 | use stdx::{format_to, split_delim}; | 29 | use stdx::{format_to, split_delim}; |
@@ -407,8 +407,19 @@ pub(crate) fn handle_runnables( | |||
407 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | 407 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; |
408 | let line_index = snap.analysis.file_line_index(file_id)?; | 408 | let line_index = snap.analysis.file_line_index(file_id)?; |
409 | let offset = params.position.map(|it| from_proto::offset(&line_index, it)); | 409 | let offset = params.position.map(|it| from_proto::offset(&line_index, it)); |
410 | let mut res = Vec::new(); | ||
411 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; | 410 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; |
411 | |||
412 | let expect_test = match offset { | ||
413 | Some(offset) => { | ||
414 | let source_file = snap.analysis.parse(file_id)?; | ||
415 | algo::find_node_at_offset::<ast::MacroCall>(source_file.syntax(), offset) | ||
416 | .and_then(|it| it.path()?.segment()?.name_ref()) | ||
417 | .map_or(false, |it| it.text() == "expect") | ||
418 | } | ||
419 | None => false, | ||
420 | }; | ||
421 | |||
422 | let mut res = Vec::new(); | ||
412 | for runnable in snap.analysis.runnables(file_id)? { | 423 | for runnable in snap.analysis.runnables(file_id)? { |
413 | if let Some(offset) = offset { | 424 | if let Some(offset) = offset { |
414 | if !runnable.nav.full_range().contains_inclusive(offset) { | 425 | if !runnable.nav.full_range().contains_inclusive(offset) { |
@@ -418,8 +429,12 @@ pub(crate) fn handle_runnables( | |||
418 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | 429 | if should_skip_target(&runnable, cargo_spec.as_ref()) { |
419 | continue; | 430 | continue; |
420 | } | 431 | } |
421 | 432 | let mut runnable = to_proto::runnable(&snap, file_id, runnable)?; | |
422 | res.push(to_proto::runnable(&snap, file_id, runnable)?); | 433 | if expect_test { |
434 | runnable.label = format!("{} + expect", runnable.label); | ||
435 | runnable.args.expect_test = Some(true); | ||
436 | } | ||
437 | res.push(runnable); | ||
423 | } | 438 | } |
424 | 439 | ||
425 | // Add `cargo check` and `cargo test` for the whole package | 440 | // Add `cargo check` and `cargo test` for the whole package |
@@ -438,6 +453,7 @@ pub(crate) fn handle_runnables( | |||
438 | spec.package.clone(), | 453 | spec.package.clone(), |
439 | ], | 454 | ], |
440 | executable_args: Vec::new(), | 455 | executable_args: Vec::new(), |
456 | expect_test: None, | ||
441 | }, | 457 | }, |
442 | }) | 458 | }) |
443 | } | 459 | } |
@@ -451,6 +467,7 @@ pub(crate) fn handle_runnables( | |||
451 | workspace_root: None, | 467 | workspace_root: None, |
452 | cargo_args: vec!["check".to_string(), "--workspace".to_string()], | 468 | cargo_args: vec!["check".to_string(), "--workspace".to_string()], |
453 | executable_args: Vec::new(), | 469 | executable_args: Vec::new(), |
470 | expect_test: None, | ||
454 | }, | 471 | }, |
455 | }); | 472 | }); |
456 | } | 473 | } |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 1371f6cb4..1befe678c 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -161,6 +161,8 @@ pub struct CargoRunnable { | |||
161 | pub cargo_args: Vec<String>, | 161 | pub cargo_args: Vec<String>, |
162 | // stuff after -- | 162 | // stuff after -- |
163 | pub executable_args: Vec<String>, | 163 | pub executable_args: Vec<String>, |
164 | #[serde(skip_serializing_if = "Option::is_none")] | ||
165 | pub expect_test: Option<bool>, | ||
164 | } | 166 | } |
165 | 167 | ||
166 | pub enum InlayHints {} | 168 | pub enum InlayHints {} |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index f6cb8e4bb..a03222ae9 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -666,6 +666,7 @@ pub(crate) fn runnable( | |||
666 | workspace_root: workspace_root.map(|it| it.into()), | 666 | workspace_root: workspace_root.map(|it| it.into()), |
667 | cargo_args, | 667 | cargo_args, |
668 | executable_args, | 668 | executable_args, |
669 | expect_test: None, | ||
669 | }, | 670 | }, |
670 | }) | 671 | }) |
671 | } | 672 | } |