You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 6.7 of #28: prove the drop-in claim. Wire up upstream's own test scripts against our libzstd.so.1 + zstd binary, run cross-validation against C libzstd on diverse corpora, verify our .so exports the exact symbol set upstream does, and sample-build a handful of real reverse-dependencies to confirm they work without recompilation.
Why this is its own issue
The earlier Phase 6 issues land the implementation; this one lands the evidence. Without it, "drop-in replacement" is a claim — with it, the claim is testable and graphable per commit.
Deliverables
1. Upstream test-suite harness
Vendor (or git-submodule) upstream's tests/ directory at v1.5.7. Wire up CI jobs to run:
tests/playTests.sh — upstream's main shell-driven integration suite. Hundreds of compress/decompress/stdin-pipe/dictionary/multi-frame scenarios. Runs against ${ZSTD} and ${DATAGEN}; we set ZSTD=$(target_dir)/release/zstd and DATAGEN to either upstream's datagen binary or a pure-Rust replacement.
tests/fileTests.sh — file-handling edge cases.
tests/playTests.sh --test-large-data — multi-GB stream tests on CI with enough disk.
tests/dict-builder/ scripts — exercise our ZDICT_* FFI vs upstream's dictbuilder reference.
CI job structure:
.github/workflows/conformance.yml:
- matrix: { os: [ubuntu, macos], target: [x86_64, aarch64] }
- steps: build c-api .so + cli binary → set ZSTD env → run playTests.sh
- failure: dump frames + first diff vs upstream output
2. Cross-validation harness
Per-corpus matrix:
silesia/ (standard zstd benchmark corpus, vendored or downloaded in CI)
canterbury/ (small canonical files)
enwiki-1m/ (Wikipedia text sample)
decodecorpus-generated synthetic (already in our test infra)
For each corpus × compression_level ∈ {1, 3, 9, 19, 22}:
B1: compressed_size_ours / compressed_size_upstream per (corpus, level) — tracked over time in a CI graph; ≤ 1.05 (5% slack) is the parity target.
C1: compress_time_ours / compress_time_upstream per (corpus, level) — tracked; ≤ 1.5× is the parity target (Phase 6 perf claim).
A failure means either (i) we produced an invalid frame upstream rejects, or (ii) we rejected a valid upstream frame, or (iii) we silently corrupted output. All three are dealbreakers for drop-in.
3. ABI symbol verification
c-api/tests/symbol_snapshot.rs: stores nm -D libstructured_zstd.so | sort output as a checked-in fixture. CI regenerates and diffs — any new export, removed export, or changed binding (e.g. T → W) breaks the build.
Sentinel snapshot: also check that nm -D of upstream libzstd.so.1.5.7 against our .so produces an identical symbol set (modulo Rust-internal _ZN... mangled symbols which we mark with #[no_mangle] everywhere visible).
Vendor a handful of small real-world consumers and build them against our .so instead of upstream's:
tar with --zstd: take tar source, LD_PRELOAD=path/to/libstructured_zstd.so.1 tar --zstd -cf out.tar.zst dir/, roundtrip extract via upstream tar --zstd → input dir.
zstdcat | wc -c over a known archive: smoke check the CLI dispatch + stdin/stdout glue work in a real shell pipeline.
Kernel zstd userspace tool (linux/lib/zstd/'s userspace test binary): compile against our headers, run the kernel's own unit tests against our impl.
rsync --compress-choice=zstd (if available): rsync with zstd compression negotiated; verify the receiving side decompresses.
This isn't a packaging effort — we don't ship .apk/.deb/.rpm from CI. It's a "does it actually work when wired into a real binary that didn't know it was being swapped" smoke check.
5. Performance graph publication
Reuse the existing bench infra (#28 Phase 3+): publish compressed_size_ours / upstream and compress_time_ours / upstream per (corpus, level) on gh-pages, charted over time. Alerts fire when either ratio crosses a threshold.
Out of scope
Building actual .apk / .deb packages — that's downstream packaging work, not us.
Replacing system libzstd.so.1 on a developer machine via LD_PRELOAD in everyday workflows — possible from this work but not required.
Bug-for-bug compatibility with upstream's UB / undefined-on-purpose behaviour. We match the spec, not the bugs.
Acceptance criteria
tests/playTests.sh runs end-to-end with zero failures on our binary.
Cross-validation matrix (corpus × level × A1/A2/A3): byte-exact roundtrip pass on every cell.
Ratio parity gate: compressed_size_ours / upstream ≤ 1.05 for every (corpus, level) combo on the standard corpora.
Time parity gate: compress_time_ours / upstream ≤ 1.5 on Linux x86_64 (the platform the bench-matrix targets).
nm -D of our .so is a strict superset of upstream's exported symbol set; CI fails on regressions.
At least 3 of the 4 reverse-dep smoke tests pass (tar --zstd, zstdcat | wc, kernel userspace tool, rsync — rsync may be marked optional if not present in CI image).
Performance graph published on gh-pages with per-corpus charts.
Estimate
~10-12 working days (vendor + harness wiring + ABI snapshot infra + reverse-dep glue + graph integration).
Summary
Phase 6.7 of #28: prove the drop-in claim. Wire up upstream's own test scripts against our
libzstd.so.1+zstdbinary, run cross-validation against Clibzstdon diverse corpora, verify our.soexports the exact symbol set upstream does, and sample-build a handful of real reverse-dependencies to confirm they work without recompilation.Why this is its own issue
The earlier Phase 6 issues land the implementation; this one lands the evidence. Without it, "drop-in replacement" is a claim — with it, the claim is testable and graphable per commit.
Deliverables
1. Upstream test-suite harness
Vendor (or git-submodule) upstream's
tests/directory at v1.5.7. Wire up CI jobs to run:tests/playTests.sh— upstream's main shell-driven integration suite. Hundreds of compress/decompress/stdin-pipe/dictionary/multi-frame scenarios. Runs against${ZSTD}and${DATAGEN}; we setZSTD=$(target_dir)/release/zstdandDATAGENto either upstream's datagen binary or a pure-Rust replacement.tests/fileTests.sh— file-handling edge cases.tests/playTests.sh --test-large-data— multi-GB stream tests on CI with enough disk.tests/dict-builder/scripts — exercise ourZDICT_*FFI vs upstream's dictbuilder reference.CI job structure:
2. Cross-validation harness
Per-corpus matrix:
silesia/(standard zstd benchmark corpus, vendored or downloaded in CI)canterbury/(small canonical files)enwiki-1m/(Wikipedia text sample)decodecorpus-generated synthetic (already in our test infra)For each corpus ×
compression_level ∈ {1, 3, 9, 19, 22}:zstdencode → upstreamzstddecode → byte-exact match required.zstdencode → ourzstddecode → byte-exact match required.zstdencode → ourzstddecode → byte-exact (basic roundtrip, already covered, kept as regression).compressed_size_ours / compressed_size_upstreamper (corpus, level) — tracked over time in a CI graph; ≤ 1.05 (5% slack) is the parity target.compress_time_ours / compress_time_upstreamper (corpus, level) — tracked; ≤ 1.5× is the parity target (Phase 6 perf claim).A failure means either (i) we produced an invalid frame upstream rejects, or (ii) we rejected a valid upstream frame, or (iii) we silently corrupted output. All three are dealbreakers for drop-in.
3. ABI symbol verification
c-api/tests/symbol_snapshot.rs: storesnm -D libstructured_zstd.so | sortoutput as a checked-in fixture. CI regenerates anddiffs — any new export, removed export, or changed binding (e.g.T→W) breaks the build.nm -Dof upstreamlibzstd.so.1.5.7against our.soproduces an identical symbol set (modulo Rust-internal_ZN...mangled symbols which we mark with#[no_mangle]everywhere visible).abi.rs):ZSTD_inBuffer/ZSTD_outBuffer/ZSTD_frameHeadersize_ofmatches upstreamsizeof(...).4. Reverse-dep smoke build
Vendor a handful of small real-world consumers and build them against our
.soinstead of upstream's:tarwith--zstd: taketarsource,LD_PRELOAD=path/to/libstructured_zstd.so.1 tar --zstd -cf out.tar.zst dir/, roundtrip extract via upstreamtar --zstd→ input dir.zstdcat | wc -cover a known archive: smoke check the CLI dispatch + stdin/stdout glue work in a real shell pipeline.zstduserspace tool (linux/lib/zstd/'s userspace test binary): compile against our headers, run the kernel's own unit tests against our impl.rsync --compress-choice=zstd(if available): rsync with zstd compression negotiated; verify the receiving side decompresses.This isn't a packaging effort — we don't ship
.apk/.deb/.rpmfrom CI. It's a "does it actually work when wired into a real binary that didn't know it was being swapped" smoke check.5. Performance graph publication
Reuse the existing bench infra (#28 Phase 3+): publish
compressed_size_ours / upstreamandcompress_time_ours / upstreamper (corpus, level) on gh-pages, charted over time. Alerts fire when either ratio crosses a threshold.Out of scope
.apk/.debpackages — that's downstream packaging work, not us.libzstd.so.1on a developer machine viaLD_PRELOADin everyday workflows — possible from this work but not required.Acceptance criteria
tests/playTests.shruns end-to-end with zero failures on our binary.compressed_size_ours / upstream ≤ 1.05for every (corpus, level) combo on the standard corpora.compress_time_ours / upstream ≤ 1.5on Linux x86_64 (the platform the bench-matrix targets).nm -Dof our.sois a strict superset of upstream's exported symbol set; CI fails on regressions.tar --zstd,zstdcat | wc, kernel userspace tool,rsync—rsyncmay be marked optional if not present in CI image).Estimate
~10-12 working days (vendor + harness wiring + ABI snapshot infra + reverse-dep glue + graph integration).
Blocked by
zstdCLI binary with full upstream v1.5.7 parity #128 (Phase 6.3 — CLI) —tests/playTests.shneeds a working CLI binary.playTests.shhas MT-aware scenarios that need real MT.playTests.shcovers legacy archives.References
tests/directory v1.5.7.github/workflows/ci.ymlbench-matrixjobs