backpak 0.3.0

A content-addressed backup system with deduplication and compression
Documentation
use std::{
    fs,
    os::unix::{self, fs::PermissionsExt},
    process::Command,
};

use anyhow::Result;
use tempfile::tempdir;

mod common;

use common::*;

#[test]
fn diff_src() -> Result<()> {
    let backup_dir = tempdir()?;
    let backup_path = backup_dir.path();

    let working_dir = tempdir()?;
    let working_path = working_dir.path();

    // Let's make a copy of src so we don't fudge the actual code
    assert!(
        Command::new("cp")
            .args(&["-a", "src"])
            .arg(working_path)
            .status()?
            .success()
    );

    cli_run(working_path, backup_path)?
        .args(["init", "filesystem"])
        .assert()
        .success();

    // And back it up
    cli_run(working_path, backup_path)?
        .arg("backup")
        .arg(working_path.join("src"))
        .assert()
        .success();

    let diffit = || {
        let diff_run = cli_run(working_path, backup_path)
            .unwrap()
            .args(&["diff", "--metadata", "LAST"])
            .assert()
            .success();
        let diff_output: Vec<_> = stdout(&diff_run)
            .trim()
            .lines()
            // I don't care about atime. They change if you sneeze (and based on mount opts).
            .filter(|l| !l.starts_with("A "))
            .map(str::to_owned)
            .collect();
        diff_output
    };

    let compare = |expected: &[&str]| {
        assert_eq!(expected, diffit());
    };

    compare(&[]);

    // Obvious stuff - added, removed, moved, modified, perms...
    fs::remove_file(working_path.join("src/diff.rs"))?;
    fs::File::create(working_path.join("src/aNewFile"))?;
    fs::rename(
        working_path.join("src/backend"),
        working_path.join("src/wackend"),
    )?;
    fs::write(working_path.join("src/lib.rs"), "I want some butts!")?;
    fs::set_permissions(
        working_path.join("src/main.rs"),
        fs::Permissions::from_mode(0o777),
    )?;

    compare(&[
        "+ src/aNewFile",
        "- src/backend/",
        "- src/backend/backblaze.rs",
        "- src/backend/cache.rs",
        "- src/backend/filter.rs",
        "- src/backend/fs.rs",
        "- src/backend/memory.rs",
        "- src/backend/semaphored.rs",
        "- src/diff.rs",
        "C src/lib.rs",
        "P src/main.rs",
        "+ src/wackend/",
        "+ src/wackend/backblaze.rs",
        "+ src/wackend/cache.rs",
        "+ src/wackend/filter.rs",
        "+ src/wackend/fs.rs",
        "+ src/wackend/memory.rs",
        "+ src/wackend/semaphored.rs",
        "T src/",
    ]);

    // Wipe the slate.
    cli_run(working_path, backup_path)?
        .arg("backup")
        .arg(working_path.join("src"))
        .assert()
        .success();

    compare(&[]);

    // Changed type!
    fs::remove_file(working_path.join("src/ls.rs"))?;
    unix::fs::symlink("/dev/null", working_path.join("src/ls.rs"))?;

    compare(&["- src/ls.rs", "+ src/ls.rs -> /dev/null", "T src/"]);

    // Wipe the slate.
    cli_run(working_path, backup_path)?
        .arg("backup")
        .arg(working_path.join("src"))
        .assert()
        .success();

    // Symlink modified (should be -/+, not M)
    fs::remove_file(working_path.join("src/ls.rs"))?;
    unix::fs::symlink("/dev/urandom", working_path.join("src/ls.rs"))?;

    compare(&[
        "- src/ls.rs -> /dev/null",
        "+ src/ls.rs -> /dev/urandom",
        "T src/",
    ]);

    Ok(())
}