Update conformance tests with updated relaxed duplicate key handling in ProtoJSON The spec has been relaxed such that the preferred behavior is last-wins, and parse-failure if last-wins is not implementable. The conformance test is updated correspondingly to pass under either of those permissible behaviors. https://github.com/protocolbuffers/protobuf/issues/28791 PiperOrigin-RevId: 957979215
diff --git a/conformance/BUILD b/conformance/BUILD index b6bbbc4..f94a990 100644 --- a/conformance/BUILD +++ b/conformance/BUILD
@@ -145,6 +145,7 @@ "//src/google/protobuf", "//src/google/protobuf:protobuf_lite", "//src/google/protobuf/json", + "//src/google/protobuf/util:differencer", "//src/google/protobuf/util:type_resolver", "@abseil-cpp//absl/log:absl_check", "@abseil-cpp//absl/log:absl_log",
diff --git a/conformance/binary_json_conformance_suite.cc b/conformance/binary_json_conformance_suite.cc index d35b659..0b91278 100644 --- a/conformance/binary_json_conformance_suite.cc +++ b/conformance/binary_json_conformance_suite.cc
@@ -40,6 +40,7 @@ #include "google/protobuf/test_messages_proto3.pb.h" #include "google/protobuf/text_format.h" #include "google/protobuf/unknown_field_set.h" +#include "google/protobuf/util/message_differencer.h" #include "google/protobuf/util/type_resolver_util.h" #include "google/protobuf/wire_format_lite.h" @@ -846,6 +847,66 @@ template <typename MessageType> void BinaryAndJsonConformanceSuiteImpl<MessageType>:: + RunValidJsonTestOrParseFailure(const std::string& test_name, + ConformanceLevel level, + const std::string& input_json, + const std::string& equivalent_text_format) { + MessageType prototype; + ConformanceRequestSetting setting( + level, ::conformance::JSON, ::conformance::PROTOBUF, + ::conformance::JSON_TEST, prototype, test_name, input_json); + const ConformanceRequest& request = setting.GetRequest(); + ConformanceResponse response; + std::string effective_test_name = + absl::StrCat(setting.ConformanceLevelToString(level), ".", + SyntaxIdentifier(), ".JsonInput.", test_name); + + if (!suite_.RunTest(effective_test_name, request, &response)) { + return; + } + + TestStatus test; + test.set_name(effective_test_name); + if (response.result_case() == ConformanceResponse::kParseError) { + suite_.ReportSuccess(test); + } else if (response.result_case() == ConformanceResponse::kSkipped) { + suite_.ReportSkip(test, request, response); + } else { + std::unique_ptr<Message> reference_message(setting.NewTestMessage()); + ABSL_CHECK(TextFormat::ParseFromString(equivalent_text_format, + reference_message.get())) + << "Failed to parse data for test case: " << setting.GetTestName() + << ", data: " << equivalent_text_format; + std::unique_ptr<Message> test_message(setting.NewTestMessage()); + bool parsed = false; + if (response.result_case() == ConformanceResponse::kProtobufPayload) { + parsed = test_message->ParseFromString(response.protobuf_payload()); + } + if (!parsed) { + test.set_failure_message("Malformed protobuf response"); + suite_.ReportFailure(test, level, request, response); + return; + } + + util::MessageDifferencer differencer; + util::DefaultFieldComparator field_comparator; + field_comparator.set_treat_nan_as_equal(true); + differencer.set_field_comparator(&field_comparator); + std::string differences; + differencer.ReportDifferencesToString(&differences); + if (differencer.Compare(*reference_message, *test_message)) { + suite_.ReportSuccess(test); + } else { + test.set_failure_message( + "Should have failed to parse or matched expected output but did " + "not."); + suite_.ReportFailure(test, level, request, response); + } + } +} + +template <typename MessageType> +void BinaryAndJsonConformanceSuiteImpl<MessageType>:: ExpectSerializeFailureForJson(const std::string& test_name, ConformanceLevel level, const std::string& text_format) { @@ -2369,22 +2430,27 @@ ExpectParseFailureForJson( "MissingCommaMultiline", RECOMMENDED, "{\n \"optionalInt32\": 1\n \"optionalInt64\": 2\n}"); - // Duplicated field names are not allowed. - ExpectParseFailureForJson("FieldNameDuplicate", RECOMMENDED, - R"({ - "optionalNestedMessage": {"a": 1}, - "optionalNestedMessage": {} - })"); - ExpectParseFailureForJson("FieldNameDuplicateDifferentCasing1", RECOMMENDED, - R"({ - "optional_nested_message": {"a": 1}, - "optionalNestedMessage": {} - })"); - ExpectParseFailureForJson("FieldNameDuplicateDifferentCasing2", RECOMMENDED, - R"({ - "optionalNestedMessage": {"a": 1}, - "optional_nested_message": {} - })"); + // Duplicated field names have either last-wins or parse failure. + RunValidJsonTestOrParseFailure("FieldNameDuplicate", RECOMMENDED, + R"({ + "optionalNestedMessage": {"a": 1}, + "optionalNestedMessage": {} + })", + "optional_nested_message: {}"); + RunValidJsonTestOrParseFailure("FieldNameDuplicateDifferentCasing1", + RECOMMENDED, + R"({ + "optional_nested_message": {"a": 1}, + "optionalNestedMessage": {} + })", + "optional_nested_message: {}"); + RunValidJsonTestOrParseFailure("FieldNameDuplicateDifferentCasing2", + RECOMMENDED, + R"({ + "optionalNestedMessage": {"a": 1}, + "optional_nested_message": {} + })", + "optional_nested_message: {}"); // Serializers should use lowerCamelCase by default. RunValidJsonTestWithValidator("FieldNameInLowerCamelCase", REQUIRED, R"({ @@ -2883,8 +2949,12 @@ "optional_nested_message: {a: 1234}"); // Oneof fields. - ExpectParseFailureForJson("OneofFieldDuplicate", REQUIRED, - R"({"oneofUint32": 1, "oneofString": "test"})"); + RunValidJsonTestOrParseFailure("OneofFieldDuplicate", REQUIRED, + R"({"oneofUint32": 1, "oneofString": "test"})", + "oneof_string: \"test\""); + RunValidJsonTestOrParseFailure("OneofFieldDuplicate2", REQUIRED, + R"({"oneofString": "test", "oneofUint32": 1})", + "oneof_uint32: 1"); RunValidJsonTest("OneofFieldNullFirst", REQUIRED, R"({"oneofUint32": null, "oneofString": "test"})", "oneof_string: \"test\"");
diff --git a/conformance/binary_json_conformance_suite.h b/conformance/binary_json_conformance_suite.h index c178df0..81dd8d1 100644 --- a/conformance/binary_json_conformance_suite.h +++ b/conformance/binary_json_conformance_suite.h
@@ -147,6 +147,9 @@ void ExpectParseFailureForJson(const std::string& test_name, ConformanceLevel level, const std::string& input_json); + void RunValidJsonTestOrParseFailure( + const std::string& test_name, ConformanceLevel level, + const std::string& input_json, const std::string& equivalent_text_format); void ExpectSerializeFailureForJson(const std::string& test_name, ConformanceLevel level, const std::string& text_format);
diff --git a/conformance/failure_list_cpp.txt b/conformance/failure_list_cpp.txt index 8eee23e..001156b 100644 --- a/conformance/failure_list_cpp.txt +++ b/conformance/failure_list_cpp.txt
@@ -9,9 +9,9 @@ Recommended.*.JsonInput.BoolFieldDoubleQuotedFalse # Should have failed to parse, but didn't. Recommended.*.JsonInput.BoolFieldDoubleQuotedTrue # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Recommended.*.JsonInput.FieldNameNotQuoted # Should have failed to parse, but didn't. Recommended.*.JsonInput.MapFieldValueIsNull # Should have failed to parse, but didn't. Recommended.*.JsonInput.RepeatedFieldMessageElementIsNull # Should have failed to parse, but didn't.
diff --git a/conformance/failure_list_csharp.txt b/conformance/failure_list_csharp.txt index 20e87c4..adbcfd9 100644 --- a/conformance/failure_list_csharp.txt +++ b/conformance/failure_list_csharp.txt
@@ -1,6 +1,3 @@ -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. Recommended.Proto2.JsonInput.FieldNameExtension.Validator Recommended.Proto2.JsonInput.BytesFieldBase64Url.JsonOutput Recommended.Proto2.JsonInput.BytesFieldBase64Url.ProtobufOutput
diff --git a/conformance/failure_list_java.txt b/conformance/failure_list_java.txt index 325f394..f77ba72 100644 --- a/conformance/failure_list_java.txt +++ b/conformance/failure_list_java.txt
@@ -14,7 +14,6 @@ Recommended.*.JsonInput.DoubleFieldInfinityNotQuoted # Should have failed to parse, but didn't. Recommended.*.JsonInput.DoubleFieldNanNotQuoted # Should have failed to parse, but didn't. Recommended.*.JsonInput.DoubleFieldNegativeInfinityNotQuoted # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. Recommended.*.JsonInput.FieldNameExtension.Validator # Expected JSON payload but got type 1 Recommended.*.JsonInput.FieldNameNotQuoted # Should have failed to parse, but didn't. Recommended.*.JsonInput.FloatFieldInfinityNotQuoted # Should have failed to parse, but didn't.
diff --git a/conformance/failure_list_jruby.txt b/conformance/failure_list_jruby.txt index 3f09c36..3294c7f 100644 --- a/conformance/failure_list_jruby.txt +++ b/conformance/failure_list_jruby.txt
@@ -11,7 +11,6 @@ Recommended.Proto2.JsonInput.DoubleFieldInfinityNotQuoted Recommended.Proto2.JsonInput.DoubleFieldNanNotQuoted Recommended.Proto2.JsonInput.DoubleFieldNegativeInfinityNotQuoted -Recommended.Proto2.JsonInput.FieldNameDuplicate Recommended.Proto2.JsonInput.FieldNameExtension.Validator Recommended.Proto2.JsonInput.FieldNameNotQuoted Recommended.Proto2.JsonInput.FloatFieldInfinityNotQuoted @@ -39,7 +38,6 @@ Recommended.Proto3.JsonInput.DoubleFieldNanNotQuoted Recommended.Proto3.JsonInput.DoubleFieldNegativeInfinityNotQuoted Recommended.Proto3.JsonInput.FieldMaskInvalidCharacter -Recommended.Proto3.JsonInput.FieldNameDuplicate Recommended.Proto3.JsonInput.FieldNameNotQuoted Recommended.Proto3.JsonInput.FloatFieldInfinityNotQuoted Recommended.Proto3.JsonInput.FloatFieldNanNotQuoted @@ -86,7 +84,6 @@ Recommended.Editions_Proto2.JsonInput.DoubleFieldInfinityNotQuoted Recommended.Editions_Proto2.JsonInput.DoubleFieldNanNotQuoted Recommended.Editions_Proto2.JsonInput.DoubleFieldNegativeInfinityNotQuoted -Recommended.Editions_Proto2.JsonInput.FieldNameDuplicate Recommended.Editions_Proto2.JsonInput.FieldNameExtension.Validator Recommended.Editions_Proto2.JsonInput.FieldNameNotQuoted Recommended.Editions_Proto2.JsonInput.FloatFieldInfinityNotQuoted @@ -114,7 +111,6 @@ Recommended.Editions_Proto3.JsonInput.DoubleFieldNanNotQuoted Recommended.Editions_Proto3.JsonInput.DoubleFieldNegativeInfinityNotQuoted Recommended.Editions_Proto3.JsonInput.FieldMaskInvalidCharacter -Recommended.Editions_Proto3.JsonInput.FieldNameDuplicate Recommended.Editions_Proto3.JsonInput.FieldNameNotQuoted Recommended.Editions_Proto3.JsonInput.FloatFieldInfinityNotQuoted Recommended.Editions_Proto3.JsonInput.FloatFieldNanNotQuoted
diff --git a/conformance/failure_list_jruby_ffi.txt b/conformance/failure_list_jruby_ffi.txt index cd374f2..d1d4c33 100644 --- a/conformance/failure_list_jruby_ffi.txt +++ b/conformance/failure_list_jruby_ffi.txt
@@ -1,6 +1,6 @@ -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output. Required.*.JsonInput.AnyWithNoType.* # Failed to parse input or produce output. # TODO: Uncomment once conformance tests can express failures that are not expected to be fixed.
diff --git a/conformance/failure_list_php.txt b/conformance/failure_list_php.txt index c21c5bb..4bad658 100644 --- a/conformance/failure_list_php.txt +++ b/conformance/failure_list_php.txt
@@ -4,9 +4,8 @@ Recommended.*.JsonInput.BytesFieldBase64Url.JsonOutput Recommended.*.JsonInput.BytesFieldBase64Url.ProtobufOutput Recommended.*.JsonInput.FieldMaskInvalidCharacter -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Recommended.*.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput Recommended.*.ValueRejectInfNumberValue.JsonOutput # Should have failed to serialize, but didn't. Recommended.*.ValueRejectNanNumberValue.JsonOutput # Should have failed to serialize, but didn't. @@ -17,7 +16,6 @@ Required.*.JsonInput.FloatFieldTooSmall Required.*.JsonInput.Int32FieldNotInteger Required.*.JsonInput.Int64FieldNotInteger -Required.*.JsonInput.OneofFieldDuplicate Required.*.JsonInput.OneofFieldNullSecond.JsonOutput Required.*.JsonInput.OneofFieldNullSecond.ProtobufOutput Required.*.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt
diff --git a/conformance/failure_list_php_c.txt b/conformance/failure_list_php_c.txt index 78281f5..9c54491 100644 --- a/conformance/failure_list_php_c.txt +++ b/conformance/failure_list_php_c.txt
@@ -1,6 +1,6 @@ -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Recommended.Proto2.JsonInput.FieldNameExtension.Validator Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output. Required.Proto2.JsonInput.BoolFieldFalse.JsonOutput
diff --git a/conformance/failure_list_python.txt b/conformance/failure_list_python.txt index 09c3b36..31b122c 100644 --- a/conformance/failure_list_python.txt +++ b/conformance/failure_list_python.txt
@@ -1,5 +1,5 @@ -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't.
diff --git a/conformance/failure_list_python_cpp.txt b/conformance/failure_list_python_cpp.txt index dc0e034..d5bedf1 100644 --- a/conformance/failure_list_python_cpp.txt +++ b/conformance/failure_list_python_cpp.txt
@@ -7,8 +7,8 @@ # TODO: insert links to corresponding bugs tracking the issue. # Should we use GitHub issues or the Google-internal bug tracker? -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't.
diff --git a/conformance/failure_list_python_upb.txt b/conformance/failure_list_python_upb.txt index 22d445d..12574f0 100644 --- a/conformance/failure_list_python_upb.txt +++ b/conformance/failure_list_python_upb.txt
@@ -1,5 +1,5 @@ -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.DoubleFieldFalseValue # Should have failed to parse, but didn't. Required.*.JsonInput.DoubleFieldTrueValue # Should have failed to parse, but didn't. Required.*.JsonInput.EnumFieldFalseValue # Should have failed to parse, but didn't.
diff --git a/conformance/failure_list_ruby.txt b/conformance/failure_list_ruby.txt index dbd57a4..4a79ef7 100644 --- a/conformance/failure_list_ruby.txt +++ b/conformance/failure_list_ruby.txt
@@ -1,6 +1,6 @@ -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output. Required.*.JsonInput.AnyWithNoType.* # TODO: Uncomment once conformance tests can express failures that are not expected to be fixed.
diff --git a/upb/conformance/conformance_upb_failures.txt b/upb/conformance/conformance_upb_failures.txt index cc17691..d1fa6e5 100644 --- a/upb/conformance/conformance_upb_failures.txt +++ b/upb/conformance/conformance_upb_failures.txt
@@ -1,6 +1,6 @@ -Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse, but didn't. -Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse, but didn't. +Recommended.*.JsonInput.FieldNameDuplicate # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing1 # Should have failed to parse or matched expected output but did not. +Recommended.*.JsonInput.FieldNameDuplicateDifferentCasing2 # Should have failed to parse or matched expected output but did not. Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output. Required.*.JsonInput.AnyWithNoType.* # Failed to parse input or produce output. # TODO: Uncomment once conformance tests can express failures that are not expected to be fixed.