Skip to content

test(ci): #28 Phase 6.7 — conformance + cross-validation + ABI snapshot + reverse-dep matrix #132

Description

@polaz

Summary

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}:

  • A1: our zstd encode → upstream zstd decode → byte-exact match required.
  • A2: upstream zstd encode → our zstd decode → byte-exact match required.
  • A3: our zstd encode → our zstd decode → byte-exact (basic roundtrip, already covered, kept as regression).
  • 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. TW) 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).
  • Static struct size asserts (extending feat(c-api): #28 Phase 6.1 — C ABI core (cdylib + vendored headers + simple/context/error wrappers) #126's abi.rs): ZSTD_inBuffer/ZSTD_outBuffer/ZSTD_frameHeader size_of matches upstream sizeof(...).

4. Reverse-dep smoke build

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, rsyncrsync 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).

Blocked by

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2-mediumMedium priority — important improvementenhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions