use lz4::block::compress::compress_bound;
use lz4::block::stream::Lz4Stream;
use lz4::block::types::KB;
fn make_dst(src_len: usize) -> Vec<u8> {
let bound = compress_bound(src_len as i32).max(0) as usize;
vec![0u8; bound.max(16)]
}
#[test]
fn new_produces_a_working_stream() {
let mut stream = Lz4Stream::new();
let src = b"hello lz4";
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst, 1);
assert!(n > 0, "fresh stream from new() must compress successfully");
}
#[test]
fn default_produces_same_output_as_new() {
let src = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let mut s_new = Lz4Stream::new();
let mut s_def = Lz4Stream::default();
let mut dst_new = make_dst(src.len());
let mut dst_def = make_dst(src.len());
let n1 = s_new.compress_fast_continue(src, &mut dst_new, 1);
let n2 = s_def.compress_fast_continue(src, &mut dst_def, 1);
assert_eq!(n1, n2, "new() and default() must produce identical output");
assert_eq!(&dst_new[..n1 as usize], &dst_def[..n2 as usize]);
}
#[test]
fn reset_clears_state_after_use() {
let src = b"hello world, repeated repeated repeated repeated repeated repeated";
let mut fresh = Lz4Stream::new();
let mut dst_fresh = make_dst(src.len());
let n_fresh = fresh.compress_fast_continue(src, &mut dst_fresh, 1);
assert!(n_fresh > 0);
let mut reused = Lz4Stream::new();
let junk = b"garbage data garbage data garbage data garbage data";
let mut dst_junk = make_dst(junk.len());
let _ = reused.compress_fast_continue(junk, &mut dst_junk, 1);
reused.reset();
let mut dst_reused = make_dst(src.len());
let n_reused = reused.compress_fast_continue(src, &mut dst_reused, 1);
assert_eq!(
n_fresh, n_reused,
"reset stream must produce same output as fresh stream"
);
assert_eq!(
&dst_fresh[..n_fresh as usize],
&dst_reused[..n_reused as usize]
);
}
#[test]
fn reset_allows_fresh_compression() {
let mut stream = Lz4Stream::new();
let src = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(src, &mut dst, 1);
stream.reset();
let mut dst2 = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst2, 1);
assert!(n > 0, "compress after reset should succeed");
}
#[test]
fn reset_fast_allows_fresh_compression() {
let mut stream = Lz4Stream::new();
let src = b"The quick brown fox jumps over the lazy dog. Repeated data: ";
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(src, &mut dst, 1);
stream.reset_fast();
let mut dst2 = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst2, 1);
assert!(n > 0, "compress after reset_fast should succeed");
}
#[test]
fn load_dict_empty_returns_zero() {
let mut stream = Lz4Stream::new();
let dict: &[u8] = &[];
let result = stream.load_dict(dict);
assert_eq!(result, 0, "empty dict should return 0");
}
#[test]
fn load_dict_tiny_below_hash_unit_returns_zero() {
let mut stream = Lz4Stream::new();
let dict = [0u8; 3];
let result = stream.load_dict(&dict);
assert_eq!(result, 0, "dict of 3 bytes should return 0");
}
#[test]
fn load_dict_small_dict_returns_its_size() {
let mut stream = Lz4Stream::new();
let dict = vec![0xAAu8; 1024];
let result = stream.load_dict(&dict);
assert_eq!(result, 1024, "load_dict should return actual dict size");
}
#[test]
fn load_dict_large_dict_clamped_to_64kb() {
let mut stream = Lz4Stream::new();
let dict = vec![0u8; 128 * KB];
let result = stream.load_dict(&dict);
assert_eq!(
result,
64 * KB as i32,
"large dict must be clamped to 64 KB"
);
}
#[test]
fn load_dict_exactly_64kb() {
let mut stream = Lz4Stream::new();
let dict = vec![0u8; 64 * KB];
let result = stream.load_dict(&dict);
assert_eq!(result, 64 * KB as i32);
}
#[test]
fn load_dict_dict_size_reflected_by_save_dict() {
let mut stream = Lz4Stream::new();
let dict = vec![0u8; 512];
stream.load_dict(&dict);
let mut save_buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut save_buf);
assert_eq!(
saved, 512,
"save_dict should return amount loaded by load_dict"
);
}
#[test]
fn load_dict_resets_stream_before_loading() {
let mut stream = Lz4Stream::new();
let src = b"some data that gets compressed";
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(src, &mut dst, 1);
let dict = vec![0x42u8; 256];
let result = stream.load_dict(&dict);
assert_eq!(result, 256);
}
#[test]
fn load_dict_slow_empty_returns_zero() {
let mut stream = Lz4Stream::new();
let result = stream.load_dict_slow(&[]);
assert_eq!(result, 0);
}
#[test]
fn load_dict_slow_small_dict_returns_its_size() {
let mut stream = Lz4Stream::new();
let dict = vec![0xBBu8; 512];
let result = stream.load_dict_slow(&dict);
assert_eq!(result, 512);
}
#[test]
fn load_dict_slow_large_dict_clamped_to_64kb() {
let mut stream = Lz4Stream::new();
let dict = vec![0u8; 100 * KB];
let result = stream.load_dict_slow(&dict);
assert_eq!(result, 64 * KB as i32);
}
#[test]
fn load_dict_slow_and_fast_agree_on_dict_size() {
let dict = vec![0xCCu8; 1024];
let mut s1 = Lz4Stream::new();
let mut s2 = Lz4Stream::new();
let r1 = s1.load_dict(&dict);
let r2 = s2.load_dict_slow(&dict);
assert_eq!(r1, r2, "fast and slow load_dict must return the same size");
}
#[test]
fn attach_dictionary_none_allows_compress() {
let mut stream = Lz4Stream::new();
unsafe {
stream.attach_dictionary(None);
}
let src = b"hello after detach aaaaaaaaaaaaaaaaaaaaaa";
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst, 1);
assert!(
n > 0,
"stream with None attached dict must compress successfully"
);
}
#[test]
fn attach_dictionary_empty_dict_stream_allows_compress() {
let dict_stream = Lz4Stream::new(); let mut working = Lz4Stream::new();
unsafe {
working.attach_dictionary(Some(&*dict_stream as *const Lz4Stream));
}
let src = b"hello after empty attach aaaaaaaaaaaaaaaaaaa";
let mut dst = make_dst(src.len());
let n = working.compress_fast_continue(src, &mut dst, 1);
assert!(
n > 0,
"stream with empty attached dict must compress successfully"
);
}
#[test]
fn attach_dictionary_non_empty_dict_allows_compress() {
let mut dict_stream = Lz4Stream::new();
let dict_data = vec![0xDDu8; 1024];
dict_stream.load_dict(&dict_data);
let mut working = Lz4Stream::new();
unsafe {
working.attach_dictionary(Some(&*dict_stream as *const Lz4Stream));
}
let src: Vec<u8> = (0u8..=255u8).cycle().take(512).collect();
let mut dst = make_dst(src.len());
let n = working.compress_fast_continue(&src, &mut dst, 1);
assert!(
n > 0,
"stream with non-empty attached dict must compress successfully"
);
}
#[test]
fn attach_dictionary_bumps_current_offset_when_zero() {
let mut dict_stream = Lz4Stream::new();
let dict_data = vec![0xEEu8; 512];
dict_stream.load_dict(&dict_data);
let mut working = Lz4Stream::new();
unsafe {
working.attach_dictionary(Some(&*dict_stream as *const Lz4Stream));
}
let src: Vec<u8> = (0u8..=255u8).cycle().take(256).collect();
let mut dst = make_dst(src.len());
let n = working.compress_fast_continue(&src, &mut dst, 1);
assert!(
n > 0,
"compress after attach must succeed even from zero initial offset"
);
}
#[test]
fn renorm_dict_with_normal_offset_is_safe() {
let mut stream = Lz4Stream::new();
stream.renorm_dict(1024); let src = b"hello after renorm aaaaaaaaaaaaaaaaaaaaaa";
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst, 1);
assert!(n > 0, "compress after no-op renorm_dict must succeed");
}
#[test]
fn save_dict_into_empty_buffer_returns_zero() {
let mut stream = Lz4Stream::new();
let src = b"data to compress so we have a dict";
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(src, &mut dst, 1);
let mut save_buf: Vec<u8> = vec![];
let saved = stream.save_dict(&mut save_buf);
assert_eq!(saved, 0, "zero-capacity save buffer must return 0");
}
#[test]
fn save_dict_without_prior_compress_returns_zero() {
let mut stream = Lz4Stream::new();
let mut buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut buf);
assert_eq!(saved, 0);
}
#[test]
fn save_dict_after_compress_returns_positive() {
let mut stream = Lz4Stream::new();
let src: Vec<u8> = b"Hello from lz4 streaming compression. This is test data."
.iter()
.cycle()
.take(1024)
.cloned()
.collect();
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0);
let mut save_buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut save_buf);
assert!(
saved > 0,
"save_dict should return the number of saved bytes"
);
assert!(saved as usize <= 64 * KB);
}
#[test]
fn save_dict_clamps_to_64kb() {
let mut stream = Lz4Stream::new();
let src = vec![0xAAu8; 128 * KB];
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0);
let mut save_buf = vec![0u8; 128 * KB];
let saved = stream.save_dict(&mut save_buf);
assert!(saved as usize <= 64 * KB, "save_dict must not exceed 64 KB");
}
#[test]
fn save_dict_clamps_to_buffer_size() {
let mut stream = Lz4Stream::new();
let src = vec![0xBBu8; 128 * KB];
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(&src, &mut dst, 1);
let cap = 256usize;
let mut save_buf = vec![0u8; cap];
let saved = stream.save_dict(&mut save_buf);
assert!(
saved as usize <= cap,
"save_dict must not exceed save buffer capacity"
);
}
#[test]
fn save_dict_updates_history_so_subsequent_save_returns_same_size() {
let mut stream = Lz4Stream::new();
let src = vec![0xCCu8; 1024];
let mut dst = make_dst(src.len());
let _ = stream.compress_fast_continue(&src, &mut dst, 1);
let mut save_buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut save_buf);
assert!(saved > 0);
let mut save_buf2 = vec![0u8; 64 * KB];
let saved2 = stream.save_dict(&mut save_buf2);
assert_eq!(
saved, saved2,
"second save_dict must return same size as first"
);
}
#[test]
fn compress_fast_continue_single_block_returns_positive() {
let mut stream = Lz4Stream::new();
let src = b"Hello, LZ4 streaming world! aaaaaaaaaaaaaaaaaaaaaa";
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(src, &mut dst, 1);
assert!(
n > 0,
"single-block compress should return positive byte count"
);
}
#[test]
fn compress_fast_continue_empty_input_returns_one_byte() {
let mut stream = Lz4Stream::new();
let src: &[u8] = &[];
let mut dst = [0u8; 16];
let n = stream.compress_fast_continue(src, &mut dst, 1);
assert_eq!(n, 1, "empty block must produce 1 byte (0x00 token)");
assert_eq!(dst[0], 0x00);
}
#[test]
fn compress_fast_continue_output_too_small_returns_zero() {
let mut stream = Lz4Stream::new();
let src: Vec<u8> =
b"This is a string long enough that it cannot possibly fit in 1 byte after compression."
.to_vec();
let mut dst = [0u8; 1];
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert_eq!(n, 0, "output too small should return 0");
}
#[test]
fn compress_fast_continue_all_zeros_compresses_well() {
let mut stream = Lz4Stream::new();
let src = vec![0u8; 4096];
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0);
assert!(
(n as usize) < src.len(),
"highly compressible data should compress to less than input size"
);
}
#[test]
fn compress_fast_continue_two_independent_blocks() {
let mut stream = Lz4Stream::new();
let block1 = b"Block one: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let block2 = b"Block two: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
let mut dst1 = make_dst(block1.len());
let n1 = stream.compress_fast_continue(block1, &mut dst1, 1);
assert!(n1 > 0);
let mut dst2 = make_dst(block2.len());
let n2 = stream.compress_fast_continue(block2, &mut dst2, 1);
assert!(n2 > 0);
}
#[test]
fn compress_fast_continue_prefix_mode_accumulates_history() {
let mut stream = Lz4Stream::new();
let buffer: Vec<u8> = (0u8..=255u8).cycle().take(4096).collect();
let (block1, block2) = buffer.split_at(2048);
let mut dst1 = make_dst(block1.len());
let n1 = stream.compress_fast_continue(block1, &mut dst1, 1);
assert!(n1 > 0);
let mut dst2 = make_dst(block2.len());
let n2 = stream.compress_fast_continue(block2, &mut dst2, 1);
assert!(n2 > 0);
}
#[test]
fn compress_fast_continue_deterministic() {
let src = b"determinism check: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let mut s1 = Lz4Stream::new();
let mut s2 = Lz4Stream::new();
let mut dst1 = make_dst(src.len());
let mut dst2 = make_dst(src.len());
let n1 = s1.compress_fast_continue(src, &mut dst1, 1);
let n2 = s2.compress_fast_continue(src, &mut dst2, 1);
assert_eq!(n1, n2);
assert_eq!(&dst1[..n1 as usize], &dst2[..n2 as usize]);
}
#[test]
fn compress_fast_continue_acceleration_clamped_below_default() {
let src = b"acceleration clamp test: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let mut s_clamped = Lz4Stream::new();
let mut s_default = Lz4Stream::new();
let mut dst1 = make_dst(src.len());
let mut dst2 = make_dst(src.len());
let n1 = s_clamped.compress_fast_continue(src, &mut dst1, 0); let n2 = s_default.compress_fast_continue(src, &mut dst2, 1);
assert_eq!(n1, n2);
assert_eq!(&dst1[..n1 as usize], &dst2[..n2 as usize]);
}
#[test]
fn compress_fast_continue_acceleration_clamped_above_max() {
let src: Vec<u8> = (0u8..=255u8).cycle().take(1024).collect();
let mut s_clamped = Lz4Stream::new();
let mut s_max = Lz4Stream::new();
let mut dst1 = make_dst(src.len());
let mut dst2 = make_dst(src.len());
let n1 = s_clamped.compress_fast_continue(&src, &mut dst1, i32::MAX);
let n2 = s_max.compress_fast_continue(&src, &mut dst2, 65_537);
assert_eq!(n1, n2);
assert_eq!(&dst1[..n1 as usize], &dst2[..n2 as usize]);
}
#[test]
fn compress_fast_continue_after_load_dict_succeeds() {
let mut stream = Lz4Stream::new();
let dict: Vec<u8> = (0u8..=255u8).cycle().take(512).collect();
let loaded = stream.load_dict(&dict);
assert!(loaded > 0);
let src: Vec<u8> = b"data similar to dict: "
.iter()
.cycle()
.take(256)
.cloned()
.collect();
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0, "compress after load_dict must succeed");
}
#[test]
fn compress_fast_continue_after_load_dict_slow_succeeds() {
let mut stream = Lz4Stream::new();
let dict: Vec<u8> = (0u8..=255u8).cycle().take(512).collect();
stream.load_dict_slow(&dict);
let src: Vec<u8> = (0u8..=255u8).cycle().take(256).collect();
let mut dst = make_dst(src.len());
let n = stream.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0);
}
#[test]
fn compress_fast_continue_with_attached_dict_succeeds() {
let mut dict_stream = Lz4Stream::new();
let dict_data: Vec<u8> = (0u8..=255u8).cycle().take(1024).collect();
dict_stream.load_dict(&dict_data);
let mut working = Lz4Stream::new();
unsafe {
working.attach_dictionary(Some(&*dict_stream as *const Lz4Stream));
}
let src: Vec<u8> = (0u8..=255u8).cycle().take(512).collect();
let mut dst = make_dst(src.len());
let n = working.compress_fast_continue(&src, &mut dst, 1);
assert!(n > 0, "compress with attached dict must succeed");
}
#[test]
fn save_dict_then_compress_continues_cleanly() {
let mut stream = Lz4Stream::new();
let src1: Vec<u8> = b"First block data. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
.iter()
.cycle()
.take(512)
.cloned()
.collect();
let mut dst1 = make_dst(src1.len());
let n1 = stream.compress_fast_continue(&src1, &mut dst1, 1);
assert!(n1 > 0);
let mut save_buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut save_buf);
assert!(saved > 0);
let src2: Vec<u8> = b"Second block data. bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb."
.iter()
.cycle()
.take(512)
.cloned()
.collect();
let mut dst2 = make_dst(src2.len());
let n2 = stream.compress_fast_continue(&src2, &mut dst2, 1);
assert!(n2 > 0, "compress after save_dict must succeed");
}
#[test]
fn compress_force_ext_dict_produces_compressed_output() {
let mut stream = Lz4Stream::new();
let dict = vec![0x00u8; 512];
stream.load_dict(&dict);
let src = vec![0x00u8; 256]; let mut dst = make_dst(src.len());
let n = unsafe {
stream.compress_force_ext_dict(
src.as_ptr(),
dst.as_mut_ptr(),
src.len() as i32,
dst.len() as i32,
)
};
assert!(
n > 0,
"compress_force_ext_dict must return > 0 bytes for compressible input"
);
}
#[test]
fn compress_force_ext_dict_updates_dict_via_save_dict() {
let mut stream = Lz4Stream::new();
let dict = vec![0xAAu8; 256];
stream.load_dict(&dict);
let src = [0xBBu8; 128];
let mut dst = make_dst(src.len());
unsafe {
stream.compress_force_ext_dict(
src.as_ptr(),
dst.as_mut_ptr(),
src.len() as i32,
dst.len() as i32,
);
}
let mut save_buf = vec![0u8; 64 * KB];
let saved = stream.save_dict(&mut save_buf);
assert_eq!(
saved as usize,
src.len(),
"save_dict after compress_force_ext_dict must return src.len()"
);
}