jj_lib/
file_util.rs

1// Copyright 2021 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![expect(missing_docs)]
16
17use std::borrow::Cow;
18use std::ffi::OsString;
19use std::fs;
20use std::fs::File;
21use std::io;
22use std::io::ErrorKind;
23use std::io::Read;
24use std::io::Write;
25use std::path::Component;
26use std::path::Path;
27use std::path::PathBuf;
28use std::pin::Pin;
29use std::task::Poll;
30
31use tempfile::NamedTempFile;
32use tempfile::PersistError;
33use thiserror::Error;
34use tokio::io::AsyncRead;
35use tokio::io::AsyncReadExt as _;
36use tokio::io::ReadBuf;
37
38#[cfg(unix)]
39pub use self::platform::check_executable_bit_support;
40pub use self::platform::check_symlink_support;
41pub use self::platform::symlink_dir;
42pub use self::platform::symlink_file;
43
44#[derive(Debug, Error)]
45#[error("Cannot access {path}")]
46pub struct PathError {
47    pub path: PathBuf,
48    pub source: io::Error,
49}
50
51pub trait IoResultExt<T> {
52    fn context(self, path: impl AsRef<Path>) -> Result<T, PathError>;
53}
54
55impl<T> IoResultExt<T> for io::Result<T> {
56    fn context(self, path: impl AsRef<Path>) -> Result<T, PathError> {
57        self.map_err(|error| PathError {
58            path: path.as_ref().to_path_buf(),
59            source: error,
60        })
61    }
62}
63
64/// Creates a directory or does nothing if the directory already exists.
65///
66/// Returns the underlying error if the directory can't be created.
67/// The function will also fail if intermediate directories on the path do not
68/// already exist.
69pub fn create_or_reuse_dir(dirname: &Path) -> io::Result<()> {
70    match fs::create_dir(dirname) {
71        Ok(()) => Ok(()),
72        Err(_) if dirname.is_dir() => Ok(()),
73        Err(e) => Err(e),
74    }
75}
76
77/// Removes all files in the directory, but not the directory itself.
78///
79/// The directory must exist, and there should be no sub directories.
80pub fn remove_dir_contents(dirname: &Path) -> Result<(), PathError> {
81    for entry in dirname.read_dir().context(dirname)? {
82        let entry = entry.context(dirname)?;
83        let path = entry.path();
84        fs::remove_file(&path).context(&path)?;
85    }
86    Ok(())
87}
88
89/// Checks if path points at an empty directory.
90pub fn is_empty_dir(path: &Path) -> Result<bool, PathError> {
91    match path.read_dir() {
92        Ok(mut entries) => Ok(entries.next().is_none()),
93        Err(error) => match error.kind() {
94            ErrorKind::NotADirectory => Ok(false),
95            ErrorKind::NotFound => Ok(false),
96            _ => Err(error).context(path)?,
97        },
98    }
99}
100
101#[derive(Debug, Error)]
102#[error(transparent)]
103pub struct BadPathEncoding(platform::BadOsStrEncoding);
104
105/// Constructs [`Path`] from `bytes` in platform-specific manner.
106///
107/// On Unix, this function never fails because paths are just bytes. On Windows,
108/// this may return error if the input wasn't well-formed UTF-8.
109pub fn path_from_bytes(bytes: &[u8]) -> Result<&Path, BadPathEncoding> {
110    let s = platform::os_str_from_bytes(bytes).map_err(BadPathEncoding)?;
111    Ok(Path::new(s))
112}
113
114/// Converts `path` to bytes in platform-specific manner.
115///
116/// On Unix, this function never fails because paths are just bytes. On Windows,
117/// this may return error if the input wasn't well-formed UTF-8.
118///
119/// The returned byte sequence can be considered a superset of ASCII (such as
120/// UTF-8 bytes.)
121pub fn path_to_bytes(path: &Path) -> Result<&[u8], BadPathEncoding> {
122    platform::os_str_to_bytes(path.as_ref()).map_err(BadPathEncoding)
123}
124
125/// Expands "~/" to "$HOME/".
126pub fn expand_home_path(path_str: &str) -> PathBuf {
127    if let Some(remainder) = path_str.strip_prefix("~/")
128        && let Ok(home_dir_str) = std::env::var("HOME")
129    {
130        return PathBuf::from(home_dir_str).join(remainder);
131    }
132    PathBuf::from(path_str)
133}
134
135/// Turns the given `to` path into relative path starting from the `from` path.
136///
137/// Both `from` and `to` paths are supposed to be absolute and normalized in the
138/// same manner.
139pub fn relative_path(from: &Path, to: &Path) -> PathBuf {
140    // Find common prefix.
141    for (i, base) in from.ancestors().enumerate() {
142        if let Ok(suffix) = to.strip_prefix(base) {
143            if i == 0 && suffix.as_os_str().is_empty() {
144                return ".".into();
145            } else {
146                let mut result = PathBuf::from_iter(std::iter::repeat_n("..", i));
147                result.push(suffix);
148                return result;
149            }
150        }
151    }
152
153    // No common prefix found. Return the original (absolute) path.
154    to.to_owned()
155}
156
157/// Consumes as much `..` and `.` as possible without considering symlinks.
158pub fn normalize_path(path: &Path) -> PathBuf {
159    let mut result = PathBuf::new();
160    for c in path.components() {
161        match c {
162            Component::CurDir => {}
163            Component::ParentDir
164                if matches!(result.components().next_back(), Some(Component::Normal(_))) =>
165            {
166                // Do not pop ".."
167                let popped = result.pop();
168                assert!(popped);
169            }
170            _ => {
171                result.push(c);
172            }
173        }
174    }
175
176    if result.as_os_str().is_empty() {
177        ".".into()
178    } else {
179        result
180    }
181}
182
183/// Converts the given `path` to Unix-like path separated by "/".
184///
185/// The returned path might not work on Windows if it was canonicalized. On
186/// Unix, this function is noop.
187pub fn slash_path(path: &Path) -> Cow<'_, Path> {
188    if cfg!(windows) {
189        Cow::Owned(to_slash_separated(path).into())
190    } else {
191        Cow::Borrowed(path)
192    }
193}
194
195fn to_slash_separated(path: &Path) -> OsString {
196    let mut buf = OsString::with_capacity(path.as_os_str().len());
197    let mut components = path.components();
198    match components.next() {
199        Some(c) => buf.push(c),
200        None => return buf,
201    }
202    for c in components {
203        buf.push("/");
204        buf.push(c);
205    }
206    buf
207}
208
209/// Persists the temporary file after synchronizing the content.
210///
211/// After system crash, the persisted file should have a valid content if
212/// existed. However, the persisted file name (or directory entry) could be
213/// lost. It's up to caller to synchronize the directory entries.
214///
215/// See also <https://lwn.net/Articles/457667/> for the behavior on Linux.
216pub fn persist_temp_file<P: AsRef<Path>>(
217    temp_file: NamedTempFile,
218    new_path: P,
219) -> io::Result<File> {
220    // Ensure persisted file content is flushed to disk.
221    temp_file.as_file().sync_data()?;
222    temp_file
223        .persist(new_path)
224        .map_err(|PersistError { error, file: _ }| error)
225}
226
227/// Like [`persist_temp_file()`], but doesn't try to overwrite the existing
228/// target on Windows.
229pub fn persist_content_addressed_temp_file<P: AsRef<Path>>(
230    temp_file: NamedTempFile,
231    new_path: P,
232) -> io::Result<File> {
233    // Ensure new file content is flushed to disk, so the old file content
234    // wouldn't be lost if existed at the same location.
235    temp_file.as_file().sync_data()?;
236    if cfg!(windows) {
237        // On Windows, overwriting file can fail if the file is opened without
238        // FILE_SHARE_DELETE for example. We don't need to take a risk if the
239        // file already exists.
240        match temp_file.persist_noclobber(&new_path) {
241            Ok(file) => Ok(file),
242            Err(PersistError { error, file: _ }) => {
243                if let Ok(existing_file) = File::open(new_path) {
244                    // TODO: Update mtime to help GC keep this file
245                    Ok(existing_file)
246                } else {
247                    Err(error)
248                }
249            }
250        }
251    } else {
252        // On Unix, rename() is atomic and should succeed even if the
253        // destination file exists. Checking if the target exists might involve
254        // non-atomic operation, so don't use persist_noclobber().
255        temp_file
256            .persist(new_path)
257            .map_err(|PersistError { error, file: _ }| error)
258    }
259}
260
261/// Opaque value that can be tested to know whether file or directory paths
262/// point to the same filesystem entity.
263///
264/// The primary use case is to detect file name aliases on case-insensitive
265/// filesystem. On Unix, device and inode numbers are compared.
266#[derive(Debug, Eq, Hash, PartialEq)]
267pub struct FileIdentity(platform::FileIdentity);
268
269impl FileIdentity {
270    /// Queries file identity without following symlinks.
271    ///
272    /// BUG: On Windows, symbolic links would be followed.
273    pub fn from_symlink_path(path: impl AsRef<Path>) -> io::Result<Self> {
274        platform::file_identity_from_symlink_path(path.as_ref()).map(Self)
275    }
276
277    /// Queries file identity of the given `file`.
278    // TODO: do not consume file object
279    pub fn from_file(file: File) -> io::Result<Self> {
280        platform::file_identity_from_file(file).map(Self)
281    }
282}
283
284/// Reads from an async source and writes to a sync destination. Does not spawn
285/// a task, so writes will block.
286pub async fn copy_async_to_sync<R: AsyncRead, W: Write + ?Sized>(
287    reader: R,
288    writer: &mut W,
289) -> io::Result<usize> {
290    let mut buf = vec![0; 16 << 10];
291    let mut total_written_bytes = 0;
292
293    let mut reader = std::pin::pin!(reader);
294    loop {
295        let written_bytes = reader.read(&mut buf).await?;
296        if written_bytes == 0 {
297            return Ok(total_written_bytes);
298        }
299        writer.write_all(&buf[0..written_bytes])?;
300        total_written_bytes += written_bytes;
301    }
302}
303
304/// `AsyncRead` implementation backed by a `Read`. It is not actually async;
305/// the goal is simply to avoid reading the full contents from the `Read` into
306/// memory.
307pub struct BlockingAsyncReader<R> {
308    reader: R,
309}
310
311impl<R: Read + Unpin> BlockingAsyncReader<R> {
312    /// Creates a new `BlockingAsyncReader`
313    pub fn new(reader: R) -> Self {
314        Self { reader }
315    }
316}
317
318impl<R: Read + Unpin> AsyncRead for BlockingAsyncReader<R> {
319    fn poll_read(
320        mut self: Pin<&mut Self>,
321        _cx: &mut std::task::Context<'_>,
322        buf: &mut ReadBuf<'_>,
323    ) -> Poll<io::Result<()>> {
324        let num_bytes_read = self.reader.read(buf.initialize_unfilled())?;
325        buf.advance(num_bytes_read);
326        Poll::Ready(Ok(()))
327    }
328}
329
330#[cfg(unix)]
331mod platform {
332    use std::convert::Infallible;
333    use std::ffi::OsStr;
334    use std::fs;
335    use std::fs::File;
336    use std::io;
337    use std::os::unix::ffi::OsStrExt as _;
338    use std::os::unix::fs::MetadataExt as _;
339    use std::os::unix::fs::PermissionsExt;
340    use std::os::unix::fs::symlink;
341    use std::path::Path;
342
343    pub type BadOsStrEncoding = Infallible;
344
345    pub fn os_str_from_bytes(data: &[u8]) -> Result<&OsStr, BadOsStrEncoding> {
346        Ok(OsStr::from_bytes(data))
347    }
348
349    pub fn os_str_to_bytes(data: &OsStr) -> Result<&[u8], BadOsStrEncoding> {
350        Ok(data.as_bytes())
351    }
352
353    /// Whether changing executable bits is permitted on the filesystem of this
354    /// directory, and whether attempting to flip one has an observable effect.
355    pub fn check_executable_bit_support(path: impl AsRef<Path>) -> io::Result<bool> {
356        // Get current permissions and try to flip just the user's executable bit.
357        let temp_file = tempfile::tempfile_in(path)?;
358        let old_mode = temp_file.metadata()?.permissions().mode();
359        let new_mode = old_mode ^ 0o100;
360        let result = temp_file.set_permissions(PermissionsExt::from_mode(new_mode));
361        match result {
362            // If permission was denied, we do not have executable bit support.
363            Err(err) if err.kind() == io::ErrorKind::PermissionDenied => Ok(false),
364            Err(err) => Err(err),
365            Ok(()) => {
366                // Verify that the permission change was not silently ignored.
367                let mode = temp_file.metadata()?.permissions().mode();
368                Ok(mode == new_mode)
369            }
370        }
371    }
372
373    /// Symlinks are always available on Unix.
374    pub fn check_symlink_support() -> io::Result<bool> {
375        Ok(true)
376    }
377
378    /// Creates a new symlink `link` pointing to the `original` path.
379    ///
380    /// On Unix, the `original` path doesn't have to be a directory.
381    pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
382        symlink(original, link)
383    }
384
385    /// Creates a new symlink `link` pointing to the `original` path.
386    ///
387    /// On Unix, the `original` path doesn't have to be a file.
388    pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
389        symlink(original, link)
390    }
391
392    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
393    pub struct FileIdentity {
394        // https://github.com/BurntSushi/same-file/blob/1.0.6/src/unix.rs#L30
395        dev: u64,
396        ino: u64,
397    }
398
399    impl FileIdentity {
400        fn from_metadata(metadata: fs::Metadata) -> Self {
401            Self {
402                dev: metadata.dev(),
403                ino: metadata.ino(),
404            }
405        }
406    }
407
408    pub fn file_identity_from_symlink_path(path: &Path) -> io::Result<FileIdentity> {
409        path.symlink_metadata().map(FileIdentity::from_metadata)
410    }
411
412    pub fn file_identity_from_file(file: File) -> io::Result<FileIdentity> {
413        file.metadata().map(FileIdentity::from_metadata)
414    }
415}
416
417#[cfg(windows)]
418mod platform {
419    use std::fs::File;
420    use std::io;
421    pub use std::os::windows::fs::symlink_dir;
422    pub use std::os::windows::fs::symlink_file;
423    use std::path::Path;
424
425    use winreg::RegKey;
426    use winreg::enums::HKEY_LOCAL_MACHINE;
427
428    pub use super::fallback::BadOsStrEncoding;
429    pub use super::fallback::os_str_from_bytes;
430    pub use super::fallback::os_str_to_bytes;
431
432    /// Symlinks may or may not be enabled on Windows. They require the
433    /// Developer Mode setting, which is stored in the registry key below.
434    ///
435    /// Note: If developer mode is not enabled, the error code of symlink
436    /// creation will be 1314, `ERROR_PRIVILEGE_NOT_HELD`.
437    pub fn check_symlink_support() -> io::Result<bool> {
438        let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
439        let sideloading =
440            hklm.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock")?;
441        let developer_mode: u32 = sideloading.get_value("AllowDevelopmentWithoutDevLicense")?;
442        Ok(developer_mode == 1)
443    }
444
445    pub type FileIdentity = same_file::Handle;
446
447    // FIXME: This shouldn't follow symlinks when querying file identity.
448    // Perhaps, we need to open file with FILE_FLAG_BACKUP_SEMANTICS and
449    // FILE_FLAG_OPEN_REPARSE_POINT, then pass it to from_file(). Alternatively,
450    // maybe we can use symlink_metadata(), volume_serial_number(), and
451    // file_index() when they get stabilized. See the same-file crate and std
452    // lstat() implementation. https://github.com/rust-lang/rust/issues/63010
453    pub fn file_identity_from_symlink_path(path: &Path) -> io::Result<FileIdentity> {
454        same_file::Handle::from_path(path)
455    }
456
457    pub fn file_identity_from_file(file: File) -> io::Result<FileIdentity> {
458        same_file::Handle::from_file(file)
459    }
460}
461
462#[cfg_attr(unix, expect(dead_code))]
463mod fallback {
464    use std::ffi::OsStr;
465
466    use thiserror::Error;
467
468    // Define error per platform so we can explicitly say UTF-8 is expected.
469    #[derive(Debug, Error)]
470    #[error("Invalid UTF-8 sequence")]
471    pub struct BadOsStrEncoding;
472
473    pub fn os_str_from_bytes(data: &[u8]) -> Result<&OsStr, BadOsStrEncoding> {
474        Ok(str::from_utf8(data).map_err(|_| BadOsStrEncoding)?.as_ref())
475    }
476
477    pub fn os_str_to_bytes(data: &OsStr) -> Result<&[u8], BadOsStrEncoding> {
478        Ok(data.to_str().ok_or(BadOsStrEncoding)?.as_ref())
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use std::io::Cursor;
485    use std::io::Write as _;
486
487    use itertools::Itertools as _;
488    use pollster::FutureExt as _;
489    use test_case::test_case;
490
491    use super::*;
492    use crate::tests::new_temp_dir;
493
494    #[test]
495    #[cfg(unix)]
496    fn exec_bit_support_in_temp_dir() {
497        // Temporary directories on Unix should always have executable support.
498        // Note that it would be problematic to test in a non-temp directory, as
499        // a developer's filesystem may or may not have executable bit support.
500        let dir = new_temp_dir();
501        let supported = check_executable_bit_support(dir.path()).unwrap();
502        assert!(supported);
503    }
504
505    #[test]
506    fn test_path_bytes_roundtrip() {
507        let bytes = b"ascii";
508        let path = path_from_bytes(bytes).unwrap();
509        assert_eq!(path_to_bytes(path).unwrap(), bytes);
510
511        let bytes = b"utf-8.\xc3\xa0";
512        let path = path_from_bytes(bytes).unwrap();
513        assert_eq!(path_to_bytes(path).unwrap(), bytes);
514
515        let bytes = b"latin1.\xe0";
516        if cfg!(unix) {
517            let path = path_from_bytes(bytes).unwrap();
518            assert_eq!(path_to_bytes(path).unwrap(), bytes);
519        } else {
520            assert!(path_from_bytes(bytes).is_err());
521        }
522    }
523
524    #[test]
525    fn normalize_too_many_dot_dot() {
526        assert_eq!(normalize_path(Path::new("foo/..")), Path::new("."));
527        assert_eq!(normalize_path(Path::new("foo/../..")), Path::new(".."));
528        assert_eq!(
529            normalize_path(Path::new("foo/../../..")),
530            Path::new("../..")
531        );
532        assert_eq!(
533            normalize_path(Path::new("foo/../../../bar/baz/..")),
534            Path::new("../../bar")
535        );
536    }
537
538    #[test]
539    fn test_slash_path() {
540        assert_eq!(slash_path(Path::new("")), Path::new(""));
541        assert_eq!(slash_path(Path::new("foo")), Path::new("foo"));
542        assert_eq!(slash_path(Path::new("foo/bar")), Path::new("foo/bar"));
543        assert_eq!(slash_path(Path::new("foo/bar/..")), Path::new("foo/bar/.."));
544        assert_eq!(
545            slash_path(Path::new(r"foo\bar")),
546            if cfg!(windows) {
547                Path::new("foo/bar")
548            } else {
549                Path::new(r"foo\bar")
550            }
551        );
552        assert_eq!(
553            slash_path(Path::new(r"..\foo\bar")),
554            if cfg!(windows) {
555                Path::new("../foo/bar")
556            } else {
557                Path::new(r"..\foo\bar")
558            }
559        );
560    }
561
562    #[test]
563    fn test_persist_no_existing_file() {
564        let temp_dir = new_temp_dir();
565        let target = temp_dir.path().join("file");
566        let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
567        temp_file.write_all(b"contents").unwrap();
568        assert!(persist_content_addressed_temp_file(temp_file, target).is_ok());
569    }
570
571    #[test_case(false ; "existing file open")]
572    #[test_case(true ; "existing file closed")]
573    fn test_persist_target_exists(existing_file_closed: bool) {
574        let temp_dir = new_temp_dir();
575        let target = temp_dir.path().join("file");
576        let mut temp_file = NamedTempFile::new_in(&temp_dir).unwrap();
577        temp_file.write_all(b"contents").unwrap();
578
579        let mut file = File::create(&target).unwrap();
580        file.write_all(b"contents").unwrap();
581        if existing_file_closed {
582            drop(file);
583        }
584
585        assert!(persist_content_addressed_temp_file(temp_file, &target).is_ok());
586    }
587
588    #[test]
589    fn test_file_identity_hard_link() {
590        let temp_dir = new_temp_dir();
591        let file_path = temp_dir.path().join("file");
592        let other_file_path = temp_dir.path().join("other_file");
593        let link_path = temp_dir.path().join("link");
594        fs::write(&file_path, "").unwrap();
595        fs::write(&other_file_path, "").unwrap();
596        fs::hard_link(&file_path, &link_path).unwrap();
597        assert_eq!(
598            FileIdentity::from_symlink_path(&file_path).unwrap(),
599            FileIdentity::from_symlink_path(&link_path).unwrap()
600        );
601        assert_ne!(
602            FileIdentity::from_symlink_path(&other_file_path).unwrap(),
603            FileIdentity::from_symlink_path(&link_path).unwrap()
604        );
605        assert_eq!(
606            FileIdentity::from_symlink_path(&file_path).unwrap(),
607            FileIdentity::from_file(File::open(&link_path).unwrap()).unwrap()
608        );
609    }
610
611    #[cfg(unix)]
612    #[test]
613    fn test_file_identity_unix_symlink_dir() {
614        let temp_dir = new_temp_dir();
615        let dir_path = temp_dir.path().join("dir");
616        let symlink_path = temp_dir.path().join("symlink");
617        fs::create_dir(&dir_path).unwrap();
618        std::os::unix::fs::symlink("dir", &symlink_path).unwrap();
619        // symlink should be identical to itself
620        assert_eq!(
621            FileIdentity::from_symlink_path(&symlink_path).unwrap(),
622            FileIdentity::from_symlink_path(&symlink_path).unwrap()
623        );
624        // symlink should be different from the target directory
625        assert_ne!(
626            FileIdentity::from_symlink_path(&dir_path).unwrap(),
627            FileIdentity::from_symlink_path(&symlink_path).unwrap()
628        );
629        // File::open() follows symlinks
630        assert_eq!(
631            FileIdentity::from_symlink_path(&dir_path).unwrap(),
632            FileIdentity::from_file(File::open(&symlink_path).unwrap()).unwrap()
633        );
634        assert_ne!(
635            FileIdentity::from_symlink_path(&symlink_path).unwrap(),
636            FileIdentity::from_file(File::open(&symlink_path).unwrap()).unwrap()
637        );
638    }
639
640    #[cfg(unix)]
641    #[test]
642    fn test_file_identity_unix_symlink_loop() {
643        let temp_dir = new_temp_dir();
644        let lower_file_path = temp_dir.path().join("file");
645        let upper_file_path = temp_dir.path().join("FILE");
646        let lower_symlink_path = temp_dir.path().join("symlink");
647        let upper_symlink_path = temp_dir.path().join("SYMLINK");
648        fs::write(&lower_file_path, "").unwrap();
649        std::os::unix::fs::symlink("symlink", &lower_symlink_path).unwrap();
650        let is_icase_fs = upper_file_path.try_exists().unwrap();
651        // symlink should be identical to itself
652        assert_eq!(
653            FileIdentity::from_symlink_path(&lower_symlink_path).unwrap(),
654            FileIdentity::from_symlink_path(&lower_symlink_path).unwrap()
655        );
656        assert_ne!(
657            FileIdentity::from_symlink_path(&lower_symlink_path).unwrap(),
658            FileIdentity::from_symlink_path(&lower_file_path).unwrap()
659        );
660        if is_icase_fs {
661            assert_eq!(
662                FileIdentity::from_symlink_path(&lower_symlink_path).unwrap(),
663                FileIdentity::from_symlink_path(&upper_symlink_path).unwrap()
664            );
665        } else {
666            assert!(FileIdentity::from_symlink_path(&upper_symlink_path).is_err());
667        }
668    }
669
670    #[test]
671    fn test_copy_async_to_sync_small() {
672        let input = b"hello";
673        let mut output = vec![];
674
675        let result = copy_async_to_sync(Cursor::new(&input), &mut output).block_on();
676        assert!(result.is_ok());
677        assert_eq!(result.unwrap(), 5);
678        assert_eq!(output, input);
679    }
680
681    #[test]
682    fn test_copy_async_to_sync_large() {
683        // More than 1 buffer worth of data
684        let input = (0..100u8).cycle().take(40000).collect_vec();
685        let mut output = vec![];
686
687        let result = copy_async_to_sync(Cursor::new(&input), &mut output).block_on();
688        assert!(result.is_ok());
689        assert_eq!(result.unwrap(), 40000);
690        assert_eq!(output, input);
691    }
692
693    #[test]
694    fn test_blocking_async_reader() {
695        let input = b"hello";
696        let sync_reader = Cursor::new(&input);
697        let mut async_reader = BlockingAsyncReader::new(sync_reader);
698
699        let mut buf = [0u8; 3];
700        let num_bytes_read = async_reader.read(&mut buf).block_on().unwrap();
701        assert_eq!(num_bytes_read, 3);
702        assert_eq!(&buf, &input[0..3]);
703
704        let num_bytes_read = async_reader.read(&mut buf).block_on().unwrap();
705        assert_eq!(num_bytes_read, 2);
706        assert_eq!(&buf[0..2], &input[3..5]);
707    }
708
709    #[test]
710    fn test_blocking_async_reader_read_to_end() {
711        let input = b"hello";
712        let sync_reader = Cursor::new(&input);
713        let mut async_reader = BlockingAsyncReader::new(sync_reader);
714
715        let mut buf = vec![];
716        let num_bytes_read = async_reader.read_to_end(&mut buf).block_on().unwrap();
717        assert_eq!(num_bytes_read, input.len());
718        assert_eq!(&buf, &input);
719    }
720}