blob: b61296f0515b0b9141c5ef57a717f5eb8cb1e33e [file]
/*
* 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