~bigbes/tarantool

tarantool-protobuf

39233ed5be83b65ef40ec2a275e85a01ab7fa284 — Eugene Blikh 3 months ago 19c8560
text: wire pb.text into conformance runner TEXT_FORMAT output

The runner short-circuited every TEXT_FORMAT request to `skipped`, even
though pb.text.encode has been encode-capable since M7. Plug it into
cmd/conformance/core.lua so protobuf/JSON input → text output exercises
the existing encoder end-to-end. Text-format input remains deferred
(pb.text is encode-only).

Text-format suite: 0 ✓ / 430 skipped / 4 expected fails →
                   2 ✓ / 426 skipped / 6 expected fails (Scalar/Message
*_Print added — unknown-field rendering still missing).
4 files changed, 108 insertions(+), 19 deletions(-)

M PLAN.md
M cmd/conformance/core.lua
M test/conformance/known_failures_text.txt
M test/conformance_test.lua
M PLAN.md => PLAN.md +12 -7
@@ 144,13 144,18 @@ fiber and bridges client ↔ handler via `fiber.channel`. All four flavors
      Watchlists at `test/conformance/known_failures.txt` (main suite)
      and `test/conformance/known_failures_text.txt` (text-format
      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.
        - Binary+JSON suite: 1478 ✓ / 1313 skipped /  15 expected fails
        - Text-format suite:    2 ✓ /  426 skipped /   6 expected fails
      JSON output runs end-to-end; remaining expected fails are
      Recommended-only edge cases (FieldMask round-trip,
      duplicate-field-name rejection, null-in-collection rejection,
      unknown-enum-name rejection, NullValue oneof validator).
      Text-format output is wired through `pb.text.encode`; the six
      expected fails are all unknown-field cases (Group/Repeated parse
      rejection, *_Print not rendering captured unknowns). Text-format
      *input* parsing is still deferred. 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/`

M cmd/conformance/core.lua => cmd/conformance/core.lua +14 -4
@@ 57,8 57,11 @@ local function dispatch(req)
            return {parse_error = 'json decode failed: ' .. tostring(decoded)}
        end
        msg = decoded
    elseif req.jspb_payload ~= nil or req.text_payload ~= nil then
        return {skipped = 'jspb/text input not supported'}
    elseif req.jspb_payload ~= nil then
        return {skipped = 'jspb input not supported'}
    elseif req.text_payload ~= nil then
        -- pb.text is encode-only; text-format input parsing is deferred.
        return {skipped = 'text-format input not supported'}
    else
        return {runtime_error = 'no payload set in ConformanceRequest'}
    end


@@ 92,8 95,15 @@ local function dispatch(req)
                tostring(jbytes)}
        end
        return {json_payload = jbytes}
    elseif out_fmt == JSPB or out_fmt == TEXT_FORMAT then
        return {skipped = 'jspb/text output not supported'}
    elseif out_fmt == TEXT_FORMAT then
        local ok, tbytes = pcall(pb.text.encode, desc, msg)
        if not ok then
            return {serialize_error = 'text encode failed: ' ..
                tostring(tbytes)}
        end
        return {text_payload = tbytes}
    elseif out_fmt == JSPB then
        return {skipped = 'jspb output not supported'}
    else
        return {runtime_error = 'unknown requested_output_format: ' ..
            tostring(out_fmt)}

M test/conformance/known_failures_text.txt => test/conformance/known_failures_text.txt +20 -5
@@ 1,11 1,26 @@
# conformance_test_runner --text_format_failure_list
#
# Text-format conformance is intentionally deferred — pb.text only does
# encoding from a Lua table, not text-protobuf input parsing nor protobuf-
# input→text-output, so the harness's text-format suite has nothing to
# exercise yet. These tests fail because we can't produce text-format
# output from the protobuf payloads they ship.
# Text-format OUTPUT is now wired through cmd/conformance/core.lua to
# pb.text.encode (protobuf/JSON input → text output). Input parsing is
# still deferred — pb.text only encodes — so any test whose payload is
# text_payload still returns `skipped`.
#
# Remaining expected failures all stem from unknown-field handling:
#
#   * Group/Repeated *_Drop: our decoder rejects payloads carrying
#     wire types 3/4 (proto2 groups) and length-mismatched repeated
#     unknown bytes, so we never reach the encoder. Expected output is
#     an empty text body, which we'd produce trivially if the decoder
#     accepted the input. Fixing requires skip_field to tolerate groups.
#
#   * *_Print: with `print_unknown_fields: true` the harness expects the
#     text output to include the original unknown fields. We capture
#     unknown bytes during decode but pb.text doesn't render them, so
#     output comes back empty. Fixing requires the text encoder to walk
#     the captured unknown set.
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput
Recommended.Proto3.ProtobufInput.GroupUnknownFields_Print.TextFormatOutput
Recommended.Proto3.ProtobufInput.MessageUnknownFields_Print.TextFormatOutput
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput
Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Print.TextFormatOutput
Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Print.TextFormatOutput

M test/conformance_test.lua => test/conformance_test.lua +62 -3
@@ 128,14 128,73 @@ core_g.test_unsupported_message_type_skipped = function()
    t.assert_str_contains(resp.skipped or '', 'unsupported message type')
end

core_g.test_text_format_skipped = function()
    local input = proto3.TestAllTypesProto3_encode({optional_int32 = 1})
core_g.test_pb_to_text = function()
    -- protobuf input + TEXT_FORMAT output runs pb.text.encode on the
    -- decoded Lua table. Result should look like `protoc --decode` output.
    local input = proto3.TestAllTypesProto3_encode({
        optional_int32  = 42,
        optional_string = 'hello',
        repeated_int32  = {1, 2, 3},
    })
    local resp = decode_resp(core.handle_request(encode_req({
        protobuf_payload = input,
        requested_output_format = TEXT,
        message_type = PROTO3_NAME,
    })))
    t.assert_str_contains(resp.skipped or '', 'jspb/text')
    t.assert_not(resp.parse_error, resp.parse_error)
    t.assert_not(resp.serialize_error, resp.serialize_error)
    t.assert_not(resp.skipped, resp.skipped)
    t.assert_str_contains(resp.text_payload, 'optional_int32: 42')
    t.assert_str_contains(resp.text_payload, 'optional_string: "hello"')
    -- Repeated fields emit once per element, snake_case, mainline form.
    t.assert_str_contains(resp.text_payload, 'repeated_int32: 1')
    t.assert_str_contains(resp.text_payload, 'repeated_int32: 2')
    t.assert_str_contains(resp.text_payload, 'repeated_int32: 3')
end

core_g.test_json_to_text = function()
    local resp = decode_resp(core.handle_request(encode_req({
        json_payload = [[{"optionalInt32": 7, "optionalString": "abc"}]],
        requested_output_format = TEXT,
        message_type = PROTO3_NAME,
    })))
    t.assert_not(resp.parse_error, resp.parse_error)
    t.assert_not(resp.serialize_error, resp.serialize_error)
    t.assert_str_contains(resp.text_payload, 'optional_int32: 7')
    t.assert_str_contains(resp.text_payload, 'optional_string: "abc"')
end

core_g.test_empty_message_to_text = function()
    -- Empty proto3 message has no fields to print; text encoder produces
    -- the empty string. The result field is still text_payload (empty
    -- string), not skipped/runtime_error.
    local resp = decode_resp(core.handle_request(encode_req({
        protobuf_payload = '',
        requested_output_format = TEXT,
        message_type = PROTO3_NAME,
    })))
    t.assert_not(resp.parse_error, resp.parse_error)
    t.assert_not(resp.serialize_error, resp.serialize_error)
    t.assert_equals(resp.text_payload, '')
end

core_g.test_text_input_skipped = function()
    -- Text-format input parsing is still deferred (pb.text is encode-only).
    local resp = decode_resp(core.handle_request(encode_req({
        text_payload = 'optional_int32: 1\n',
        requested_output_format = PROTOBUF,
        message_type = PROTO3_NAME,
    })))
    t.assert_str_contains(resp.skipped or '', 'text-format input')
end

core_g.test_jspb_output_skipped = function()
    local resp = decode_resp(core.handle_request(encode_req({
        protobuf_payload = '',
        requested_output_format = conformance.WireFormat.JSPB,
        message_type = PROTO3_NAME,
    })))
    t.assert_str_contains(resp.skipped or '', 'jspb')
end

core_g.test_empty_payload_decodes_as_empty_message = function()