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