// Protocol Buffers - Google's data interchange format // Copyright 2023 Google LLC. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd #ifndef UPB_UTIL_DEF_TO_PROTO_TEST_H_ #define UPB_UTIL_DEF_TO_PROTO_TEST_H_ #include #include "google/protobuf/descriptor.pb.h" #include "google/protobuf/descriptor.upb.h" #include #include #include "google/protobuf/descriptor.h" #include "google/protobuf/dynamic_message.h" #include "google/protobuf/util/field_comparator.h" #include "upb/base/status.hpp" #include "upb/mem/arena.hpp" #include "upb/reflection/def.hpp" #include "upb/util/def_to_proto.h" namespace upb_test { // A gtest matcher that verifies that a proto is equal to `proto`. Both `proto` // and `arg` must be messages of type `msgdef_func` (a .upbdefs.h function that // loads a known msgdef into the given defpool). MATCHER_P(EqualsProtoTreatNansAsEqual, proto, negation ? "are not equal" : "are equal") { upb::DefPool defpool; google::protobuf::DescriptorPool pool; google::protobuf::DynamicMessageFactory factory; std::string differences; google::protobuf::util::DefaultFieldComparator comparator; comparator.set_treat_nan_as_equal(true); google::protobuf::util::MessageDifferencer differencer; differencer.set_field_comparator(&comparator); differencer.ReportDifferencesToString(&differences); bool eq = differencer.Compare(proto, arg); if (!eq) { *result_listener << differences; } return eq; } class NullErrorCollector : public google::protobuf::DescriptorPool::ErrorCollector { void AddError(const std::string& filename, const std::string& element_name, const google::protobuf::Message* descriptor, ErrorLocation location, const std::string& message) override {} void RecordWarning(absl::string_view filename, absl::string_view element_name, const google::protobuf::Message* descriptor, ErrorLocation location, absl::string_view message) override {} }; static void AddFile(google::protobuf::FileDescriptorProto& file, upb::DefPool* pool, google::protobuf::DescriptorPool* desc_pool) { NullErrorCollector collector; const google::protobuf::FileDescriptor* file_desc = desc_pool->BuildFileCollectingErrors(file, &collector); if (file_desc != nullptr) { // The file descriptor was valid according to proto2. google::protobuf::FileDescriptorProto normalized_file; file_desc->CopyTo(&normalized_file); std::string serialized; normalized_file.SerializeToString(&serialized); upb::Arena arena; upb::Status status; google_protobuf_FileDescriptorProto* proto = google_protobuf_FileDescriptorProto_parse( serialized.data(), serialized.size(), arena.ptr()); ASSERT_NE(proto, nullptr); upb::FileDefPtr file_def = pool->AddFile(proto, &status); // Ideally we could assert that file_def is present here. After all, any // descriptor accepted by C++ should be by definition valid. However C++ // performs some of its validation at the .proto file parser level instead // of when validating descriptors. As as result, C++ will accept some // unreasonable descriptors like: // file { name: "" package: "0" } // // There is no .proto file that will produce this descriptor, but // BuildFile() accepts it. We should probably clean up these cases so C++ // will reject them too. if (!file_def) return; ASSERT_TRUE(status.ok()) << status.error_message(); google_protobuf_FileDescriptorProto* upb_proto = upb_FileDef_ToProto(file_def.ptr(), arena.ptr()); size_t size; const char* buf = google_protobuf_FileDescriptorProto_serialize(upb_proto, arena.ptr(), &size); google::protobuf::FileDescriptorProto google_proto; bool ok = google_proto.ParseFromArray(buf, size); ASSERT_TRUE(ok); EXPECT_THAT(google_proto, EqualsProtoTreatNansAsEqual(normalized_file)); } else { // This file was invalid according to proto2. When we parse it with upb, // it may or may not be accepted, since upb does not perform as much // validation as proto2. However it must not crash. std::string serialized; file.SerializeToString(&serialized); upb::Arena arena; upb::Status status; google_protobuf_FileDescriptorProto* proto = google_protobuf_FileDescriptorProto_parse( serialized.data(), serialized.size(), arena.ptr()); ASSERT_NE(proto, nullptr); pool->AddFile(proto, &status); } } inline void RoundTripDescriptor(const google::protobuf::FileDescriptorSet& set) { upb::DefPool defpool; google::protobuf::DescriptorPool desc_pool; desc_pool.EnforceWeakDependencies(true); for (const auto& file : set.file()) { google::protobuf::FileDescriptorProto mutable_file(file); AddFile(mutable_file, &defpool, &desc_pool); } } } // namespace upb_test #endif // UPB_UTIL_DEF_TO_PROTO_TEST_H_