ic-host-fs 0.30.1

Local host filesystem reads, durable publication, file locking and a bounded file CLI
Documentation
use super::*;
use crate::test_support::fixture;
use std::{
    fs,
    io::Write as _,
    os::unix::fs::{PermissionsExt as _, symlink},
};

#[test]
fn scoped_files_are_private_unique_and_anchored_to_the_physical_parent() {
    let fixture = fixture();
    let parent = fixture.path().join("physical");
    fs::create_dir(&parent).unwrap();
    let alias = fixture.path().join("alias");
    symlink(&parent, &alias).unwrap();
    let mut file = TemporaryFile::new_in(&alias, "arguments").unwrap();
    let other = TemporaryFile::new_in(&alias, "arguments").unwrap();
    let bytes = [b"binary\0data".as_slice(), &vec![0xff; 128 * 1024]].concat();
    file.as_file_mut().write_all(&bytes).unwrap();
    assert_eq!(file.path().parent(), Some(parent.as_path()));
    assert!(file.path().is_absolute());
    assert_ne!(file.path(), other.path());
    assert_eq!(fs::read(file.path()).unwrap(), bytes);
    assert_eq!(
        file.as_file().metadata().unwrap().permissions().mode() & 0o177,
        0
    );
    let path = file.path().to_path_buf();
    drop(file);
    assert!(!path.exists());
    assert!(other.path().exists());
}

#[test]
fn child_can_read_binary_argument_file_while_the_guard_is_alive() {
    let fixture = fixture();
    let mut file = TemporaryFile::new_in(fixture.path(), "argument").unwrap();
    file.as_file_mut().write_all(b"raw\0\xffbytes").unwrap();
    let output = std::process::Command::new("/bin/cat")
        .arg(file.path())
        .output()
        .unwrap();
    assert!(output.status.success());
    assert_eq!(output.stdout, b"raw\0\xffbytes");
    let path = file.path().to_path_buf();
    file.close().unwrap();
    assert!(!path.exists());
}

#[test]
fn return_errors_and_unwinding_remove_only_the_scoped_file() {
    let fixture = fixture();
    let marker = fixture.path().join("retained");
    fs::write(&marker, b"evidence").unwrap();
    let mut path = None;
    let result: io::Result<()> = (|| {
        let mut file = TemporaryFile::new_in(fixture.path(), "failed").unwrap();
        path = Some(file.path().to_path_buf());
        file.as_file_mut().write_all(b"partial")?;
        Err(io::Error::from(io::ErrorKind::BrokenPipe))
    })();
    assert_eq!(result.unwrap_err().kind(), io::ErrorKind::BrokenPipe);
    assert!(!path.unwrap().exists());
    let file = TemporaryFile::new_in(fixture.path(), "unwind").unwrap();
    let path = file.path().to_path_buf();
    assert!(
        std::panic::catch_unwind(move || {
            assert!(file.path().exists());
            panic!("exercise scoped cleanup");
        })
        .is_err()
    );
    assert!(!path.exists());
    assert_eq!(fs::read(marker).unwrap(), b"evidence");
}

#[test]
fn explicit_close_preserves_original_removal_error() {
    let fixture = fixture();
    let file = TemporaryFile::new_in(fixture.path(), "missing").unwrap();
    fs::remove_file(file.path()).unwrap();
    assert_eq!(file.close().unwrap_err().kind(), io::ErrorKind::NotFound);
}

#[test]
fn invalid_prefixes_and_missing_parents_create_nothing() {
    let fixture = fixture();
    for prefix in ["", ".", "..", "a/b", "a/", "a\n", "a\r", "a\0"] {
        assert_eq!(
            TemporaryFile::new_in(&fixture.path().join("absent"), prefix)
                .unwrap_err()
                .kind(),
            io::ErrorKind::InvalidInput
        );
    }
    assert_eq!(
        TemporaryFile::new_in(&fixture.path().join("absent"), "valid")
            .unwrap_err()
            .kind(),
        io::ErrorKind::NotFound
    );
    assert_eq!(
        TemporaryFile::new_in(&fixture.path().join("line\nfeed"), "valid")
            .unwrap_err()
            .kind(),
        io::ErrorKind::InvalidInput
    );
    assert_eq!(fs::read_dir(fixture.path()).unwrap().count(), 0);
}

#[test]
fn physical_parent_with_forbidden_characters_is_refused_through_alias() {
    let fixture = fixture();
    let parent = fixture.path().join("invalid\rparent");
    fs::create_dir(&parent).unwrap();
    let alias = fixture.path().join("alias");
    symlink(&parent, &alias).unwrap();
    assert_eq!(
        TemporaryFile::new_in(&alias, "valid").unwrap_err().kind(),
        io::ErrorKind::InvalidInput
    );
    assert_eq!(fs::read_dir(parent).unwrap().count(), 0);
}