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