@@ -12,6 +12,8 @@ use std::sync::Mutex;
12
12
use std:: sync:: OnceLock ;
13
13
use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
14
14
15
+ use crate :: compare;
16
+
15
17
static CARGO_INTEGRATION_TEST_DIR : & str = "cit" ;
16
18
17
19
static GLOBAL_ROOT : OnceLock < Mutex < Option < PathBuf > > > = OnceLock :: new ( ) ;
@@ -152,6 +154,8 @@ pub trait CargoPathExt {
152
154
fn move_in_time < F > ( & self , travel_amount : F )
153
155
where
154
156
F : Fn ( i64 , u32 ) -> ( i64 , u32 ) ;
157
+
158
+ fn verify_file_layout ( & self , expected : impl snapbox:: IntoData ) ;
155
159
}
156
160
157
161
impl CargoPathExt for Path {
@@ -236,6 +240,18 @@ impl CargoPathExt for Path {
236
240
} ) ;
237
241
}
238
242
}
243
+
244
+ #[ track_caller]
245
+ fn verify_file_layout ( & self , expected : impl snapbox:: IntoData ) {
246
+ let layout = generate_tree ( & self ) ;
247
+
248
+ let name = format ! ( "verify_file_layout {}" , self . display( ) ) ;
249
+ if let Err ( err) =
250
+ compare:: assert_ui ( ) . try_eq ( Some ( & name) , layout. into ( ) , expected. into_data ( ) )
251
+ {
252
+ panic ! ( "{err}" ) ;
253
+ }
254
+ }
239
255
}
240
256
241
257
impl CargoPathExt for PathBuf {
@@ -260,6 +276,11 @@ impl CargoPathExt for PathBuf {
260
276
{
261
277
self . as_path ( ) . move_in_time ( travel_amount)
262
278
}
279
+
280
+ #[ track_caller]
281
+ fn verify_file_layout ( & self , expected : impl snapbox:: IntoData ) {
282
+ self . as_path ( ) . verify_file_layout ( expected) ;
283
+ }
263
284
}
264
285
265
286
fn do_op < F > ( path : & Path , desc : & str , mut f : F )
@@ -414,3 +435,46 @@ pub fn windows_reserved_names_are_allowed() -> bool {
414
435
true
415
436
}
416
437
}
438
+
439
+ pub fn generate_tree ( path : & Path ) -> String {
440
+ let mut output = format ! ( "{}\n " , path. display( ) ) ;
441
+ generate_tree_inner ( path, & [ ] , true , & mut output) ;
442
+ return output;
443
+ }
444
+
445
+ pub fn generate_tree_inner ( path : & Path , prefix_state : & [ bool ] , last : bool , output : & mut String ) {
446
+ let file_name = path
447
+ . file_name ( )
448
+ . map ( |s| s. to_string_lossy ( ) )
449
+ . unwrap_or_else ( || path. to_string_lossy ( ) ) ;
450
+
451
+ if !prefix_state. is_empty ( ) {
452
+ // Build indentation based on ancestor "lastness"
453
+ let mut indent = String :: new ( ) ;
454
+ for & has_more_siblings in & prefix_state[ ..prefix_state. len ( ) - 1 ] {
455
+ indent. push_str ( if has_more_siblings { "│ " } else { " " } ) ;
456
+ }
457
+ let connector = if last { "└── " } else { "├── " } ;
458
+ output. push_str ( & format ! ( "{indent}{connector}{file_name}\n " ) ) ;
459
+ }
460
+
461
+ if path. is_dir ( ) {
462
+ let mut entries: Vec < _ > = walkdir:: WalkDir :: new ( path)
463
+ . max_depth ( 1 )
464
+ . into_iter ( )
465
+ . filter_map ( Result :: ok)
466
+ . filter ( |e| e. path ( ) != path) // skip the dir itself
467
+ . collect ( ) ;
468
+
469
+ entries. sort_by_key ( |e| e. file_name ( ) . to_os_string ( ) ) ;
470
+
471
+ let count = entries. len ( ) ;
472
+ for ( i, entry) in entries. into_iter ( ) . enumerate ( ) {
473
+ let is_last = i == count - 1 ;
474
+ // push info about *this* level for children
475
+ let mut new_prefix_state = prefix_state. to_vec ( ) ;
476
+ new_prefix_state. push ( !is_last) ;
477
+ generate_tree_inner ( entry. path ( ) , & new_prefix_state, is_last, output) ;
478
+ }
479
+ }
480
+ }
0 commit comments