diff --git a/PLAN.md b/PLAN.md index d78bfaa8ee88b6dcf7ca8577a416c5cbac0800a5..d0e9796208ae16d31bde627e1a4890291fb0a276 100644 --- a/PLAN.md +++ b/PLAN.md @@ -143,13 +143,16 @@ `just conformance` regenerates Lua then runs the harness against `cmd/conformance-runner.lua` with the repo mounted as a volume. Watchlists at `test/conformance/known_failures.txt` (main suite) and `test/conformance/known_failures_text.txt` (text-format - suite). Current baseline (2026-05-15): - - Binary+JSON suite: 803 ✓ / 1864 skipped / 139 expected fails - - Text-format suite: 0 ✓ / 430 skipped / 4 expected fails - JSON output is gated behind `PB_CONFORMANCE_SKIP_JSON=1` (set in - the container ENV) until the JSON codec round-trips cleanly with - jsoncpp's strict parser. CI wire-up pending — the image build is - the long pole (~10–15 min on a clean cache). + suite). Current baseline (2026-05-16): + - Binary+JSON suite: 1389 ✓ / 1313 skipped / 79 expected fails + - Text-format suite: 0 ✓ / 430 skipped / 4 expected fails + JSON output now runs end-to-end through the harness. The remaining + expected fails are canonical-form edge cases (Duration/Timestamp + formatting, double precision, NaN handling, JSON-input rejection + rules). The `PB_CONFORMANCE_SKIP_JSON=1` env var still short- + circuits JSON output if a new encoder bug crashes jsoncpp. + CI wire-up pending — the image build is the long pole (~10–15 min + on a clean cache). - [x] Cross-impl interop: 18-fixture corpus in `test/interop/fixtures/` produced by mainline `protoc --encode`; tests assert byte-for-byte equality. diff --git a/README.md b/README.md index dce10fd058cb8659d0ec46aaf176a8982b145555..df5c1bd68b4b8c1ea85a7f0e0a11d4f26bf33a35 100644 --- a/README.md +++ b/README.md @@ -141,13 +141,16 @@ Current baseline (2026-05-15, protobuf v34.1): | Suite | Successes | Skipped | Expected failures | |-------|-----------|---------|-------------------| -| Binary + JSON | 803 | 1864 | 139 | -| Text-format | 0 | 430 | 4 | +| Binary + JSON | 1389 | 1313 | 79 | +| Text-format | 0 | 430 | 4 | -JSON output is gated behind `PB_CONFORMANCE_SKIP_JSON=1` (set in the -container `ENV`) until the JSON codec is hardened — the harness's -strict jsoncpp comparator crashes on a subset of our half-finished -output. Host-side `make test` still exercises the full JSON path. +JSON output runs end-to-end through the conformance harness. The +remaining failures are mostly canonical-form edge cases — Duration / +Timestamp serialization rules, double-precision formatting, NaN +canonicalization, JSON-input rejection rules — listed in +`test/conformance/known_failures.txt`. Set `PB_CONFORMANCE_SKIP_JSON=1` +to short-circuit JSON output requests with `skipped` if a new encoder +bug starts crashing jsoncpp again. The runner currently supports `protobuf_test_messages.proto3.TestAllTypesProto3` in both protobuf and JSON formats; proto2 / editions / JSPB / text-format diff --git a/cmd/conformance/core.lua b/cmd/conformance/core.lua index 217b4f38310716e948f9b97ea7428f52facc9f11..f41be45fcb82132494f7177b21f148eb36066a2d 100644 --- a/cmd/conformance/core.lua +++ b/cmd/conformance/core.lua @@ -80,7 +80,9 @@ -- PB_CONFORMANCE_SKIP_JSON=1 in the container short-circuits to -- `skipped` so the suite completes and PROTOBUF coverage stays -- measurable; the host tests run without the flag and still -- exercise the JSON output path end-to-end. - if os.getenv('PB_CONFORMANCE_SKIP_JSON') then + -- Check explicitly for "1" so docker `-e PB_CONFORMANCE_SKIP_JSON=` + -- (empty string) can disable the gate without rebuilding the image. + if os.getenv('PB_CONFORMANCE_SKIP_JSON') == '1' then return {skipped = 'JSON output deferred (see test/conformance/known_failures.txt)'} end diff --git a/docker/conformance.Dockerfile b/docker/conformance.Dockerfile index 751d0cfd7c01269c5d5b0a36fcc162eaa9a75905..166de04e349e57ca57954a5efd808df4f61e0b96 100644 --- a/docker/conformance.Dockerfile +++ b/docker/conformance.Dockerfile @@ -69,8 +69,11 @@ RUN ldconfig # All scripts expect to find generated modules + the runtime under /work. WORKDIR /work -ENV LUA_PATH="./runtime/?/init.lua;./runtime/?.lua;./examples/expected/?.lua;./examples/expected/?/init.lua;./cmd/?.lua;./cmd/?/init.lua;;" \ - PB_CONFORMANCE_SKIP_JSON=1 +ENV LUA_PATH="./runtime/?/init.lua;./runtime/?.lua;./examples/expected/?.lua;./examples/expected/?/init.lua;./cmd/?.lua;./cmd/?/init.lua;;" +# Set PB_CONFORMANCE_SKIP_JSON=1 to short-circuit JSON output requests with +# `skipped` — useful when our JSON encoder hits a jsoncpp-crashing edge case +# and the suite would abort. Off by default now that the encoder no longer +# emits empty messages as `[]`. # Default entrypoint exercises the Google suite against our runner. # conformance_test_runner uses execv (not execvp), so the testee binary diff --git a/runtime/pb/json.lua b/runtime/pb/json.lua index 13016175ac5d82f815b8b147ce4e41bff1e66fc9..7f9618eb884624bedd27c48edd7b1569ba72e942 100644 --- a/runtime/pb/json.lua +++ b/runtime/pb/json.lua @@ -50,8 +50,15 @@ end return tostring(v) end --- Convert a JSON string/number back to int64 or uint64 cdata. +-- Convert a JSON string/number/cdata back to int64 or uint64 cdata. +-- Tarantool's json.decode parses out-of-double-range integer literals as +-- int64_t / uint64_t cdata, so that branch is hit even when the proto3 +-- JSON spec says 64-bit integers should be quoted. local function string_to_int64(v, is_unsigned) + if type(v) == 'cdata' then + if is_unsigned then return ffi.cast('uint64_t', v) end + return ffi.cast('int64_t', v) + end if type(v) == 'number' then v = string.format('%.0f', v) end if type(v) ~= 'string' then error('expected JSON string or number for int64', 0) end local cdata = tonumber64(v) @@ -284,7 +291,9 @@ if t == nil then return nil end local override = encode_wkt(desc, t) if override ~= nil then return override end - local out = {} + -- A proto3 message with no set fields must serialize as a JSON object + -- `{}`, not the empty-table default `[]`. Mark the table as a map. + local out = setmetatable({}, {__serialize = 'map'}) for _, f in ipairs(desc.fields) do local v = t[f.name] if v ~= nil then diff --git a/test/conformance/known_failures.txt b/test/conformance/known_failures.txt index a7dc8ab6c68a6aa12e3ac72e630dfd5943b8c248..950f0aaf1b30ef7d9fb095e8fb534a46699bcff1 100644 --- a/test/conformance/known_failures.txt +++ b/test/conformance/known_failures.txt @@ -18,17 +18,82 @@ # * Required.{TimestampProtoInputTooLarge,…}.JsonOutput — synthetic # because of the global JSON-output skip. # # Re-generate this file after fixes via `just conformance-refresh-failures`. -Required.Proto3.JsonInput.AllFieldAcceptNull.JsonOutput Required.Proto3.JsonInput.AnyNested.JsonOutput -Required.Proto3.JsonInput.AnyWithInt32ValueWrapper.JsonOutput Required.Proto3.JsonInput.AnyWithStruct.JsonOutput -Required.Proto3.JsonInput.AnyWithValueForInteger.JsonOutput Required.Proto3.JsonInput.AnyWithValueForJsonObject.JsonOutput -Required.Proto3.JsonInput.Int64FieldMaxValueNotQuoted.JsonOutput -Required.Proto3.JsonInput.Int64FieldMaxValueNotQuoted.ProtobufOutput -Required.Proto3.JsonInput.Int64FieldMinValueNotQuoted.JsonOutput -Required.Proto3.JsonInput.Int64FieldMinValueNotQuoted.ProtobufOutput -Required.Proto3.JsonInput.Uint64FieldMaxValueNotQuoted.JsonOutput -Required.Proto3.JsonInput.Uint64FieldMaxValueNotQuoted.ProtobufOutput -Required.Proto3.JsonInput.WrapperTypesWithNullValue.JsonOutput Required.Proto3.TimestampProtoNegativeNanos.JsonOutput +Required.Proto3.DurationProtoInputTooLarge.JsonOutput +Required.Proto3.DurationProtoInputTooSmall.JsonOutput +Required.Proto3.DurationProtoNanosTooLarge.JsonOutput +Required.Proto3.DurationProtoNanosTooSmall.JsonOutput +Required.Proto3.DurationProtoNanosWrongSign.JsonOutput +Required.Proto3.DurationProtoNanosWrongSignNegativeSecs.JsonOutput +Required.Proto3.JsonInput.AnyWithNoType.JsonOutput +Required.Proto3.JsonInput.AnyWktRepresentationWithBadType +Required.Proto3.JsonInput.AnyWktRepresentationWithEmptyTypeAndValue +Required.Proto3.JsonInput.DoubleFieldEmptyString +Required.Proto3.JsonInput.DoubleFieldStringValueNonNumeric +Required.Proto3.JsonInput.DoubleFieldStringValuePartiallyNumeric +Required.Proto3.JsonInput.DoubleFieldTooLarge +Required.Proto3.JsonInput.DoubleFieldTooSmall +Required.Proto3.JsonInput.DurationJsonInputTooLarge +Required.Proto3.JsonInput.DurationJsonInputTooSmall +Required.Proto3.JsonInput.DurationMinValue.JsonOutput +Required.Proto3.JsonInput.DurationMissingS +Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput +Required.Proto3.JsonInput.DurationRepeatedValue.JsonOutput +Required.Proto3.JsonInput.FloatFieldEmptyString +Required.Proto3.JsonInput.FloatFieldStringValueNonNumeric +Required.Proto3.JsonInput.FloatFieldStringValuePartiallyNumeric +Required.Proto3.JsonInput.FloatFieldStringValuePartiallyNumericComma +Required.Proto3.JsonInput.FloatFieldStringValuePartiallyNumericSpace +Required.Proto3.JsonInput.FloatFieldStringValuePartiallyNumericUnicode +Required.Proto3.JsonInput.FloatFieldTooLarge +Required.Proto3.JsonInput.FloatFieldTooSmall +Required.Proto3.JsonInput.Int32FieldEmptyString +Required.Proto3.JsonInput.Int32FieldLeadingSpace +Required.Proto3.JsonInput.Int32FieldNotInteger +Required.Proto3.JsonInput.Int32FieldNotNumber +Required.Proto3.JsonInput.Int32FieldStringValueNonNumeric +Required.Proto3.JsonInput.Int32FieldStringValuePartiallyNumeric +Required.Proto3.JsonInput.Int32FieldStringValuePartiallyNumericComma +Required.Proto3.JsonInput.Int32FieldStringValuePartiallyNumericSpace +Required.Proto3.JsonInput.Int32FieldStringValuePartiallyNumericUnicode +Required.Proto3.JsonInput.Int32FieldTooLarge +Required.Proto3.JsonInput.Int32FieldTooSmall +Required.Proto3.JsonInput.Int32FieldTrailingSpace +Required.Proto3.JsonInput.Int64FieldTooLarge +Required.Proto3.JsonInput.OneofFieldDuplicate +Required.Proto3.JsonInput.RejectTopLevelNull +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotBool +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotMessage +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotString +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotBool +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt +Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotMessage +Required.Proto3.JsonInput.SingleValueForRepeatedFieldMessage +Required.Proto3.JsonInput.StringFieldNotAString +Required.Proto3.JsonInput.TimestampJsonInputLowercaseT +Required.Proto3.JsonInput.TimestampJsonInputLowercaseZ +Required.Proto3.JsonInput.TimestampJsonInputMissingT +Required.Proto3.JsonInput.TimestampJsonInputMissingZ +Required.Proto3.JsonInput.TimestampJsonInputTooLarge +Required.Proto3.JsonInput.TimestampJsonInputTooSmall +Required.Proto3.JsonInput.TimestampWithMissingColonInOffset +Required.Proto3.JsonInput.TimestampWithNegativeOffset.JsonOutput +Required.Proto3.JsonInput.TimestampWithPositiveOffset.JsonOutput +Required.Proto3.JsonInput.Uint32FieldEmptyString +Required.Proto3.JsonInput.Uint32FieldNotInteger +Required.Proto3.JsonInput.Uint32FieldNotNumber +Required.Proto3.JsonInput.Uint32FieldTooLarge +Required.Proto3.JsonInput.ValueAcceptNull.JsonOutput +Required.Proto3.ProtobufInput.DoubleFieldNormalizeSignalingNan.JsonOutput +Required.Proto3.ProtobufInput.FloatFieldNormalizeSignalingNan.JsonOutput +Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.DOUBLE.JsonOutput +Required.Proto3.ProtobufInput.ValidDataRepeated.DOUBLE.PackedInput.JsonOutput +Required.Proto3.ProtobufInput.ValidDataRepeated.DOUBLE.UnpackedInput.JsonOutput +Required.Proto3.ProtobufInput.ValidDataScalar.DOUBLE[2].JsonOutput +Required.Proto3.ProtobufInput.ValidDataScalar.DOUBLE[3].JsonOutput +Required.Proto3.TimestampProtoInputTooLarge.JsonOutput +Required.Proto3.TimestampProtoInputTooSmall.JsonOutput +Required.Proto3.TimestampProtoNanoTooLarge.JsonOutput