@@ -287,9 +287,110 @@ pub async fn tests(
287287 let semaphore = Arc :: new ( Semaphore :: new ( common_options. job_limit ) ) ;
288288 let mut handles = vec ! [ ] ;
289289
290- for ( _, member) in results. members . into_iter ( ) . filter ( |( _, member) | {
291- !member. test_detail . skip . unwrap_or_default ( ) && ( member. perform_test || options. run_all )
292- } ) {
290+ // Precompute transitive dependency counts for sorting and display.
291+ let dep_graph = results. crate_graph . dependency_graph ( ) ;
292+ let transitive_dep_counts: HashMap < String , usize > = results
293+ . members
294+ . values ( )
295+ . filter_map ( |m| {
296+ m. package_id . as_ref ( ) . map ( |id| {
297+ (
298+ m. package . clone ( ) ,
299+ dep_graph. get_transitive_dependencies ( id. clone ( ) ) . len ( ) ,
300+ )
301+ } )
302+ } )
303+ . collect ( ) ;
304+
305+ // Sort members so directly changed crates run first (fail fast), then
306+ // dependency-changed, then unchanged (only present with --run-all).
307+ // Within each group, crates with more transitive dependencies run first
308+ // (higher in the tree, more likely to surface issues), then alphabetically.
309+ let mut members: Vec < _ > = results
310+ . members
311+ . into_values ( )
312+ . filter ( |member| {
313+ !member. test_detail . skip . unwrap_or_default ( ) && ( member. perform_test || options. run_all )
314+ } )
315+ . collect ( ) ;
316+ members. sort_by ( |a, b| {
317+ let a_deps = transitive_dep_counts. get ( & a. package ) . copied ( ) . unwrap_or ( 0 ) ;
318+ let b_deps = transitive_dep_counts. get ( & b. package ) . copied ( ) . unwrap_or ( 0 ) ;
319+ b. changed
320+ . cmp ( & a. changed )
321+ . then ( b. dependencies_changed . cmp ( & a. dependencies_changed ) )
322+ . then ( b_deps. cmp ( & a_deps) )
323+ . then ( a. package . cmp ( & b. package ) )
324+ } ) ;
325+
326+ // Print execution order grouped by change status.
327+ {
328+ let name_w = members
329+ . iter ( )
330+ . map ( |m| m. package . len ( ) )
331+ . max ( )
332+ . unwrap_or ( 7 )
333+ . max ( 7 ) ;
334+ let deps_w = 4 ; // "Deps" header
335+ #[ expect( clippy:: type_complexity) ]
336+ let groups: & [ ( & str , Box < dyn Fn ( & super :: check_workspace:: Result ) -> bool > ) ] = & [
337+ ( "Directly changed" , Box :: new ( |m| m. changed ) ) ,
338+ (
339+ "Dependency changed" ,
340+ Box :: new ( |m| !m. changed && m. dependencies_changed ) ,
341+ ) ,
342+ (
343+ "Unchanged" ,
344+ Box :: new ( |m| !m. changed && !m. dependencies_changed ) ,
345+ ) ,
346+ ] ;
347+ let active_groups: Vec < _ > = groups
348+ . iter ( )
349+ . filter_map ( |( label, pred) | {
350+ let rows: Vec < _ > = members. iter ( ) . filter ( |m| pred ( m) ) . collect ( ) ;
351+ if rows. is_empty ( ) {
352+ None
353+ } else {
354+ Some ( ( * label, rows) )
355+ }
356+ } )
357+ . collect ( ) ;
358+ if !active_groups. is_empty ( ) {
359+ // Column widths including padding: " content "
360+ let nc = name_w + 2 ; // name column cell width
361+ let dc = deps_w + 2 ; // deps column cell width
362+ // Total width between outer box chars: nc + 1(│) + dc
363+ let total = nc + 1 + dc;
364+ let nb = "─" . repeat ( nc) ;
365+ let db = "─" . repeat ( dc) ;
366+ let fb = "─" . repeat ( total) ;
367+ let mut table = String :: new ( ) ;
368+ table. push_str ( & format ! ( "╭{fb}╮\n " ) ) ;
369+ for ( i, ( label, rows) ) in active_groups. iter ( ) . enumerate ( ) {
370+ if i > 0 {
371+ table. push_str ( & format ! ( "├{nb}┴{db}┤\n " ) ) ;
372+ }
373+ table. push_str ( & format ! ( "│ {:<w$}│\n " , label, w = total - 1 ) ) ;
374+ table. push_str ( & format ! ( "├{nb}┬{db}┤\n " ) ) ;
375+ table. push_str ( & format ! (
376+ "│ {:<name_w$} │ {:<deps_w$} │\n " ,
377+ "Package" , "Deps"
378+ ) ) ;
379+ table. push_str ( & format ! ( "├{nb}┼{db}┤\n " ) ) ;
380+ for m in rows {
381+ let dep_count = transitive_dep_counts. get ( & m. package ) . copied ( ) . unwrap_or ( 0 ) ;
382+ table. push_str ( & format ! (
383+ "│ {:<name_w$} │ {:<deps_w$} │\n " ,
384+ m. package, dep_count,
385+ ) ) ;
386+ }
387+ }
388+ table. push_str ( & format ! ( "╰{nb}┴{db}╯" ) ) ;
389+ tracing:: info!( "Test execution order:\n {table}" ) ;
390+ }
391+ }
392+
393+ for member in members {
293394 let common_opts = Arc :: new ( common_options. clone ( ) ) ;
294395 let base_revspec = options
295396 . diff
0 commit comments