buf-fs 0.1.3

A buffer based, in-memory filesystem.
Documentation
use embedded_sdmmc::{Mode, VolumeIdx};
use relative_path::RelativePathBuf;

use crate::{FileSystem, device::Device};

#[test]
fn device_can_open() {
    let cases = vec![Device::MIN_SIZE, 4 * 1024 * 1024, Device::MAX_SIZE];

    for c in cases {
        let dev = Device::new(c).unwrap();
        let mut mgr = dev.clone().open();
        let mut vol = mgr.open_volume(VolumeIdx(0)).unwrap();
        let mut root = vol.open_root_dir().unwrap();

        root.make_dir_in_dir("var").unwrap();

        let mut var = root.open_dir("var").unwrap();

        var.make_dir_in_dir("lib").unwrap();

        let mut lib = var.open_dir("lib").unwrap();
        let mut foo = lib
            .open_file_in_dir("foo.txt", Mode::ReadWriteCreateOrAppend)
            .unwrap();

        foo.write(b"bar").unwrap();

        foo.close().unwrap();
        lib.close().unwrap();
        var.close().unwrap();
        root.close().unwrap();

        let bytes = dev.try_to_bytes().unwrap();
        let dev = Device::try_from_bytes(&bytes).unwrap();

        let mut mgr = dev.open();
        let mut vol = mgr.open_volume(VolumeIdx(0)).unwrap();
        let mut root = vol.open_root_dir().unwrap();
        let mut var = root.open_dir("var").unwrap();
        let mut lib = var.open_dir("lib").unwrap();
        let mut foo = lib.open_file_in_dir("foo.txt", Mode::ReadOnly).unwrap();
        let mut len = foo.length() as usize;

        assert_eq!(len, 3);

        let mut buffer = vec![0u8; len];
        let mut read = 0;

        while len > 0 {
            let n = foo.read(&mut buffer[read..]).unwrap();

            read += n;
            len -= n;
        }

        assert_eq!(&buffer, b"bar");
    }
}

fn create_test_fs(contents: &[(Option<&[&str]>, &str, Option<&[u8]>)]) -> FileSystem {
    let c = 4 * 1024 * 1024;
    let dev = Device::new(c).unwrap();
    let mut mgr = dev.clone().open();
    let mut vol = mgr.open_volume(VolumeIdx(0)).unwrap();

    for (path, name, contents) in contents {
        let mut root = vol.open_root_dir().unwrap();

        if let Some(p) = path {
            for n in *p {
                root.change_dir(*n).unwrap();
            }
        }

        match contents {
            Some(c) => root
                .open_file_in_dir(*name, Mode::ReadWriteCreateOrAppend)
                .unwrap()
                .write(c)
                .unwrap(),
            None => root.make_dir_in_dir(*name).unwrap(),
        }

        root.close().unwrap();
    }

    FileSystem::from(dev)
}

#[test]
fn cd_works() -> anyhow::Result<()> {
    let mut fs = create_test_fs(&[
        (None, "var", None),
        (Some(&["var"]), "lib", None),
        (Some(&["var", "lib"]), "foo.bar", Some(b"baz")),
    ]);

    let cases = vec![
        ("/", "/"),
        (".", "/"),
        ("..", "/"),
        ("/var", "/VAR"),
        ("..", "/"),
        ("/var/lib", "/VAR/LIB"),
        ("..", "/VAR"),
        ("..", "/"),
        ("/var/lib", "/VAR/LIB"),
        ("../..", "/"),
        ("/var/lib", "/VAR/LIB"),
        ("./../..", "/"),
        ("/var/lib", "/VAR/LIB"),
        ("../../", "/"),
        ("/var/lib", "/VAR/LIB"),
        ("./../../", "/"),
        ("/var", "/VAR"),
        ("./", "/VAR"),
        ("./lib", "/VAR/LIB"),
        ("/", "/"),
        ("/var/lib", "/VAR/LIB"),
    ];

    for (cd, p) in cases {
        fs.cd(cd)?;
        assert_eq!(fs.cwd(), RelativePathBuf::from(p));
    }

    assert!(fs.cd("foo.bar").is_err());

    Ok(())
}

#[test]
fn ls_works() -> anyhow::Result<()> {
    let fs = create_test_fs(&[
        (None, "var", None),
        (Some(&["var"]), "lib", None),
        (Some(&["var"]), "share", None),
        (Some(&["var", "lib"]), "x", None),
        (Some(&["var", "lib"]), "foo.bin", Some(b"contents")),
        (Some(&["var", "lib"]), "bar.bin", Some(b"contents")),
    ]);

    let cases: Vec<(&str, &[&str])> = vec![
        ("/", &["VAR"]),
        ("/var", &["LIB"]),
        ("/var/lib", &["X", "BAR.BIN", "FOO.BIN"]),
        ("/var/lib/x", &[]),
    ];

    for (l, d) in cases {
        let c = fs.ls(l)?;

        for i in 0..d.len() {
            assert_eq!(c[i].path().as_str(), d[i]);
        }
    }

    Ok(())
}

#[test]
fn rm_works() -> anyhow::Result<()> {
    let mut fs = create_test_fs(&[
        (None, "var", None),
        (Some(&["var"]), "lib", None),
        (Some(&["var"]), "share", None),
        (Some(&["var", "lib"]), "x", None),
        (Some(&["var", "lib"]), "foo.bin", Some(b"contents")),
        (Some(&["var", "lib"]), "bar.bin", Some(b"contents")),
    ]);

    let cases = vec![
        ("/", "/", false),
        ("/var/lib", "foo.bin", true),
        ("/var/lib", "foo.bin", false),
        (".", "../lib/bar.bin", true),
        (".", "../lib/bar.bin", false),
    ];

    for (cd, p, ok) in cases {
        fs.cd(cd)?;

        let ret = fs.rm(p);

        assert_eq!(ret.is_ok(), ok, "unexpected result: {ret:?}");
    }

    Ok(())
}

#[test]
fn open_save_works() -> anyhow::Result<()> {
    let mut fs = create_test_fs(&[]);

    let data = b"foo";

    fs.mkdir("/var/share/baz")?;

    fs.open("/var/share/data.bin")?
        .update(|b| b.extend(data))
        .save(&mut fs)?;

    fs.open("/var/share/bar.bin")?
        .update(|b| b.extend(data))
        .save(&mut fs)?;

    assert_eq!(fs.open("/var/share/data.bin")?.contents, data);

    let contents = fs.ls("/var/share")?;

    assert_eq!(contents.len(), 3);
    assert_eq!(contents[0].path(), RelativePathBuf::from("BAZ"));
    assert_eq!(contents[1].path(), RelativePathBuf::from("BAR.BIN"));
    assert_eq!(contents[2].path(), RelativePathBuf::from("DATA.BIN"));

    fs.cd("/var")?;

    let contents = fs.ls("share")?;

    assert_eq!(contents.len(), 3);
    assert_eq!(contents[0].path(), RelativePathBuf::from("BAZ"));
    assert_eq!(contents[1].path(), RelativePathBuf::from("BAR.BIN"));
    assert_eq!(contents[2].path(), RelativePathBuf::from("DATA.BIN"));

    fs.cd("share")?;

    let contents = fs.ls(".")?;

    assert_eq!(contents.len(), 3);
    assert_eq!(contents[0].path(), RelativePathBuf::from("BAZ"));
    assert_eq!(contents[1].path(), RelativePathBuf::from("BAR.BIN"));
    assert_eq!(contents[2].path(), RelativePathBuf::from("DATA.BIN"));

    Ok(())
}