Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,79 @@ jobs:
exit 1
fi
echo "conformance/ matches upstream"

# The port exists to be wire compatible; everything else is detail. This job is
# the only place that claim is MEASURED against the reference itself: the exact
# release candidate in this repository exchanges bytes with the exact C++
# library, in both directions, on every push and pull request. Nothing here is a
# local one-off, and no expectation is regenerated from this port's own codec.
#
# What is exchanged: interop/serialize/interop/Interop.java and
# interop/interop.cpp serialize the SAME boundary message -- every operation
# STANDARD.md defines, at the values where implementations disagree. Both halves
# write it and the files must be byte identical; each half then decodes the
# other's file, checks every value and re-encodes it byte for byte. Then the
# hostile half: every proper prefix of the other's stream is a truncated stream
# and both must refuse it, and both run the shared conformance corpus, where the
# refusal vectors are the point.
#
# THE FAMILY PIN -- ONE POLICY, ONE VERSION. The C++ checkout is pinned to a
# RELEASED tag of mas-bandwidth/serialize, never main, and to the SAME tag in
# every port's wire-compat gate, so "compatible with C++" means the same thing
# in all of them. Never main, because an upstream merge mid-day must not change
# what a commit's CI proves. Find the newest tag with:
# gh release list --repo mas-bandwidth/serialize --limit 1
# Bump family-wide, deliberately, in its own commit per repo. SERIALIZE_TAG
# below is the ONE place this repository names a version of the reference.
# (The spec-sync job above deliberately stays on upstream main: drift DETECTION
# is that job's whole purpose.)
interop:
name: interop with the C++ reference
runs-on: ubuntu-latest
env:
SERIALIZE_TAG: v1.16.0
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21

- name: Check out the C++ reference at the pinned release
run: git clone --quiet --depth 1 --branch "$SERIALIZE_TAG" https://github.com/mas-bandwidth/serialize.git /tmp/cpp-serialize

# Asserts stay ON (no -DNDEBUG): they are the C++ half of "API misuse
# panics", and the degenerate ranges the message carries must pass with the
# library's own checks enabled rather than around them. No -ffp-contract=off
# either: the compressed float arithmetic is pinned in the library's source,
# and building with default flags proves that rather than masking it.
- name: Build the C++ halves against the pinned library
run: |
c++ -O2 -std=c++17 -Wall -I /tmp/cpp-serialize -o /tmp/cpp_interop interop/interop.cpp
c++ -O2 -std=c++17 -Wall -I /tmp/cpp-serialize -o /tmp/cpp_conformance /tmp/cpp-serialize/conformance.cpp

- name: Both halves write byte identical wire data
run: |
/tmp/cpp_interop write /tmp/cpp.bin
make interop JDK_HOME="$JAVA_HOME" MODE=write FILE=/tmp/java.bin
cmp /tmp/cpp.bin /tmp/java.bin

- name: Each half decodes the other's bytes and re-encodes them exactly
run: |
make interop JDK_HOME="$JAVA_HOME" MODE=read FILE=/tmp/cpp.bin
/tmp/cpp_interop read /tmp/java.bin

- name: Both halves refuse every truncation of the other's stream
run: |
make interop JDK_HOME="$JAVA_HOME" MODE=refuse FILE=/tmp/cpp.bin
/tmp/cpp_interop refuse /tmp/java.bin

# The corpus is the family's one conformance instrument, and its refusal
# vectors are what this step is for: the pinned reference reader and this
# port's reader are held to the same accepts and the same refusals, from the
# same vendored files. The spec-sync job above proves those files match
# upstream.
- name: Both halves run the shared conformance corpus
run: |
/tmp/cpp_conformance conformance/*.txt
make conformance JDK_HOME="$JAVA_HOME"
28 changes: 24 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ JAVAC := $(JDK_HOME)/bin/javac
JAVA := $(JDK_HOME)/bin/java

SRC := $(wildcard src/serialize/*.java)
TEST_SRC := $(wildcard test/serialize/tests/*.java)
TEST_SRC := $(wildcard test/serialize/tests/*.java)
INTEROP_SRC := $(wildcard interop/serialize/interop/*.java)

CLASSES := build/classes
TEST_CLASSES := build/test-classes
CLASSES := build/classes
TEST_CLASSES := build/test-classes
INTEROP_CLASSES := build/interop-classes

.PHONY: all test test-release clean
.PHONY: all test test-release conformance interop clean

all: test test-release

Expand All @@ -37,5 +39,23 @@ test: $(TEST_CLASSES)/.stamp
test-release: $(TEST_CLASSES)/.stamp
$(JAVA) -da -cp $(CLASSES):$(TEST_CLASSES) serialize.tests.AllTests --release

# the shared conformance corpus alone, so the interop job can hold this reader
# and the pinned C++ reader to the same vendored files in one place. make test
# runs it too, as one suite among many.
conformance: $(TEST_CLASSES)/.stamp
$(JAVA) -ea -cp $(CLASSES):$(TEST_CLASSES) serialize.tests.ConformanceTests

# the interop harness: compiled against the library classes only, like the tests.
# One exchange with the C++ reference per invocation:
# make interop MODE=write FILE=/tmp/java.bin
$(INTEROP_CLASSES)/.stamp: $(INTEROP_SRC) $(CLASSES)/.stamp
$(JAVAC) --release 17 -Xlint:all -Werror -cp $(CLASSES) -d $(INTEROP_CLASSES) $(INTEROP_SRC)
@touch $@

# asserts on: the write-side contracts are asserts here as everywhere, and the
# degenerate ranges the message carries must pass with them enabled
interop: $(INTEROP_CLASSES)/.stamp
$(JAVA) -ea -cp $(CLASSES):$(INTEROP_CLASSES) serialize.interop.Interop $(MODE) $(FILE)

clean:
rm -rf build
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ specification in
[mas-bandwidth/serialize](https://github.com/mas-bandwidth/serialize),
which CI checks for drift — is the authority on every byte.

Version 1.1.0 (`SerializeUtil.VERSION`).
Version 1.1.1 (`SerializeUtil.VERSION`).

## The surface

Expand Down Expand Up @@ -129,6 +129,17 @@ plus a sabotage sweep proving every consumed bit of the golden stream is
load bearing, refusal and terminality proofs for hostile input, and the
measure bound.

[`interop/`](interop) takes it further: the CI `interop` job builds the
C++ reference at a pinned release and runs it head to head with this
port. Both halves write the same boundary message — every operation the
standard defines, at its boundary values — and the files must be byte
identical; each then decodes the other's bytes and re-encodes them
exactly; both must refuse every truncation of the other's stream; and
both run the corpus. The release candidate in this repository exchanges
bytes with the reference on every push, so wire compatibility is
measured rather than asserted. `make interop MODE=write FILE=out.bin`
runs one exchange by hand.

Benchmarking for the serialize family lives in [mas-bandwidth/schema](https://github.com/mas-bandwidth/schema)'s data-driven bench, which measures the generated codecs across every language on one corpus.

## License
Expand Down
Loading
Loading