liteboxfs 0.2.0

A modern POSIX filesystem in a SQLite database
Documentation
use std::path::Path;

use liteboxfs::{
    Connection, CreateOptions, Error, TransactionBehavior,
    metadata::{FileKind, Owner},
};
use xpct::{be_err, be_ok, expect, match_pattern, pattern};

#[test]
fn exec_with_deferred_behavior_runs_closure() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    conn.exec_with(TransactionBehavior::Deferred, |fs| {
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
        liteboxfs::Result::Ok(())
    })?;

    conn.exec(|fs| {
        expect!(fs.open("a.txt")).to(be_ok());
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn exec_with_immediate_behavior_runs_closure() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    conn.exec_with(TransactionBehavior::Immediate, |fs| {
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
        liteboxfs::Result::Ok(())
    })?;

    conn.exec(|fs| {
        expect!(fs.open("a.txt")).to(be_ok());
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn exec_rolls_back_on_error() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    // First transaction creates a file then returns an error. The file creation should be rolled
    // back when the closure returns Err.
    let result: liteboxfs::Result<()> = conn.exec(|fs| {
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
        Err(Error::FileNotFound {
            file: liteboxfs::FileOrigin::Host {
                path: Path::new("/sentinel").into(),
            },
        })
    });

    expect!(result)
        .to(be_err())
        .to(match_pattern(pattern!(Error::FileNotFound { .. })));

    // The file should not exist in a subsequent transaction.
    conn.exec(|fs| {
        expect!(fs.open("a.txt"))
            .to(be_err())
            .to(match_pattern(pattern!(Error::FileNotFound { .. })));
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn explicit_commit_persists_changes() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    let mut tx = conn.tx()?;
    {
        let mut fs = tx.fs()?;
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
    }
    tx.commit()?;

    conn.exec(|fs| {
        expect!(fs.open("a.txt")).to(be_ok());
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn explicit_commit_with_no_changes_is_ok() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    // Opening a transaction and committing without writing anything should skip the cleanup
    // pass and still succeed.
    let tx = conn.tx()?;
    expect!(tx.commit()).to(be_ok());

    Ok(())
}

#[test]
fn explicit_rollback_discards_changes() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    let mut tx = conn.tx()?;
    {
        let mut fs = tx.fs()?;
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
    }
    tx.rollback()?;

    conn.exec(|fs| {
        expect!(fs.open("a.txt"))
            .to(be_err())
            .to(match_pattern(pattern!(Error::FileNotFound { .. })));
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn dropped_transaction_rolls_back() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    {
        let mut tx = conn.tx()?;
        let mut fs = tx.fs()?;
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
        drop(fs);
        drop(tx);
    }

    conn.exec(|fs| {
        expect!(fs.open("a.txt"))
            .to(be_err())
            .to(match_pattern(pattern!(Error::FileNotFound { .. })));
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}

#[test]
fn fs_can_be_called_multiple_times() -> liteboxfs::Result<()> {
    let mut conn = Connection::open_in_memory(&CreateOptions::new())?;

    let mut tx = conn.tx()?;

    {
        let mut fs = tx.fs()?;
        fs.create("a.txt", FileKind::Regular, Owner::ROOT)?;
    }

    {
        let mut fs = tx.fs()?;
        expect!(fs.open("a.txt")).to(be_ok());
        fs.create("b.txt", FileKind::Regular, Owner::ROOT)?;
    }

    tx.commit()?;

    conn.exec(|fs| {
        expect!(fs.open("a.txt")).to(be_ok());
        expect!(fs.open("b.txt")).to(be_ok());
        liteboxfs::Result::Ok(())
    })?;

    Ok(())
}