use alloc::format;
use alloc::vec::Vec;
use super::resolve_level_params;
use crate::encoding::CompressionLevel;
use crate::encoding::cparams::get_cparams;
use crate::encoding::strategy::{SearchMethod, StrategyTag};
#[test]
fn a_dictionary_lends_its_shape_whatever_the_source_size() {
let level = CompressionLevel::Level(9);
let sizes = crate::encoding::DictionarySizes::raw_content(4096);
let overrides = crate::encoding::parameters::ParamOverrides::default();
let (small, _) =
super::resolve_level_params_with_dict(level, Some(8 * 1024), sizes, &overrides);
let (large, _) =
super::resolve_level_params_with_dict(level, Some(4 * 1024 * 1024), sizes, &overrides);
assert_eq!(small.strategy_tag, large.strategy_tag);
assert_eq!(small.search, large.search);
assert_eq!(small.lazy_depth, large.lazy_depth);
assert!(
large.window_log >= small.window_log,
"the window still follows the source: {} against {}",
large.window_log,
small.window_log
);
}
#[test]
fn a_window_log_beyond_the_encoders_own_maximum_is_bounded() {
let huge = super::estimated_compression_workspace_bytes_for_run(
CompressionLevel::Level(3),
None,
Some(64),
false,
None,
);
let largest = super::estimated_compression_workspace_bytes_for_run(
CompressionLevel::Level(3),
None,
Some(30),
false,
None,
);
assert_eq!(
huge, largest,
"a window past the maximum is the maximum, not a shift off the end of the type"
);
}
#[cfg(feature = "ldm")]
#[test]
fn the_estimate_saturates_rather_than_wrapping() {
let widest = crate::encoding::ldm::table::LdmHashTable::estimated_workspace_bytes(30, 8);
assert!(
widest >= (1usize << 30).saturating_mul(2),
"the widest table is counted, not wrapped away: {widest}"
);
let with_ldm = super::estimated_compression_workspace_bytes_for_run(
CompressionLevel::Level(22),
None,
Some(30),
true,
None,
);
let without_ldm = super::estimated_compression_workspace_bytes_for_run(
CompressionLevel::Level(22),
None,
Some(30),
false,
None,
);
assert!(
with_ldm > without_ldm,
"the matcher's table is part of the figure: {with_ldm} vs {without_ldm}"
);
assert!(
without_ldm >= 1usize << 30,
"and the window alone is already a gibibyte at this log: {without_ldm}"
);
}
#[test]
fn the_parameters_estimate_is_the_levels_until_a_knob_resizes_the_frame() {
use crate::encoding::{CompressionParameters, DictionarySizes, Strategy};
for level in -7..=22 {
for source in [None, Some(4 << 10), Some(300 << 10), Some(64 << 20)] {
for dictionary in [None, Some(DictionarySizes::raw_content(32 << 10))] {
let plain = CompressionParameters::builder(CompressionLevel::Level(level))
.build()
.unwrap();
assert_eq!(
super::estimated_compression_workspace_bytes_for_parameters(
&plain, source, dictionary
),
super::estimated_compression_workspace_bytes_for_run(
CompressionLevel::Level(level),
source,
None,
false,
dictionary,
),
"level {level}, source {source:?}, dictionary {dictionary:?}"
);
}
}
}
let source = Some(32 << 10);
let base = CompressionParameters::builder(CompressionLevel::Level(1))
.build()
.unwrap();
let wider = CompressionParameters::builder(CompressionLevel::Level(1))
.hash_log(16)
.build()
.unwrap();
let optimal = CompressionParameters::builder(CompressionLevel::Level(1))
.strategy(Strategy::Btultra2)
.build()
.unwrap();
let estimate = |p: &CompressionParameters| {
super::estimated_compression_workspace_bytes_for_parameters(p, source, None)
};
assert!(estimate(&wider) > estimate(&base), "a wider hash table");
assert!(
estimate(&optimal) > estimate(&base),
"the optimal parser's tables and scratch"
);
}
#[test]
fn a_table_wider_than_the_address_space_is_not_estimated_as_nothing() {
use crate::encoding::CompressionParameters;
let wide = CompressionParameters::builder(CompressionLevel::Level(1))
.hash_log(30)
.build()
.unwrap();
let estimate = super::estimated_compression_workspace_bytes_for_parameters(&wide, None, None);
let table = (4u64 << 30).min(usize::MAX as u64) as usize;
assert!(
estimate >= table,
"a 2^30-entry table is counted, not shifted away: {estimate}"
);
}
#[test]
fn oversized_attach_dictionary_resolves_the_copy_geometry() {
use crate::encoding::DictionarySizes;
use crate::encoding::cparams::{copy_cparams, get_cdict_cparams};
let sizes = DictionarySizes::raw_content(17 * 1024 * 1024);
for level in [1, 3] {
let (params, _plan) = super::resolve_level_params_with_dict(
CompressionLevel::Level(level),
Some(4096),
sizes,
&Default::default(),
);
let cdict = get_cdict_cparams(level, sizes.serialized, &Default::default());
let copy = copy_cparams(
cdict,
u32::from(
super::resolve_level_params(CompressionLevel::Level(level), Some(4096)).window_log,
),
);
match level {
1 => {
let f = params.fast.expect("fast config");
assert_eq!(
f.hash_log, copy.hash_log,
"L1: copy-mode dictionary frame keeps the CDict hashLog"
);
}
_ => {
let d = params.dfast.expect("dfast config");
assert_eq!(
(u32::from(d.long_hash_log), u32::from(d.short_hash_log)),
(copy.hash_log, copy.chain_log),
"L3: copy-mode dictionary frame keeps the CDict table geometry"
);
}
}
}
}
#[test]
fn search_log_override_keeps_the_full_depth_for_chain_and_tree() {
use crate::encoding::parameters::ParamOverrides;
let ov = ParamOverrides {
search_log: Some(7),
..Default::default()
};
let mut params = resolve_level_params(CompressionLevel::Level(15), Some(1 << 20));
super::apply_param_overrides(&mut params, &ov);
let row = params.row.expect("btlazy2 carries a row config");
assert_eq!(row.row_log, 6, "rowLog clamps to 4..=6");
assert_eq!(
row.search_depth,
1 << 7,
"the tree walk budget is 1 << searchLog, not capped by rowLog"
);
}
#[test]
fn a_target_length_override_sets_the_fast_step() {
use crate::encoding::parameters::ParamOverrides;
for (target_length, step) in [(8, 9), (1, 2), (0, 2)] {
let ov = ParamOverrides {
target_length: Some(target_length),
..Default::default()
};
let mut params = resolve_level_params(CompressionLevel::Level(1), Some(1 << 20));
super::apply_param_overrides(&mut params, &ov);
assert_eq!(
params.fast.expect("level 1 is a Fast row").step_size,
step,
"targetLength {target_length}"
);
}
}
#[test]
fn a_strategy_override_counts_only_the_backend_it_selects() {
use crate::encoding::{CompressionParameters, Strategy};
let as_fast = CompressionParameters::builder(CompressionLevel::Level(22))
.strategy(Strategy::Fast)
.build()
.unwrap();
let level_one = CompressionParameters::builder(CompressionLevel::Level(1))
.window_log(27)
.build()
.unwrap();
let estimate = |p: &CompressionParameters| {
super::estimated_compression_workspace_bytes_for_parameters(p, None, None)
};
assert_eq!(estimate(&as_fast), estimate(&level_one));
}
#[test]
fn resolved_level_params_follow_upstream_cparams_over_a_size_grid() {
const SIZES: [u64; 8] = [
1024,
4096,
10 * 1024,
16 * 1024,
224_787,
1 << 20,
1_022_035,
100 << 20,
];
let mut mismatches = Vec::new();
for level in (-7..=22).filter(|&l| l != 0) {
for &size in &SIZES {
let p = resolve_level_params(CompressionLevel::Level(level), Some(size));
let cp = get_cparams(level, size, 0);
let mut bad = Vec::new();
let (tag, depth, search) = match cp.strategy {
1 => (StrategyTag::Fast, 0, SearchMethod::Fast),
2 => (StrategyTag::Dfast, 0, SearchMethod::DoubleFast),
3 => (StrategyTag::Greedy, 0, SearchMethod::RowHash),
4 => (StrategyTag::Lazy, 1, SearchMethod::RowHash),
5 => (StrategyTag::Lazy, 2, SearchMethod::RowHash),
6 => (StrategyTag::Btlazy2, 2, SearchMethod::BinaryTreeLazy),
7 => (StrategyTag::BtOpt, 2, SearchMethod::BinaryTree),
8 => (StrategyTag::BtUltra, 2, SearchMethod::BinaryTree),
9 => (StrategyTag::BtUltra2, 2, SearchMethod::BinaryTree),
other => panic!("unknown upstream strategy {other}"),
};
if p.strategy_tag != tag || p.search != search {
bad.push(format!(
"strategy {:?}/{:?} != upstream {} ({:?}/{:?})",
p.strategy_tag, p.search, cp.strategy, tag, search
));
}
if matches!(tag, StrategyTag::Lazy) && p.lazy_depth != depth {
bad.push(format!("lazy_depth {} != {depth}", p.lazy_depth));
}
if u32::from(p.window_log) != cp.window_log {
bad.push(format!("window_log {} != {}", p.window_log, cp.window_log));
}
let search_depth = 1usize << cp.search_log;
match tag {
StrategyTag::Fast => {
let f = p.fast.expect("fast config");
if f.hash_log != cp.hash_log || f.mls != cp.min_match {
bad.push(format!(
"fast hash/mls {}/{} != {}/{}",
f.hash_log, f.mls, cp.hash_log, cp.min_match
));
}
let step = (cp.target_length as usize).max(1) + 1;
if f.step_size != step {
bad.push(format!("fast step {} != {step}", f.step_size));
}
}
StrategyTag::Dfast => {
let d = p.dfast.expect("dfast config");
if u32::from(d.long_hash_log) != cp.hash_log
|| u32::from(d.short_hash_log) != cp.chain_log
{
bad.push(format!(
"dfast long/short {}/{} != {}/{}",
d.long_hash_log, d.short_hash_log, cp.hash_log, cp.chain_log
));
}
}
StrategyTag::Greedy | StrategyTag::Lazy | StrategyTag::Btlazy2 => {
let r = p.row.expect("row config");
let expect = (
cp.hash_log as usize,
cp.chain_log as usize,
cp.search_log.clamp(4, 6) as usize,
search_depth,
cp.target_length as usize,
cp.min_match as usize,
tag == StrategyTag::Btlazy2,
);
let got = (
r.hash_bits,
r.chain_log,
r.row_log,
r.search_depth,
r.target_len,
r.mls,
r.bt,
);
if got != expect {
bad.push(format!(
"row (hash,chain,row_log,depth,target,mls,bt) {got:?} != {expect:?}"
));
}
}
StrategyTag::BtOpt | StrategyTag::BtUltra | StrategyTag::BtUltra2 => {
let h = p.hc.expect("hc config");
let expect = (
cp.hash_log as usize,
cp.chain_log as usize,
search_depth,
cp.target_length as usize,
cp.min_match.clamp(4, 6) as usize,
);
let got = (
h.hash_log,
h.chain_log,
h.search_depth,
h.target_len,
h.search_mls,
);
if got != expect {
bad.push(format!(
"hc (hash,chain,depth,target,mls) {got:?} != {expect:?}"
));
}
}
}
if !bad.is_empty() {
mismatches.push(format!("L{level} size {size}: {}", bad.join("; ")));
}
}
}
assert!(
mismatches.is_empty(),
"{} (level, size) cells diverge from ZSTD_getCParams:\n{}",
mismatches.len(),
mismatches.join("\n")
);
}