use oxgraph_snapshot::{
FORMAT_MAGIC, FORMAT_MAJOR, FORMAT_MINOR, HEADER_SIZE, MAX_ALIGNMENT_LOG2, MAX_SECTION_COUNT,
PlanError, SECTION_ENTRY_SIZE, Snapshot, SnapshotBuilder, SnapshotError,
};
fn add(builder: &mut SnapshotBuilder, kind: u32, version: u32, alignment_log2: u8, payload: &[u8]) {
if let Err(error) = builder.add_section(kind, version, alignment_log2, payload.to_vec()) {
let formatted: PlanError = error;
panic!("add_section({kind}): {formatted:?}");
}
}
fn baseline_snapshot() -> Vec<u8> {
let mut builder = SnapshotBuilder::new();
add(&mut builder, 1, 0, 2, b"abcd");
add(&mut builder, 2, 0, 0, b"xyz");
match builder.finish() {
Ok(bytes) => bytes,
Err(error) => panic!("builder finish: {error:?}"),
}
}
fn set_u32(bytes: &mut [u8], offset: usize, value: u32) {
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}
fn assert_open_error(bytes: &[u8], expected: &SnapshotError) {
match Snapshot::open(bytes) {
Ok(_) => panic!("expected {expected:?}, got Ok"),
Err(actual) => assert_eq!(&actual, expected),
}
}
fn header_size_u32() -> u32 {
match u32::try_from(HEADER_SIZE) {
Ok(value) => value,
Err(error) => panic!("HEADER_SIZE does not fit u32: {error:?}"),
}
}
#[test]
fn opens_baseline_snapshot() -> Result<(), SnapshotError> {
let bytes = baseline_snapshot();
let snapshot = Snapshot::open(&bytes)?;
assert_eq!(snapshot.format_major(), FORMAT_MAJOR);
assert_eq!(snapshot.format_minor(), FORMAT_MINOR);
assert_eq!(snapshot.section_count(), 2);
assert_eq!(
snapshot.section(1).map(|s| s.bytes()),
Some(b"abcd".as_ref())
);
assert_eq!(
snapshot.section(2).map(|s| s.bytes()),
Some(b"xyz".as_ref())
);
assert!(snapshot.section(3).is_none());
Ok(())
}
#[test]
fn rejects_truncated_header() {
assert_open_error(
&[0u8; 8],
&SnapshotError::TruncatedHeader {
needed: HEADER_SIZE,
actual: 8,
},
);
}
#[test]
fn rejects_bad_magic() {
let mut bytes = baseline_snapshot();
bytes[0] = 0;
let mut expected = FORMAT_MAGIC;
expected[0] = 0;
assert_open_error(&bytes, &SnapshotError::BadMagic { actual: expected });
}
#[test]
fn rejects_wrong_major() {
let mut bytes = baseline_snapshot();
set_u32(&mut bytes, 8, FORMAT_MAJOR + 1);
assert_open_error(
&bytes,
&SnapshotError::FormatMajorMismatch {
actual: FORMAT_MAJOR + 1,
supported: FORMAT_MAJOR,
},
);
}
#[test]
fn rejects_minor_too_new() {
let mut bytes = baseline_snapshot();
set_u32(&mut bytes, 12, FORMAT_MINOR + 1);
assert_open_error(
&bytes,
&SnapshotError::FormatMinorTooNew {
actual: FORMAT_MINOR + 1,
max_supported: FORMAT_MINOR,
},
);
}
#[test]
fn rejects_wrong_header_size() {
let mut bytes = baseline_snapshot();
set_u32(&mut bytes, 16, 64);
assert_open_error(
&bytes,
&SnapshotError::HeaderSizeMismatch {
actual: 64,
expected: header_size_u32(),
},
);
}
#[test]
fn rejects_non_zero_header_reserved() {
let mut bytes = baseline_snapshot();
bytes[24] = 1;
assert_open_error(&bytes, &SnapshotError::NonZeroHeaderReserved);
}
#[test]
fn rejects_section_count_too_large() {
let mut bytes = baseline_snapshot();
set_u32(&mut bytes, 20, MAX_SECTION_COUNT + 1);
assert_open_error(
&bytes,
&SnapshotError::SectionCountTooLarge {
count: MAX_SECTION_COUNT + 1,
max: MAX_SECTION_COUNT,
},
);
}
#[test]
fn rejects_truncated_section_table() {
let mut bytes = baseline_snapshot();
bytes.truncate(HEADER_SIZE + SECTION_ENTRY_SIZE);
match Snapshot::open(&bytes) {
Err(SnapshotError::TruncatedSectionTable { .. }) => {}
other => panic!("expected TruncatedSectionTable, got {other:?}"),
}
}
#[test]
fn rejects_non_zero_entry_checksum() {
let mut bytes = baseline_snapshot();
let entry_offset = HEADER_SIZE;
bytes[entry_offset + 24] = 1;
assert_open_error(&bytes, &SnapshotError::NonZeroEntryChecksum { kind: 1 });
}
#[test]
fn rejects_unsupported_flags() {
let mut bytes = baseline_snapshot();
let entry_offset = HEADER_SIZE;
bytes[entry_offset + 29] = 0b0000_0001;
assert_open_error(
&bytes,
&SnapshotError::UnsupportedFlags {
kind: 1,
flags: 0b0000_0001,
},
);
}
#[test]
fn rejects_non_zero_entry_reserved() {
let mut bytes = baseline_snapshot();
let entry_offset = HEADER_SIZE;
bytes[entry_offset + 30] = 1;
assert_open_error(&bytes, &SnapshotError::NonZeroEntryReserved { kind: 1 });
}
#[test]
fn rejects_alignment_log2_too_large() {
let mut bytes = baseline_snapshot();
let entry_offset = HEADER_SIZE;
bytes[entry_offset + 28] = MAX_ALIGNMENT_LOG2 + 1;
assert_open_error(
&bytes,
&SnapshotError::AlignmentLog2TooLarge {
kind: 1,
alignment_log2: MAX_ALIGNMENT_LOG2 + 1,
},
);
}
#[test]
fn rejects_section_out_of_bounds() {
let mut bytes = baseline_snapshot();
let entry_offset = HEADER_SIZE;
let huge = (bytes.len() as u64).wrapping_add(1024);
bytes[entry_offset + 8..entry_offset + 16].copy_from_slice(&huge.to_le_bytes());
match Snapshot::open(&bytes) {
Err(SnapshotError::SectionOutOfBounds { kind: 1, .. }) => {}
other => panic!("expected SectionOutOfBounds, got {other:?}"),
}
}
#[test]
fn rejects_unsorted_section_table() {
let mut bytes = baseline_snapshot();
let first = HEADER_SIZE;
let second = HEADER_SIZE + SECTION_ENTRY_SIZE;
let mut tmp = [0u8; SECTION_ENTRY_SIZE];
tmp.copy_from_slice(&bytes[first..first + SECTION_ENTRY_SIZE]);
bytes.copy_within(second..second + SECTION_ENTRY_SIZE, first);
bytes[second..second + SECTION_ENTRY_SIZE].copy_from_slice(&tmp);
match Snapshot::open(&bytes) {
Err(SnapshotError::UnsortedSectionTable { .. }) => {}
other => panic!("expected UnsortedSectionTable, got {other:?}"),
}
}
#[test]
fn rejects_duplicate_kind() {
let mut bytes = baseline_snapshot();
let second_entry = HEADER_SIZE + SECTION_ENTRY_SIZE;
set_u32(&mut bytes, second_entry + 16, 1);
assert_open_error(&bytes, &SnapshotError::DuplicateKind { kind: 1 });
}