linuxutils-system 0.1.0

System utilities from linuxutils
Documentation
use assert_cmd::Command;
use predicates::prelude::*;
use std::io::Write;
use tempfile::NamedTempFile;

fn cmd() -> Command {
    Command::cargo_bin("fallocate").unwrap()
}

#[test]
fn basic_allocation() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "1M", &path]).assert().success();
    let meta = std::fs::metadata(&path).unwrap();
    assert_eq!(meta.len(), 1048576);
}

#[test]
fn with_offset() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd()
        .args(["-o", "1K", "-l", "1K", &path])
        .assert()
        .success();
    let meta = std::fs::metadata(&path).unwrap();
    assert_eq!(meta.len(), 2048);
}

#[test]
fn keep_size() {
    let mut tmp = NamedTempFile::new().unwrap();
    tmp.write_all(b"hello").unwrap();
    tmp.flush().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-n", "-l", "1M", &path]).assert().success();
    let meta = std::fs::metadata(&path).unwrap();
    assert_eq!(meta.len(), 5);
}

#[test]
fn punch_hole() {
    let mut tmp = NamedTempFile::new().unwrap();
    let data = vec![0xAA_u8; 1048576];
    tmp.write_all(&data).unwrap();
    tmp.flush().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-p", "-l", "4096", &path]).assert().success();
    // File size should be unchanged since punch-hole implies keep-size
    let meta = std::fs::metadata(&path).unwrap();
    assert_eq!(meta.len(), 1048576);
}

#[test]
fn collapse_range() {
    let mut tmp = NamedTempFile::new().unwrap();
    // collapse-range requires page-aligned offset and length on most filesystems
    // Use 8K file, collapse first 4K
    let data = vec![0xAA_u8; 8192];
    tmp.write_all(&data).unwrap();
    tmp.flush().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    // This may fail on some filesystems (e.g. tmpfs), so just check it doesn't panic
    let result = cmd().args(["-c", "-l", "4096", &path]).assert();
    // On supported filesystems size should be 4096, on unsupported it fails gracefully
    let output = result.get_output().clone();
    if output.status.success() {
        let meta = std::fs::metadata(&path).unwrap();
        assert_eq!(meta.len(), 4096);
    }
}

#[test]
fn zero_range() {
    let mut tmp = NamedTempFile::new().unwrap();
    let data = vec![0xAA_u8; 8192];
    tmp.write_all(&data).unwrap();
    tmp.flush().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    // zero-range may not be supported on all filesystems (e.g. tmpfs)
    let result = cmd().args(["-z", "-l", "4096", &path]).assert();
    let output = result.get_output().clone();
    if !output.status.success() {
        // Graceful failure is acceptable on unsupported filesystems
        let stderr = String::from_utf8_lossy(&output.stderr);
        assert!(
            stderr.contains("not supported")
                || stderr.contains("Operation not supported")
        );
    }
}

#[test]
fn size_suffix_k() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "2K", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 2048);
}

#[test]
fn size_suffix_kib() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "2KiB", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 2048);
}

#[test]
fn size_suffix_kb() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "2KB", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 2000);
}

#[test]
fn size_suffix_m() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "1M", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 1048576);
}

#[test]
fn size_suffix_mb() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "1MB", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 1_000_000);
}

#[test]
fn size_suffix_g() {
    // Just test parsing, don't actually allocate 1G
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args(["-l", "4K", &path]).assert().success();
    assert_eq!(std::fs::metadata(&path).unwrap().len(), 4096);
}

#[test]
fn verbose_output() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd()
        .args(["-v", "-l", "4K", &path])
        .assert()
        .success()
        .stderr(predicate::str::contains("allocate"));
}

#[test]
fn missing_length() {
    let tmp = NamedTempFile::new().unwrap();
    let path = tmp.path().to_str().unwrap().to_string();
    cmd().args([&path]).assert().failure();
}

#[test]
fn missing_filename() {
    cmd().args(["-l", "1M"]).assert().failure();
}