putter 0.1.0

A tool to put files in the right place
Documentation
use std::{fs, io, path::Path};

/// Determines whether the two given paths are files having the same content.
pub fn file_eq(path1: &Path, path2: &Path) -> io::Result<bool> {
    let path1 = &path1.canonicalize()?;
    let path2 = &path2.canonicalize()?;

    // If the two paths canonicalize to the exact same path, then they are
    // trivially equal.
    if path1 == path2 {
        return Ok(true);
    }

    if fs::metadata(path1)?.len() != fs::metadata(path2)?.len() {
        return Ok(false);
    }

    Ok(fs::read(path1)? == fs::read(path2)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    use assert_fs::prelude::{FileWriteStr, PathChild, SymlinkToFile};
    use std::{io, path::Path};

    #[test]
    fn files_with_same_canonicalized_paths_returns_true() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file1 = dir.child("file1");

        // An alternative way to express the path to file1. Specifically, if
        // file1 is /foo/bar/file1 then the alternative is
        // /foo/bar/../bar/file1.
        let file1_alt = {
            let mut f = dir.to_path_buf();
            f.push("..");
            f.push(dir.file_name().unwrap());
            f.push(file1.file_name().unwrap());
            f
        };

        file1.write_str("abc").unwrap();

        let actual = file_eq(&file1, &file1_alt);
        assert!(actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn symlinks_leading_to_same_file_returns_true() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file = dir.child("file");
        let symlink1 = dir.child("symlink1");
        let symlink2a = dir.child("symlink2a");
        let symlink2b = dir.child("symlink2b");

        file.write_str("abc").unwrap();

        symlink1.symlink_to_file(&file).unwrap();
        symlink2a.symlink_to_file(&file).unwrap();
        symlink2b.symlink_to_file(&symlink2a).unwrap();

        let actual = file_eq(&symlink1, &symlink2b);
        assert!(actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn symlinks_leading_to_same_directory_returns_true() {
        let dir = assert_fs::TempDir::new().unwrap();
        let subdir = dir.child("subdir");
        let symlink1 = dir.child("symlink1");
        let symlink2a = dir.child("symlink2a");
        let symlink2b = dir.child("symlink2b");

        subdir.write_str("abc").unwrap();

        symlink1.symlink_to_file(&subdir).unwrap();
        symlink2a.symlink_to_file(&subdir).unwrap();
        symlink2b.symlink_to_file(&symlink2a).unwrap();

        let actual = file_eq(&symlink1, &symlink2b);
        assert!(actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn files_with_same_content_returns_true() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file1 = dir.child("file1");
        let file2 = dir.child("file2");

        file1.write_str("abc").unwrap();
        file2.write_str("abc").unwrap();

        let actual = file_eq(&file1, &file2);
        assert!(actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn files_of_different_size_returns_false() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file1 = dir.child("file1");
        let file2 = dir.child("file2");

        file1.write_str("abc").unwrap();
        file2.write_str("abcd").unwrap();

        let actual = file_eq(&file1, &file2);
        assert!(!actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn files_of_same_size_but_different_content_returns_false() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file1 = dir.child("file1");
        let file2 = dir.child("file2");

        file1.write_str("abc").unwrap();
        file2.write_str("xyz").unwrap();

        let actual = file_eq(&file1, &file2);
        assert!(!actual.unwrap());

        dir.close().unwrap();
    }

    #[test]
    fn bad_path_returns_path_not_found() {
        let dir = assert_fs::TempDir::new().unwrap();
        let file1 = dir.child("file1");

        file1.write_str("abc").unwrap();

        let actual = file_eq(&file1, Path::new("no-such-file"));
        assert_eq!(actual.unwrap_err().kind(), io::ErrorKind::NotFound);
    }
}