Skip to content

Commit 1a9da62

Browse files
author
duncan
committed
fix testing for packages with multiple targets
fix test running by invoking cargo per package remove hack_recover_crate_name make clippy happy
1 parent 039ac84 commit 1a9da62

File tree

7 files changed

+207
-127
lines changed

7 files changed

+207
-127
lines changed

crates/project-model/src/cargo_workspace.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! See [`CargoWorkspace`].
22
3-
use std::ops;
43
use std::str::from_utf8;
4+
use std::{fmt, ops};
55

66
use anyhow::Context;
77
use base_db::Env;
@@ -228,6 +228,25 @@ pub enum TargetKind {
228228
Other,
229229
}
230230

231+
impl fmt::Display for TargetKind {
232+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233+
write!(
234+
f,
235+
"{}",
236+
match self {
237+
TargetKind::Bin => "bin",
238+
TargetKind::Lib { is_proc_macro: true } => "proc-macro",
239+
TargetKind::Lib { is_proc_macro: false } => "lib",
240+
TargetKind::Example => "example",
241+
TargetKind::Test => "test",
242+
TargetKind::Bench => "bench",
243+
TargetKind::BuildScript => "custom-build",
244+
TargetKind::Other => "other",
245+
}
246+
)
247+
}
248+
}
249+
231250
impl TargetKind {
232251
fn new(kinds: &[String]) -> TargetKind {
233252
for kind in kinds {

crates/rust-analyzer/src/command.rs

+55-8
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,60 @@ use process_wrap::std::{StdChildWrapper, StdCommandWrap};
1414
use stdx::process::streaming_output;
1515

1616
/// Cargo output is structured as a one JSON per line. This trait abstracts parsing one line of
17-
/// cargo output into a Rust data type.
17+
/// cargo output into a Rust data type where the data is self-describing.
1818
pub(crate) trait ParseFromLine: Sized + Send + 'static {
1919
fn from_line(line: &str, error: &mut String) -> Option<Self>;
2020
fn from_eof() -> Option<Self>;
2121
}
2222

23+
/// When Cargo output is not fully self-describing, a parser object can supply sufficient
24+
/// context to interpret the output correctly.
25+
pub(crate) trait CargoParser<T>: Send + 'static {
26+
fn from_line(&self, line: &str, error: &mut String) -> Option<T>;
27+
fn from_eof(&self) -> Option<T>;
28+
}
29+
30+
/// Allow using the ParseFromLine trait as a parser object
31+
struct StatelessParser<T: ParseFromLine> {
32+
marker: PhantomData<T>,
33+
}
34+
35+
impl<T: ParseFromLine> StatelessParser<T> {
36+
pub(crate) fn new() -> Self {
37+
StatelessParser { marker: PhantomData }
38+
}
39+
}
40+
41+
impl<T: ParseFromLine> CargoParser<T> for StatelessParser<T> {
42+
fn from_line(&self, line: &str, error: &mut String) -> Option<T> {
43+
T::from_line(line, error)
44+
}
45+
46+
fn from_eof(&self) -> Option<T> {
47+
T::from_eof()
48+
}
49+
}
50+
2351
struct CargoActor<T> {
52+
parser: Box<dyn CargoParser<T>>,
2453
sender: Sender<T>,
2554
stdout: ChildStdout,
2655
stderr: ChildStderr,
2756
}
2857

29-
impl<T: ParseFromLine> CargoActor<T> {
30-
fn new(sender: Sender<T>, stdout: ChildStdout, stderr: ChildStderr) -> Self {
31-
CargoActor { sender, stdout, stderr }
58+
impl<T: Sized + Send + 'static> CargoActor<T> {
59+
fn new(
60+
parser: impl CargoParser<T>,
61+
sender: Sender<T>,
62+
stdout: ChildStdout,
63+
stderr: ChildStderr,
64+
) -> Self {
65+
let parser = Box::new(parser);
66+
CargoActor { parser, sender, stdout, stderr }
3267
}
68+
}
3369

70+
impl<T: Sized + Send + 'static> CargoActor<T> {
3471
fn run(self) -> io::Result<(bool, String)> {
3572
// We manually read a line at a time, instead of using serde's
3673
// stream deserializers, because the deserializer cannot recover
@@ -47,7 +84,7 @@ impl<T: ParseFromLine> CargoActor<T> {
4784
let mut read_at_least_one_stderr_message = false;
4885
let process_line = |line: &str, error: &mut String| {
4986
// Try to deserialize a message from Cargo or Rustc.
50-
if let Some(t) = T::from_line(line, error) {
87+
if let Some(t) = self.parser.from_line(line, error) {
5188
self.sender.send(t).unwrap();
5289
true
5390
} else {
@@ -68,7 +105,7 @@ impl<T: ParseFromLine> CargoActor<T> {
68105
}
69106
},
70107
&mut || {
71-
if let Some(t) = T::from_eof() {
108+
if let Some(t) = self.parser.from_eof() {
72109
self.sender.send(t).unwrap();
73110
}
74111
},
@@ -117,7 +154,17 @@ impl<T> fmt::Debug for CommandHandle<T> {
117154
}
118155

119156
impl<T: ParseFromLine> CommandHandle<T> {
120-
pub(crate) fn spawn(mut command: Command, sender: Sender<T>) -> std::io::Result<Self> {
157+
pub(crate) fn spawn(command: Command, sender: Sender<T>) -> std::io::Result<Self> {
158+
Self::spawn_with_parser(command, StatelessParser::<T>::new(), sender)
159+
}
160+
}
161+
162+
impl<T: Sized + Send + 'static> CommandHandle<T> {
163+
pub(crate) fn spawn_with_parser(
164+
mut command: Command,
165+
parser: impl CargoParser<T>,
166+
sender: Sender<T>,
167+
) -> std::io::Result<Self> {
121168
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
122169

123170
let program = command.get_program().into();
@@ -134,7 +181,7 @@ impl<T: ParseFromLine> CommandHandle<T> {
134181
let stdout = child.0.stdout().take().unwrap();
135182
let stderr = child.0.stderr().take().unwrap();
136183

137-
let actor = CargoActor::<T>::new(sender, stdout, stderr);
184+
let actor = CargoActor::<T>::new(parser, sender, stdout, stderr);
138185
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
139186
.name("CommandHandle".to_owned())
140187
.spawn(move || actor.run())

crates/rust-analyzer/src/hack_recover_crate_name.rs

-25
This file was deleted.

crates/rust-analyzer/src/handlers/request.rs

+65-54
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ use crate::{
3636
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
3737
diagnostics::convert_diagnostic,
3838
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
39-
hack_recover_crate_name,
4039
line_index::LineEndings,
4140
lsp::{
4241
ext::{
@@ -194,74 +193,88 @@ pub(crate) fn handle_view_item_tree(
194193
Ok(res)
195194
}
196195

197-
// cargo test requires the real package name which might contain hyphens but
198-
// the test identifier passed to this function is the namespace form where hyphens
199-
// are replaced with underscores so we have to reverse this and find the real package name
200-
fn find_package_name(namespace_root: &str, cargo: &CargoWorkspace) -> Option<String> {
196+
// cargo test requires:
197+
// - the package name - the root of the test identifier supplied to this handler can be
198+
// a package or a target inside a package.
199+
// - the target name - if the test identifier is a target, it's needed in addition to the
200+
// package name to run the right test
201+
// - real names - the test identifier uses the namespace form where hyphens are replaced with
202+
// underscores. cargo test requires the real name.
203+
// - the target kind e.g. bin or lib
204+
fn find_test_target(namespace_root: &str, cargo: &CargoWorkspace) -> Option<TestTarget> {
201205
cargo.packages().find_map(|p| {
202206
let package_name = &cargo[p].name;
203-
if package_name.replace('-', "_") == namespace_root {
204-
Some(package_name.clone())
205-
} else {
206-
None
207+
for target in cargo[p].targets.iter() {
208+
let target_name = &cargo[*target].name;
209+
if target_name.replace('-', "_") == namespace_root {
210+
return Some(TestTarget {
211+
package: package_name.clone(),
212+
target: target_name.clone(),
213+
kind: cargo[*target].kind,
214+
});
215+
}
207216
}
217+
None
208218
})
209219
}
210220

221+
fn get_all_targets(cargo: &CargoWorkspace) -> Vec<TestTarget> {
222+
cargo
223+
.packages()
224+
.flat_map(|p| {
225+
let package_name = &cargo[p].name;
226+
cargo[p].targets.iter().map(|target| {
227+
let target_name = &cargo[*target].name;
228+
TestTarget {
229+
package: package_name.clone(),
230+
target: target_name.clone(),
231+
kind: cargo[*target].kind,
232+
}
233+
})
234+
})
235+
.collect()
236+
}
237+
211238
pub(crate) fn handle_run_test(
212239
state: &mut GlobalState,
213240
params: lsp_ext::RunTestParams,
214241
) -> anyhow::Result<()> {
215242
if let Some(_session) = state.test_run_session.take() {
216243
state.send_notification::<lsp_ext::EndRunTest>(());
217244
}
218-
// We detect the lowest common ancestor of all included tests, and
219-
// run it. We ignore excluded tests for now, the client will handle
220-
// it for us.
221-
let lca = match params.include {
222-
Some(tests) => tests
223-
.into_iter()
224-
.reduce(|x, y| {
225-
let mut common_prefix = "".to_owned();
226-
for (xc, yc) in x.chars().zip(y.chars()) {
227-
if xc != yc {
228-
break;
229-
}
230-
common_prefix.push(xc);
231-
}
232-
common_prefix
233-
})
234-
.unwrap_or_default(),
235-
None => "".to_owned(),
236-
};
237-
let (namespace_root, test_path) = if lca.is_empty() {
238-
(None, None)
239-
} else if let Some((namespace_root, path)) = lca.split_once("::") {
240-
(Some(namespace_root), Some(path))
241-
} else {
242-
(Some(lca.as_str()), None)
243-
};
245+
244246
let mut handles = vec![];
245247
for ws in &*state.workspaces {
246248
if let ProjectWorkspaceKind::Cargo { cargo, .. } = &ws.kind {
247-
let test_target = if let Some(namespace_root) = namespace_root {
248-
if let Some(package_name) = find_package_name(namespace_root, cargo) {
249-
TestTarget::Package(package_name)
250-
} else {
251-
TestTarget::Workspace
252-
}
253-
} else {
254-
TestTarget::Workspace
249+
let tests = match params.include {
250+
Some(ref include) => include
251+
.iter()
252+
.filter_map(|test| {
253+
let (root, remainder) = match test.split_once("::") {
254+
Some((root, remainder)) => (root.to_owned(), Some(remainder)),
255+
None => (test.clone(), None),
256+
};
257+
if let Some(target) = find_test_target(&root, cargo) {
258+
Some((target, remainder))
259+
} else {
260+
tracing::error!("Test target not found for: {test}");
261+
None
262+
}
263+
})
264+
.collect_vec(),
265+
None => get_all_targets(cargo).into_iter().map(|target| (target, None)).collect(),
255266
};
256267

257-
let handle = CargoTestHandle::new(
258-
test_path,
259-
state.config.cargo_test_options(None),
260-
cargo.workspace_root(),
261-
test_target,
262-
state.test_run_sender.clone(),
263-
)?;
264-
handles.push(handle);
268+
for (target, path) in tests {
269+
let handle = CargoTestHandle::new(
270+
path,
271+
state.config.cargo_test_options(None),
272+
cargo.workspace_root(),
273+
target,
274+
state.test_run_sender.clone(),
275+
)?;
276+
handles.push(handle);
277+
}
265278
}
266279
}
267280
// Each process send finished signal twice, once for stdout and once for stderr
@@ -285,9 +298,7 @@ pub(crate) fn handle_discover_test(
285298
}
286299
None => (snap.analysis.discover_test_roots()?, None),
287300
};
288-
for t in &tests {
289-
hack_recover_crate_name::insert_name(t.id.clone());
290-
}
301+
291302
Ok(lsp_ext::DiscoverTestResults {
292303
tests: tests
293304
.into_iter()

crates/rust-analyzer/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ mod command;
1515
mod diagnostics;
1616
mod discover;
1717
mod flycheck;
18-
mod hack_recover_crate_name;
1918
mod line_index;
2019
mod main_loop;
2120
mod mem_docs;

0 commit comments

Comments
 (0)