From 20d3237dfa5bc2eaf0caaa70612720a9f9df9785 Mon Sep 17 00:00:00 2001 From: duncan Date: Fri, 21 Jun 2024 16:02:33 +0100 Subject: [PATCH] 17470 - run test explorer tests at the package level and add missing extra_test_bin_args settings --- crates/flycheck/src/lib.rs | 2 +- crates/flycheck/src/test_runner.rs | 23 +++++++++++- crates/rust-analyzer/src/handlers/request.rs | 39 ++++++++++++++++---- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index ce148037fc..3dd2a91d8f 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -24,7 +24,7 @@ pub mod project_json; mod test_runner; use command::{CommandHandle, ParseFromLine}; -pub use test_runner::{CargoTestHandle, CargoTestMessage, TestState}; +pub use test_runner::{CargoTestHandle, CargoTestMessage, TestState, TestTarget}; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum InvocationStrategy { diff --git a/crates/flycheck/src/test_runner.rs b/crates/flycheck/src/test_runner.rs index c136dd1366..74ebca3410 100644 --- a/crates/flycheck/src/test_runner.rs +++ b/crates/flycheck/src/test_runner.rs @@ -59,19 +59,38 @@ pub struct CargoTestHandle { } // Example of a cargo test command: -// cargo test --workspace --no-fail-fast -- module::func -Z unstable-options --format=json +// cargo test --workspace --no-fail-fast -- -Z unstable-options --format=json +// or +// cargo test --package my-package --no-fail-fast -- module::func -Z unstable-options --format=json + +#[derive(Debug)] +pub enum TestTarget { + Workspace, + Package(String), +} impl CargoTestHandle { pub fn new( path: Option<&str>, options: CargoOptions, root: &AbsPath, + test_target: TestTarget, sender: Sender, ) -> std::io::Result { let mut cmd = Command::new(Tool::Cargo.path()); cmd.env("RUSTC_BOOTSTRAP", "1"); cmd.arg("test"); - cmd.arg("--workspace"); + + match &test_target { + TestTarget::Package(package) => { + cmd.arg("--package"); + cmd.arg(package); + } + TestTarget::Workspace => { + cmd.arg("--workspace"); + } + }; + // --no-fail-fast is needed to ensure that all requested tests will run cmd.arg("--no-fail-fast"); cmd.arg("--manifest-path"); diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 44ff273b5a..9bb3146322 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -27,7 +27,7 @@ use lsp_types::{ SemanticTokensResult, SymbolInformation, SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, }; use paths::Utf8PathBuf; -use project_model::{ManifestPath, ProjectWorkspaceKind, TargetKind}; +use project_model::{CargoWorkspace, ManifestPath, ProjectWorkspaceKind, TargetKind}; use serde_json::json; use stdx::{format_to, never}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; @@ -199,6 +199,20 @@ pub(crate) fn handle_view_item_tree( Ok(res) } +// cargo test requires the real package name which might contain hyphens but +// the test identifier passed to this function is the namespace form where hyphens +// are replaced with underscores so we have to reverse this and find the real package name +fn find_package_name(namespace_root: &str, cargo: &CargoWorkspace) -> Option { + cargo.packages().find_map(|p| { + let package_name = &cargo[p].name; + if package_name.replace('-', "_") == namespace_root { + Some(package_name.clone()) + } else { + None + } + }) +} + pub(crate) fn handle_run_test( state: &mut GlobalState, params: lsp_ext::RunTestParams, @@ -206,7 +220,7 @@ pub(crate) fn handle_run_test( if let Some(_session) = state.test_run_session.take() { state.send_notification::(()); } - // We detect the lowest common ansector of all included tests, and + // 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 { @@ -225,20 +239,31 @@ pub(crate) fn handle_run_test( .unwrap_or_default(), None => "".to_owned(), }; - let test_path = if lca.is_empty() { - None - } else if let Some((_, path)) = lca.split_once("::") { - Some(path) + 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 { - None + (Some(lca.as_str()), None) }; let mut handles = vec![]; for ws in &*state.workspaces { if let ProjectWorkspaceKind::Cargo { cargo, .. } = &ws.kind { + let test_target = if let Some(namespace_root) = namespace_root { + if let Some(package_name) = find_package_name(namespace_root, cargo) { + flycheck::TestTarget::Package(package_name) + } else { + flycheck::TestTarget::Workspace + } + } else { + flycheck::TestTarget::Workspace + }; + let handle = flycheck::CargoTestHandle::new( test_path, state.config.cargo_test_options(), cargo.workspace_root(), + test_target, state.test_run_sender.clone(), )?; handles.push(handle);