Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# bzip2 interop checker
|
||||
|
||||
This helper verifies interoperability between:
|
||||
|
||||
- V module: `compress.bzip2`
|
||||
- System CLI: `bzip2`
|
||||
- Python stdlib: `bz2`
|
||||
|
||||
It runs deterministic test vectors and compression levels, compresses each vector with all
|
||||
three producers, then cross-decompresses every produced stream with all three decoders.
|
||||
|
||||
A case passes only if every decompressed output is byte-identical to the original input.
|
||||
|
||||
Current levels are `1`, `6`, and `9` (see `compression_levels` in
|
||||
`vlib/compress/bzip2/interop/bzip2_interop_check.vsh`).
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
v run vlib/compress/bzip2/interop/bzip2_interop_check.vsh
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- `bzip2` available in `PATH`
|
||||
- `python3` with `bz2`
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env -S v
|
||||
|
||||
import compress.bzip2
|
||||
|
||||
const compression_levels = [1, 6, 9]!
|
||||
|
||||
struct TestVector {
|
||||
name string
|
||||
data []u8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
ensure_tools() or {
|
||||
eprintln('SKIP: ${err.msg()}')
|
||||
exit(2)
|
||||
}
|
||||
vectors := make_test_vectors()
|
||||
mut total_checks := 0
|
||||
mut total_runs := 0
|
||||
tmp_root := join_path(temp_dir(), 'v_bzip2_interop_${getpid()}')
|
||||
mkdir_all(tmp_root) or { panic(err) }
|
||||
defer {
|
||||
rmdir_all(tmp_root) or {}
|
||||
}
|
||||
for level in compression_levels {
|
||||
for i, vec in vectors {
|
||||
total_checks += run_case(tmp_root, level, i, vec) or {
|
||||
eprintln('FAIL: ${vec.name} (level=${level}): ${err}')
|
||||
exit(1)
|
||||
0
|
||||
}
|
||||
total_runs++
|
||||
println('ok ${total_runs}/${vectors.len * compression_levels.len}: ${vec.name} (level=${level}, ${vec.data.len} bytes)')
|
||||
}
|
||||
}
|
||||
println('PASS: ${vectors.len} vectors x ${compression_levels.len} levels, ${total_checks} cross-checks')
|
||||
}
|
||||
|
||||
fn ensure_tools() ! {
|
||||
must_succeed('bzip2 --help >/dev/null 2>&1', 'system bzip2 command is not available')!
|
||||
must_succeed("python3 -c 'import bz2' >/dev/null 2>&1",
|
||||
'python3 with bz2 module is not available')!
|
||||
}
|
||||
|
||||
fn make_test_vectors() []TestVector {
|
||||
mut vectors := []TestVector{}
|
||||
vectors << TestVector{'empty', []u8{}}
|
||||
vectors << TestVector{'ascii_text', 'The quick brown fox jumps over the lazy dog.\n'.repeat(64).bytes()}
|
||||
vectors << TestVector{'repeated_byte', []u8{len: 10000, init: `A`}}
|
||||
vectors << TestVector{'all_bytes_x4', all_bytes_repeated(4)}
|
||||
vectors << TestVector{'lcg_64k', lcg_bytes(65536)}
|
||||
return vectors
|
||||
}
|
||||
|
||||
fn run_case(tmp_root string, level int, case_idx int, vec TestVector) !int {
|
||||
case_dir := join_path(tmp_root, 'case_l${level}_${case_idx:02}_${vec.name}')
|
||||
mkdir_all(case_dir)!
|
||||
|
||||
v_bz2 := bzip2.compress(vec.data, block_size: level)!
|
||||
cli_bz2 := cli_compress(case_dir, vec.data, level)!
|
||||
py_bz2 := py_compress(case_dir, vec.data, level)!
|
||||
|
||||
mut checks := 0
|
||||
|
||||
producers := {
|
||||
'v': v_bz2
|
||||
'cli': cli_bz2
|
||||
'py': py_bz2
|
||||
}
|
||||
for producer, compressed in producers {
|
||||
v_plain := bzip2.decompress(compressed)!
|
||||
assert_equal_bytes('v.decompress(${producer}.compress, level=${level})', vec.data, v_plain)!
|
||||
checks++
|
||||
|
||||
cli_plain := cli_decompress(case_dir, producer, compressed)!
|
||||
assert_equal_bytes('cli.decompress(${producer}.compress, level=${level})', vec.data,
|
||||
cli_plain)!
|
||||
checks++
|
||||
|
||||
py_plain := py_decompress(case_dir, producer, compressed)!
|
||||
assert_equal_bytes('py.decompress(${producer}.compress, level=${level})', vec.data,
|
||||
py_plain)!
|
||||
checks++
|
||||
}
|
||||
return checks
|
||||
}
|
||||
|
||||
fn cli_compress(case_dir string, plain []u8, level int) ![]u8 {
|
||||
in_path := join_path(case_dir, 'plain.in')
|
||||
out_path := join_path(case_dir, 'cli_l${level}.bz2')
|
||||
write_file_array(in_path, plain)!
|
||||
must_succeed('bzip2 -${level} -c -- ${shell_quote(in_path)} > ${shell_quote(out_path)}',
|
||||
'bzip2 compression failed')!
|
||||
return read_bytes(out_path)!
|
||||
}
|
||||
|
||||
fn cli_decompress(case_dir string, producer string, compressed []u8) ![]u8 {
|
||||
in_path := join_path(case_dir, '${producer}.for_cli.bz2')
|
||||
out_path := join_path(case_dir, '${producer}.from_cli.out')
|
||||
write_file_array(in_path, compressed)!
|
||||
must_succeed('bzip2 -d -c -- ${shell_quote(in_path)} > ${shell_quote(out_path)}',
|
||||
'bzip2 decompression failed')!
|
||||
return read_bytes(out_path)!
|
||||
}
|
||||
|
||||
fn py_compress(case_dir string, plain []u8, level int) ![]u8 {
|
||||
in_path := join_path(case_dir, 'plain_py.in')
|
||||
out_path := join_path(case_dir, 'py_l${level}.bz2')
|
||||
write_file_array(in_path, plain)!
|
||||
py_code := 'import bz2, pathlib, sys; p=pathlib.Path(sys.argv[1]); o=pathlib.Path(sys.argv[2]); l=int(sys.argv[3]); o.write_bytes(bz2.compress(p.read_bytes(), compresslevel=l))'
|
||||
must_succeed('python3 -c ${shell_quote(py_code)} ${shell_quote(in_path)} ${shell_quote(out_path)} ${level}',
|
||||
'python bz2 compression failed')!
|
||||
return read_bytes(out_path)!
|
||||
}
|
||||
|
||||
fn py_decompress(case_dir string, producer string, compressed []u8) ![]u8 {
|
||||
in_path := join_path(case_dir, '${producer}.for_py.bz2')
|
||||
out_path := join_path(case_dir, '${producer}.from_py.out')
|
||||
write_file_array(in_path, compressed)!
|
||||
py_code := 'import bz2, pathlib, sys; p=pathlib.Path(sys.argv[1]); o=pathlib.Path(sys.argv[2]); o.write_bytes(bz2.decompress(p.read_bytes()))'
|
||||
must_succeed('python3 -c ${shell_quote(py_code)} ${shell_quote(in_path)} ${shell_quote(out_path)}',
|
||||
'python bz2 decompression failed')!
|
||||
return read_bytes(out_path)!
|
||||
}
|
||||
|
||||
fn all_bytes_repeated(times int) []u8 {
|
||||
mut out := []u8{cap: 256 * times}
|
||||
for _ in 0 .. times {
|
||||
for i in 0 .. 256 {
|
||||
out << u8(i)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn lcg_bytes(n int) []u8 {
|
||||
mut out := []u8{len: n}
|
||||
mut x := u32(0x12345678)
|
||||
for i in 0 .. n {
|
||||
x = x * u32(1664525) + u32(1013904223)
|
||||
out[i] = u8((x >> 16) & u32(0xff))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
fn assert_equal_bytes(label string, expected []u8, got []u8) ! {
|
||||
if expected.len != got.len {
|
||||
return error('${label}: length mismatch expected=${expected.len} got=${got.len}')
|
||||
}
|
||||
for i in 0 .. expected.len {
|
||||
if expected[i] != got[i] {
|
||||
return error('${label}: byte mismatch at offset ${i}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn must_succeed(command string, context string) ! {
|
||||
res := execute(command)
|
||||
if res.exit_code != 0 {
|
||||
return error('${context}\ncommand: ${command}\nexit_code: ${res.exit_code}\n${res.output}')
|
||||
}
|
||||
}
|
||||
|
||||
fn shell_quote(s string) string {
|
||||
return "'${s.replace("'", "'\\''")}'"
|
||||
}
|
||||
Reference in new issue
Block a user