From 5a4b6c648d8ea8f52bf04d1b7d7a09c07c3e4bfb Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:07:18 -0700 Subject: [PATCH 1/9] conformance: generate the test messages with the table codec behind a feature The `table` feature selects CodecStrategy::Table for every conformance schema. A compile-time pin in main.rs names the table statics of the messages that must use it, so a fallback rule cannot silently turn the run back into an unrolled one. --- .gitignore | 1 + conformance/Cargo.toml | 4 ++++ conformance/build.rs | 23 ++++++++++++++++++----- conformance/known_failures_table.txt | 5 +++++ conformance/src/main.rs | 23 +++++++++++++++++++++++ 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 conformance/known_failures_table.txt diff --git a/.gitignore b/.gitignore index e3ba196b..088f70d0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # Conformance proto files are fetched from the tools image. conformance/protos/ conformance/target-nostd/ +conformance/target-table/ # Cargo build artefacts target/ diff --git a/conformance/Cargo.toml b/conformance/Cargo.toml index e35a6368..647ba40a 100644 --- a/conformance/Cargo.toml +++ b/conformance/Cargo.toml @@ -31,6 +31,10 @@ default = ["buffa-std"] # whose generated code therefore omits the reflect impls. buffa-std = ["buffa/std", "buffa-types/std", "reflect"] reflect = ["buffa-types/reflect"] +# Generates the test messages with `CodecStrategy::Table`, for the via-table +# run. Needs Rust 1.77 (`offset_of!`); buffa-build refuses older compilers, so +# the default build stays at the 1.75 MSRV. The table runtime needs no std. +table = [] [dependencies] buffa = { path = "../buffa", default-features = false, features = ["json", "text"] } diff --git a/conformance/build.rs b/conformance/build.rs index fdb66123..3af89352 100644 --- a/conformance/build.rs +++ b/conformance/build.rs @@ -27,7 +27,7 @@ fn main() { // so the no_std binary — built `--no-default-features` — omits it. // TestAllTypesProto3 with serde + textproto enabled. - buffa_build::Config::new() + config() .files(&["protos/google/protobuf/test_messages_proto3.proto"]) .includes(&["protos/"]) .generate_json(true) @@ -39,7 +39,7 @@ fn main() { .expect("buffa_build failed for test_messages_proto3.proto"); // TestAllTypesProto2 with serde + textproto enabled for proto2 conformance. - buffa_build::Config::new() + config() .files(&["protos/google/protobuf/test_messages_proto2.proto"]) .includes(&["protos/"]) .generate_json(true) @@ -54,7 +54,7 @@ fn main() { // Editions test messages: proto3 behavior via editions. let editions_proto3 = protos_dir.join("editions/golden/test_messages_proto3_editions.proto"); if editions_proto3.exists() { - buffa_build::Config::new() + config() .files(&[&editions_proto3]) .includes(&[&protos_dir]) .generate_json(true) @@ -66,7 +66,7 @@ fn main() { .expect("buffa_build failed for test_messages_proto3_editions.proto"); // Editions test messages: proto2 behavior via editions. - buffa_build::Config::new() + config() .files(&["protos/editions/golden/test_messages_proto2_editions.proto"]) .includes(&["protos/"]) .generate_json(true) @@ -82,7 +82,7 @@ fn main() { // JSON enabled for the extension registry (the text `[pkg.ext]` bracket // syntax resolves through the same registry structs); text enabled for // the RunDelimitedTests suite. - buffa_build::Config::new() + config() .files(&["protos/conformance/test_protos/test_messages_edition2023.proto"]) .includes(&["protos/"]) .generate_json(true) @@ -103,6 +103,19 @@ fn main() { emit_reflect_fds(&manifest_dir); } +/// The generator configuration shared by every schema. With the crate's +/// `table` feature it selects [`buffa_build::CodecStrategy::Table`], so that +/// the via-table run drives the table codec through the corpus; the feature +/// needs Rust 1.77, which `compile()` checks. +fn config() -> buffa_build::Config { + let config = buffa_build::Config::new(); + if std::env::var_os("CARGO_FEATURE_TABLE").is_some() { + config.codec_strategy(buffa_build::CodecStrategy::Table) + } else { + config + } +} + /// Write `OUT_DIR/conformance_protos.fds` containing the conformance test /// message types and their transitive imports. fn emit_reflect_fds(manifest_dir: &std::path::Path) { diff --git a/conformance/known_failures_table.txt b/conformance/known_failures_table.txt new file mode 100644 index 00000000..e58a785b --- /dev/null +++ b/conformance/known_failures_table.txt @@ -0,0 +1,5 @@ +# Known conformance test failures for the table codec (CodecStrategy::Table). +# +# Tests listed here are expected to fail in the via-table run and will not +# cause it to report a failure. Each line is a test name (matched by the +# runner with --failure_list). Remove entries as failures are fixed. diff --git a/conformance/src/main.rs b/conformance/src/main.rs index b031098e..92c44450 100644 --- a/conformance/src/main.rs +++ b/conformance/src/main.rs @@ -129,6 +129,29 @@ fn setup_type_registry() { set_type_registry(reg); } +// ── Table codec ────────────────────────────────────────────────────────── +// +// Built with the `table` feature, the test messages are generated with +// `CodecStrategy::Table` and this binary is the via-table run. A message that +// the strategy leaves unrolled has no `__BUFFA_TABLE_*` static, so naming the +// ones below fails the build if a fallback rule ever moves them back to the +// unrolled codec and the run would silently stop testing the table. +// `TestAllTypesProto2` is not among them: its extension ranges and group +// fields keep it unrolled, so proto2 reaches the table through its nested and +// sibling messages. + +#[cfg(all(not(no_protos), feature = "table"))] +#[allow(dead_code)] +fn table_messages_use_the_table() { + fn is_table(_: &'static buffa::table::Table) {} + is_table(&proto3::__BUFFA_TABLE_TestAllTypesProto3); + is_table(&proto3::test_all_types_proto3::__BUFFA_TABLE_NestedMessage); + is_table(&proto2::__BUFFA_TABLE_TestLargeOneof); + is_table(&proto2::test_all_types_proto2::__BUFFA_TABLE_NestedMessage); + #[cfg(has_editions_protos)] + is_table(&editions_proto3::__BUFFA_TABLE_TestAllTypesProto3); +} + // ── Via-view mode ──────────────────────────────────────────────────────── // // When `BUFFA_VIA_VIEW=1`, binary input is routed through From 490ffc200999eef7763f345f267bccb72942d60d Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:07:18 -0700 Subject: [PATCH 2/9] conformance: run the table build as an eighth suite Adds the table binary to the Docker image, the local script and the CI conformance job, with its own known-failures list. The build needs Rust 1.77; buffa-build refuses an older compiler. --- .github/workflows/ci.yml | 17 +++++++++++++++++ Taskfile.yml | 4 ++-- conformance/Dockerfile | 6 ++++++ conformance/run-conformance.sh | 11 +++++++++++ scripts/run-conformance-local.sh | 23 ++++++++++++++++++----- 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4afdc2aa..99385249 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -486,6 +486,16 @@ jobs: - name: Save std binary run: cp conformance/target/${{ steps.host.outputs.triple }}/release/conformance /tmp/buffa-conformance-std + # The table strategy needs Rust 1.77 (offset_of!); buffa-build fails the + # build on an older compiler rather than skipping the run. + - name: Build conformance binary (table) + run: >- + cargo build --release --manifest-path conformance/Cargo.toml + --features table --target ${{ steps.host.outputs.triple }} + + - name: Save table binary + run: cp conformance/target/${{ steps.host.outputs.triple }}/release/conformance /tmp/buffa-conformance-table + - name: Build conformance binary (no_std) run: >- cargo build --release --manifest-path conformance/Cargo.toml @@ -549,3 +559,10 @@ jobs: --failure_list conformance/known_failures_view_vtable.txt --maximum_edition 2024 /tmp/buffa-conformance-std + + - name: Run conformance tests (via-table) + run: >- + conformance_test_runner + --failure_list conformance/known_failures_table.txt + --maximum_edition 2024 + /tmp/buffa-conformance-table diff --git a/Taskfile.yml b/Taskfile.yml index 3e0ac5ac..d5a59b4a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -77,9 +77,9 @@ tasks: conformance-local: desc: >- - Run the full six-run conformance suite without Docker, using the + Run the full eight-run conformance suite without Docker, using the runner from conformance-tools-local and host cargo builds of the - std/no_std conformance binaries. Requires protoc v30+ on PATH or + std/table/no_std conformance binaries. Requires protoc v30+ on PATH or $PROTOC. cmds: - scripts/run-conformance-local.sh diff --git a/conformance/Dockerfile b/conformance/Dockerfile index 7e21daf8..461e004c 100644 --- a/conformance/Dockerfile +++ b/conformance/Dockerfile @@ -36,6 +36,9 @@ COPY protoc-gen-buffa/ protoc-gen-buffa/ COPY conformance/ conformance/ RUN cargo build --release --manifest-path conformance/Cargo.toml +# The table strategy needs Rust 1.77; this stage's toolchain is newer. +RUN cargo build --release --manifest-path conformance/Cargo.toml \ + --features table --target-dir conformance/target-table RUN cargo build --release --manifest-path conformance/Cargo.toml \ --no-default-features --target-dir conformance/target-nostd @@ -50,8 +53,11 @@ COPY --from=rust-builder \ /workspace/conformance/target/release/conformance /usr/local/bin/buffa-conformance COPY --from=rust-builder \ /workspace/conformance/target-nostd/release/conformance /usr/local/bin/buffa-conformance-nostd +COPY --from=rust-builder \ + /workspace/conformance/target-table/release/conformance /usr/local/bin/buffa-conformance-table COPY conformance/known_failures.txt /known_failures.txt COPY conformance/known_failures_nostd.txt /known_failures_nostd.txt +COPY conformance/known_failures_table.txt /known_failures_table.txt COPY conformance/known_failures_view.txt /known_failures_view.txt COPY conformance/known_failures_lazy.txt /known_failures_lazy.txt COPY conformance/known_failures_view_json.txt /known_failures_view_json.txt diff --git a/conformance/run-conformance.sh b/conformance/run-conformance.sh index d9bceeb6..79ce0036 100755 --- a/conformance/run-conformance.sh +++ b/conformance/run-conformance.sh @@ -87,3 +87,14 @@ BUFFA_VIA_VTABLE=1 run_suite vtable \ --failure_list /known_failures_view_vtable.txt \ --maximum_edition 2024 \ /usr/local/bin/buffa-conformance + +# Via-table mode: a binary whose test messages are generated with +# CodecStrategy::Table, run through the full binary, JSON and text suites like +# the std run. Messages the table cannot handle (proto2 groups, MessageSet) +# stay unrolled inside it. +run_suite table \ + conformance_test_runner \ + --failure_list /known_failures_table.txt \ + --text_format_failure_list /known_failures_text.txt \ + --maximum_edition 2024 \ + /usr/local/bin/buffa-conformance-table diff --git a/scripts/run-conformance-local.sh b/scripts/run-conformance-local.sh index 869342c6..910702e3 100755 --- a/scripts/run-conformance-local.sh +++ b/scripts/run-conformance-local.sh @@ -2,9 +2,9 @@ # Run the full protobuf conformance suite without Docker. # # Native equivalent of conformance/run-conformance.sh + conformance/Dockerfile: -# builds the std and no_std conformance binaries with the host cargo, then -# drives them through conformance_test_runner (built by -# scripts/build-conformance-tools.sh) for the same seven runs as the Docker +# builds the std, table and no_std conformance binaries with the host cargo, +# then drives them through conformance_test_runner (built by +# scripts/build-conformance-tools.sh) for the same eight runs as the Docker # image. The runner talks to the testee over stdin/stdout pipes, so no # container plumbing is needed. # @@ -31,12 +31,16 @@ if [ ! -f "${CONF}/protos/conformance.proto" ]; then exit 1 fi -echo "=== Building conformance binaries (std + no_std) ===" +echo "=== Building conformance binaries (std + table + no_std) ===" cargo build --release --manifest-path "${CONF}/Cargo.toml" +# Needs Rust 1.77 (offset_of!); buffa-build refuses an older compiler. +cargo build --release --manifest-path "${CONF}/Cargo.toml" \ + --features table --target-dir "${CONF}/target-table" cargo build --release --manifest-path "${CONF}/Cargo.toml" \ --no-default-features --target-dir "${CONF}/target-nostd" STD_BIN="${CONF}/target/release/conformance" +TABLE_BIN="${CONF}/target-table/release/conformance" NOSTD_BIN="${CONF}/target-nostd/release/conformance" run_suite() { @@ -96,4 +100,13 @@ BUFFA_VIA_VTABLE=1 run_suite vtable \ --maximum_edition 2024 \ "${STD_BIN}" -echo "All seven conformance runs completed." +# Via-table mode: a build whose test messages use CodecStrategy::Table, run +# through the full binary, JSON and text suites like the std run. +run_suite table \ + "${RUNNER}" \ + --failure_list "${CONF}/known_failures_table.txt" \ + --text_format_failure_list "${CONF}/known_failures_text.txt" \ + --maximum_edition 2024 \ + "${TABLE_BIN}" + +echo "All eight conformance runs completed." From 68d8ea42d95cf88051dc7eca9d78c0bd8b39303c Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:07:18 -0700 Subject: [PATCH 3/9] docs: describe the via-table conformance run --- CONTRIBUTING.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b3d8594..0f04708a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,36 +82,37 @@ needed — the runner just has to be built from protobuf source once: ```bash task conformance-tools-local # one-time: cmake-builds the runner into .local/bin, # populates conformance/protos/ (~10-20 min) -task conformance-local # seven runs, same failure lists as the Docker path +task conformance-local # eight runs, same failure lists as the Docker path ``` Requires cmake, a C++ toolchain, and protoc v30+ on PATH or `$PROTOC` (`task install-protoc`). Set `CONFORMANCE_OUT=` to tee per-run logs. -**Understanding the output**: The conformance runner executes seven runs -(std, no_std, via-view, via-lazy, view-json, via-reflect, via-vtable), each +**Understanding the output**: The conformance runner executes eight runs +(std, no_std, via-view, via-lazy, view-json, via-reflect, via-vtable, via-table), each producing two suites: -1. Binary + JSON suite — expects thousands of successes (~5500 std, ~5500 no_std). The via-view and via-lazy runs only handle binary→binary (~2800); the view-json, via-reflect, and via-vtable runs handle binary→JSON (and via-reflect also binary→binary). -2. Text format suite — 883 successes for std and no_std (the full suite); via-view, via-lazy, view-json, via-reflect, and via-vtable show `0 successes, 883 skipped` (those modes have no `TextFormat` path). +1. Binary + JSON suite — expects thousands of successes (~5500 std, ~5500 no_std, ~5500 table). The via-view and via-lazy runs only handle binary→binary (~2800); the view-json, via-reflect, and via-vtable runs handle binary→JSON (and via-reflect also binary→binary). +2. Text format suite — 883 successes for std, no_std and table (the full suite); via-view, via-lazy, view-json, via-reflect, and via-vtable show `0 successes, 883 skipped` (those modes have no `TextFormat` path). -So a healthy run shows **14 `CONFORMANCE SUITE PASSED` lines**. +So a healthy run shows **16 `CONFORMANCE SUITE PASSED` lines**. -The Dockerfile builds **two binaries**: one with default features (std) and one with `--no-default-features` (no_std). The std binary is reused for the view/reflect runs by setting an env var: +The Dockerfile builds **three binaries**: one with default features (std), one with `--no-default-features` (no_std) and one with `--features table` (table). The std binary is reused for the view/reflect runs by setting an env var: - **via-view** (`BUFFA_VIA_VIEW=1`) — binary input through `decode_view → to_owned_message → encode`, verifying owned/view decoder parity. - **via-lazy** (`BUFFA_VIA_LAZY=1`) — binary input through `decode_lazy → to_owned_message → encode` on the lazy view family (`lazy_views(true)`), verifying the lazy decoder (record arms, fragment merge, budget capture) against the corpus. - **view-json** (`BUFFA_VIEW_JSON=1`) — binary→JSON through `decode_view → serde_json::to_string(&view)`, verifying the generated view `Serialize` impls (and the hand-written WKT view `Serialize` impls in `buffa-types`). - **via-reflect** (`BUFFA_VIA_REFLECT=1`) — binary/JSON I/O through `DynamicMessage`'s descriptor-driven codec and reflective serde, verifying the runtime reflection codec independently of any generated type. - **via-vtable** (`BUFFA_VIA_VTABLE=1`) — binary→JSON: decode the view, walk its vtable `ReflectMessage` surface to rebuild a `DynamicMessage`, then serialize to JSON. Verifies the generated `impl ReflectMessage for FooView`. It reuses `DynamicMessage`'s JSON serializer (which passes the corpus cleanly under via-reflect), so any failure isolates a bug in the vtable `get`/`has`/`for_each_set` surface. Requires the conformance crate's `reflect` feature, so it is absent from the no_std binary. +- **via-table** (the `table` binary, no env var) — the whole std run, binary, JSON and text, against test messages generated with `CodecStrategy::Table`, so the table codec's decode, size and encode paths, oneofs, maps and the bridge to well-known-type children see the corpus. The messages the table cannot handle stay unrolled inside it: the proto2 groups and their field types, `MessageSet`, and the two `TestAllTypesProto2` messages that have extension ranges under JSON. `build.rs` prints which messages fell back, and `table_messages_use_the_table` in `conformance/src/main.rs` fails the build if the messages that must use the table stop doing so. The build needs Rust 1.77, which `buffa-build` enforces, so the default conformance build keeps the 1.75 MSRV. -**Expected failures** are listed in `conformance/known_failures.txt` (std binary+JSON), `conformance/known_failures_nostd.txt` (no_std binary+JSON), `conformance/known_failures_view.txt` (via-view), `conformance/known_failures_lazy.txt` (via-lazy), `conformance/known_failures_view_json.txt` (view-json), `conformance/known_failures_reflect.txt` (via-reflect), `conformance/known_failures_view_vtable.txt` (via-vtable), and `conformance/known_failures_text.txt` (text format — shared between std and no_std; currently empty). The text list is passed via `--text_format_failure_list` since the runner validates each suite's list independently. When a previously-failing test starts passing, remove it from the relevant file; when a new test is expected to fail, add it. +**Expected failures** are listed in `conformance/known_failures.txt` (std binary+JSON), `conformance/known_failures_nostd.txt` (no_std binary+JSON), `conformance/known_failures_view.txt` (via-view), `conformance/known_failures_lazy.txt` (via-lazy), `conformance/known_failures_view_json.txt` (view-json), `conformance/known_failures_reflect.txt` (via-reflect), `conformance/known_failures_view_vtable.txt` (via-vtable), `conformance/known_failures_table.txt` (via-table; currently empty), and `conformance/known_failures_text.txt` (text format — shared between std, no_std and table; currently empty). The text list is passed via `--text_format_failure_list` since the runner validates each suite's list independently. When a previously-failing test starts passing, remove it from the relevant file; when a new test is expected to fail, add it. **Capturing output**: To save per-run logs for analysis, mount a directory and set `CONFORMANCE_OUT`: ```bash docker run --rm -v /tmp/conf:/out -e CONFORMANCE_OUT=/out buffa-conformance -# logs: /tmp/conf/conformance-{std,nostd,view,lazy,view-json,reflect,vtable}.log +# logs: /tmp/conf/conformance-{std,nostd,view,lazy,view-json,reflect,vtable,table}.log ``` **Upgrading the protobuf version**: bump `TOOLS_IMAGE` in `Taskfile.yml` and `PROTOC_VERSION` in `.github/workflows/ci.yml`, then: From 24407939ad5e6eb7d75e6e1a6fcbdf8fdfb5b67b Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:10:32 -0700 Subject: [PATCH 4/9] conformance: fail the suite script when a run fails under a log pipe With CONFORMANCE_OUT set, each run's output goes through tee, and without pipefail the pipeline exits with tee's status, so a failing conformance_test_runner left the container exit code at zero. --- conformance/run-conformance.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/run-conformance.sh b/conformance/run-conformance.sh index 79ce0036..feda4153 100755 --- a/conformance/run-conformance.sh +++ b/conformance/run-conformance.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -e +set -eo pipefail # The conformance_test_runner always runs two suites per invocation: # 1. Binary + JSON (expect thousands of successes) From e962d1a085979b138e47c58884964d74cada517c Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:10:45 -0700 Subject: [PATCH 5/9] conformance: build the three container binaries in one target dir The std, table and no_std builds now share a target dir, so the dependencies and build scripts they have in common compile once. Each binary is copied out before the next build overwrites it. --- conformance/Dockerfile | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/conformance/Dockerfile b/conformance/Dockerfile index 461e004c..7e45f3bd 100644 --- a/conformance/Dockerfile +++ b/conformance/Dockerfile @@ -35,12 +35,18 @@ COPY buffa-types/ buffa-types/ COPY protoc-gen-buffa/ protoc-gen-buffa/ COPY conformance/ conformance/ -RUN cargo build --release --manifest-path conformance/Cargo.toml -# The table strategy needs Rust 1.77; this stage's toolchain is newer. -RUN cargo build --release --manifest-path conformance/Cargo.toml \ - --features table --target-dir conformance/target-table -RUN cargo build --release --manifest-path conformance/Cargo.toml \ - --no-default-features --target-dir conformance/target-nostd +# All three builds share one target dir, so the dependencies and build scripts +# that do not depend on the features (buffa-codegen, buffa-build, syn, ...) are +# compiled once. Each binary is copied out before the next build overwrites it. +RUN mkdir /out \ + && cargo build --release --manifest-path conformance/Cargo.toml \ + && cp conformance/target/release/conformance /out/buffa-conformance \ + && cargo build --release --manifest-path conformance/Cargo.toml \ + --features table \ + && cp conformance/target/release/conformance /out/buffa-conformance-table \ + && cargo build --release --manifest-path conformance/Cargo.toml \ + --no-default-features \ + && cp conformance/target/release/conformance /out/buffa-conformance-nostd # ── Stage 3: runtime image ──────────────────────────────────────────────── FROM ubuntu:24.04 @@ -49,12 +55,9 @@ RUN apt-get update && apt-get install -y libstdc++6 libgcc-s1 \ && rm -rf /var/lib/apt/lists/* COPY --from=tools /conformance_test_runner /usr/local/bin/conformance_test_runner -COPY --from=rust-builder \ - /workspace/conformance/target/release/conformance /usr/local/bin/buffa-conformance -COPY --from=rust-builder \ - /workspace/conformance/target-nostd/release/conformance /usr/local/bin/buffa-conformance-nostd -COPY --from=rust-builder \ - /workspace/conformance/target-table/release/conformance /usr/local/bin/buffa-conformance-table +COPY --from=rust-builder /out/buffa-conformance /usr/local/bin/buffa-conformance +COPY --from=rust-builder /out/buffa-conformance-nostd /usr/local/bin/buffa-conformance-nostd +COPY --from=rust-builder /out/buffa-conformance-table /usr/local/bin/buffa-conformance-table COPY conformance/known_failures.txt /known_failures.txt COPY conformance/known_failures_nostd.txt /known_failures_nostd.txt COPY conformance/known_failures_table.txt /known_failures_table.txt From a564cf716408d080649fbacbf7ea5209ae994ad9 Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:12:16 -0700 Subject: [PATCH 6/9] ci: say why the no_std conformance build stays last All three builds share a target dir and the no_std run uses its artifact in place, so the std and table binaries are copied out first. --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99385249..b787b64c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -471,9 +471,11 @@ jobs: docker rm "$CID" chmod +x /usr/local/bin/conformance_test_runner /usr/local/bin/protoc - # Both builds use the same target dir so build-script artifacts - # (buffa-codegen, buffa-build, syn, etc.) are compiled only once. - # The std binary is copied out before the no_std build overwrites it. + # All three builds use the same target dir so build-script artifacts + # (buffa-codegen, buffa-build, syn, etc.) are compiled only once. The std + # and table binaries are copied out before the next build overwrites + # them; the no_std build stays last because its run uses the artifact in + # place. - name: Determine host target id: host run: echo "triple=$(rustc -vV | grep '^host:' | cut -d' ' -f2)" >> "$GITHUB_OUTPUT" @@ -486,8 +488,6 @@ jobs: - name: Save std binary run: cp conformance/target/${{ steps.host.outputs.triple }}/release/conformance /tmp/buffa-conformance-std - # The table strategy needs Rust 1.77 (offset_of!); buffa-build fails the - # build on an older compiler rather than skipping the run. - name: Build conformance binary (table) run: >- cargo build --release --manifest-path conformance/Cargo.toml From 42fbe16f8ead40c599ebd8a2eed0b9edfb1be7f4 Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:12:16 -0700 Subject: [PATCH 7/9] conformance: keep the Rust 1.77 requirement on the table feature and make the pin compile-time only The requirement is stated once, on the feature. The pin becomes an unnamed const block, so it does not read as a test and needs no dead_code allow. --- conformance/Cargo.toml | 2 +- conformance/build.rs | 3 +-- conformance/src/main.rs | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/conformance/Cargo.toml b/conformance/Cargo.toml index 647ba40a..61dc4702 100644 --- a/conformance/Cargo.toml +++ b/conformance/Cargo.toml @@ -33,7 +33,7 @@ buffa-std = ["buffa/std", "buffa-types/std", "reflect"] reflect = ["buffa-types/reflect"] # Generates the test messages with `CodecStrategy::Table`, for the via-table # run. Needs Rust 1.77 (`offset_of!`); buffa-build refuses older compilers, so -# the default build stays at the 1.75 MSRV. The table runtime needs no std. +# the default build stays at the 1.75 MSRV. table = [] [dependencies] diff --git a/conformance/build.rs b/conformance/build.rs index 3af89352..c8f37528 100644 --- a/conformance/build.rs +++ b/conformance/build.rs @@ -105,8 +105,7 @@ fn main() { /// The generator configuration shared by every schema. With the crate's /// `table` feature it selects [`buffa_build::CodecStrategy::Table`], so that -/// the via-table run drives the table codec through the corpus; the feature -/// needs Rust 1.77, which `compile()` checks. +/// the via-table run drives the table codec through the corpus. fn config() -> buffa_build::Config { let config = buffa_build::Config::new(); if std::env::var_os("CARGO_FEATURE_TABLE").is_some() { diff --git a/conformance/src/main.rs b/conformance/src/main.rs index 92c44450..e80a2044 100644 --- a/conformance/src/main.rs +++ b/conformance/src/main.rs @@ -141,16 +141,17 @@ fn setup_type_registry() { // sibling messages. #[cfg(all(not(no_protos), feature = "table"))] -#[allow(dead_code)] -fn table_messages_use_the_table() { +const _: () = { fn is_table(_: &'static buffa::table::Table) {} - is_table(&proto3::__BUFFA_TABLE_TestAllTypesProto3); - is_table(&proto3::test_all_types_proto3::__BUFFA_TABLE_NestedMessage); - is_table(&proto2::__BUFFA_TABLE_TestLargeOneof); - is_table(&proto2::test_all_types_proto2::__BUFFA_TABLE_NestedMessage); - #[cfg(has_editions_protos)] - is_table(&editions_proto3::__BUFFA_TABLE_TestAllTypesProto3); -} + fn pin() { + is_table(&proto3::__BUFFA_TABLE_TestAllTypesProto3); + is_table(&proto3::test_all_types_proto3::__BUFFA_TABLE_NestedMessage); + is_table(&proto2::__BUFFA_TABLE_TestLargeOneof); + is_table(&proto2::test_all_types_proto2::__BUFFA_TABLE_NestedMessage); + #[cfg(has_editions_protos)] + is_table(&editions_proto3::__BUFFA_TABLE_TestAllTypesProto3); + } +}; // ── Via-view mode ──────────────────────────────────────────────────────── // From 3ce7f8b722f68af9a9912d217577ae6417973286 Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:12:16 -0700 Subject: [PATCH 8/9] conformance: trim the via-table script comments and run the local script from the repo root rust-toolchain.toml selects the toolchain from the working directory, so the local script now changes to the repository root before it builds. --- conformance/run-conformance.sh | 5 +---- scripts/run-conformance-local.sh | 7 ++++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/conformance/run-conformance.sh b/conformance/run-conformance.sh index feda4153..29724504 100755 --- a/conformance/run-conformance.sh +++ b/conformance/run-conformance.sh @@ -88,10 +88,7 @@ BUFFA_VIA_VTABLE=1 run_suite vtable \ --maximum_edition 2024 \ /usr/local/bin/buffa-conformance -# Via-table mode: a binary whose test messages are generated with -# CodecStrategy::Table, run through the full binary, JSON and text suites like -# the std run. Messages the table cannot handle (proto2 groups, MessageSet) -# stay unrolled inside it. +# Via-table mode: the std run against a binary generated with CodecStrategy::Table. run_suite table \ conformance_test_runner \ --failure_list /known_failures_table.txt \ diff --git a/scripts/run-conformance-local.sh b/scripts/run-conformance-local.sh index 910702e3..f5b59ecc 100755 --- a/scripts/run-conformance-local.sh +++ b/scripts/run-conformance-local.sh @@ -21,6 +21,10 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" RUNNER="${ROOT}/.local/bin/conformance_test_runner" CONF="${ROOT}/conformance" +# rust-toolchain.toml selects the toolchain from the working directory, so the +# builds below run from the repository root whatever the caller's directory is. +cd "${ROOT}" + if [ ! -x "${RUNNER}" ]; then echo "conformance_test_runner not found at ${RUNNER}." echo "Run: task conformance-tools-local" @@ -33,7 +37,6 @@ fi echo "=== Building conformance binaries (std + table + no_std) ===" cargo build --release --manifest-path "${CONF}/Cargo.toml" -# Needs Rust 1.77 (offset_of!); buffa-build refuses an older compiler. cargo build --release --manifest-path "${CONF}/Cargo.toml" \ --features table --target-dir "${CONF}/target-table" cargo build --release --manifest-path "${CONF}/Cargo.toml" \ @@ -100,8 +103,6 @@ BUFFA_VIA_VTABLE=1 run_suite vtable \ --maximum_edition 2024 \ "${STD_BIN}" -# Via-table mode: a build whose test messages use CodecStrategy::Table, run -# through the full binary, JSON and text suites like the std run. run_suite table \ "${RUNNER}" \ --failure_list "${CONF}/known_failures_table.txt" \ From 2af27cd2fa467fc682c4778ef33639900bf9a180 Mon Sep 17 00:00:00 2001 From: Iain McGinniss <309153+iainmcgin@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:12:30 -0700 Subject: [PATCH 9/9] docs: say which conformance messages the table run does not exercise The via-table paragraph names the categories that fall back to the unrolled codec, including the top-level TestAllTypesProto2, and gives the measured success count. The text failure list notes that the table run shares it. --- CONTRIBUTING.md | 5 +++-- conformance/known_failures_table.txt | 4 ++++ conformance/known_failures_text.txt | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f04708a..b8105d7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,7 +92,7 @@ Requires cmake, a C++ toolchain, and protoc v30+ on PATH or `$PROTOC` (std, no_std, via-view, via-lazy, view-json, via-reflect, via-vtable, via-table), each producing two suites: -1. Binary + JSON suite — expects thousands of successes (~5500 std, ~5500 no_std, ~5500 table). The via-view and via-lazy runs only handle binary→binary (~2800); the view-json, via-reflect, and via-vtable runs handle binary→JSON (and via-reflect also binary→binary). +1. Binary + JSON suite — expects thousands of successes (~5500 std and no_std; 5,549 table). The via-view and via-lazy runs only handle binary→binary (~2800); the view-json, via-reflect, and via-vtable runs handle binary→JSON (and via-reflect also binary→binary). 2. Text format suite — 883 successes for std, no_std and table (the full suite); via-view, via-lazy, view-json, via-reflect, and via-vtable show `0 successes, 883 skipped` (those modes have no `TextFormat` path). So a healthy run shows **16 `CONFORMANCE SUITE PASSED` lines**. @@ -104,7 +104,8 @@ The Dockerfile builds **three binaries**: one with default features (std), one w - **view-json** (`BUFFA_VIEW_JSON=1`) — binary→JSON through `decode_view → serde_json::to_string(&view)`, verifying the generated view `Serialize` impls (and the hand-written WKT view `Serialize` impls in `buffa-types`). - **via-reflect** (`BUFFA_VIA_REFLECT=1`) — binary/JSON I/O through `DynamicMessage`'s descriptor-driven codec and reflective serde, verifying the runtime reflection codec independently of any generated type. - **via-vtable** (`BUFFA_VIA_VTABLE=1`) — binary→JSON: decode the view, walk its vtable `ReflectMessage` surface to rebuild a `DynamicMessage`, then serialize to JSON. Verifies the generated `impl ReflectMessage for FooView`. It reuses `DynamicMessage`'s JSON serializer (which passes the corpus cleanly under via-reflect), so any failure isolates a bug in the vtable `get`/`has`/`for_each_set` surface. Requires the conformance crate's `reflect` feature, so it is absent from the no_std binary. -- **via-table** (the `table` binary, no env var) — the whole std run, binary, JSON and text, against test messages generated with `CodecStrategy::Table`, so the table codec's decode, size and encode paths, oneofs, maps and the bridge to well-known-type children see the corpus. The messages the table cannot handle stay unrolled inside it: the proto2 groups and their field types, `MessageSet`, and the two `TestAllTypesProto2` messages that have extension ranges under JSON. `build.rs` prints which messages fell back, and `table_messages_use_the_table` in `conformance/src/main.rs` fails the build if the messages that must use the table stop doing so. The build needs Rust 1.77, which `buffa-build` enforces, so the default conformance build keeps the 1.75 MSRV. + +**via-table** is a separate binary, built with `--features table` (no env var), that runs the whole std run — binary, JSON and text — against test messages generated with `CodecStrategy::Table`. It exercises the table codec's decode, size and encode paths, oneofs, maps and the bridge to well-known-type children on the proto3, editions and nested proto2 messages. The messages the table cannot handle run through the unrolled codec instead: messages with extension ranges under JSON, `MessageSet` messages, group types and the messages that hold them. That includes the top-level `TestAllTypesProto2`, which via-table therefore does not exercise on the table. `buffa-build` prints a `cargo:warning` naming the messages that fell back, and a compile-time check in `conformance/src/main.rs` fails the build if the messages that must use the table stop doing so. The `table` feature needs Rust 1.77; `conformance/Cargo.toml` says why. **Expected failures** are listed in `conformance/known_failures.txt` (std binary+JSON), `conformance/known_failures_nostd.txt` (no_std binary+JSON), `conformance/known_failures_view.txt` (via-view), `conformance/known_failures_lazy.txt` (via-lazy), `conformance/known_failures_view_json.txt` (view-json), `conformance/known_failures_reflect.txt` (via-reflect), `conformance/known_failures_view_vtable.txt` (via-vtable), `conformance/known_failures_table.txt` (via-table; currently empty), and `conformance/known_failures_text.txt` (text format — shared between std, no_std and table; currently empty). The text list is passed via `--text_format_failure_list` since the runner validates each suite's list independently. When a previously-failing test starts passing, remove it from the relevant file; when a new test is expected to fail, add it. diff --git a/conformance/known_failures_table.txt b/conformance/known_failures_table.txt index e58a785b..4a5c7264 100644 --- a/conformance/known_failures_table.txt +++ b/conformance/known_failures_table.txt @@ -3,3 +3,7 @@ # Tests listed here are expected to fail in the via-table run and will not # cause it to report a failure. Each line is a test name (matched by the # runner with --failure_list). Remove entries as failures are fixed. +# +# The messages the table cannot handle run through the unrolled codec in this +# run, including the top-level TestAllTypesProto2, so its tests do not exercise +# the table. diff --git a/conformance/known_failures_text.txt b/conformance/known_failures_text.txt index cc6b28c4..35a6f91d 100644 --- a/conformance/known_failures_text.txt +++ b/conformance/known_failures_text.txt @@ -4,4 +4,5 @@ # The text-format suite runs separately from the binary+JSON suite and # validates this list independently. Remove entries as failures are fixed. # -# std and no_std share this file — textproto has no std-only paths. +# All text-format runs (std, no_std, table) share this file — textproto has +# no std-only paths.