use lz4::io::sparse::{fwrite_sparse_end, read_le32, SPARSE_SEGMENT_SIZE};
use std::io::{Read, Seek, SeekFrom, Write};
fn read_all(f: &mut std::fs::File) -> Vec<u8> {
f.seek(SeekFrom::Start(0)).unwrap();
let mut buf = Vec::new();
f.read_to_end(&mut buf).unwrap();
buf
}
#[test]
fn sparse_segment_size_is_32kib() {
assert_eq!(SPARSE_SEGMENT_SIZE, 32 * 1024);
}
#[test]
fn read_le32_zero_bytes() {
assert_eq!(read_le32(&[0x00, 0x00, 0x00, 0x00]), 0u32);
}
#[test]
fn read_le32_value_one() {
assert_eq!(read_le32(&[0x01, 0x00, 0x00, 0x00]), 1u32);
}
#[test]
fn read_le32_max_value() {
assert_eq!(read_le32(&[0xFF, 0xFF, 0xFF, 0xFF]), u32::MAX);
}
#[test]
fn read_le32_known_vector() {
assert_eq!(read_le32(&[0x01, 0x02, 0x03, 0x04]), 0x0403_0201u32);
}
#[test]
fn read_le32_extra_bytes_ignored() {
let result = read_le32(&[0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]);
assert_eq!(
result, 1u32,
"extra bytes beyond the first 4 must be ignored"
);
}
#[test]
fn read_le32_high_byte_set() {
assert_eq!(read_le32(&[0x00, 0x00, 0x00, 0x80]), 0x8000_0000u32);
}
#[test]
fn read_le32_byte_weight() {
assert_eq!(read_le32(&[0x01, 0x00, 0x00, 0x00]), 1);
assert_eq!(read_le32(&[0x00, 0x01, 0x00, 0x00]), 256);
assert_eq!(read_le32(&[0x00, 0x00, 0x01, 0x00]), 65_536);
assert_eq!(read_le32(&[0x00, 0x00, 0x00, 0x01]), 16_777_216);
}
#[test]
fn fwrite_sparse_end_zero_skips_is_noop() {
let mut f = tempfile::tempfile().unwrap();
fwrite_sparse_end(&mut f, 0).unwrap();
let contents = read_all(&mut f);
assert!(
contents.is_empty(),
"stored_skips=0 must leave the file empty"
);
}
#[test]
fn fwrite_sparse_end_one_skip_writes_one_byte() {
let mut f = tempfile::tempfile().unwrap();
fwrite_sparse_end(&mut f, 1).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, 1, "stored_skips=1 should produce a 1-byte file");
let contents = read_all(&mut f);
assert_eq!(contents, &[0u8], "the byte written must be zero");
}
#[test]
fn fwrite_sparse_end_four_skips_extends_file() {
let mut f = tempfile::tempfile().unwrap();
fwrite_sparse_end(&mut f, 4).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(
len, 4,
"stored_skips=4 should produce a 4-byte logical file"
);
}
#[test]
fn fwrite_sparse_end_large_skips_correct_size() {
let mut f = tempfile::tempfile().unwrap();
fwrite_sparse_end(&mut f, 1024).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, 1024);
}
#[test]
fn fwrite_sparse_end_appends_after_existing_data() {
let mut f = tempfile::tempfile().unwrap();
f.write_all(&[0xABu8; 8]).unwrap();
fwrite_sparse_end(&mut f, 8).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(
len, 16,
"sparse end after written data should produce correct total size"
);
}
#[cfg(unix)]
mod unix_fwrite_sparse {
use super::*;
use lz4::io::sparse::fwrite_sparse;
use std::mem;
const WORD: usize = mem::size_of::<usize>();
#[test]
fn plain_nonzero_data_written_in_full() {
let mut f = tempfile::tempfile().unwrap();
let data: Vec<u8> = (1u8..=32).collect();
let skips = fwrite_sparse(&mut f, &data, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0, "no trailing zeros → skips must be 0");
let contents = read_all(&mut f);
assert_eq!(contents, data, "file contents must match input exactly");
}
#[test]
fn single_nonzero_byte_written() {
let mut f = tempfile::tempfile().unwrap();
let skips = fwrite_sparse(&mut f, &[0x42u8], SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0);
let contents = read_all(&mut f);
assert_eq!(contents, &[0x42u8]);
}
#[test]
fn all_zeros_word_aligned_accumulates_skips() {
let mut f = tempfile::tempfile().unwrap();
let zeros = vec![0u8; WORD * 4]; let skips = fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, zeros.len() as u64, "all zeros must be accumulated");
let pos = f.seek(SeekFrom::Current(0)).unwrap();
assert_eq!(
pos, 0,
"no seek/write should have occurred for all-zero buffer"
);
}
#[test]
fn single_zero_byte_accumulates_one_skip() {
let mut f = tempfile::tempfile().unwrap();
let skips = fwrite_sparse(&mut f, &[0u8], SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 1);
let pos = f.seek(SeekFrom::Current(0)).unwrap();
assert_eq!(pos, 0);
}
#[test]
fn zeros_64_bytes_accumulates_64_skips() {
let mut f = tempfile::tempfile().unwrap();
let zeros = vec![0u8; 64];
let skips = fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 64);
}
#[test]
fn carry_in_stored_skips_flushed_on_nonzero_data() {
let mut f = tempfile::tempfile().unwrap();
let data = vec![0xABu8; WORD]; let initial_skips = 8u64;
let skips = fwrite_sparse(&mut f, &data, SPARSE_SEGMENT_SIZE, initial_skips, true).unwrap();
assert_eq!(skips, 0, "non-zero data must flush all accumulated skips");
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, initial_skips + WORD as u64);
}
#[test]
fn carry_in_stored_skips_added_to_zero_accumulation() {
let mut f = tempfile::tempfile().unwrap();
let zeros = vec![0u8; WORD];
let initial_skips = 5u64;
let skips =
fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, initial_skips, true).unwrap();
assert_eq!(skips, initial_skips + WORD as u64);
}
#[test]
fn zero_words_then_nonzero_writes_correct_data() {
let mut f = tempfile::tempfile().unwrap();
let mut buf = vec![0u8; WORD]; buf.extend_from_slice(&[0xCDu8].repeat(WORD));
let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0, "non-zero data at end must flush pending skips");
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, (2 * WORD) as u64);
}
#[test]
fn nonzero_then_intra_segment_trailing_zeros_are_written() {
let mut f = tempfile::tempfile().unwrap();
let mut buf = vec![0xFFu8; WORD]; buf.extend_from_slice(&[0u8; WORD]);
let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(
skips, 0,
"intra-segment trailing zeros are written, not accumulated"
);
let contents = read_all(&mut f);
assert_eq!(contents, buf, "file must contain both words verbatim");
}
#[test]
fn all_zeros_round_trip() {
let mut f = tempfile::tempfile().unwrap();
let original = vec![0u8; 64];
let skips = fwrite_sparse(&mut f, &original, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
fwrite_sparse_end(&mut f, skips).unwrap();
let contents = read_all(&mut f);
assert_eq!(
contents, original,
"all-zeros round-trip must match original"
);
}
#[test]
fn nonzero_round_trip() {
let mut f = tempfile::tempfile().unwrap();
let original: Vec<u8> = (1u8..=16).collect();
let skips = fwrite_sparse(&mut f, &original, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
fwrite_sparse_end(&mut f, skips).unwrap();
let contents = read_all(&mut f);
assert_eq!(
contents, original,
"plain data round-trip must match original"
);
}
#[test]
fn mixed_content_round_trip() {
let mut f = tempfile::tempfile().unwrap();
let mut original = Vec::new();
original.extend_from_slice(&[0xABu8; 8]); original.extend_from_slice(&[0u8; 16]); original.extend_from_slice(&[0xCDu8; 8]);
let skips = fwrite_sparse(&mut f, &original, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
fwrite_sparse_end(&mut f, skips).unwrap();
let contents = read_all(&mut f);
assert_eq!(
contents, original,
"mixed content round-trip must match original"
);
}
#[test]
fn multi_segment_round_trip() {
let mut f = tempfile::tempfile().unwrap();
let original: Vec<u8> = (0u8..=255).cycle().take(128 * 1024).collect();
let skips = fwrite_sparse(&mut f, &original, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
fwrite_sparse_end(&mut f, skips).unwrap();
let contents = read_all(&mut f);
assert_eq!(
contents.len(),
original.len(),
"multi-segment round-trip length must match"
);
assert_eq!(
contents, original,
"multi-segment round-trip content must match"
);
}
#[test]
fn non_word_aligned_nonzero_trailing_bytes() {
let mut f = tempfile::tempfile().unwrap();
let mut buf = vec![0xAAu8; WORD]; buf.push(0xBBu8); let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0);
let contents = read_all(&mut f);
assert_eq!(contents, buf);
}
#[test]
fn non_word_aligned_zero_trailing_byte_accumulated() {
let mut f = tempfile::tempfile().unwrap();
let mut buf = vec![0xFFu8; WORD]; buf.push(0u8); let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(
skips, 1,
"single trailing zero byte must be accumulated as 1 skip"
);
}
#[test]
fn sub_word_nonzero_buffer_written() {
let mut f = tempfile::tempfile().unwrap();
let buf = vec![0x01u8, 0x02, 0x03]; let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0);
let contents = read_all(&mut f);
assert_eq!(contents, buf);
}
#[test]
fn sub_word_zero_buffer_accumulated() {
let mut f = tempfile::tempfile().unwrap();
let buf = vec![0u8; 3];
let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 3);
let pos = f.seek(SeekFrom::Current(0)).unwrap();
assert_eq!(pos, 0);
}
#[test]
fn empty_buffer_is_noop() {
let mut f = tempfile::tempfile().unwrap();
let initial_skips = 7u64;
let skips = fwrite_sparse(&mut f, &[], SPARSE_SEGMENT_SIZE, initial_skips, true).unwrap();
assert_eq!(
skips, initial_skips,
"empty buffer must not modify stored_skips"
);
let pos = f.seek(SeekFrom::Current(0)).unwrap();
assert_eq!(pos, 0, "empty buffer must not move the file pointer");
}
#[test]
fn stored_skips_over_one_gb_triggers_overflow_guard() {
let buf = vec![0x01u8; WORD];
let mut f = tempfile::tempfile().unwrap();
let over_one_gb: u64 = (1u64 << 30) + 1; let skips = fwrite_sparse(&mut f, &buf, SPARSE_SEGMENT_SIZE, over_one_gb, true).unwrap();
assert_eq!(
skips, 0,
"non-zero buf must flush all pending skips after ONE_GB guard"
);
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, over_one_gb + WORD as u64);
}
#[test]
fn zeros_then_sparse_end_gives_correct_size() {
let mut f = tempfile::tempfile().unwrap();
let zeros = vec![0u8; 16];
let skips = fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
fwrite_sparse_end(&mut f, skips).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, 16, "file logical size must match buffer length");
}
#[test]
fn multiple_calls_accumulate_skips() {
let mut f = tempfile::tempfile().unwrap();
let zeros = vec![0u8; WORD];
let s1 = fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(s1, WORD as u64);
let s2 = fwrite_sparse(&mut f, &zeros, SPARSE_SEGMENT_SIZE, s1, true).unwrap();
assert_eq!(s2, 2 * WORD as u64);
fwrite_sparse_end(&mut f, s2).unwrap();
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, 2 * WORD as u64);
}
}
#[cfg(not(unix))]
mod non_unix_fwrite_sparse {
use super::*;
use lz4::io::sparse::fwrite_sparse;
#[test]
fn fallback_writes_full_buffer() {
let mut f = tempfile::tempfile().unwrap();
let data = vec![0u8; 32]; let skips = fwrite_sparse(&mut f, &data, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0, "non-Unix fallback always returns 0");
let len = f.seek(SeekFrom::End(0)).unwrap();
assert_eq!(len, 32, "non-Unix fallback must write all bytes");
}
#[test]
fn fallback_nonzero_data_written() {
let mut f = tempfile::tempfile().unwrap();
let data: Vec<u8> = (1u8..=16).collect();
let skips = fwrite_sparse(&mut f, &data, SPARSE_SEGMENT_SIZE, 0, true).unwrap();
assert_eq!(skips, 0);
let contents = read_all(&mut f);
assert_eq!(contents, data);
}
}