/*
 * Copyright (C) 2026 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/util/descriptors.h"

#include <cstdint>
#include <vector>

#include "perfetto/protozero/scattered_heap_buffer.h"
#include "protos/perfetto/common/descriptor.pbzero.h"
#include "test/gtest_and_gmock.h"

namespace perfetto::trace_processor {
namespace {

using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
using FileDescriptorSet = protos::pbzero::FileDescriptorSet;

// Builds a descriptor set with two extension messages registered at the same
// extendee tag. Each extension message contains a *nested* message-typed
// field, so verifying their equivalence forces DescriptorsStructurallyEqual
// to perform a deep comparison using its worklist. `deep_difference` controls
// whether the difference (when present) lives one level deep inside the nested
// message rather than at the top level.
std::vector<uint8_t> BuildDescriptorSet(bool structurally_equal_extensions,
                                        bool deep_difference) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  // Inner messages that the extension messages point at. InnerA and InnerB
  // are structurally identical unless `deep_difference` is set, in which case
  // InnerB's field differs one level below the extension message.
  auto* inner_a = file->add_message_type();
  inner_a->set_name("InnerA");
  auto* inner_a_field = inner_a->add_field();
  inner_a_field->set_name("leaf");
  inner_a_field->set_number(1);
  inner_a_field->set_type(FieldDescriptorProto::TYPE_INT32);
  inner_a_field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* inner_b = file->add_message_type();
  inner_b->set_name("InnerB");
  auto* inner_b_field = inner_b->add_field();
  inner_b_field->set_name("leaf");
  inner_b_field->set_number(1);
  if (structurally_equal_extensions || !deep_difference) {
    inner_b_field->set_type(FieldDescriptorProto::TYPE_INT32);
  } else {
    // Difference one level deep, inside the nested message.
    inner_b_field->set_type(FieldDescriptorProto::TYPE_STRING);
  }
  inner_b_field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  // ExtMsgA: has a message-typed field pointing at InnerA.
  auto* ext_msg_a = file->add_message_type();
  ext_msg_a->set_name("ExtMsgA");
  auto* field_a = ext_msg_a->add_field();
  field_a->set_name("nested");
  field_a->set_number(1);
  field_a->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  field_a->set_type_name(".test.InnerA");
  field_a->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  // ExtMsgB: same shape, points at InnerB. When a non-deep difference is
  // requested, the top-level field type itself differs instead.
  auto* ext_msg_b = file->add_message_type();
  ext_msg_b->set_name("ExtMsgB");
  auto* field_b = ext_msg_b->add_field();
  field_b->set_name("nested");
  field_b->set_number(1);
  if (structurally_equal_extensions || deep_difference) {
    field_b->set_type(FieldDescriptorProto::TYPE_MESSAGE);
    field_b->set_type_name(".test.InnerB");
  } else {
    // Shallow (top-level) difference.
    field_b->set_type(FieldDescriptorProto::TYPE_STRING);
  }
  field_b->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext1->set_type_name(".test.ExtMsgA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext2->set_type_name(".test.ExtMsgB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

// Builds a descriptor set with two extension messages that are each
// self-referential (a field of the message points back at the message
// itself), registered at the same extendee tag. This forces the cycle guard
// in DescriptorsStructurallyEqual to trigger; without it the comparison
// would not terminate.
std::vector<uint8_t> BuildSelfReferentialDescriptorSet() {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* rec_a = file->add_message_type();
  rec_a->set_name("RecA");
  auto* rec_a_self = rec_a->add_field();
  rec_a_self->set_name("self");
  rec_a_self->set_number(1);
  rec_a_self->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  rec_a_self->set_type_name(".test.RecA");
  rec_a_self->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* rec_b = file->add_message_type();
  rec_b->set_name("RecB");
  auto* rec_b_self = rec_b->add_field();
  rec_b_self->set_name("self");
  rec_b_self->set_number(1);
  rec_b_self->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  rec_b_self->set_type_name(".test.RecB");
  rec_b_self->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext1->set_type_name(".test.RecA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext2->set_type_name(".test.RecB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

// Builds a descriptor set that re-declares an extension at the same tag in a
// way controlled by the arguments, to exercise the non-deferred branches of
// CheckExtensionField:
//  - `second_is_scalar_mismatch`: the second declaration uses a different
//    fundamental type (int32 vs the first's message), which must be rejected
//    immediately.
//  - otherwise the second declaration is byte-identical to the first
//    (same tag, same type, same type name): a compatible re-declaration that
//    must be accepted without deferring a structural check.
std::vector<uint8_t> BuildReDeclDescriptorSet(bool second_is_scalar_mismatch) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* ext_msg = file->add_message_type();
  ext_msg->set_name("ExtMsg");
  auto* ext_msg_field = ext_msg->add_field();
  ext_msg_field->set_name("val");
  ext_msg_field->set_number(1);
  ext_msg_field->set_type(FieldDescriptorProto::TYPE_INT32);
  ext_msg_field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext1->set_type_name(".test.ExtMsg");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_extendee(".test.BaseMessage");
  if (second_is_scalar_mismatch) {
    // Different fundamental type at the same tag: rejected immediately by
    // CheckExtensionField, before any deferred structural check.
    ext2->set_type(FieldDescriptorProto::TYPE_INT32);
  } else {
    // Identical re-declaration: same tag, same type, same type name. Must
    // be accepted with no deferral.
    ext2->set_type(FieldDescriptorProto::TYPE_MESSAGE);
    ext2->set_type_name(".test.ExtMsg");
  }

  return fds.SerializeAsArray();
}

// Builds a descriptor set with two enum-typed extensions registered at the
// same tag, pointing at two differently-named enums. When `enums_equal` is
// true the two enums have identical values (a pure rename); otherwise one
// value's number differs.
std::vector<uint8_t> BuildEnumExtDescriptorSet(bool enums_equal) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* enum_a = file->add_enum_type();
  enum_a->set_name("EnumA");
  auto* a0 = enum_a->add_value();
  a0->set_name("UNKNOWN");
  a0->set_number(0);
  auto* a1 = enum_a->add_value();
  a1->set_name("RUNNING");
  a1->set_number(1);

  auto* enum_b = file->add_enum_type();
  enum_b->set_name("EnumB");
  auto* b0 = enum_b->add_value();
  b0->set_name("UNKNOWN");
  b0->set_number(0);
  auto* b1 = enum_b->add_value();
  b1->set_name(enums_equal ? "RUNNING" : "STOPPED");
  b1->set_number(1);

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_ENUM);
  ext1->set_type_name(".test.EnumA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_ENUM);
  ext2->set_type_name(".test.EnumB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

// Builds a descriptor set with two enum-typed extensions at the same tag.
// Both enums share value 0 -> "UNKNOWN". `extra_on_first` adds value
// 2 -> "EXTRA" to the first (existing) enum only; otherwise it is added to
// the second (candidate) enum only. If `conflict_shared` is true, the shared
// value 0 is given a different name on the second enum, which must be
// rejected regardless of the extra-value placement.
std::vector<uint8_t> BuildEnumSupersetExtDescriptorSet(bool extra_on_first,
                                                       bool conflict_shared) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* enum_a = file->add_enum_type();
  enum_a->set_name("EnumA");
  auto* a0 = enum_a->add_value();
  a0->set_name("UNKNOWN");
  a0->set_number(0);
  if (extra_on_first) {
    auto* a2 = enum_a->add_value();
    a2->set_name("EXTRA");
    a2->set_number(2);
  }

  auto* enum_b = file->add_enum_type();
  enum_b->set_name("EnumB");
  auto* b0 = enum_b->add_value();
  b0->set_name(conflict_shared ? "DIFFERENT" : "UNKNOWN");
  b0->set_number(0);
  if (!extra_on_first) {
    auto* b2 = enum_b->add_value();
    b2->set_name("EXTRA");
    b2->set_number(2);
  }

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_ENUM);
  ext1->set_type_name(".test.EnumA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_ENUM);
  ext2->set_type_name(".test.EnumB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

// Builds a descriptor set with two extension messages at the same tag whose
// shared field (tag 1) is identical, but one side has an extra field (tag 2)
// that the other lacks. `extra_on_existing` puts the extra field on the
// first-declared (existing) side; otherwise it is on the re-declared
// (candidate) side. Used to verify that a superset on either side is
// accepted, while the shared field still matches.
std::vector<uint8_t> BuildSupersetExtDescriptorSet(bool extra_on_existing) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* ext_msg_a = file->add_message_type();
  ext_msg_a->set_name("ExtMsgA");
  auto* a1 = ext_msg_a->add_field();
  a1->set_name("shared");
  a1->set_number(1);
  a1->set_type(FieldDescriptorProto::TYPE_INT32);
  a1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  if (extra_on_existing) {
    auto* a2 = ext_msg_a->add_field();
    a2->set_name("extra");
    a2->set_number(2);
    a2->set_type(FieldDescriptorProto::TYPE_INT32);
    a2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  }

  auto* ext_msg_b = file->add_message_type();
  ext_msg_b->set_name("ExtMsgB");
  auto* b1 = ext_msg_b->add_field();
  b1->set_name("shared");
  b1->set_number(1);
  b1->set_type(FieldDescriptorProto::TYPE_INT32);
  b1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  if (!extra_on_existing) {
    auto* b2 = ext_msg_b->add_field();
    b2->set_name("extra");
    b2->set_number(2);
    b2->set_type(FieldDescriptorProto::TYPE_INT32);
    b2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  }

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext1->set_type_name(".test.ExtMsgA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext2->set_type_name(".test.ExtMsgB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

std::vector<uint8_t> BuildScalarTypeDescriptorSet(
    protos::pbzero::FieldDescriptorProto::Type type_a,
    protos::pbzero::FieldDescriptorProto::Type type_b) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");

  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");

  auto* ext_msg_a = file->add_message_type();
  ext_msg_a->set_name("ExtMsgA");
  auto* field_a = ext_msg_a->add_field();
  field_a->set_name("val");
  field_a->set_number(1);
  field_a->set_type(type_a);
  field_a->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* ext_msg_b = file->add_message_type();
  ext_msg_b->set_name("ExtMsgB");
  auto* field_b = ext_msg_b->add_field();
  field_b->set_name("val");
  field_b->set_number(1);
  field_b->set_type(type_b);
  field_b->set_label(FieldDescriptorProto::LABEL_OPTIONAL);

  auto* ext1 = file->add_extension();
  ext1->set_name("ext_field");
  ext1->set_number(10);
  ext1->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext1->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext1->set_type_name(".test.ExtMsgA");
  ext1->set_extendee(".test.BaseMessage");

  auto* ext2 = file->add_extension();
  ext2->set_name("ext_field");
  ext2->set_number(10);
  ext2->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext2->set_type(FieldDescriptorProto::TYPE_MESSAGE);
  ext2->set_type_name(".test.ExtMsgB");
  ext2->set_extendee(".test.BaseMessage");

  return fds.SerializeAsArray();
}

// Two differently-named extension messages that are recursively identical
// (including through a nested message) are accepted, and the field resolves.
TEST(DescriptorsTest, IdenticalExtensionReDeclarationAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildDescriptorSet(/*structurally_equal_extensions=*/true,
                         /*deep_difference=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();

  auto base_idx = pool.FindDescriptorIdx(".test.BaseMessage");
  ASSERT_TRUE(base_idx.has_value());
  const auto& desc = pool.descriptors()[base_idx.value()];
  const auto* field = desc.FindFieldByTag(10);
  ASSERT_NE(field, nullptr);
  EXPECT_EQ(field->name(), "ext_field");
}

// A difference at the top level of the extension message is rejected.
TEST(DescriptorsTest, NonIdenticalExtensionReDeclarationRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildDescriptorSet(/*structurally_equal_extensions=*/false,
                         /*deep_difference=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

// A difference one level deep inside a nested message is rejected. This
// proves the comparison genuinely descends into nested types, not just the
// top level.
TEST(DescriptorsTest, DeepNestedDifferenceRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildDescriptorSet(/*structurally_equal_extensions=*/false,
                         /*deep_difference=*/true);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

// Self-referential (cyclic) messages that are structurally identical must
// terminate and be accepted, exercising the coinductive cycle guard.
TEST(DescriptorsTest, SelfReferentialIdenticalExtensionAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildSelfReferentialDescriptorSet();
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// CheckExtensionField: re-declaring an extension at the same tag with a
// different fundamental type (message vs int32) is rejected immediately,
// independent of the structural-equality path.
TEST(DescriptorsTest, ExtensionReDeclaredWithDifferentFundamentalTypeRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildReDeclDescriptorSet(/*second_is_scalar_mismatch=*/true);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("re-introduced with different type"));
}

// CheckExtensionField: re-declaring an extension at the same tag with an
// identical type (same name, same kind) is accepted and does not trigger a
// deferred structural check.
TEST(DescriptorsTest, IdenticalExtensionReDeclarationSameNameAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildReDeclDescriptorSet(/*second_is_scalar_mismatch=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
  auto base_idx = pool.FindDescriptorIdx(".test.BaseMessage");
  ASSERT_TRUE(base_idx.has_value());
  const auto* field = pool.descriptors()[base_idx.value()].FindFieldByTag(10);
  ASSERT_NE(field, nullptr);
  EXPECT_EQ(field->name(), "ext_field");
}

// CheckExtensionField: a descriptor set with no extension re-declaration at
// all exercises the "no existing field at this tag" early return for a
// genuine extension (not just incidentally via non-extension fields).
TEST(DescriptorsTest, SingleExtensionDeclarationAllowed) {
  protozero::HeapBuffered<FileDescriptorSet> fds;
  auto* file = fds->add_file();
  file->set_name("test.proto");
  file->set_package("test");
  auto* base_msg = file->add_message_type();
  base_msg->set_name("BaseMessage");
  auto* ext = file->add_extension();
  ext->set_name("ext_field");
  ext->set_number(10);
  ext->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
  ext->set_type(FieldDescriptorProto::TYPE_INT32);
  ext->set_extendee(".test.BaseMessage");
  std::vector<uint8_t> fds_bytes = fds.SerializeAsArray();

  DescriptorPool pool;
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
  auto base_idx = pool.FindDescriptorIdx(".test.BaseMessage");
  ASSERT_TRUE(base_idx.has_value());
  EXPECT_NE(pool.descriptors()[base_idx.value()].FindFieldByTag(10), nullptr);
}

// A renamed enum with identical values is accepted.
TEST(DescriptorsTest, IdenticalEnumExtensionReDeclarationAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildEnumExtDescriptorSet(/*enums_equal=*/true);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
  auto base_idx = pool.FindDescriptorIdx(".test.BaseMessage");
  ASSERT_TRUE(base_idx.has_value());
  EXPECT_NE(pool.descriptors()[base_idx.value()].FindFieldByTag(10), nullptr);
}

// A renamed enum whose shared value number maps to a different name is a
// genuine conflict and is rejected. (Differing value *sets* without a shared
// conflict are allowed; that case is covered by the superset tests.)
TEST(DescriptorsTest, ConflictingEnumValueReDeclarationRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildEnumExtDescriptorSet(/*enums_equal=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

// The re-declared (candidate) side has an extra field the existing side
// lacks. This is the Android-adds-a-field case and must be accepted.
TEST(DescriptorsTest, SupersetOnCandidateSideAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildSupersetExtDescriptorSet(/*extra_on_existing=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
  auto base_idx = pool.FindDescriptorIdx(".test.BaseMessage");
  ASSERT_TRUE(base_idx.has_value());
  EXPECT_NE(pool.descriptors()[base_idx.value()].FindFieldByTag(10), nullptr);
}

// The existing side has an extra field the re-declared side lacks. With the
// deprecate-in-place convention this is also a superset relationship and
// must be accepted (symmetric).
TEST(DescriptorsTest, SupersetOnExistingSideAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes =
      BuildSupersetExtDescriptorSet(/*extra_on_existing=*/true);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// Candidate enum has an extra value the existing enum lacks: accepted.
TEST(DescriptorsTest, EnumSupersetOnCandidateSideAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildEnumSupersetExtDescriptorSet(
      /*extra_on_first=*/false, /*conflict_shared=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// Existing enum has an extra value the candidate enum lacks: also accepted
// (symmetric).
TEST(DescriptorsTest, EnumSupersetOnExistingSideAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildEnumSupersetExtDescriptorSet(
      /*extra_on_first=*/true, /*conflict_shared=*/false);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// A shared enum number with a different name is a genuine conflict and is
// still rejected even though one side also has an extra value.
TEST(DescriptorsTest, EnumConflictingSharedValueRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildEnumSupersetExtDescriptorSet(
      /*extra_on_first=*/false, /*conflict_shared=*/true);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

// Same wire type (int32 -> int64, both kVarInt): accepted.
TEST(DescriptorsTest, SameWireTypeScalarChangeAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildScalarTypeDescriptorSet(
      FieldDescriptorProto::TYPE_INT32, FieldDescriptorProto::TYPE_INT64);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// int32 -> sint32: same wire type (kVarInt), accepted by the agreed
// wire-type-match contract.
TEST(DescriptorsTest, SameWireTypeIntToSintAllowed) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildScalarTypeDescriptorSet(
      FieldDescriptorProto::TYPE_INT32, FieldDescriptorProto::TYPE_SINT32);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_TRUE(status.ok()) << status.message();
}

// Different wire type (int32 kVarInt -> fixed32 kFixed32): rejected.
TEST(DescriptorsTest, DifferentWireTypeScalarChangeRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildScalarTypeDescriptorSet(
      FieldDescriptorProto::TYPE_INT32, FieldDescriptorProto::TYPE_FIXED32);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

// Different wire type (fixed32 -> fixed64): rejected.
TEST(DescriptorsTest, DifferentFixedWidthScalarChangeRejected) {
  DescriptorPool pool;
  std::vector<uint8_t> fds_bytes = BuildScalarTypeDescriptorSet(
      FieldDescriptorProto::TYPE_FIXED32, FieldDescriptorProto::TYPE_FIXED64);
  auto status =
      pool.AddFromFileDescriptorSet(fds_bytes.data(), fds_bytes.size());
  EXPECT_FALSE(status.ok());
  EXPECT_THAT(status.c_message(),
              testing::HasSubstr("not structurally identical"));
}

}  // namespace
}  // namespace perfetto::trace_processor
