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
23 changes: 20 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -486,6 +488,14 @@ jobs:
- name: Save std binary
run: cp conformance/target/${{ steps.host.outputs.triple }}/release/conformance /tmp/buffa-conformance-std

- 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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
20 changes: 11 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,36 +82,38 @@ 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=<dir>` 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 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 **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.

**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.
**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.

**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:
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions conformance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
table = []

[dependencies]
buffa = { path = "../buffa", default-features = false, features = ["json", "text"] }
Expand Down
23 changes: 16 additions & 7 deletions conformance/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +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
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
Expand All @@ -46,12 +55,12 @@ 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 /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
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
Expand Down
22 changes: 17 additions & 5 deletions conformance/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -103,6 +103,18 @@ 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.
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) {
Expand Down
9 changes: 9 additions & 0 deletions conformance/known_failures_table.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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.
#
# 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.
3 changes: 2 additions & 1 deletion conformance/known_failures_text.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
10 changes: 9 additions & 1 deletion conformance/run-conformance.sh
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -87,3 +87,11 @@ 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: the std run against a binary generated with CodecStrategy::Table.
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
24 changes: 24 additions & 0 deletions conformance/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,30 @@ 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"))]
const _: () = {
fn is_table<M: buffa::Message>(_: &'static buffa::table::Table<M>) {}
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 ────────────────────────────────────────────────────────
//
// When `BUFFA_VIA_VIEW=1`, binary input is routed through
Expand Down
Loading
Loading