tarzan 0.2.0

Random-access, seekable .tar.zst archives with an embedded table-of-contents index
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

use tempfile::tempdir;

fn fixture_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("testdata/fixtures/tiny-tree")
        .canonicalize()
        .expect("fixture path should exist")
}

fn create_tar_from_fixture(output_tar: &Path) {
    let fixture = fixture_root();
    let mut cmd = Command::new("tar");
    #[cfg(target_os = "macos")]
    cmd.env("COPYFILE_DISABLE", "1");
    let status = cmd
        .arg("-cf")
        .arg(output_tar)
        .arg("-C")
        .arg(&fixture)
        .arg(".")
        .status()
        .expect("failed to run tar command");
    assert!(status.success(), "tar command failed");
}

fn tarzan_bin() -> PathBuf {
    PathBuf::from(std::env::var("CARGO_BIN_EXE_tarzan").expect("missing tarzan test binary"))
}

fn wrap_fixture(temp: &tempfile::TempDir) -> PathBuf {
    let tar_path = temp.path().join("input.tar");
    let archive_path = temp.path().join("archive.tar.zst");
    create_tar_from_fixture(&tar_path);
    let status = Command::new(tarzan_bin())
        .arg("wrap")
        .arg(&tar_path)
        .arg("-f")
        .arg(&archive_path)
        .status()
        .expect("failed to run tarzan wrap");
    assert!(status.success(), "tarzan wrap failed");
    archive_path
}

#[test]
fn cat_extracts_readme_txt() {
    let temp = tempdir().expect("tempdir");
    let archive = wrap_fixture(&temp);

    let output = Command::new(tarzan_bin())
        .args(["cat", "-f"])
        .arg(&archive)
        .arg("./README.txt")
        .output()
        .expect("failed to run tarzan cat");

    assert!(
        output.status.success(),
        "tarzan cat failed; stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let expected = fs::read(fixture_root().join("README.txt")).expect("read fixture");
    assert_eq!(
        output.stdout, expected,
        "cat output should match fixture file bytes"
    );
}

#[test]
fn cat_extracts_src_main_rs() {
    let temp = tempdir().expect("tempdir");
    let archive = wrap_fixture(&temp);

    let output = Command::new(tarzan_bin())
        .args(["cat", "-f"])
        .arg(&archive)
        .arg("./src/main.rs")
        .output()
        .expect("failed to run tarzan cat");

    assert!(
        output.status.success(),
        "tarzan cat failed; stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let expected = fs::read(fixture_root().join("src/main.rs")).expect("read fixture");
    assert_eq!(output.stdout, expected);
}

#[test]
fn cat_missing_path_exits_nonzero() {
    let temp = tempdir().expect("tempdir");
    let archive = wrap_fixture(&temp);

    let status = Command::new(tarzan_bin())
        .args(["cat", "-f"])
        .arg(&archive)
        .arg("no/such/file.txt")
        .status()
        .expect("failed to run tarzan cat");

    assert!(!status.success(), "tarzan cat on missing path should fail");
}

#[test]
fn verify_exits_zero_on_fresh_archive() {
    let temp = tempdir().expect("tempdir");
    let archive = wrap_fixture(&temp);

    let output = Command::new(tarzan_bin())
        .args(["verify", "-f"])
        .arg(&archive)
        .output()
        .expect("failed to run tarzan verify");

    assert!(
        output.status.success(),
        "tarzan verify failed; stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
}

#[test]
fn verify_single_file_exits_zero() {
    let temp = tempdir().expect("tempdir");
    let archive = wrap_fixture(&temp);

    let output = Command::new(tarzan_bin())
        .args(["verify", "-f"])
        .arg(&archive)
        .arg("./README.txt")
        .output()
        .expect("failed to run tarzan verify");

    assert!(
        output.status.success(),
        "tarzan verify single file failed; stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
}