@@ -35,7 +35,6 @@ use crate::{
3535 config:: { Config , RustfmtConfig , WorkspaceSymbolConfig } ,
3636 diagnostics:: convert_diagnostic,
3737 global_state:: { FetchWorkspaceRequest , GlobalState , GlobalStateSnapshot } ,
38- hack_recover_crate_name,
3938 line_index:: LineEndings ,
4039 lsp:: {
4140 LspError , completion_item_hash,
@@ -195,74 +194,90 @@ pub(crate) fn handle_view_item_tree(
195194 Ok ( res)
196195}
197196
198- // cargo test requires the real package name which might contain hyphens but
199- // the test identifier passed to this function is the namespace form where hyphens
200- // are replaced with underscores so we have to reverse this and find the real package name
201- fn find_package_name ( namespace_root : & str , cargo : & CargoWorkspace ) -> Option < String > {
197+ // cargo test requires:
198+ // - the package name - the root of the test identifier supplied to this handler can be
199+ // a package or a target inside a package.
200+ // - the target name - if the test identifier is a target, it's needed in addition to the
201+ // package name to run the right test
202+ // - real names - the test identifier uses the namespace form where hyphens are replaced with
203+ // underscores. cargo test requires the real name.
204+ // - the target kind e.g. bin or lib
205+ fn find_test_target ( namespace_root : & str , cargo : & CargoWorkspace ) -> Option < TestTarget > {
202206 cargo. packages ( ) . find_map ( |p| {
203207 let package_name = & cargo[ p] . name ;
204- if package_name. replace ( '-' , "_" ) == namespace_root {
205- Some ( package_name. clone ( ) )
206- } else {
207- None
208+ for target in cargo[ p] . targets . iter ( ) {
209+ let target_name = & cargo[ * target] . name ;
210+ if target_name. replace ( '-' , "_" ) == namespace_root {
211+ return Some ( TestTarget {
212+ package : package_name. clone ( ) ,
213+ target : target_name. clone ( ) ,
214+ kind : cargo[ * target] . kind ,
215+ } ) ;
216+ }
208217 }
218+ None
209219 } )
210220}
211221
222+ fn get_all_targets ( cargo : & CargoWorkspace ) -> Vec < TestTarget > {
223+ cargo
224+ . packages ( )
225+ . flat_map ( |p| {
226+ let package_name = & cargo[ p] . name ;
227+ cargo[ p] . targets . iter ( ) . map ( |target| {
228+ let target_name = & cargo[ * target] . name ;
229+ TestTarget {
230+ package : package_name. clone ( ) ,
231+ target : target_name. clone ( ) ,
232+ kind : cargo[ * target] . kind ,
233+ }
234+ } )
235+ } )
236+ . collect ( )
237+ }
238+
212239pub ( crate ) fn handle_run_test (
213240 state : & mut GlobalState ,
214241 params : lsp_ext:: RunTestParams ,
215242) -> anyhow:: Result < ( ) > {
216243 if let Some ( _session) = state. test_run_session . take ( ) {
217244 state. send_notification :: < lsp_ext:: EndRunTest > ( ( ) ) ;
218245 }
219- // We detect the lowest common ancestor of all included tests, and
220- // run it. We ignore excluded tests for now, the client will handle
221- // it for us.
222- let lca = match params. include {
223- Some ( tests) => tests
224- . into_iter ( )
225- . reduce ( |x, y| {
226- let mut common_prefix = "" . to_owned ( ) ;
227- for ( xc, yc) in x. chars ( ) . zip ( y. chars ( ) ) {
228- if xc != yc {
229- break ;
230- }
231- common_prefix. push ( xc) ;
232- }
233- common_prefix
234- } )
235- . unwrap_or_default ( ) ,
236- None => "" . to_owned ( ) ,
237- } ;
238- let ( namespace_root, test_path) = if lca. is_empty ( ) {
239- ( None , None )
240- } else if let Some ( ( namespace_root, path) ) = lca. split_once ( "::" ) {
241- ( Some ( namespace_root) , Some ( path) )
242- } else {
243- ( Some ( lca. as_str ( ) ) , None )
244- } ;
246+
245247 let mut handles = vec ! [ ] ;
246248 for ws in & * state. workspaces {
247249 if let ProjectWorkspaceKind :: Cargo { cargo, .. } = & ws. kind {
248- let test_target = if let Some ( namespace_root) = namespace_root {
249- if let Some ( package_name) = find_package_name ( namespace_root, cargo) {
250- TestTarget :: Package ( package_name)
251- } else {
252- TestTarget :: Workspace
253- }
254- } else {
255- TestTarget :: Workspace
250+ // need to deduplicate `include` to avoid redundant test runs
251+ let tests = match params. include {
252+ Some ( ref include) => include
253+ . iter ( )
254+ . unique ( )
255+ . filter_map ( |test| {
256+ let ( root, remainder) = match test. split_once ( "::" ) {
257+ Some ( ( root, remainder) ) => ( root. to_owned ( ) , Some ( remainder) ) ,
258+ None => ( test. clone ( ) , None ) ,
259+ } ;
260+ if let Some ( target) = find_test_target ( & root, cargo) {
261+ Some ( ( target, remainder) )
262+ } else {
263+ tracing:: error!( "Test target not found for: {test}" ) ;
264+ None
265+ }
266+ } )
267+ . collect_vec ( ) ,
268+ None => get_all_targets ( cargo) . into_iter ( ) . map ( |target| ( target, None ) ) . collect ( ) ,
256269 } ;
257270
258- let handle = CargoTestHandle :: new (
259- test_path,
260- state. config . cargo_test_options ( None ) ,
261- cargo. workspace_root ( ) ,
262- test_target,
263- state. test_run_sender . clone ( ) ,
264- ) ?;
265- handles. push ( handle) ;
271+ for ( target, path) in tests {
272+ let handle = CargoTestHandle :: new (
273+ path,
274+ state. config . cargo_test_options ( None ) ,
275+ cargo. workspace_root ( ) ,
276+ target,
277+ state. test_run_sender . clone ( ) ,
278+ ) ?;
279+ handles. push ( handle) ;
280+ }
266281 }
267282 }
268283 // Each process send finished signal twice, once for stdout and once for stderr
@@ -286,9 +301,7 @@ pub(crate) fn handle_discover_test(
286301 }
287302 None => ( snap. analysis . discover_test_roots ( ) ?, None ) ,
288303 } ;
289- for t in & tests {
290- hack_recover_crate_name:: insert_name ( t. id . clone ( ) ) ;
291- }
304+
292305 Ok ( lsp_ext:: DiscoverTestResults {
293306 tests : tests
294307 . into_iter ( )
0 commit comments