mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-27 10:17:15 +00:00
fix testing for packages with multiple targets
fix test running by invoking cargo per package remove hack_recover_crate_name make clippy happy fix testing for packages with multiple targets fix test running by invoking cargo per package remove hack_recover_crate_name make clippy happy fix testing for packages with multiple targets fix bad merge replace TargetKind::fmt with TargetKind::as_cargo_target to clarify intention dedupulicate requested test runs replace ParseFromLine with CargoParser formatting - remove trailing space formatting for rustfmt CI
This commit is contained in:
parent
1795a85be3
commit
2f5a7a619d
9 changed files with 186 additions and 143 deletions
|
|
@ -224,6 +224,7 @@ pub enum TargetKind {
|
||||||
Example,
|
Example,
|
||||||
Test,
|
Test,
|
||||||
Bench,
|
Bench,
|
||||||
|
/// Cargo calls this kind `custom-build`
|
||||||
BuildScript,
|
BuildScript,
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +253,22 @@ impl TargetKind {
|
||||||
pub fn is_proc_macro(self) -> bool {
|
pub fn is_proc_macro(self) -> bool {
|
||||||
matches!(self, TargetKind::Lib { is_proc_macro: true })
|
matches!(self, TargetKind::Lib { is_proc_macro: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this is a valid cargo target, returns the name cargo uses in command line arguments
|
||||||
|
/// and output, otherwise None.
|
||||||
|
/// https://docs.rs/cargo_metadata/latest/cargo_metadata/enum.TargetKind.html
|
||||||
|
pub fn as_cargo_target(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
TargetKind::Bin => Some("bin"),
|
||||||
|
TargetKind::Lib { is_proc_macro: true } => Some("proc-macro"),
|
||||||
|
TargetKind::Lib { is_proc_macro: false } => Some("lib"),
|
||||||
|
TargetKind::Example => Some("example"),
|
||||||
|
TargetKind::Test => Some("test"),
|
||||||
|
TargetKind::Bench => Some("bench"),
|
||||||
|
TargetKind::BuildScript => Some("custom-build"),
|
||||||
|
TargetKind::Other => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
#[derive(Default, Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
|
||||||
|
|
@ -13,24 +13,33 @@ use crossbeam_channel::Sender;
|
||||||
use process_wrap::std::{StdChildWrapper, StdCommandWrap};
|
use process_wrap::std::{StdChildWrapper, StdCommandWrap};
|
||||||
use stdx::process::streaming_output;
|
use stdx::process::streaming_output;
|
||||||
|
|
||||||
/// Cargo output is structured as a one JSON per line. This trait abstracts parsing one line of
|
/// Cargo output is structured as one JSON per line. This trait abstracts parsing one line of
|
||||||
/// cargo output into a Rust data type.
|
/// cargo output into a Rust data type
|
||||||
pub(crate) trait ParseFromLine: Sized + Send + 'static {
|
pub(crate) trait CargoParser<T>: Send + 'static {
|
||||||
fn from_line(line: &str, error: &mut String) -> Option<Self>;
|
fn from_line(&self, line: &str, error: &mut String) -> Option<T>;
|
||||||
fn from_eof() -> Option<Self>;
|
fn from_eof(&self) -> Option<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CargoActor<T> {
|
struct CargoActor<T> {
|
||||||
|
parser: Box<dyn CargoParser<T>>,
|
||||||
sender: Sender<T>,
|
sender: Sender<T>,
|
||||||
stdout: ChildStdout,
|
stdout: ChildStdout,
|
||||||
stderr: ChildStderr,
|
stderr: ChildStderr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ParseFromLine> CargoActor<T> {
|
impl<T: Sized + Send + 'static> CargoActor<T> {
|
||||||
fn new(sender: Sender<T>, stdout: ChildStdout, stderr: ChildStderr) -> Self {
|
fn new(
|
||||||
CargoActor { sender, stdout, stderr }
|
parser: impl CargoParser<T>,
|
||||||
|
sender: Sender<T>,
|
||||||
|
stdout: ChildStdout,
|
||||||
|
stderr: ChildStderr,
|
||||||
|
) -> Self {
|
||||||
|
let parser = Box::new(parser);
|
||||||
|
CargoActor { parser, sender, stdout, stderr }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sized + Send + 'static> CargoActor<T> {
|
||||||
fn run(self) -> io::Result<(bool, String)> {
|
fn run(self) -> io::Result<(bool, String)> {
|
||||||
// We manually read a line at a time, instead of using serde's
|
// We manually read a line at a time, instead of using serde's
|
||||||
// stream deserializers, because the deserializer cannot recover
|
// stream deserializers, because the deserializer cannot recover
|
||||||
|
|
@ -47,7 +56,7 @@ impl<T: ParseFromLine> CargoActor<T> {
|
||||||
let mut read_at_least_one_stderr_message = false;
|
let mut read_at_least_one_stderr_message = false;
|
||||||
let process_line = |line: &str, error: &mut String| {
|
let process_line = |line: &str, error: &mut String| {
|
||||||
// Try to deserialize a message from Cargo or Rustc.
|
// Try to deserialize a message from Cargo or Rustc.
|
||||||
if let Some(t) = T::from_line(line, error) {
|
if let Some(t) = self.parser.from_line(line, error) {
|
||||||
self.sender.send(t).unwrap();
|
self.sender.send(t).unwrap();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -68,7 +77,7 @@ impl<T: ParseFromLine> CargoActor<T> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
&mut || {
|
&mut || {
|
||||||
if let Some(t) = T::from_eof() {
|
if let Some(t) = self.parser.from_eof() {
|
||||||
self.sender.send(t).unwrap();
|
self.sender.send(t).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -116,8 +125,12 @@ impl<T> fmt::Debug for CommandHandle<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ParseFromLine> CommandHandle<T> {
|
impl<T: Sized + Send + 'static> CommandHandle<T> {
|
||||||
pub(crate) fn spawn(mut command: Command, sender: Sender<T>) -> std::io::Result<Self> {
|
pub(crate) fn spawn(
|
||||||
|
mut command: Command,
|
||||||
|
parser: impl CargoParser<T>,
|
||||||
|
sender: Sender<T>,
|
||||||
|
) -> std::io::Result<Self> {
|
||||||
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
|
command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
|
||||||
|
|
||||||
let program = command.get_program().into();
|
let program = command.get_program().into();
|
||||||
|
|
@ -134,7 +147,7 @@ impl<T: ParseFromLine> CommandHandle<T> {
|
||||||
let stdout = child.0.stdout().take().unwrap();
|
let stdout = child.0.stdout().take().unwrap();
|
||||||
let stderr = child.0.stderr().take().unwrap();
|
let stderr = child.0.stderr().take().unwrap();
|
||||||
|
|
||||||
let actor = CargoActor::<T>::new(sender, stdout, stderr);
|
let actor = CargoActor::<T>::new(parser, sender, stdout, stderr);
|
||||||
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
|
let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
|
||||||
.name("CommandHandle".to_owned())
|
.name("CommandHandle".to_owned())
|
||||||
.spawn(move || actor.run())
|
.spawn(move || actor.run())
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tracing::{info_span, span::EnteredSpan};
|
use tracing::{info_span, span::EnteredSpan};
|
||||||
|
|
||||||
use crate::command::{CommandHandle, ParseFromLine};
|
use crate::command::{CargoParser, CommandHandle};
|
||||||
|
|
||||||
pub(crate) const ARG_PLACEHOLDER: &str = "{arg}";
|
pub(crate) const ARG_PLACEHOLDER: &str = "{arg}";
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ impl DiscoverCommand {
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
|
|
||||||
Ok(DiscoverHandle {
|
Ok(DiscoverHandle {
|
||||||
_handle: CommandHandle::spawn(cmd, self.sender.clone())?,
|
_handle: CommandHandle::spawn(cmd, DiscoverProjectParser, self.sender.clone())?,
|
||||||
span: info_span!("discover_command").entered(),
|
span: info_span!("discover_command").entered(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +115,10 @@ impl DiscoverProjectMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseFromLine for DiscoverProjectMessage {
|
struct DiscoverProjectParser;
|
||||||
fn from_line(line: &str, _error: &mut String) -> Option<Self> {
|
|
||||||
|
impl CargoParser<DiscoverProjectMessage> for DiscoverProjectParser {
|
||||||
|
fn from_line(&self, line: &str, _error: &mut String) -> Option<DiscoverProjectMessage> {
|
||||||
// can the line even be deserialized as JSON?
|
// can the line even be deserialized as JSON?
|
||||||
let Ok(data) = serde_json::from_str::<Value>(line) else {
|
let Ok(data) = serde_json::from_str::<Value>(line) else {
|
||||||
let err = DiscoverProjectData::Error { error: line.to_owned(), source: None };
|
let err = DiscoverProjectData::Error { error: line.to_owned(), source: None };
|
||||||
|
|
@ -131,7 +133,7 @@ impl ParseFromLine for DiscoverProjectMessage {
|
||||||
Some(msg)
|
Some(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_eof() -> Option<Self> {
|
fn from_eof(&self) -> Option<DiscoverProjectMessage> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub(crate) use cargo_metadata::diagnostic::{
|
||||||
use toolchain::Tool;
|
use toolchain::Tool;
|
||||||
use triomphe::Arc;
|
use triomphe::Arc;
|
||||||
|
|
||||||
use crate::command::{CommandHandle, ParseFromLine};
|
use crate::command::{CargoParser, CommandHandle};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub(crate) enum InvocationStrategy {
|
pub(crate) enum InvocationStrategy {
|
||||||
|
|
@ -324,7 +324,7 @@ impl FlycheckActor {
|
||||||
|
|
||||||
tracing::debug!(?command, "will restart flycheck");
|
tracing::debug!(?command, "will restart flycheck");
|
||||||
let (sender, receiver) = unbounded();
|
let (sender, receiver) = unbounded();
|
||||||
match CommandHandle::spawn(command, sender) {
|
match CommandHandle::spawn(command, CargoCheckParser, sender) {
|
||||||
Ok(command_handle) => {
|
Ok(command_handle) => {
|
||||||
tracing::debug!(command = formatted_command, "did restart flycheck");
|
tracing::debug!(command = formatted_command, "did restart flycheck");
|
||||||
self.command_handle = Some(command_handle);
|
self.command_handle = Some(command_handle);
|
||||||
|
|
@ -550,8 +550,10 @@ enum CargoCheckMessage {
|
||||||
Diagnostic { diagnostic: Diagnostic, package_id: Option<Arc<PackageId>> },
|
Diagnostic { diagnostic: Diagnostic, package_id: Option<Arc<PackageId>> },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseFromLine for CargoCheckMessage {
|
struct CargoCheckParser;
|
||||||
fn from_line(line: &str, error: &mut String) -> Option<Self> {
|
|
||||||
|
impl CargoParser<CargoCheckMessage> for CargoCheckParser {
|
||||||
|
fn from_line(&self, line: &str, error: &mut String) -> Option<CargoCheckMessage> {
|
||||||
let mut deserializer = serde_json::Deserializer::from_str(line);
|
let mut deserializer = serde_json::Deserializer::from_str(line);
|
||||||
deserializer.disable_recursion_limit();
|
deserializer.disable_recursion_limit();
|
||||||
if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
|
if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
|
||||||
|
|
@ -580,7 +582,7 @@ impl ParseFromLine for CargoCheckMessage {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_eof() -> Option<Self> {
|
fn from_eof(&self) -> Option<CargoCheckMessage> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
//! Currently cargo does not emit crate name in the `cargo test --format=json`, which needs to be changed. This
|
|
||||||
//! module contains a way to recover crate names in a very hacky and wrong way.
|
|
||||||
|
|
||||||
// FIXME(hack_recover_crate_name): Remove this module.
|
|
||||||
|
|
||||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
|
||||||
|
|
||||||
use ide_db::FxHashMap;
|
|
||||||
|
|
||||||
static STORAGE: OnceLock<Mutex<FxHashMap<String, String>>> = OnceLock::new();
|
|
||||||
|
|
||||||
fn get_storage() -> MutexGuard<'static, FxHashMap<String, String>> {
|
|
||||||
STORAGE.get_or_init(|| Mutex::new(FxHashMap::default())).lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn insert_name(name_with_crate: String) {
|
|
||||||
let Some((_, name_without_crate)) = name_with_crate.split_once("::") else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
get_storage().insert(name_without_crate.to_owned(), name_with_crate);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn lookup_name(name_without_crate: String) -> Option<String> {
|
|
||||||
get_storage().get(&name_without_crate).cloned()
|
|
||||||
}
|
|
||||||
|
|
@ -36,7 +36,6 @@ use crate::{
|
||||||
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
|
config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
|
||||||
diagnostics::convert_diagnostic,
|
diagnostics::convert_diagnostic,
|
||||||
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
|
global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
|
||||||
hack_recover_crate_name,
|
|
||||||
line_index::LineEndings,
|
line_index::LineEndings,
|
||||||
lsp::{
|
lsp::{
|
||||||
ext::{
|
ext::{
|
||||||
|
|
@ -196,20 +195,48 @@ pub(crate) fn handle_view_item_tree(
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cargo test requires the real package name which might contain hyphens but
|
// cargo test requires:
|
||||||
// the test identifier passed to this function is the namespace form where hyphens
|
// - the package name - the root of the test identifier supplied to this handler can be
|
||||||
// are replaced with underscores so we have to reverse this and find the real package name
|
// a package or a target inside a package.
|
||||||
fn find_package_name(namespace_root: &str, cargo: &CargoWorkspace) -> Option<String> {
|
// - the target name - if the test identifier is a target, it's needed in addition to the
|
||||||
|
// package name to run the right test
|
||||||
|
// - real names - the test identifier uses the namespace form where hyphens are replaced with
|
||||||
|
// underscores. cargo test requires the real name.
|
||||||
|
// - the target kind e.g. bin or lib
|
||||||
|
fn find_test_target(namespace_root: &str, cargo: &CargoWorkspace) -> Option<TestTarget> {
|
||||||
cargo.packages().find_map(|p| {
|
cargo.packages().find_map(|p| {
|
||||||
let package_name = &cargo[p].name;
|
let package_name = &cargo[p].name;
|
||||||
if package_name.replace('-', "_") == namespace_root {
|
for target in cargo[p].targets.iter() {
|
||||||
Some(package_name.clone())
|
let target_name = &cargo[*target].name;
|
||||||
} else {
|
if target_name.replace('-', "_") == namespace_root {
|
||||||
None
|
return Some(TestTarget {
|
||||||
|
package: package_name.clone(),
|
||||||
|
target: target_name.clone(),
|
||||||
|
kind: cargo[*target].kind,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_all_targets(cargo: &CargoWorkspace) -> Vec<TestTarget> {
|
||||||
|
cargo
|
||||||
|
.packages()
|
||||||
|
.flat_map(|p| {
|
||||||
|
let package_name = &cargo[p].name;
|
||||||
|
cargo[p].targets.iter().map(|target| {
|
||||||
|
let target_name = &cargo[*target].name;
|
||||||
|
TestTarget {
|
||||||
|
package: package_name.clone(),
|
||||||
|
target: target_name.clone(),
|
||||||
|
kind: cargo[*target].kind,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_run_test(
|
pub(crate) fn handle_run_test(
|
||||||
state: &mut GlobalState,
|
state: &mut GlobalState,
|
||||||
params: lsp_ext::RunTestParams,
|
params: lsp_ext::RunTestParams,
|
||||||
|
|
@ -217,53 +244,41 @@ pub(crate) fn handle_run_test(
|
||||||
if let Some(_session) = state.test_run_session.take() {
|
if let Some(_session) = state.test_run_session.take() {
|
||||||
state.send_notification::<lsp_ext::EndRunTest>(());
|
state.send_notification::<lsp_ext::EndRunTest>(());
|
||||||
}
|
}
|
||||||
// We detect the lowest common ancestor of all included tests, and
|
|
||||||
// run it. We ignore excluded tests for now, the client will handle
|
|
||||||
// it for us.
|
|
||||||
let lca = match params.include {
|
|
||||||
Some(tests) => tests
|
|
||||||
.into_iter()
|
|
||||||
.reduce(|x, y| {
|
|
||||||
let mut common_prefix = "".to_owned();
|
|
||||||
for (xc, yc) in x.chars().zip(y.chars()) {
|
|
||||||
if xc != yc {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
common_prefix.push(xc);
|
|
||||||
}
|
|
||||||
common_prefix
|
|
||||||
})
|
|
||||||
.unwrap_or_default(),
|
|
||||||
None => "".to_owned(),
|
|
||||||
};
|
|
||||||
let (namespace_root, test_path) = if lca.is_empty() {
|
|
||||||
(None, None)
|
|
||||||
} else if let Some((namespace_root, path)) = lca.split_once("::") {
|
|
||||||
(Some(namespace_root), Some(path))
|
|
||||||
} else {
|
|
||||||
(Some(lca.as_str()), None)
|
|
||||||
};
|
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
for ws in &*state.workspaces {
|
for ws in &*state.workspaces {
|
||||||
if let ProjectWorkspaceKind::Cargo { cargo, .. } = &ws.kind {
|
if let ProjectWorkspaceKind::Cargo { cargo, .. } = &ws.kind {
|
||||||
let test_target = if let Some(namespace_root) = namespace_root {
|
// need to deduplicate `include` to avoid redundant test runs
|
||||||
if let Some(package_name) = find_package_name(namespace_root, cargo) {
|
let tests = match params.include {
|
||||||
TestTarget::Package(package_name)
|
Some(ref include) => include
|
||||||
} else {
|
.iter()
|
||||||
TestTarget::Workspace
|
.unique()
|
||||||
}
|
.filter_map(|test| {
|
||||||
} else {
|
let (root, remainder) = match test.split_once("::") {
|
||||||
TestTarget::Workspace
|
Some((root, remainder)) => (root.to_owned(), Some(remainder)),
|
||||||
|
None => (test.clone(), None),
|
||||||
|
};
|
||||||
|
if let Some(target) = find_test_target(&root, cargo) {
|
||||||
|
Some((target, remainder))
|
||||||
|
} else {
|
||||||
|
tracing::error!("Test target not found for: {test}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_vec(),
|
||||||
|
None => get_all_targets(cargo).into_iter().map(|target| (target, None)).collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let handle = CargoTestHandle::new(
|
for (target, path) in tests {
|
||||||
test_path,
|
let handle = CargoTestHandle::new(
|
||||||
state.config.cargo_test_options(None),
|
path,
|
||||||
cargo.workspace_root(),
|
state.config.cargo_test_options(None),
|
||||||
test_target,
|
cargo.workspace_root(),
|
||||||
state.test_run_sender.clone(),
|
target,
|
||||||
)?;
|
state.test_run_sender.clone(),
|
||||||
handles.push(handle);
|
)?;
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Each process send finished signal twice, once for stdout and once for stderr
|
// Each process send finished signal twice, once for stdout and once for stderr
|
||||||
|
|
@ -287,9 +302,7 @@ pub(crate) fn handle_discover_test(
|
||||||
}
|
}
|
||||||
None => (snap.analysis.discover_test_roots()?, None),
|
None => (snap.analysis.discover_test_roots()?, None),
|
||||||
};
|
};
|
||||||
for t in &tests {
|
|
||||||
hack_recover_crate_name::insert_name(t.id.clone());
|
|
||||||
}
|
|
||||||
Ok(lsp_ext::DiscoverTestResults {
|
Ok(lsp_ext::DiscoverTestResults {
|
||||||
tests: tests
|
tests: tests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ mod command;
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
mod discover;
|
mod discover;
|
||||||
mod flycheck;
|
mod flycheck;
|
||||||
mod hack_recover_crate_name;
|
|
||||||
mod line_index;
|
mod line_index;
|
||||||
mod main_loop;
|
mod main_loop;
|
||||||
mod mem_docs;
|
mod mem_docs;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ use crate::{
|
||||||
file_id_to_url, url_to_file_id, FetchBuildDataResponse, FetchWorkspaceRequest,
|
file_id_to_url, url_to_file_id, FetchBuildDataResponse, FetchWorkspaceRequest,
|
||||||
FetchWorkspaceResponse, GlobalState,
|
FetchWorkspaceResponse, GlobalState,
|
||||||
},
|
},
|
||||||
hack_recover_crate_name,
|
|
||||||
handlers::{
|
handlers::{
|
||||||
dispatch::{NotificationDispatcher, RequestDispatcher},
|
dispatch::{NotificationDispatcher, RequestDispatcher},
|
||||||
request::empty_diagnostic_report,
|
request::empty_diagnostic_report,
|
||||||
|
|
@ -37,7 +36,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
lsp_ext,
|
lsp_ext,
|
||||||
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
|
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
|
||||||
test_runner::{CargoTestMessage, TestState},
|
test_runner::{CargoTestMessage, CargoTestOutput, TestState},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
|
pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> {
|
||||||
|
|
@ -659,9 +658,7 @@ impl GlobalState {
|
||||||
.filter_map(|f| snapshot.analysis.discover_tests_in_file(f).ok())
|
.filter_map(|f| snapshot.analysis.discover_tests_in_file(f).ok())
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
for t in &tests {
|
|
||||||
hack_recover_crate_name::insert_name(t.id.clone());
|
|
||||||
}
|
|
||||||
Task::DiscoverTest(lsp_ext::DiscoverTestResults {
|
Task::DiscoverTest(lsp_ext::DiscoverTestResults {
|
||||||
tests: tests
|
tests: tests
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -958,30 +955,29 @@ impl GlobalState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_cargo_test_msg(&mut self, message: CargoTestMessage) {
|
fn handle_cargo_test_msg(&mut self, message: CargoTestMessage) {
|
||||||
match message {
|
match message.output {
|
||||||
CargoTestMessage::Test { name, state } => {
|
CargoTestOutput::Test { name, state } => {
|
||||||
let state = match state {
|
let state = match state {
|
||||||
TestState::Started => lsp_ext::TestState::Started,
|
TestState::Started => lsp_ext::TestState::Started,
|
||||||
TestState::Ignored => lsp_ext::TestState::Skipped,
|
TestState::Ignored => lsp_ext::TestState::Skipped,
|
||||||
TestState::Ok => lsp_ext::TestState::Passed,
|
TestState::Ok => lsp_ext::TestState::Passed,
|
||||||
TestState::Failed { stdout } => lsp_ext::TestState::Failed { message: stdout },
|
TestState::Failed { stdout } => lsp_ext::TestState::Failed { message: stdout },
|
||||||
};
|
};
|
||||||
let Some(test_id) = hack_recover_crate_name::lookup_name(name) else {
|
let test_id = format!("{}::{name}", message.target.target);
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.send_notification::<lsp_ext::ChangeTestState>(
|
self.send_notification::<lsp_ext::ChangeTestState>(
|
||||||
lsp_ext::ChangeTestStateParams { test_id, state },
|
lsp_ext::ChangeTestStateParams { test_id, state },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CargoTestMessage::Suite => (),
|
CargoTestOutput::Suite => (),
|
||||||
CargoTestMessage::Finished => {
|
CargoTestOutput::Finished => {
|
||||||
self.test_run_remaining_jobs = self.test_run_remaining_jobs.saturating_sub(1);
|
self.test_run_remaining_jobs = self.test_run_remaining_jobs.saturating_sub(1);
|
||||||
if self.test_run_remaining_jobs == 0 {
|
if self.test_run_remaining_jobs == 0 {
|
||||||
self.send_notification::<lsp_ext::EndRunTest>(());
|
self.send_notification::<lsp_ext::EndRunTest>(());
|
||||||
self.test_run_session = None;
|
self.test_run_session = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CargoTestMessage::Custom { text } => {
|
CargoTestOutput::Custom { text } => {
|
||||||
self.send_notification::<lsp_ext::AppendOutputToRunTest>(text);
|
self.send_notification::<lsp_ext::AppendOutputToRunTest>(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use paths::AbsPath;
|
use paths::AbsPath;
|
||||||
|
use project_model::TargetKind;
|
||||||
use serde::Deserialize as _;
|
use serde::Deserialize as _;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use toolchain::Tool;
|
use toolchain::Tool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command::{CommandHandle, ParseFromLine},
|
command::{CargoParser, CommandHandle},
|
||||||
flycheck::CargoOptions,
|
flycheck::CargoOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,9 +26,15 @@ pub(crate) enum TestState {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct CargoTestMessage {
|
||||||
|
pub target: TestTarget,
|
||||||
|
pub output: CargoTestOutput,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub(crate) enum CargoTestMessage {
|
pub(crate) enum CargoTestOutput {
|
||||||
Test {
|
Test {
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
|
@ -40,19 +47,33 @@ pub(crate) enum CargoTestMessage {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseFromLine for CargoTestMessage {
|
pub(crate) struct CargoTestOutputParser {
|
||||||
fn from_line(line: &str, _: &mut String) -> Option<Self> {
|
pub target: TestTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CargoTestOutputParser {
|
||||||
|
pub(crate) fn new(test_target: &TestTarget) -> Self {
|
||||||
|
Self { target: test_target.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CargoParser<CargoTestMessage> for CargoTestOutputParser {
|
||||||
|
fn from_line(&self, line: &str, _error: &mut String) -> Option<CargoTestMessage> {
|
||||||
let mut deserializer = serde_json::Deserializer::from_str(line);
|
let mut deserializer = serde_json::Deserializer::from_str(line);
|
||||||
deserializer.disable_recursion_limit();
|
deserializer.disable_recursion_limit();
|
||||||
if let Ok(message) = CargoTestMessage::deserialize(&mut deserializer) {
|
|
||||||
return Some(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(CargoTestMessage::Custom { text: line.to_owned() })
|
Some(CargoTestMessage {
|
||||||
|
target: self.target.clone(),
|
||||||
|
output: if let Ok(message) = CargoTestOutput::deserialize(&mut deserializer) {
|
||||||
|
message
|
||||||
|
} else {
|
||||||
|
CargoTestOutput::Custom { text: line.to_owned() }
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_eof() -> Option<Self> {
|
fn from_eof(&self) -> Option<CargoTestMessage> {
|
||||||
Some(CargoTestMessage::Finished)
|
Some(CargoTestMessage { target: self.target.clone(), output: CargoTestOutput::Finished })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,14 +83,14 @@ pub(crate) struct CargoTestHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example of a cargo test command:
|
// Example of a cargo test command:
|
||||||
// cargo test --workspace --no-fail-fast -- -Z unstable-options --format=json
|
//
|
||||||
// or
|
// cargo test --package my-package --bin my_bin --no-fail-fast -- module::func -Z unstable-options --format=json
|
||||||
// cargo test --package my-package --no-fail-fast -- module::func -Z unstable-options --format=json
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum TestTarget {
|
pub(crate) struct TestTarget {
|
||||||
Workspace,
|
pub package: String,
|
||||||
Package(String),
|
pub target: String,
|
||||||
|
pub kind: TargetKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CargoTestHandle {
|
impl CargoTestHandle {
|
||||||
|
|
@ -84,15 +105,18 @@ impl CargoTestHandle {
|
||||||
cmd.env("RUSTC_BOOTSTRAP", "1");
|
cmd.env("RUSTC_BOOTSTRAP", "1");
|
||||||
cmd.arg("test");
|
cmd.arg("test");
|
||||||
|
|
||||||
match &test_target {
|
cmd.arg("--package");
|
||||||
TestTarget::Package(package) => {
|
cmd.arg(&test_target.package);
|
||||||
cmd.arg("--package");
|
|
||||||
cmd.arg(package);
|
if let TargetKind::Lib { .. } = test_target.kind {
|
||||||
}
|
// no name required with lib because there can only be one lib target per package
|
||||||
TestTarget::Workspace => {
|
cmd.arg("--lib");
|
||||||
cmd.arg("--workspace");
|
} else if let Some(cargo_target) = test_target.kind.as_cargo_target() {
|
||||||
}
|
cmd.arg(format!("--{cargo_target}"));
|
||||||
};
|
cmd.arg(&test_target.target);
|
||||||
|
} else {
|
||||||
|
tracing::warn!("Running test for unknown cargo target {:?}", test_target.kind);
|
||||||
|
}
|
||||||
|
|
||||||
// --no-fail-fast is needed to ensure that all requested tests will run
|
// --no-fail-fast is needed to ensure that all requested tests will run
|
||||||
cmd.arg("--no-fail-fast");
|
cmd.arg("--no-fail-fast");
|
||||||
|
|
@ -110,6 +134,8 @@ impl CargoTestHandle {
|
||||||
cmd.arg(extra_arg);
|
cmd.arg(extra_arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { _handle: CommandHandle::spawn(cmd, sender)? })
|
Ok(Self {
|
||||||
|
_handle: CommandHandle::spawn(cmd, CargoTestOutputParser::new(&test_target), sender)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue