acyclic-fs 0.1.5

Rust SDK for versioned workspaces and embedded native mounts
Documentation
use super::*;
use crate::foundation::Digest;
use crate::kernel::{FileKind, FilePayload};
use crate::path::PortablePath;

fn path(value: &str) -> Result<NamespacePath, Box<dyn std::error::Error>> {
    let limits = VolumeLimits::default();
    Ok(NamespacePath::from_portable(
        &PortablePath::parse(value, limits)?,
        limits,
    )?)
}

fn object(kind: ObjectKind, byte: u8) -> ObjectId {
    ObjectId {
        kind,
        digest: Digest::from_bytes([byte; 32]),
    }
}

fn assert_rejected(
    mutation: Mutation,
    expected: MutationPlanError,
) -> Result<(), Box<dyn std::error::Error>> {
    let failure = MutationPlan::compile(
        vec![mutation],
        VolumeLimits::default(),
        WorkBudget::UNBOUNDED,
    )
    .err()
    .ok_or("invalid mutation unexpectedly compiled")?;
    assert_eq!(failure.error, expected);
    assert_eq!(*failure.work, WorkCounters::default());
    Ok(())
}

#[test]
fn mixed_batch_compiles_shared_ancestors_once() -> Result<(), Box<dyn std::error::Error>> {
    let operations = vec![
        Mutation::Resize {
            path: path("/workspace/src/a")?,
            logical_bytes: 10,
        },
        Mutation::SetMetadata {
            path: path("/workspace/src/b")?,
            metadata: ObjectId {
                kind: ObjectKind::Metadata,
                digest: Digest::from_bytes([1; 32]),
            },
        },
        Mutation::Rename {
            source: path("/workspace/src/a")?,
            destination: path("/workspace/dst/a")?,
            replace: false,
        },
    ];
    let plan = MutationPlan::compile(
        operations.clone(),
        VolumeLimits::default(),
        WorkBudget::UNBOUNDED,
    )?;
    assert_eq!(plan.operations(), operations);
    assert_eq!(plan.path_nodes(), 7);
    assert_eq!(plan.ordered_paths().count(), 4);
    assert_eq!(plan.work().items_examined, 18);
    assert_eq!(plan.work().allocation_operations, 1);
    assert_eq!(plan.work().bytes_copied, 0);
    Ok(())
}

#[test]
fn identity_only_batch_retains_no_path_plan_or_namespace_work()
-> Result<(), Box<dyn std::error::Error>> {
    let file_id = FileId::from_bytes([7; 16]);
    let plan = MutationPlan::compile(
        vec![Mutation::File {
            file_id,
            mutation: FileMutation::Resize { logical_bytes: 9 },
        }],
        VolumeLimits::default(),
        WorkBudget::UNBOUNDED,
    )?;
    assert_eq!(plan.ordered_paths().count(), 0);
    assert_eq!(plan.retained_allocation_bytes(), 0);
    assert_eq!(plan.work(), WorkCounters::default());
    assert_eq!(plan.path_nodes(), 1);
    Ok(())
}

#[test]
fn identity_record_replacement_rejects_mismatched_or_invalid_records()
-> Result<(), Box<dyn std::error::Error>> {
    let file_id = FileId::from_bytes([7; 16]);
    let record = FileRecord {
        file_id,
        kind: FileKind::Regular,
        link_count: 2,
        metadata: object(ObjectKind::Metadata, 1),
        payload: FilePayload::Regular {
            logical_bytes: 6,
            extents: object(ObjectKind::ExtentPage, 2),
        },
    };
    let valid = Mutation::File {
        file_id,
        mutation: FileMutation::ReplaceRecord { record },
    };
    let plan = MutationPlan::compile(vec![valid], VolumeLimits::default(), WorkBudget::UNBOUNDED)?;
    assert_eq!(plan.ordered_paths().count(), 0);
    assert_rejected(
        Mutation::File {
            file_id: FileId::from_bytes([8; 16]),
            mutation: FileMutation::ReplaceRecord { record },
        },
        MutationPlanError::InvalidInitialRecord,
    )?;
    assert_rejected(
        Mutation::File {
            file_id,
            mutation: FileMutation::ReplaceRecord {
                record: FileRecord {
                    link_count: 0,
                    ..record
                },
            },
        },
        MutationPlanError::InvalidInitialRecord,
    )
}

#[test]
fn malformed_ranges_and_root_mutation_fail_before_external_work()
-> Result<(), Box<dyn std::error::Error>> {
    let root = MutationPlan::compile(
        vec![Mutation::Resize {
            path: path("/")?,
            logical_bytes: 1,
        }],
        VolumeLimits::default(),
        WorkBudget::UNBOUNDED,
    )
    .err()
    .ok_or("root mutation unexpectedly compiled")?;
    assert_eq!(root.error, MutationPlanError::RootMutation);
    assert_eq!(*root.work, WorkCounters::default());

    let range = MutationPlan::compile(
        vec![Mutation::ZeroRange {
            path: path("/file")?,
            offset: 0,
            length: 0,
            allocated: false,
            extend: false,
        }],
        VolumeLimits::default(),
        WorkBudget::UNBOUNDED,
    )
    .err()
    .ok_or("empty range unexpectedly compiled")?;
    assert_eq!(range.error, MutationPlanError::EmptyRange);
    assert_eq!(*range.work, WorkCounters::default());
    Ok(())
}

#[test]
fn every_mutation_precondition_is_rejected_before_plan_work()
-> Result<(), Box<dyn std::error::Error>> {
    let regular = path("/regular")?;
    let other = path("/other")?;
    let root = path("/")?;
    let file_id = FileId::from_bytes([9; 16]);
    let invalid_record = FileRecord {
        file_id,
        kind: FileKind::Regular,
        link_count: 0,
        metadata: object(ObjectKind::Metadata, 1),
        payload: FilePayload::Empty,
    };
    let namespace_cases = vec![
        (
            Mutation::Rename {
                source: regular.clone(),
                destination: root,
                replace: false,
            },
            MutationPlanError::RootMutation,
        ),
        (
            Mutation::Create {
                path: regular.clone(),
                record: invalid_record,
            },
            MutationPlanError::InvalidInitialRecord,
        ),
        (
            Mutation::Rename {
                source: regular.clone(),
                destination: regular.clone(),
                replace: false,
            },
            MutationPlanError::SameSourceAndDestination,
        ),
        (
            Mutation::Link {
                source: other.clone(),
                destination: other.clone(),
            },
            MutationPlanError::SameSourceAndDestination,
        ),
    ];
    for (mutation, expected) in namespace_cases {
        assert_rejected(mutation, expected)?;
    }

    let content_cases = vec![
        (
            Mutation::Write {
                path: regular.clone(),
                offset: 0,
                length: 1,
                content: object(ObjectKind::Metadata, 2),
                content_offset: 0,
            },
            MutationPlanError::WrongContentKind,
        ),
        (
            Mutation::Write {
                path: regular.clone(),
                offset: 0,
                length: 2,
                content: object(ObjectKind::Blob, 3),
                content_offset: u64::MAX,
            },
            MutationPlanError::RangeOverflow,
        ),
        (
            Mutation::SetMetadata {
                path: regular.clone(),
                metadata: object(ObjectKind::Blob, 4),
            },
            MutationPlanError::WrongMetadataKind,
        ),
        (
            Mutation::File {
                file_id,
                mutation: FileMutation::SetMetadata {
                    metadata: object(ObjectKind::Blob, 5),
                },
            },
            MutationPlanError::WrongMetadataKind,
        ),
        (
            Mutation::CloneRange {
                source: regular,
                source_offset: u64::MAX,
                destination: other,
                destination_offset: 0,
                length: 2,
            },
            MutationPlanError::RangeOverflow,
        ),
    ];
    for (mutation, expected) in content_cases {
        assert_rejected(mutation, expected)?;
    }
    Ok(())
}

#[test]
fn path_use_count_deduplicates_only_identical_endpoints() -> Result<(), Box<dyn std::error::Error>>
{
    let first = path("/first")?;
    let second = path("/second")?;
    let operations = [
        Mutation::Remove {
            path: first.clone(),
            expected_file_id: MetadataField::Unavailable,
        },
        Mutation::Rename {
            source: first.clone(),
            destination: first,
            replace: false,
        },
        Mutation::Rename {
            source: second.clone(),
            destination: path("/third")?,
            replace: false,
        },
    ];
    assert_eq!(path_use_count(&operations), Some(4));
    assert_eq!(path_use_count(&[]), Some(0));
    Ok(())
}

#[test]
fn operation_bound_is_checked_before_plan_allocation() -> Result<(), Box<dyn std::error::Error>> {
    let limits = VolumeLimits {
        maximum_mutations_per_batch: 1,
        ..VolumeLimits::default()
    };
    let failure = MutationPlan::compile(
        vec![
            Mutation::Resize {
                path: path("/a")?,
                logical_bytes: 1,
            },
            Mutation::Resize {
                path: path("/b")?,
                logical_bytes: 1,
            },
        ],
        limits,
        WorkBudget::UNBOUNDED,
    )
    .err()
    .ok_or("oversized batch unexpectedly compiled")?;
    assert_eq!(failure.error, MutationPlanError::TooManyMutations);
    assert_eq!(*failure.work, WorkCounters::default());
    Ok(())
}

#[test]
fn flat_plan_memory_is_admitted_before_allocator_access() -> Result<(), Box<dyn std::error::Error>>
{
    let operation = Mutation::Resize {
        path: path("/large")?,
        logical_bytes: 1,
    };
    let mut operation_budget = WorkBudget::UNBOUNDED;
    operation_budget.allocation_operations = 0;
    let operation_failure = MutationPlan::compile(
        vec![operation.clone()],
        VolumeLimits::default(),
        operation_budget,
    )
    .err()
    .ok_or("allocation operation unexpectedly admitted")?;
    assert_eq!(*operation_failure.work, WorkCounters::default());

    let mut peak_budget = WorkBudget::UNBOUNDED;
    peak_budget.peak_allocation_bytes = 0;
    let peak_failure = MutationPlan::compile(vec![operation], VolumeLimits::default(), peak_budget)
        .err()
        .ok_or("allocation peak unexpectedly admitted")?;
    assert_eq!(*peak_failure.work, WorkCounters::default());
    Ok(())
}