use crate::support::pseudo;
use crate::support::run;
use crate::support::scratch;
fn expected(level: u8) -> u32 {
match level {
1 => 66_560,
2 => 131_072,
3 => 262_144,
4 => 524_288,
5 => 1_048_576,
6 => 2_097_152,
7 => 4_194_304,
8 => 8_388_608,
9 => 16_777_216,
10 => 33_554_432,
11 => 67_108_864,
12 => 134_217_728,
13 => 268_435_456,
14 => 535_822_336,
_ => unreachable!(),
}
}
fn header_size(coded: &[u8]) -> u32 {
assert!(coded.len() >= 9, "short frame");
assert_eq!(&coded[..5], b"BZ3v1");
u32::from_le_bytes(coded[5..9].try_into().expect("header size"))
}
#[test]
fn levels_map_to_expected_block_sizes() {
let s = scratch("levels");
let bytes = pseudo(42, 200);
for level in 1..=14_u8 {
let flag = format!("-{level}");
let out = run(&s.dir, &[&flag, "-j", "1", "-c"], &bytes);
assert!(
out.status.success(),
"level {level}: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(header_size(&out.stdout), expected(level), "level {level}");
let back = run(&s.dir, &["-d", "-c"], &out.stdout);
assert!(back.status.success(), "decode level {level}");
assert_eq!(back.stdout, bytes, "round-trip level {level}");
}
}
#[test]
fn fast_and_best_alias_levels() {
let s = scratch("aliases");
let bytes = pseudo(7, 500);
let one = run(&s.dir, &["-1", "-j", "1", "-c"], &bytes);
let fast = run(&s.dir, &["--fast", "-j", "1", "-c"], &bytes);
assert!(one.status.success() && fast.status.success());
assert_eq!(one.stdout, fast.stdout, "--fast must equal -1");
let top = run(&s.dir, &["-14", "-j", "1", "-c"], &bytes);
let best = run(&s.dir, &["--best", "-j", "1", "-c"], &bytes);
assert!(top.status.success() && best.status.success());
assert_eq!(top.stdout, best.stdout, "--best must equal -14");
}
#[test]
fn min_max_levels_match_block_size_limits() {
let s = scratch("minmax");
let bytes = pseudo(13, 200);
let min = run(&s.dir, &["-1", "-j", "1", "-c"], &bytes);
assert!(min.status.success());
assert_eq!(header_size(&min.stdout), 65 * 1024);
let max = run(&s.dir, &["-14", "-j", "1", "-c"], &bytes);
assert!(max.status.success());
assert_eq!(header_size(&max.stdout), 511 * 1024 * 1024);
let def = run(&s.dir, &["-j", "1", "-c"], &bytes);
assert!(def.status.success());
assert_eq!(header_size(&def.stdout), 16 * 1024 * 1024);
}
#[test]
fn last_block_flag_wins() {
let s = scratch("lastwins");
let bytes = pseudo(9, 200);
let cases: &[(&[&str], u32)] = &[
(&["-b", "1", "-14"], expected(14)),
(&["-14", "-b", "1"], 1_048_576),
(&["--fast", "--best"], expected(14)),
(&["--best", "--fast"], expected(1)),
(&["-1", "-14"], expected(14)),
(&["-14", "-1"], expected(1)),
(&["-b", "512", "-14"], expected(14)),
];
for (args, want) in cases {
let mut full = args.to_vec();
full.extend_from_slice(&["-j", "1", "-c"]);
let out = run(&s.dir, &full, &bytes);
assert!(
out.status.success(),
"{args:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(header_size(&out.stdout), *want, "{args:?}");
}
let out = run(&s.dir, &["-14", "-b", "512", "-j", "1", "-c"], &bytes);
assert!(!out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stderr),
"Block size must be between 65 KiB and 511 MiB.\n"
);
let out = run(&s.dir, &["-b", "xx", "-14", "-c"], &bytes);
assert!(!out.status.success());
assert_eq!(
String::from_utf8_lossy(&out.stderr),
"bzip3: invalid block size: xx\n"
);
}
#[test]
fn block_size_suffixes_map_to_bytes() {
let s = scratch("suffixes");
let bytes = pseudo(21, 200);
let mut headers = Vec::new();
for size in [
"16", "16M", "16m", "16MiB", "16mib", "16384K", "16384k", "16384KiB", "16384kib",
] {
let out = run(&s.dir, &["-b", size, "-j", "1", "-c"], &bytes);
assert!(
out.status.success(),
"size {size}: {}",
String::from_utf8_lossy(&out.stderr)
);
headers.push(header_size(&out.stdout));
}
assert!(
headers.iter().all(|&h| h == 16 * 1024 * 1024),
"{headers:?}"
);
for size in ["65K", "65k", "65KiB", "65kib"] {
let out = run(&s.dir, &["-b", size, "-j", "1", "-c"], &bytes);
assert!(
out.status.success(),
"size {size}: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(header_size(&out.stdout), 65 * 1024, "{size}");
}
for size in ["511M", "511mib", "523264K", "523264kib"] {
let out = run(&s.dir, &["-b", size, "-j", "1", "-c"], &bytes);
assert!(
out.status.success(),
"size {size}: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(header_size(&out.stdout), 511 * 1024 * 1024, "{size}");
}
let spaced = run(&s.dir, &["-b", "65K", "-j", "1", "-c"], &bytes);
let attached = run(&s.dir, &["-b65K", "-j", "1", "-c"], &bytes);
let equals = run(&s.dir, &["--block=65K", "-j", "1", "-c"], &bytes);
assert!(spaced.status.success() && attached.status.success() && equals.status.success());
assert_eq!(spaced.stdout, attached.stdout);
assert_eq!(spaced.stdout, equals.stdout);
}
#[test]
fn digit_clusters_stay_unknown_flags() {
let s = scratch("clusters");
for args in [&["-19"][..], &["-91"][..], &["-0"][..], &["-15"][..]] {
let out = run(&s.dir, args, b"hi");
assert!(!out.status.success(), "{args:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("Try 'bzip3 --help'"),
"{args:?}: {}",
String::from_utf8_lossy(&out.stderr)
);
}
}
#[test]
fn zero_level_stays_unknown_flag() {
let s = scratch("zero");
let out = run(&s.dir, &["-0", "-c"], b"hi");
assert!(!out.status.success());
assert!(
String::from_utf8_lossy(&out.stderr).contains("Try 'bzip3 --help'"),
"{}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn decode_ignores_levels() {
let s = scratch("decignore");
let bytes = pseudo(11, 300);
let coded = run(&s.dir, &["-1", "-j", "1", "-c"], &bytes);
assert!(coded.status.success());
for args in [&["-d", "-c", "-14"][..], &["-d", "-c", "--best"][..]] {
let back = run(&s.dir, args, &coded.stdout);
assert!(
back.status.success(),
"{args:?}: {}",
String::from_utf8_lossy(&back.stderr)
);
assert_eq!(back.stdout, bytes);
}
}