1use std::borrow::Cow;
18use std::ffi::OsString;
19use std::fs;
20use std::fs::File;
21use std::io;
22use std::io::ErrorKind;
23use std::io::Write;
24use std::path::Component;
25use std::path::Path;
26use std::path::PathBuf;
27
28use futures::AsyncRead;
29use futures::AsyncReadExt as _;
30use tempfile::NamedTempFile;
31use tempfile::PersistError;
32use thiserror::Error;
33
34#[cfg(unix)]
35pub use self::platform::check_executable_bit_support;
36pub use self::platform::check_symlink_support;
37pub use self::platform::symlink_dir;
38pub use self::platform::symlink_file;
39
40#[derive(Debug, Error)]
42#[error("Cannot access {path}")]
43pub struct PathError {
44 pub path: PathBuf,
46 pub source: io::Error,
48}
49
50pub trait IoResultExt<T> {
52 fn context(self, path: impl AsRef<Path>) -> Result<T, PathError>;
54}
55
56impl<T> IoResultExt<T> for io::Result<T> {
57 fn context(self, path: impl AsRef<Path>) -> Result<T, PathError> {
58 self.map_err(|error| PathError {
59 path: path.as_ref().to_path_buf(),
60 source: error,
61 })
62 }
63}
64
65pub fn create_or_reuse_dir(dirname: &Path) -> io::Result<()> {
71 match fs::create_dir(dirname) {
72 Ok(()) => Ok(()),
73 Err(_) if dirname.is_dir() => Ok(()),
74 Err(e) => Err(e),
75 }
76}
77
78pub fn remove_dir_contents(dirname: &Path) -> Result<(), PathError> {
82 for entry in dirname.read_dir().context(dirname)? {
83 let entry = entry.context(dirname)?;
84 let path = entry.path();
85 fs::remove_file(&path).context(&path)?;
86 }
87 Ok(())
88}
89
90pub fn is_empty_dir(path: &Path) -> Result<bool, PathError> {
92 match path.read_dir() {
93 Ok(mut entries) => Ok(entries.next().is_none()),
94 Err(error) => match error.kind() {
95 ErrorKind::NotADirectory => Ok(false),
96 ErrorKind::NotFound => Ok(false),
97 _ => Err(error).context(path)?,
98 },
99 }
100}
101
102#[derive(Debug, Error)]
104#[error(transparent)]
105pub struct BadPathEncoding(platform::BadOsStrEncoding);
106
107pub fn path_from_bytes(bytes: &[u8]) -> Result<&Path, BadPathEncoding> {
112 let s = platform::os_str_from_bytes(bytes).map_err(BadPathEncoding)?;
113 Ok(Path::new(s))
114}
115
116pub fn path_to_bytes(path: &Path) -> Result<&[u8], BadPathEncoding> {
124 platform::os_str_to_bytes(path.as_ref()).map_err(BadPathEncoding)
125}
126
127pub fn expand_home_path(path_str: &str) -> PathBuf {
129 if let Some(remainder) = path_str.strip_prefix("~/")
130 && let Ok(home_dir) = etcetera::home_dir()
131 {
132 return home_dir.join(remainder);
133 }
134 PathBuf::from(path_str)
135}
136
137pub fn relative_path(from: &Path, to: &Path) -> PathBuf {
143 let Some((from_suffix, to_suffix)) = strip_common_path_prefix(from, to) else {
144 return to.to_owned();
146 };
147 let depth = from_suffix.components().count();
148 let mut relative = PathBuf::with_capacity(2 * depth + 1 + to_suffix.as_os_str().len());
149 for _ in 0..depth {
150 relative.push(Component::ParentDir);
151 }
152 if !to_suffix.as_os_str().is_empty() {
153 relative.push(to_suffix);
154 } else if depth == 0 {
155 relative.push(Component::CurDir);
156 }
157 relative
158}
159
160fn strip_common_path_prefix<'a, 'b>(
161 path1: &'a Path,
162 path2: &'b Path,
163) -> Option<(&'a Path, &'b Path)> {
164 let mut components1 = path1.components();
165 let mut components2 = path2.components();
166 let mut suffix_paths = None;
167 while let (Some(c1), Some(c2)) = (components1.next(), components2.next()) {
168 if c1 != c2 {
169 break;
170 }
171 suffix_paths = Some((components1.as_path(), components2.as_path()));
172 }
173 suffix_paths
174}
175
176pub fn normalize_path(path: &Path) -> PathBuf {
178 let mut result = PathBuf::new();
179 for c in path.components() {
180 match c {
181 Component::CurDir => {}
182 Component::ParentDir
183 if matches!(result.components().next_back(), Some(Component::Normal(_))) =>
184 {
185 let popped = result.pop();
187 assert!(popped);
188 }
189 _ => {
190 result.push(c);
191 }
192 }
193 }
194
195 if result.as_os_str().is_empty() {
196 ".".into()
197 } else {
198 result
199 }
200}
201
202pub fn slash_path(path: &Path) -> Cow<'_, Path> {
207 if cfg!(windows) {
208 Cow::Owned(to_slash_separated(path).into())
209 } else {
210 Cow::Borrowed(path)
211 }
212}
213
214fn to_slash_separated(path: &Path) -> OsString {
215 let mut buf = OsString::with_capacity(path.as_os_str().len());
216 let mut components = path.components();
217 match components.next() {
218 Some(c) => buf.push(c),
219 None => return buf,
220 }
221 for c in components {
222 buf.push("/");
223 buf.push(c);
224 }
225 buf
226}
227
228pub fn persist_temp_file<P: AsRef<Path>>(
236 temp_file: NamedTempFile,
237 new_path: P,
238) -> io::Result<File> {
239 temp_file.as_file().sync_data()?;
241 temp_file
242 .persist(new_path)
243 .map_err(|PersistError { error, file: _ }| error)
244}
245
246pub fn persist_content_addressed_temp_file<P: AsRef<Path>>(
249 temp_file: NamedTempFile,
250 new_path: P,
251) -> io::Result<File> {
252 temp_file.as_file().sync_data()?;
255 if cfg!(windows) {
256 match temp_file.persist_noclobber(&new_path) {
260 Ok(file) => Ok(file),
261 Err(PersistError { error, file: _ }) => {
262 if let Ok(existing_file) = File::open(new_path) {
263 Ok(existing_file)
265 } else {
266 Err(error)
267 }
268 }
269 }
270 } else {
271 temp_file
275 .persist(new_path)
276 .map_err(|PersistError { error, file: _ }| error)
277 }
278}
279
280#[derive(Debug, Eq, Hash, PartialEq)]
286pub struct FileIdentity(platform::FileIdentity);
287
288impl FileIdentity {
289 pub fn from_symlink_path(path: impl AsRef<Path>) -> io::Result<Self> {
291 platform::file_identity_from_symlink_path(path.as_ref()).map(Self)
292 }
293
294 pub fn from_file(file: File) -> io::Result<Self> {
297 platform::file_identity_from_file(file).map(Self)
298 }
299}
300
301pub async fn copy_async_to_sync<R: AsyncRead, W: Write + ?Sized>(
304 reader: R,
305 writer: &mut W,
306) -> io::Result<usize> {
307 let mut buf = vec![0; 16 << 10];
308 let mut total_written_bytes = 0;
309
310 let mut reader = std::pin::pin!(reader);
311 loop {
312 let written_bytes = reader.read(&mut buf).await?;
313 if written_bytes == 0 {
314 return Ok(total_written_bytes);
315 }
316 writer.write_all(&buf[0..written_bytes])?;
317 total_written_bytes += written_bytes;
318 }
319}
320
321#[cfg(unix)]
322mod platform {
323 use std::convert::Infallible;
324 use std::ffi::OsStr;
325 use std::fs;
326 use std::fs::File;
327 use std::io;
328 use std::os::unix::ffi::OsStrExt as _;
329 use std::os::unix::fs::MetadataExt as _;
330 use std::os::unix::fs::PermissionsExt;
331 use std::os::unix::fs::symlink;
332 use std::path::Path;
333
334 pub type BadOsStrEncoding = Infallible;
335
336 pub fn os_str_from_bytes(data: &[u8]) -> Result<&OsStr, BadOsStrEncoding> {
337 Ok(OsStr::from_bytes(data))
338 }
339
340 pub fn os_str_to_bytes(data: &OsStr) -> Result<&[u8], BadOsStrEncoding> {
341 Ok(data.as_bytes())
342 }
343
344 pub fn check_executable_bit_support(path: impl AsRef<Path>) -> io::Result<bool> {
347 let temp_file = tempfile::tempfile_in(path)?;
349 let old_mode = temp_file.metadata()?.permissions().mode();
350 let new_mode = old_mode ^ 0o100;
351 let result = temp_file.set_permissions(PermissionsExt::from_mode(new_mode));
352 match result {
353 Err(err) if err.kind() == io::ErrorKind::PermissionDenied => Ok(false),
355 Err(err) => Err(err),
356 Ok(()) => {
357 let mode = temp_file.metadata()?.permissions().mode();
359 Ok(mode == new_mode)
360 }
361 }
362 }
363
364 pub fn check_symlink_support() -> io::Result<bool> {
366 Ok(true)
367 }
368
369 pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
373 symlink(original, link)
374 }
375
376 pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
380 symlink(original, link)
381 }
382
383 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
384 pub struct FileIdentity {
385 dev: u64,
387 ino: u64,
388 }
389
390 impl FileIdentity {
391 fn from_metadata(metadata: fs::Metadata) -> Self {
392 Self {
393 dev: metadata.dev(),
394 ino: metadata.ino(),
395 }
396 }
397 }
398
399 pub fn file_identity_from_symlink_path(path: &Path) -> io::Result<FileIdentity> {
400 path.symlink_metadata().map(FileIdentity::from_metadata)
401 }
402
403 pub fn file_identity_from_file(file: File) -> io::Result<FileIdentity> {
404 file.metadata().map(FileIdentity::from_metadata)
405 }
406}
407
408#[cfg(windows)]
409mod platform {
410 use std::fs::File;
411 use std::fs::OpenOptions;
412 use std::io;
413 use std::os::windows::fs::OpenOptionsExt as _;
414 pub use std::os::windows::fs::symlink_dir;
415 pub use std::os::windows::fs::symlink_file;
416 use std::path::Path;
417
418 use winreg::RegKey;
419 use winreg::enums::HKEY_LOCAL_MACHINE;
420
421 pub use super::fallback::BadOsStrEncoding;
422 pub use super::fallback::os_str_from_bytes;
423 pub use super::fallback::os_str_to_bytes;
424
425 pub fn check_symlink_support() -> io::Result<bool> {
431 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
432 let sideloading =
433 hklm.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock")?;
434 let developer_mode: u32 = sideloading.get_value("AllowDevelopmentWithoutDevLicense")?;
435 Ok(developer_mode == 1)
436 }
437
438 pub type FileIdentity = same_file::Handle;
439
440 pub fn file_identity_from_symlink_path(path: &Path) -> io::Result<FileIdentity> {
441 const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
450 const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
451 let file = OpenOptions::new()
452 .read(true)
453 .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
454 .open(path)?;
455 same_file::Handle::from_file(file)
456 }
457
458 pub fn file_identity_from_file(file: File) -> io::Result<FileIdentity> {
459 same_file::Handle::from_file(file)
460 }
461}
462
463#[cfg_attr(unix, expect(dead_code))]
464mod fallback {
465 use std::ffi::OsStr;
466
467 use thiserror::Error;
468
469 #[derive(Debug, Error)]
471 #[error("Invalid UTF-8 sequence")]
472 pub struct BadOsStrEncoding;
473
474 pub fn os_str_from_bytes(data: &[u8]) -> Result<&OsStr, BadOsStrEncoding> {
475 Ok(str::from_utf8(data).map_err(|_| BadOsStrEncoding)?.as_ref())
476 }
477
478 pub fn os_str_to_bytes(data: &OsStr) -> Result<&[u8], BadOsStrEncoding> {
479 Ok(data.to_str().ok_or(BadOsStrEncoding)?.as_ref())
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use futures::io::Cursor;
486 use itertools::Itertools as _;
487 use pollster::FutureExt as _;
488 use test_case::test_case;
489
490 use super::*;
491 use crate::tests::TestResult;
492 use crate::tests::new_temp_dir;
493
494 #[test]
495 #[cfg(unix)]
496 fn exec_bit_support_in_temp_dir() -> TestResult {
497 let dir = new_temp_dir();
501 let supported = check_executable_bit_support(dir.path())?;
502 assert!(supported);
503 Ok(())
504 }
505
506 #[test]
507 fn test_path_bytes_roundtrip() -> TestResult {
508 let bytes = b"ascii";
509 let path = path_from_bytes(bytes)?;
510 assert_eq!(path_to_bytes(path)?, bytes);
511
512 let bytes = b"utf-8.\xc3\xa0";
513 let path = path_from_bytes(bytes)?;
514 assert_eq!(path_to_bytes(path)?, bytes);
515
516 let bytes = b"latin1.\xe0";
517 if cfg!(unix) {
518 let path = path_from_bytes(bytes)?;
519 assert_eq!(path_to_bytes(path)?, bytes);
520 } else {
521 assert!(path_from_bytes(bytes).is_err());
522 }
523 Ok(())
524 }
525
526 #[test]
527 fn test_relative_path() {
528 let p = |slash_path: &str| {
530 let mut native_path = slash_path.replace('/', std::path::MAIN_SEPARATOR_STR);
531 if cfg!(windows) && slash_path.starts_with('/') {
532 native_path.insert_str(0, "c:"); }
534 native_path
535 };
536 let relative = |from: &str, to: &str| {
537 relative_path(p(from).as_ref(), p(to).as_ref())
538 .into_os_string()
539 .into_string()
540 .unwrap()
541 };
542
543 assert_eq!(relative("/foo/bar", "/foo/bar"), p("."));
544 assert_eq!(relative("/foo", "/foo/bar"), p("bar"));
545 assert_eq!(relative("/", "/foo/bar"), p("foo/bar"));
546
547 assert_eq!(relative("/foo/bar/baz", "/foo/bar"), p(".."));
548 assert_eq!(relative("/foo/baz", "/foo/bar"), p("../bar"));
549 assert_eq!(relative("/baz", "/foo/bar"), p("../foo/bar"));
550
551 assert_eq!(relative("/foo/bar/baz/qux", "/foo/bar"), p("../.."));
552 assert_eq!(relative("/foo/baz/qux", "/foo/bar"), p("../../bar"));
553 assert_eq!(relative("/baz/qux", "/foo/bar"), p("../../foo/bar"));
554
555 assert_eq!(relative("foo/bar", "/foo/bar"), p("/foo/bar"));
557 assert_eq!(relative("/foo/bar", "foo/bar"), p("foo/bar"));
558 assert_eq!(relative("./foo/bar", "/./foo/bar"), p("/./foo/bar"));
559 assert_eq!(relative("/./foo/bar", "./foo/bar"), p("./foo/bar"));
560 assert_eq!(relative("/foo", ""), p("")); assert_eq!(relative("", "/foo"), p("/foo"));
562 assert_eq!(relative("", ""), p("")); assert_eq!(relative("/./foo/./bar", "/foo/bar"), p("."));
566 assert_eq!(relative("/foo/bar", "/./foo/./bar"), p("."));
567 assert_eq!(relative("/./foo/./bar", "/foo"), p(".."));
568 assert_eq!(relative("/foo", "/./foo/./bar"), p("bar"));
569 }
570
571 #[test]
572 fn test_relative_path_windows() {
573 let relative = |from: &str, to: &str| {
575 relative_path(from.as_ref(), to.as_ref())
576 .into_os_string()
577 .into_string()
578 .unwrap()
579 };
580
581 if cfg!(windows) {
582 assert_eq!(relative(r"c:\foo\bar", r"c:\foo\bar"), ".");
583 assert_eq!(relative(r"c:\foo", r"c:\foo\bar"), "bar");
584 assert_eq!(relative(r"c:\", r"c:\foo\bar"), r"foo\bar");
585
586 assert_eq!(relative(r"d:\foo", r"c:\foo\bar"), r"c:\foo\bar");
587 assert_eq!(relative(r"d:\", r"c:\foo\bar"), r"c:\foo\bar");
588
589 assert_eq!(relative(r"\\foo\bar", r"\foo\bar"), r"\foo\bar");
590 assert_eq!(relative(r"\\foo\bar", r"\\foo\bar"), ".");
591 assert_eq!(relative(r"\\foo\bar\baz", r"\\foo\bar\baz"), ".");
592 assert_eq!(relative(r"\\foo\bar\baz", r"\\foo\bar\qux"), r"..\qux");
593 assert_eq!(
594 relative(r"\\foo\bar\baz", r"\\qux\bar\baz"),
595 r"\\qux\bar\baz"
596 );
597 }
598 }
599
600 #[test]
601 fn normalize_too_many_dot_dot() {
602 assert_eq!(normalize_path(Path::new("foo/..")), Path::new("."));
603 assert_eq!(normalize_path(Path::new("foo/../..")), Path::new(".."));
604 assert_eq!(
605 normalize_path(Path::new("foo/../../..")),
606 Path::new("../..")
607 );
608 assert_eq!(
609 normalize_path(Path::new("foo/../../../bar/baz/..")),
610 Path::new("../../bar")
611 );
612 }
613
614 #[test]
615 fn test_slash_path() {
616 assert_eq!(slash_path(Path::new("")), Path::new(""));
617 assert_eq!(slash_path(Path::new("foo")), Path::new("foo"));
618 assert_eq!(slash_path(Path::new("foo/bar")), Path::new("foo/bar"));
619 assert_eq!(slash_path(Path::new("foo/bar/..")), Path::new("foo/bar/.."));
620 assert_eq!(
621 slash_path(Path::new(r"foo\bar")),
622 if cfg!(windows) {
623 Path::new("foo/bar")
624 } else {
625 Path::new(r"foo\bar")
626 }
627 );
628 assert_eq!(
629 slash_path(Path::new(r"..\foo\bar")),
630 if cfg!(windows) {
631 Path::new("../foo/bar")
632 } else {
633 Path::new(r"..\foo\bar")
634 }
635 );
636 }
637
638 #[test]
639 fn test_persist_no_existing_file() -> TestResult {
640 let temp_dir = new_temp_dir();
641 let target = temp_dir.path().join("file");
642 let mut temp_file = NamedTempFile::new_in(&temp_dir)?;
643 temp_file.write_all(b"contents")?;
644 assert!(persist_content_addressed_temp_file(temp_file, target).is_ok());
645 Ok(())
646 }
647
648 #[test_case(false ; "existing file open")]
649 #[test_case(true ; "existing file closed")]
650 fn test_persist_target_exists(existing_file_closed: bool) -> TestResult {
651 let temp_dir = new_temp_dir();
652 let target = temp_dir.path().join("file");
653 let mut temp_file = NamedTempFile::new_in(&temp_dir)?;
654 temp_file.write_all(b"contents")?;
655
656 let mut file = File::create(&target)?;
657 file.write_all(b"contents")?;
658 if existing_file_closed {
659 drop(file);
660 }
661
662 assert!(persist_content_addressed_temp_file(temp_file, &target).is_ok());
663 Ok(())
664 }
665
666 #[test]
667 fn test_file_identity_hard_link() -> TestResult {
668 let temp_dir = new_temp_dir();
669 let file_path = temp_dir.path().join("file");
670 let other_file_path = temp_dir.path().join("other_file");
671 let link_path = temp_dir.path().join("link");
672 fs::write(&file_path, "")?;
673 fs::write(&other_file_path, "")?;
674 fs::hard_link(&file_path, &link_path)?;
675 assert_eq!(
676 FileIdentity::from_symlink_path(&file_path)?,
677 FileIdentity::from_symlink_path(&link_path)?
678 );
679 assert_ne!(
680 FileIdentity::from_symlink_path(&other_file_path)?,
681 FileIdentity::from_symlink_path(&link_path)?
682 );
683 assert_eq!(
684 FileIdentity::from_symlink_path(&file_path)?,
685 FileIdentity::from_file(File::open(&link_path)?)?
686 );
687 Ok(())
688 }
689
690 #[cfg(unix)]
691 #[test]
692 fn test_file_identity_unix_symlink_dir() -> TestResult {
693 let temp_dir = new_temp_dir();
694 let dir_path = temp_dir.path().join("dir");
695 let symlink_path = temp_dir.path().join("symlink");
696 fs::create_dir(&dir_path)?;
697 std::os::unix::fs::symlink("dir", &symlink_path)?;
698 assert_eq!(
700 FileIdentity::from_symlink_path(&symlink_path)?,
701 FileIdentity::from_symlink_path(&symlink_path)?
702 );
703 assert_ne!(
705 FileIdentity::from_symlink_path(&dir_path)?,
706 FileIdentity::from_symlink_path(&symlink_path)?
707 );
708 assert_eq!(
710 FileIdentity::from_symlink_path(&dir_path)?,
711 FileIdentity::from_file(File::open(&symlink_path)?)?
712 );
713 assert_ne!(
714 FileIdentity::from_symlink_path(&symlink_path)?,
715 FileIdentity::from_file(File::open(&symlink_path)?)?
716 );
717 Ok(())
718 }
719
720 #[cfg(windows)]
721 #[test]
722 fn test_file_identity_windows_symlink_file() -> TestResult {
723 if !check_symlink_support()? {
724 return Ok(());
725 }
726 let temp_dir = new_temp_dir();
727 let file_path = temp_dir.path().join("file");
728 let symlink_path = temp_dir.path().join("symlink");
729 fs::write(&file_path, "")?;
730 symlink_file("file", &symlink_path)?;
731 assert_eq!(
733 FileIdentity::from_symlink_path(&symlink_path)?,
734 FileIdentity::from_symlink_path(&symlink_path)?
735 );
736 assert_ne!(
738 FileIdentity::from_symlink_path(&file_path)?,
739 FileIdentity::from_symlink_path(&symlink_path)?
740 );
741 assert_eq!(
743 FileIdentity::from_symlink_path(&file_path)?,
744 FileIdentity::from_file(File::open(&symlink_path)?)?
745 );
746 assert_ne!(
747 FileIdentity::from_symlink_path(&symlink_path)?,
748 FileIdentity::from_file(File::open(&symlink_path)?)?
749 );
750 Ok(())
751 }
752
753 #[cfg(windows)]
754 #[test]
755 fn test_file_identity_windows_symlink_dir() -> TestResult {
756 if !check_symlink_support()? {
757 return Ok(());
758 }
759 let temp_dir = new_temp_dir();
760 let dir_path = temp_dir.path().join("dir");
761 let symlink_path = temp_dir.path().join("symlink");
762 fs::create_dir(&dir_path)?;
763 symlink_dir("dir", &symlink_path)?;
764 assert_eq!(
766 FileIdentity::from_symlink_path(&symlink_path)?,
767 FileIdentity::from_symlink_path(&symlink_path)?
768 );
769 assert_ne!(
773 FileIdentity::from_symlink_path(&dir_path)?,
774 FileIdentity::from_symlink_path(&symlink_path)?
775 );
776 Ok(())
777 }
778
779 #[test]
780 fn test_file_identity_directory() -> TestResult {
781 let temp_dir = new_temp_dir();
782 let dir_path = temp_dir.path().join("dir");
783 let other_dir_path = temp_dir.path().join("other_dir");
784 fs::create_dir(&dir_path)?;
785 fs::create_dir(&other_dir_path)?;
786 assert_eq!(
788 FileIdentity::from_symlink_path(&dir_path)?,
789 FileIdentity::from_symlink_path(&dir_path)?
790 );
791 assert_ne!(
793 FileIdentity::from_symlink_path(&dir_path)?,
794 FileIdentity::from_symlink_path(&other_dir_path)?
795 );
796 Ok(())
797 }
798
799 #[cfg(unix)]
800 #[test]
801 fn test_file_identity_unix_symlink_loop() -> TestResult {
802 let temp_dir = new_temp_dir();
803 let lower_file_path = temp_dir.path().join("file");
804 let upper_file_path = temp_dir.path().join("FILE");
805 let lower_symlink_path = temp_dir.path().join("symlink");
806 let upper_symlink_path = temp_dir.path().join("SYMLINK");
807 fs::write(&lower_file_path, "")?;
808 std::os::unix::fs::symlink("symlink", &lower_symlink_path)?;
809 let is_icase_fs = upper_file_path.try_exists()?;
810 assert_eq!(
812 FileIdentity::from_symlink_path(&lower_symlink_path)?,
813 FileIdentity::from_symlink_path(&lower_symlink_path)?
814 );
815 assert_ne!(
816 FileIdentity::from_symlink_path(&lower_symlink_path)?,
817 FileIdentity::from_symlink_path(&lower_file_path)?
818 );
819 if is_icase_fs {
820 assert_eq!(
821 FileIdentity::from_symlink_path(&lower_symlink_path)?,
822 FileIdentity::from_symlink_path(&upper_symlink_path)?
823 );
824 } else {
825 assert!(FileIdentity::from_symlink_path(&upper_symlink_path).is_err());
826 }
827 Ok(())
828 }
829
830 #[test]
831 fn test_copy_async_to_sync_small() -> TestResult {
832 let input = b"hello";
833 let mut output = vec![];
834
835 let result = copy_async_to_sync(Cursor::new(&input), &mut output).block_on();
836 assert!(result.is_ok());
837 assert_eq!(result?, 5);
838 assert_eq!(output, input);
839 Ok(())
840 }
841
842 #[test]
843 fn test_copy_async_to_sync_large() -> TestResult {
844 let input = (0..100u8).cycle().take(40000).collect_vec();
846 let mut output = vec![];
847
848 let result = copy_async_to_sync(Cursor::new(&input), &mut output).block_on();
849 assert!(result.is_ok());
850 assert_eq!(result?, 40000);
851 assert_eq!(output, input);
852 Ok(())
853 }
854}