@@ -35,7 +35,6 @@ use crate::{
35
35
config:: { Config , RustfmtConfig , WorkspaceSymbolConfig } ,
36
36
diagnostics:: convert_diagnostic,
37
37
global_state:: { FetchWorkspaceRequest , GlobalState , GlobalStateSnapshot } ,
38
- hack_recover_crate_name,
39
38
line_index:: LineEndings ,
40
39
lsp:: {
41
40
LspError , completion_item_hash,
@@ -195,74 +194,90 @@ pub(crate) fn handle_view_item_tree(
195
194
Ok ( res)
196
195
}
197
196
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 > {
202
206
cargo. packages ( ) . find_map ( |p| {
203
207
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
+ }
208
217
}
218
+ None
209
219
} )
210
220
}
211
221
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
+
212
239
pub ( crate ) fn handle_run_test (
213
240
state : & mut GlobalState ,
214
241
params : lsp_ext:: RunTestParams ,
215
242
) -> anyhow:: Result < ( ) > {
216
243
if let Some ( _session) = state. test_run_session . take ( ) {
217
244
state. send_notification :: < lsp_ext:: EndRunTest > ( ( ) ) ;
218
245
}
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
+
245
247
let mut handles = vec ! [ ] ;
246
248
for ws in & * state. workspaces {
247
249
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 ( ) ,
256
269
} ;
257
270
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
+ }
266
281
}
267
282
}
268
283
// Each process send finished signal twice, once for stdout and once for stderr
@@ -286,9 +301,7 @@ pub(crate) fn handle_discover_test(
286
301
}
287
302
None => ( snap. analysis . discover_test_roots ( ) ?, None ) ,
288
303
} ;
289
- for t in & tests {
290
- hack_recover_crate_name:: insert_name ( t. id . clone ( ) ) ;
291
- }
304
+
292
305
Ok ( lsp_ext:: DiscoverTestResults {
293
306
tests : tests
294
307
. into_iter ( )
0 commit comments