// 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 <string>

#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor.upb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#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;
  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 RecordError(absl::string_view filename, absl::string_view element_name,
                   const google::protobuf::Message* descriptor, ErrorLocation location,
                   absl::string_view 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;
    (void)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.ParseFromString(absl::string_view(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;
    (void)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_
