froe 0.12.0

Reader and offline maintenance toolkit for Apache Jackrabbit Oak segment-tar (TarMK) repositories: parse archives and records, extract node data, compact, back up, and recover.
Documentation
//! Downstream-facing regressions for bounded garbage-collection journal reads.

use froe::gc_journal::{
    DEFAULT_MAXIMUM_GC_JOURNAL_ENTRIES, DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES,
    DEFAULT_MAXIMUM_GC_JOURNAL_LINE_BYTES, GarbageCollectionJournalReadError,
    GarbageCollectionJournalReadOptions, read_all_gc_journal, read_gc_journal,
};

struct SparseJournalFixture(std::path::PathBuf);

impl SparseJournalFixture {
    fn new() -> Self {
        // The thread is part of the name because the clock is not enough
        // on its own: two cases of this binary run concurrently, and a
        // platform whose `SystemTime` is coarser than a nanosecond hands
        // them the same path — where one expects the file to be missing
        // and the other has just made it enormous.
        let unique = format!(
            "froe-gc-journal-api-{}-{:?}-{}",
            std::process::id(),
            std::thread::current().id(),
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .expect("system time after Unix epoch")
                .as_nanos()
        );
        Self(std::env::temp_dir().join(unique))
    }
}

impl Drop for SparseJournalFixture {
    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.0);
    }
}

#[test]
fn downstream_callers_can_construct_and_adjust_read_limits() {
    let explicit = GarbageCollectionJournalReadOptions::new(17, 13, 11);
    assert_eq!(explicit.maximum_file_bytes, 17);
    assert_eq!(explicit.maximum_line_bytes, 13);
    assert_eq!(explicit.maximum_entries, 11);

    let mut adjusted = GarbageCollectionJournalReadOptions::default();
    adjusted.maximum_file_bytes = DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES + 1;
    adjusted.maximum_line_bytes = DEFAULT_MAXIMUM_GC_JOURNAL_LINE_BYTES + 1;
    adjusted.maximum_entries = DEFAULT_MAXIMUM_GC_JOURNAL_ENTRIES + 1;
    assert_eq!(
        adjusted,
        GarbageCollectionJournalReadOptions::new(
            DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES + 1,
            DEFAULT_MAXIMUM_GC_JOURNAL_LINE_BYTES + 1,
            DEFAULT_MAXIMUM_GC_JOURNAL_ENTRIES + 1,
        )
    );
}

#[test]
fn default_readers_report_their_froe_only_limit_to_downstream_callers() {
    let fixture = SparseJournalFixture::new();
    let file = std::fs::File::create(&fixture.0).expect("create sparse journal fixture");
    file.set_len(DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES + 1)
        .expect("extend sparse journal fixture");
    drop(file);

    for error in [
        read_gc_journal(&fixture.0).expect_err("last-entry read must report its default limit"),
        read_all_gc_journal(&fixture.0).expect_err("all-entry read must report its default limit"),
    ] {
        assert!(matches!(
            error,
            GarbageCollectionJournalReadError::FileByteLimitExceeded {
                maximum_file_bytes: DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES,
                observed_file_bytes,
            } if observed_file_bytes == DEFAULT_MAXIMUM_GC_JOURNAL_FILE_BYTES + 1
        ));
    }
}

#[test]
fn default_readers_keep_oaks_optional_file_fallback() {
    let fixture = SparseJournalFixture::new();
    let latest = read_gc_journal(&fixture.0).expect("missing optional journal is not an error");
    let all = read_all_gc_journal(&fixture.0).expect("missing optional journal is not an error");

    assert_eq!(
        latest,
        froe::gc_journal::GarbageCollectionJournalEntry::empty()
    );
    assert!(all.is_empty());
}