rust-par2 0.1.2

Pure Rust PAR2 verify and repair with SIMD-accelerated Galois field arithmetic
Documentation
//! Integration tests for rust_par2 verify and repair.
//!
//! Uses test fixtures generated by par2cmdline-turbo to verify
//! our implementation produces identical results.

use std::path::Path;

fn fixtures_dir() -> &'static Path {
    Path::new("tests/fixtures")
}

#[test]
fn test_verify_intact() {
    let dir = fixtures_dir().join("intact");
    let par2_path = dir.join("testdata.bin.par2");

    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");
    let result = rust_par2::verify(&file_set, &dir);

    assert!(
        result.all_correct(),
        "Intact set should verify as all correct: {result}"
    );
    assert_eq!(result.intact.len(), 1);
    assert!(result.damaged.is_empty());
    assert!(result.missing.is_empty());
}

#[test]
fn test_verify_damaged() {
    let dir = fixtures_dir().join("damaged");
    let par2_path = dir.join("testdata.bin.par2");

    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");
    let result = rust_par2::verify(&file_set, &dir);

    assert!(
        !result.all_correct(),
        "Damaged set should not verify as correct"
    );
    assert_eq!(result.damaged.len(), 1, "Should have 1 damaged file");
    assert!(result.damaged[0].damaged_block_count > 0);
}

#[test]
fn test_verify_missing() {
    let dir = fixtures_dir().join("missing");
    let par2_path = dir.join("testdata.bin.par2");

    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");
    let result = rust_par2::verify(&file_set, &dir);

    assert!(
        !result.all_correct(),
        "Missing set should not verify as correct"
    );
    assert_eq!(result.missing.len(), 1, "Should have 1 missing file");
}

#[test]
fn test_repair_damaged() {
    // Copy fixture to temp dir so we don't modify the original
    let tmp = tempfile::tempdir().unwrap();
    copy_dir(fixtures_dir().join("damaged"), tmp.path());

    let par2_path = tmp.path().join("testdata.bin.par2");
    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");

    // Verify damage exists
    let pre = rust_par2::verify(&file_set, tmp.path());
    assert!(!pre.all_correct(), "Should detect damage before repair");
    assert!(
        pre.repair_possible,
        "Repair should be possible (enough recovery blocks)"
    );

    // Repair
    let result = rust_par2::repair(&file_set, tmp.path()).expect("Repair should succeed");
    assert!(
        result.success,
        "Repair result should indicate success: {result}"
    );
    assert!(
        result.blocks_repaired > 0,
        "Should have repaired at least 1 block"
    );

    // Verify after repair
    let post = rust_par2::verify(&file_set, tmp.path());
    assert!(
        post.all_correct(),
        "All files should be correct after repair: {post}"
    );
}

#[test]
fn test_repair_missing() {
    let tmp = tempfile::tempdir().unwrap();
    copy_dir(fixtures_dir().join("missing"), tmp.path());

    let par2_path = tmp.path().join("testdata.bin.par2");
    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");

    // Verify file is missing
    let pre = rust_par2::verify(&file_set, tmp.path());
    assert!(!pre.all_correct());
    assert_eq!(pre.missing.len(), 1);
    assert!(
        pre.repair_possible,
        "Should be repairable with 2 recovery blocks"
    );

    // Repair
    let result = rust_par2::repair(&file_set, tmp.path()).expect("Repair should succeed");
    assert!(result.success, "Repair should succeed: {result}");

    // Verify after repair
    let post = rust_par2::verify(&file_set, tmp.path());
    assert!(
        post.all_correct(),
        "All files should be correct after repair: {post}"
    );
}

#[test]
fn test_repair_unrepairable() {
    let tmp = tempfile::tempdir().unwrap();
    copy_dir(fixtures_dir().join("unrepairable"), tmp.path());

    let par2_path = tmp.path().join("testdata.bin.par2");
    let file_set = rust_par2::parse(&par2_path).expect("Failed to parse par2");

    let pre = rust_par2::verify(&file_set, tmp.path());
    assert!(!pre.all_correct());
    assert!(!pre.repair_possible, "Should not be repairable");

    // Repair should fail with InsufficientRecovery
    let result = rust_par2::repair(&file_set, tmp.path());
    assert!(result.is_err(), "Repair should fail");
    let err = result.unwrap_err();
    assert!(
        matches!(err, rust_par2::RepairError::InsufficientRecovery { .. }),
        "Should be InsufficientRecovery error, got: {err}"
    );
}

/// Copy all files from src to dst (non-recursive).
fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) {
    let src = src.as_ref();
    let dst = dst.as_ref();
    std::fs::create_dir_all(dst).unwrap();
    for entry in std::fs::read_dir(src).unwrap() {
        let entry = entry.unwrap();
        if entry.file_type().unwrap().is_file() {
            std::fs::copy(entry.path(), dst.join(entry.file_name())).unwrap();
        }
    }
}