@@ -13,6 +13,70 @@ use std::sync::{
13
13
use tempfile:: TempDir ;
14
14
use tokio:: fs;
15
15
16
+ // Import platform-specific modules
17
+ #[ cfg( unix) ]
18
+ mod unix;
19
+ #[ cfg( windows) ]
20
+ mod windows;
21
+
22
+ // Use platform-specific functions
23
+ #[ cfg( unix) ]
24
+ use unix:: {
25
+ append as platform_append,
26
+ symlink_sync,
27
+ } ;
28
+ #[ cfg( windows) ]
29
+ use windows:: {
30
+ append as platform_append,
31
+ symlink_sync,
32
+ } ;
33
+
34
+ /// Rust path handling is hard coded to work specific ways depending on the
35
+ /// OS that is being executed on. Because of this, if Unix paths are provided,
36
+ /// they aren't recognized. For example a leading prefix of '/' isn't considered
37
+ /// an absolute path. To fix this, all test paths would need to have windows
38
+ /// equivalents which is tedious and can lead to errors and missed test cases.
39
+ /// To make writing tests easier, path normalization happens on Windows systems
40
+ /// implicitly during test runtime.
41
+ #[ cfg( test) ]
42
+ fn normalize_test_path ( path : impl AsRef < Path > ) -> PathBuf {
43
+ #[ cfg( windows) ]
44
+ {
45
+ use typed_path:: Utf8TypedPath ;
46
+ let path_ref = path. as_ref ( ) ;
47
+
48
+ // Only process string paths with forward slashes
49
+ let typed_path = Utf8TypedPath :: derive ( path_ref. to_str ( ) . unwrap ( ) ) ;
50
+ if typed_path. is_unix ( ) {
51
+ let windows_path = typed_path. with_windows_encoding ( ) . to_string ( ) ;
52
+
53
+ // If path is absolute (starts with /) and doesn't already have a drive letter
54
+ if PathBuf :: from ( & windows_path) . has_root ( ) {
55
+ // Prepend C: drive letter to make it truly absolute on Windows
56
+ return PathBuf :: from ( format ! ( "C:{}" , windows_path) ) ;
57
+ }
58
+
59
+ return PathBuf :: from ( windows_path) ;
60
+ }
61
+ }
62
+ path. as_ref ( ) . to_path_buf ( )
63
+ }
64
+
65
+ /// Cross-platform path append that handles test paths consistently
66
+ fn append ( base : impl AsRef < Path > , path : impl AsRef < Path > ) -> PathBuf {
67
+ #[ cfg( test) ]
68
+ {
69
+ // Normalize the path for tests, then use the platform-specific append
70
+ platform_append ( normalize_test_path ( base) , normalize_test_path ( path) )
71
+ }
72
+
73
+ #[ cfg( not( test) ) ]
74
+ {
75
+ // In non-test code, just use the platform-specific append directly
76
+ platform_append ( base, path)
77
+ }
78
+ }
79
+
16
80
#[ derive( Debug , Clone , Default ) ]
17
81
pub struct Fs ( inner:: Inner ) ;
18
82
@@ -285,29 +349,34 @@ impl Fs {
285
349
/// Creates a new symbolic link on the filesystem.
286
350
///
287
351
/// The `link` path will be a symbolic link pointing to the `original` path.
288
- ///
289
- /// This is a proxy to [`tokio::fs::symlink`].
290
- #[ cfg( unix) ]
291
352
pub async fn symlink ( & self , original : impl AsRef < Path > , link : impl AsRef < Path > ) -> io:: Result < ( ) > {
292
353
use inner:: Inner ;
354
+
355
+ #[ cfg( unix) ]
356
+ async fn do_symlink ( original : impl AsRef < Path > , link : impl AsRef < Path > ) -> io:: Result < ( ) > {
357
+ fs:: symlink ( original, link) . await
358
+ }
359
+
360
+ #[ cfg( windows) ]
361
+ async fn do_symlink ( original : impl AsRef < Path > , link : impl AsRef < Path > ) -> io:: Result < ( ) > {
362
+ windows:: symlink_async ( original, link) . await
363
+ }
364
+
293
365
match & self . 0 {
294
- Inner :: Real => fs :: symlink ( original, link) . await ,
295
- Inner :: Chroot ( root) => fs :: symlink ( append ( root. path ( ) , original) , append ( root. path ( ) , link) ) . await ,
366
+ Inner :: Real => do_symlink ( original, link) . await ,
367
+ Inner :: Chroot ( root) => do_symlink ( append ( root. path ( ) , original) , append ( root. path ( ) , link) ) . await ,
296
368
Inner :: Fake ( _) => panic ! ( "unimplemented" ) ,
297
369
}
298
370
}
299
371
300
372
/// Creates a new symbolic link on the filesystem.
301
373
///
302
374
/// The `link` path will be a symbolic link pointing to the `original` path.
303
- ///
304
- /// This is a proxy to [`std::os::unix::fs::symlink`].
305
- #[ cfg( unix) ]
306
375
pub fn symlink_sync ( & self , original : impl AsRef < Path > , link : impl AsRef < Path > ) -> io:: Result < ( ) > {
307
376
use inner:: Inner ;
308
377
match & self . 0 {
309
- Inner :: Real => std :: os :: unix :: fs :: symlink ( original, link) ,
310
- Inner :: Chroot ( root) => std :: os :: unix :: fs :: symlink ( append ( root. path ( ) , original) , append ( root. path ( ) , link) ) ,
378
+ Inner :: Real => symlink_sync ( original, link) ,
379
+ Inner :: Chroot ( root) => symlink_sync ( append ( root. path ( ) , original) , append ( root. path ( ) , link) ) ,
311
380
Inner :: Fake ( _) => panic ! ( "unimplemented" ) ,
312
381
}
313
382
}
@@ -403,35 +472,6 @@ impl Fs {
403
472
}
404
473
}
405
474
406
- /// Performs `a.join(b)`, except:
407
- /// - if `b` is an absolute path, then the resulting path will equal `/a/b`
408
- /// - if the prefix of `b` contains some `n` copies of a, then the resulting path will equal `/a/b`
409
- #[ cfg( unix) ]
410
- fn append ( a : impl AsRef < Path > , b : impl AsRef < Path > ) -> PathBuf {
411
- use std:: ffi:: OsString ;
412
- use std:: os:: unix:: ffi:: {
413
- OsStrExt ,
414
- OsStringExt ,
415
- } ;
416
-
417
- // Have to use byte slices since rust seems to always append
418
- // a forward slash at the end of a path...
419
- let a = a. as_ref ( ) . as_os_str ( ) . as_bytes ( ) ;
420
- let mut b = b. as_ref ( ) . as_os_str ( ) . as_bytes ( ) ;
421
- while b. starts_with ( a) {
422
- b = b. strip_prefix ( a) . unwrap ( ) ;
423
- }
424
- while b. starts_with ( b"/" ) {
425
- b = b. strip_prefix ( b"/" ) . unwrap ( ) ;
426
- }
427
- PathBuf :: from ( OsString :: from_vec ( a. to_vec ( ) ) ) . join ( PathBuf :: from ( OsString :: from_vec ( b. to_vec ( ) ) ) )
428
- }
429
-
430
- #[ cfg( windows) ]
431
- fn append ( a : impl AsRef < Path > , b : impl AsRef < Path > ) -> PathBuf {
432
- todo ! ( )
433
- }
434
-
435
475
#[ cfg( test) ]
436
476
mod tests {
437
477
use super :: * ;
@@ -466,18 +506,25 @@ mod tests {
466
506
assert_eq ! ( fs. read_to_string( dir. path( ) . join( "write" ) ) . await . unwrap( ) , "write" ) ;
467
507
}
468
508
469
- #[ test]
470
- fn test_append ( ) {
471
- macro_rules! assert_append {
472
- ( $a: expr, $b: expr, $expected: expr) => {
473
- assert_eq!( append( $a, $b) , PathBuf :: from( $expected) ) ;
474
- } ;
475
- }
476
- assert_append ! ( "/abc/test" , "/test" , "/abc/test/test" ) ;
477
- assert_append ! ( "/tmp/.dir" , "/tmp/.dir/home/myuser" , "/tmp/.dir/home/myuser" ) ;
478
- assert_append ! ( "/tmp/.dir" , "/tmp/hello" , "/tmp/.dir/tmp/hello" ) ;
479
- assert_append ! ( "/tmp/.dir" , "/tmp/.dir/tmp/.dir/home/user" , "/tmp/.dir/home/user" ) ;
480
- }
509
+ macro_rules! test_append_cases {
510
+ ( $(
511
+ $name: ident: ( $a: expr, $b: expr) => $expected: expr
512
+ ) ,* $( , ) ?) => {
513
+ $(
514
+ #[ test]
515
+ fn $name( ) {
516
+ assert_eq!( append( $a, $b) , normalize_test_path( $expected) ) ;
517
+ }
518
+ ) *
519
+ } ;
520
+ }
521
+
522
+ test_append_cases ! (
523
+ append_test_path_to_dir: ( "/abc/test" , "/test" ) => "/abc/test/test" ,
524
+ append_absolute_to_tmp_dir: ( "/tmp/.dir" , "/tmp/.dir/home/myuser" ) => "/tmp/.dir/home/myuser" ,
525
+ append_different_tmp_path: ( "/tmp/.dir" , "/tmp/hello" ) => "/tmp/.dir/tmp/hello" ,
526
+ append_nested_path_to_tmpdir: ( "/tmp/.dir" , "/tmp/.dir/tmp/.dir/home/user" ) => "/tmp/.dir/home/user" ,
527
+ ) ;
481
528
482
529
#[ tokio:: test]
483
530
async fn test_read_to_string ( ) {
0 commit comments