blob: 7498621c92f85e1449655a73c156172b81625dfa [file] [edit]
/*
* Copyright (C) 2025 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 <cstdint>
#include "perfetto/protozero/field.h"
#include "src/protovm/test/utils.h"
#include "test/gtest_and_gmock.h"
#include "src/protovm/error_handling.h"
#include "src/protovm/rw_proto.h"
#include "src/protovm/test/protos/incremental_trace.pb.h"
#include "src/protovm/test/sample_packets.h"
#include "src/protovm/test/utils.h"
namespace perfetto {
namespace protovm {
namespace test {
class RwProtoTest : public ::testing::Test {
protected:
void SetUp() override { PopulateRwProtoWithTwoElements(); }
void PopulateRwProtoWithTwoElements() {
auto root = data_trace_entry_with_two_elements_.GetRoot();
// elements[0]
{
auto element = root;
element.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
// id field
auto id = element;
id.EnterField(protos::Element::kIdFieldNumber);
id.SetScalar(Scalar::VarInt(0));
// value field
auto value = element;
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(10));
// value_fixed32 field
auto value_fixed32 = element;
value_fixed32.EnterField(protos::Element::kValueFixed32FieldNumber);
value_fixed32.SetScalar(Scalar::Fixed32(32));
// value_fixed64 field
auto value_fixed64 = element;
value_fixed64.EnterField(protos::Element::kValueFixed64FieldNumber);
value_fixed64.SetScalar(Scalar::Fixed64(64));
}
// elements[1]
{
auto element = root;
element.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1);
// id field
auto id = element;
id.EnterField(protos::Element::kIdFieldNumber);
id.SetScalar(Scalar::VarInt(1));
// value field
auto value = element;
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(11));
}
}
void CheckProtoWithTwoElements(const std::string& proto) const {
protos::TraceEntry entry;
entry.ParseFromString(proto);
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 10);
ASSERT_EQ(entry.elements(0).value_fixed32(), static_cast<uint32_t>(32));
ASSERT_EQ(entry.elements(0).value_fixed64(), static_cast<uint64_t>(64));
ASSERT_EQ(entry.elements(1).id(), 1);
ASSERT_EQ(entry.elements(1).value(), 11);
}
std::string SerializeAsString(const RwProto& rw_proto) const {
protozero::HeapBuffered<protozero::Message> proto;
rw_proto.Serialize(proto.get());
return proto.SerializeAsString();
}
Allocator allocator_{10 * 1024 * 1024};
RwProto data_empty_{&allocator_};
RwProto data_trace_entry_with_two_elements_{&allocator_};
protozero::ConstBytes bytes_empty_{nullptr, 0};
};
TEST_F(RwProtoTest, HasField_IncompatibleWireType) {
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.HasField(0).IsAbort());
}
TEST_F(RwProtoTest, HasField_FieldNotAvailable) {
{
RwProto::Cursor cursor = data_empty_.GetRoot();
ASSERT_EQ(*cursor.HasField(protos::TraceEntry::kElementsFieldNumber),
false);
}
{
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_EQ(*cursor.HasField(0), false);
}
{
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterField(protos::TraceEntry::kElementsFieldNumber);
ASSERT_EQ(*cursor.HasField(0), false);
}
}
TEST_F(RwProtoTest, HasField_FieldAvailable) {
{
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_EQ(*cursor.HasField(protos::TraceEntry::kElementsFieldNumber), true);
}
{
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_EQ(*cursor.HasField(protos::Element::kIdFieldNumber), true);
ASSERT_EQ(*cursor.HasField(protos::Element::kValueFieldNumber), true);
}
}
TEST_F(RwProtoTest, HasField_FieldNotAvailableAsBytes) {
RwProto::Cursor cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithTwoElements().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
ASSERT_EQ(*cursor.HasField(0), false);
}
TEST_F(RwProtoTest, HasField_FieldAvailableAsBytes) {
RwProto::Cursor cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithTwoElements().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
ASSERT_EQ(*cursor.HasField(protos::TraceEntry::kElementsFieldNumber), true);
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_EQ(*cursor.HasField(protos::Element::kIdFieldNumber), true);
ASSERT_EQ(*cursor.HasField(protos::Element::kValueFieldNumber), true);
}
TEST_F(RwProtoTest, EnterField_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsOk());
ASSERT_TRUE(cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
ASSERT_TRUE(cursor.EnterField(0).IsAbort());
}
TEST_F(RwProtoTest, EnterField_IncompatibleFieldType_IndexedRepeatedField) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor.EnterField(protos::TraceEntry::kElementsFieldNumber).IsAbort());
}
TEST_F(RwProtoTest, EnterField_IncompatibleFieldType_MappedRepeatedField) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
{
// Trigger internal node conversion from "indexed repeated field" to "mapped
// repeated field"
auto element = cursor;
ASSERT_TRUE(
element
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0)
.IsOk());
}
ASSERT_TRUE(
cursor.EnterField(protos::TraceEntry::kElementsFieldNumber).IsAbort());
}
TEST_F(RwProtoTest, EnterField_FieldNotAvailableGetsCreated) {
RwProto::Cursor cursor = data_empty_.GetRoot();
ASSERT_TRUE(
cursor.EnterField(protos::TraceEntry::kElementsFieldNumber).IsOk());
ASSERT_TRUE(cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
cursor.SetScalar(Scalar::VarInt(10));
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 10);
}
TEST_F(RwProtoTest, EnterField_FieldAvailable) {
RwProto::Cursor cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsOk());
ASSERT_TRUE(cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
CheckProtoWithTwoElements(
SerializeAsString(data_trace_entry_with_two_elements_));
}
TEST_F(RwProtoTest, EnterField_FieldNotAvailableAsBytesGetsCreated) {
RwProto::Cursor cursor = data_empty_.GetRoot();
auto entry = SamplePackets::TraceEntryWithTwoElements();
entry.mutable_elements(0)->clear_value();
auto proto = entry.SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
// elements[0].value = 10
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kValueFieldNumber);
cursor.SetScalar(Scalar::VarInt(10));
CheckProtoWithTwoElements(SerializeAsString(data_empty_));
}
TEST_F(RwProtoTest, EnterField_FieldAvailableAsBytes) {
auto cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithTwoElements().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsOk());
ASSERT_TRUE(cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
ASSERT_EQ(cursor.GetScalar().value(), Scalar::VarInt(0));
}
TEST_F(RwProtoTest, EnterIndexedRepeatedField_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.EnterRepeatedFieldAt(0, 0).IsAbort());
}
// TODO: support accessing repeated fields by both index and key.
// Currently, a repeated field can be organized internally as either "indexed"
// or "mapped", but not both. This means that once a field is accessed using
// a key (EnterRepeatedFieldByKey), it can no longer be accessed by
// index (EnterRepeatedFieldAt). While it's technically possible to allow
// both access methods (same internal node inserted into two intrusive maps),
// it's a low priority as current use cases don't require it.
TEST_F(RwProtoTest,
EnterIndexedRepeatedField_IncompatibleFieldType_MappedRepeatedField) {
// Trigger creation of internal "mapped repeated field" node
{
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0)
.IsOk());
}
// Attempt to access as "indexed repeated field"
{
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsAbort());
}
}
// Only append operations (insert at index == elements.size()) are supported.
// Attempting to insert a field with index > elements.size() causes an
// abort.
TEST_F(RwProtoTest,
EnterIndexedRepeatedField_FieldNotAvailable_AbortIfNotSimpleAppend) {
// Attempt to enter field_index (1) > elements.size() (0) => abort
{
auto cursor = data_empty_.GetRoot();
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1)
.IsAbort());
}
// Attempt to enter field_index (3) > elements.size() (2) => abort
{
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 3)
.IsAbort());
}
}
TEST_F(RwProtoTest, EnterIndexedRepeatedField_FieldNotAvailable_Append) {
auto cursor = data_empty_.GetRoot();
// append elements[0]
{
auto element = cursor;
ASSERT_TRUE(
element
.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsOk());
auto proto = SamplePackets::TraceEntryWithTwoElements()
.elements(0)
.SerializeAsString();
element.SetBytes(AsConstBytes(proto));
}
// append elements[1]
{
auto element = cursor;
ASSERT_TRUE(
element
.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1)
.IsOk());
auto proto = SamplePackets::TraceEntryWithTwoElements()
.elements(1)
.SerializeAsString();
element.SetBytes(AsConstBytes(proto));
}
CheckProtoWithTwoElements(SerializeAsString(data_empty_));
}
TEST_F(RwProtoTest, EnterIndexedRepeatedField_FieldAvailable) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
// elements[0].id = 100
{
auto id = cursor;
ASSERT_TRUE(
id.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0)
.IsOk());
id.EnterField(protos::Element::kIdFieldNumber);
id.SetScalar(Scalar::VarInt(100));
}
// elements[1].id = 101
{
auto id = cursor;
ASSERT_TRUE(
id.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1)
.IsOk());
id.EnterField(protos::Element::kIdFieldNumber);
id.SetScalar(Scalar::VarInt(101));
}
auto proto = SerializeAsString(data_trace_entry_with_two_elements_);
protos::TraceEntry entry;
entry.ParseFromString(proto);
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 100);
ASSERT_EQ(entry.elements(0).value(), 10);
ASSERT_EQ(entry.elements(1).id(), 101);
ASSERT_EQ(entry.elements(1).value(), 11);
}
TEST_F(RwProtoTest,
EnterIndexedRepeatedField_FieldAvailable_NotDetectedAsRepeatedYet) {
auto cursor = data_empty_.GetRoot();
// set only elements[0] -> initially considered a simple field (not repeated)
auto proto = SamplePackets::TraceEntryWithOneElement().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
// append elements[1] -> detect elements is an indexed repeated field and
// reorganize the internal nodes accordingly
ASSERT_TRUE(
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1)
.IsOk());
auto proto_element1 = SamplePackets::TraceEntryWithTwoElements()
.elements(1)
.SerializeAsString();
cursor.SetBytes(AsConstBytes(proto_element1));
CheckProtoWithTwoElements(SerializeAsString(data_empty_));
}
TEST_F(RwProtoTest, IterateRepeatedField_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.IterateRepeatedField(0).IsAbort());
}
TEST_F(RwProtoTest, IterateRepeatedField_FieldsNotAvailable) {
auto cursor = data_empty_.GetRoot();
auto status_or_it =
cursor.IterateRepeatedField(protos::TraceEntry::kElementsFieldNumber);
ASSERT_FALSE(static_cast<bool>(*status_or_it));
}
TEST_F(RwProtoTest, IterateRepeatedField_FieldsAvailable) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
auto it =
*cursor.IterateRepeatedField(protos::TraceEntry::kElementsFieldNumber);
// elements[0]
{
ASSERT_TRUE(static_cast<bool>(it));
auto value = it.GetCursor();
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(100));
}
// elements[1]
{
++it;
ASSERT_TRUE(static_cast<bool>(it));
auto value = it.GetCursor();
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(101));
}
// elements[one_past_end]
{
++it;
ASSERT_FALSE(static_cast<bool>(it));
}
auto proto = SerializeAsString(data_trace_entry_with_two_elements_);
protos::TraceEntry entry;
entry.ParseFromString(proto);
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 100);
ASSERT_EQ(entry.elements(1).id(), 1);
ASSERT_EQ(entry.elements(1).value(), 101);
}
TEST_F(RwProtoTest,
IterateRepeatedField_FieldsAvailable_NotDetectedAsRepeatedYet) {
auto cursor = data_empty_.GetRoot();
// set only elements[0] -> initially considered a simple field (not repeated)
auto proto = SamplePackets::TraceEntryWithOneElement().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
// request iteration of elements -> detect elements is an indexed repeated
// field and reorganize the internal nodes accordingly
auto it =
*cursor.IterateRepeatedField(protos::TraceEntry::kElementsFieldNumber);
// element[0]
{
ASSERT_TRUE(static_cast<bool>(it));
auto value = it.GetCursor();
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(100));
}
// element[end_past_one]
{
++it;
ASSERT_FALSE(static_cast<bool>(it));
}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 100);
}
TEST_F(RwProtoTest, EnterMappedRepeatedField_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.EnterRepeatedFieldByKey(0, 0, 0).IsAbort());
}
TEST_F(RwProtoTest, EnterMappedRepeatedField_FieldNotAvailable) {
auto cursor = data_empty_.GetRoot();
ASSERT_TRUE(
cursor
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 10)
.IsOk());
// elements[10].value = 100
cursor.EnterField(protos::Element::kValueFieldNumber);
cursor.SetScalar(Scalar::VarInt(100));
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).value(), 100);
}
TEST_F(RwProtoTest, EnterMappedRepeatedField_FieldAvailable) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(
cursor
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0)
.IsOk());
// elements[0].value = 100
cursor.EnterField(protos::Element::kValueFieldNumber);
cursor.SetScalar(Scalar::VarInt(100));
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 100);
ASSERT_EQ(entry.elements(1).id(), 1);
ASSERT_EQ(entry.elements(1).value(), 11);
}
TEST_F(RwProtoTest, EnterMappedRepeatedField_FieldAvailableAsBytes) {
auto cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithTwoElements().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
ASSERT_TRUE(
cursor
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0)
.IsOk());
// elements[0].value = 100
cursor.EnterField(protos::Element::kValueFieldNumber);
cursor.SetScalar(Scalar::VarInt(100));
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 100);
ASSERT_EQ(entry.elements(1).id(), 1);
ASSERT_EQ(entry.elements(1).value(), 11);
}
TEST_F(RwProtoTest,
EnterMappedRepeatedField_FieldAvailable_NotDetectedAsRepeatedYet) {
auto cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithOneElement().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
// elements[0].value = 100
{
auto value = cursor;
ASSERT_TRUE(
value
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0)
.IsOk());
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(100));
}
// elements[1].value = 101
{
auto value = cursor;
ASSERT_TRUE(
value
.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 1)
.IsOk());
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(101));
}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).value(), 100);
ASSERT_EQ(entry.elements(1).value(), 101);
}
TEST_F(RwProtoTest, GetScalar_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(cursor.GetScalar().IsAbort());
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_TRUE(cursor.GetScalar().IsAbort());
cursor.EnterField(1000);
ASSERT_TRUE(cursor.GetScalar().IsAbort());
}
TEST_F(RwProtoTest, GetScalar_Success) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kValueFieldNumber);
ASSERT_EQ(cursor.GetScalar().value(), Scalar::VarInt(10));
}
TEST_F(RwProtoTest, Delete_RootMessage) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(cursor.Delete().IsOk());
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 0);
}
TEST_F(RwProtoTest, Delete_Scalar) {
// delete elements[0].id
{
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.Delete().IsOk());
}
// delete elements[1].value
{
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1);
cursor.EnterField(protos::Element::kValueFieldNumber);
ASSERT_TRUE(cursor.Delete().IsOk());
}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_FALSE(entry.elements(0).has_id());
ASSERT_EQ(entry.elements(0).value(), 10);
ASSERT_EQ(entry.elements(1).id(), 1);
ASSERT_FALSE(entry.elements(1).has_value());
}
TEST_F(RwProtoTest, Delete_Message) {
auto cursor = data_empty_.GetRoot();
auto proto = SamplePackets::TraceEntryWithOneElement().SerializeAsString();
cursor.SetBytes(AsConstBytes(proto));
// field detect elements[0] as simple (not repeated) field
cursor.EnterField(protos::TraceEntry::kElementsFieldNumber);
cursor.Delete();
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
}
// TODO: deleting an element from an indexed repeated field currently creates a
// "hole" in the array, but ideally we should shift subsequent elements to
// the left. This is low-priority though as the "array delete" operation is not
// required by the current use cases.
TEST_F(RwProtoTest, Delete_IndexedRepeatedField) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_TRUE(cursor.Delete().IsOk());
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 11);
}
TEST_F(RwProtoTest, Delete_MappedRepeatedField) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldByKey(protos::TraceEntry::kElementsFieldNumber,
protos::Element::kIdFieldNumber, 0);
ASSERT_TRUE(cursor.Delete().IsOk());
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 11);
}
TEST_F(RwProtoTest, Merge_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
ASSERT_TRUE(cursor.Merge(bytes_empty_, RwProto::Cursor::kNone).IsAbort());
}
TEST_F(RwProtoTest, Merge_EmptySrc) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_TRUE(cursor.Merge(bytes_empty_, RwProto::Cursor::kNone).IsOk());
CheckProtoWithTwoElements(
SerializeAsString(data_trace_entry_with_two_elements_));
}
TEST_F(RwProtoTest, Merge_EmptyDst) {
auto cursor = data_empty_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
protos::Element element;
element.set_id(1);
element.set_value(11);
auto proto = element.SerializeAsString();
ASSERT_TRUE(cursor.Merge(AsConstBytes(proto), RwProto::Cursor::kNone).IsOk());
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 11);
}
TEST_F(RwProtoTest, Merge_FieldsUnion) {
auto cursor = data_empty_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
// initialize element = {id: 1}
{
protos::Element element;
element.set_id(1);
auto proto = element.SerializeAsString();
ASSERT_TRUE(
cursor.Merge(AsConstBytes(proto), RwProto::Cursor::kNone).IsOk());
}
// merge with element = {value: 11}
{
protos::Element element;
element.set_value(11);
auto proto = element.SerializeAsString();
ASSERT_TRUE(
cursor.Merge(AsConstBytes(proto), RwProto::Cursor::kNone).IsOk());
}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 11);
}
TEST_F(RwProtoTest, Merge_FieldsReplacement) {
auto cursor = data_empty_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
// initialize element = {id: 0, value: 10}
{
protos::Element element;
element.set_id(0);
element.set_value(10);
auto bytes = element.SerializeAsString();
cursor.SetBytes(AsConstBytes(bytes));
}
// merge with element = {id: 1, value: 11}
{
protos::Element element;
element.set_id(1);
element.set_value(11);
auto bytes = element.SerializeAsString();
ASSERT_TRUE(
cursor.Merge(AsConstBytes(bytes), RwProto::Cursor::kNone).IsOk());
}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 11);
}
TEST_F(RwProtoTest, Merge_RepeatedField) {
// initially empty
auto cursor = data_empty_.GetRoot();
// merge with elements = [{id: 1, value: 10}, {id: 2, value: 20}]
{
protos::TraceEntry entry;
auto* element0 = entry.add_elements();
element0->set_id(1);
element0->set_value(10);
auto* element1 = entry.add_elements();
element1->set_id(2);
element1->set_value(20);
auto bytes = entry.SerializeAsString();
ASSERT_TRUE(
cursor.Merge(AsConstBytes(bytes), RwProto::Cursor::kNone).IsOk());
}
// check
{
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 2);
ASSERT_EQ(entry.elements(0).id(), 1);
ASSERT_EQ(entry.elements(0).value(), 10);
ASSERT_EQ(entry.elements(1).id(), 2);
ASSERT_EQ(entry.elements(1).value(), 20);
}
// merge with elements = [{id: 0, value: 1}]
// (fully replace original elements)
{
protos::TraceEntry entry;
auto* element0 = entry.add_elements();
element0->set_id(0);
element0->set_value(1);
auto bytes = entry.SerializeAsString();
ASSERT_TRUE(
cursor.Merge(AsConstBytes(bytes), RwProto::Cursor::kNone).IsOk());
}
// check
{
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).id(), 0);
ASSERT_EQ(entry.elements(0).value(), 1);
}
}
TEST_F(RwProtoTest, Merge_SkipSubmessages) {
auto cursor = data_empty_.GetRoot();
// initialize state: single_element={id:1, value:10}, id=100
{
auto single_cursor = cursor;
ASSERT_TRUE(
single_cursor.EnterField(protos::TraceEntry::kSingleElementFieldNumber)
.IsOk());
auto id_cursor = single_cursor;
ASSERT_TRUE(id_cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
ASSERT_TRUE(id_cursor.SetScalar(Scalar::VarInt(1)).IsOk());
auto value_cursor = single_cursor;
ASSERT_TRUE(
value_cursor.EnterField(protos::Element::kValueFieldNumber).IsOk());
ASSERT_TRUE(value_cursor.SetScalar(Scalar::VarInt(10)).IsOk());
auto root_id_cursor = cursor;
ASSERT_TRUE(
root_id_cursor.EnterField(protos::TraceEntry::kIdFieldNumber).IsOk());
ASSERT_TRUE(root_id_cursor.SetScalar(Scalar::VarInt(100)).IsOk());
}
// prepare patch for root: single_element={id:2, value:20}, id=200
protos::TraceEntry root_patch;
{
auto* single = root_patch.mutable_single_element();
single->set_id(2);
single->set_value(20);
root_patch.set_id(200);
}
// merge root with skip_submessages = true
ASSERT_TRUE(cursor
.Merge(AsConstBytes(root_patch.SerializeAsString()),
RwProto::Cursor::kSkipSubmessages)
.IsOk());
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_TRUE(entry.has_single_element());
ASSERT_EQ(entry.single_element().id(), 1); // not updated
ASSERT_EQ(entry.single_element().value(), 10); // not updated
ASSERT_TRUE(entry.has_id());
ASSERT_EQ(entry.id(), 200); // updated
}
TEST_F(RwProtoTest, Merge_DelIfSrcEmpty_SingleField) {
auto root = data_empty_.GetRoot();
// root = { single_element { id: 10 }}
{
auto cursor = root;
cursor.EnterField(protos::TraceEntry::kSingleElementFieldNumber);
cursor.EnterField(protos::Element::kIdFieldNumber);
cursor.SetScalar(Scalar::VarInt(10));
}
// patch = { single_element {} }
protos::TraceEntry patch;
patch.mutable_single_element();
// Merge
ASSERT_TRUE(root.Merge(AsConstBytes(patch.SerializeAsString()),
RwProto::Cursor::kDelIfSrcEmpty)
.IsOk());
// Check root = {}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_FALSE(entry.has_single_element());
}
TEST_F(RwProtoTest, Merge_DelIfSrcEmpty_RepeatedField) {
// root = { element {...} element {...} }
auto root = data_trace_entry_with_two_elements_.GetRoot();
// patch = { element {} }
protos::TraceEntry patch;
patch.add_elements();
root.Merge(AsConstBytes(patch.SerializeAsString()),
RwProto::Cursor::kDelIfSrcEmpty);
// Check root = {}
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_trace_entry_with_two_elements_));
ASSERT_EQ(entry.elements_size(), 0);
}
TEST_F(RwProtoTest, Merge_DelIfSrcEmptyAndSkipSubmessages) {
auto root = data_empty_.GetRoot();
// root = { single_element { id: 10 } }
{
auto cursor = root;
ASSERT_TRUE(cursor.EnterField(protos::TraceEntry::kSingleElementFieldNumber)
.IsOk());
auto id_cursor = cursor;
ASSERT_TRUE(id_cursor.EnterField(protos::Element::kIdFieldNumber).IsOk());
ASSERT_TRUE(id_cursor.SetScalar(Scalar::VarInt(10)).IsOk());
}
// patch = { single_element {} }
protos::TraceEntry patch;
patch.mutable_single_element();
// Merge with both kDelIfSrcEmpty and kSkipSubmessages
ASSERT_TRUE(root.Merge(AsConstBytes(patch.SerializeAsString()),
RwProto::Cursor::kDelIfSrcEmpty |
RwProto::Cursor::kSkipSubmessages)
.IsOk());
// Check root = {} (i.e. single_element is deleted, not skipped)
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_FALSE(entry.has_single_element());
}
TEST_F(RwProtoTest, SetBytes_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.EnterField(protos::Element::kIdFieldNumber);
// bytes represent a message, hence replacing a Scalar with bytes means we are
// changing the proto schema which is a logic error
ASSERT_TRUE(cursor.SetBytes(protozero::ConstBytes{}).IsAbort());
}
TEST_F(RwProtoTest, SetBytes_CanHandleEmptyPayload) {
// root = <empty bytes>
{
auto cursor = data_empty_.GetRoot();
cursor.SetBytes(AsConstBytes(""));
protos::TraceEntry entry;
auto proto = SerializeAsString(data_empty_);
entry.ParseFromString(proto);
ASSERT_EQ(entry.elements_size(), 0);
}
// elements[0] = <empty bytes>
{
auto cursor = data_empty_.GetRoot();
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
cursor.SetBytes(AsConstBytes(""));
protos::TraceEntry entry;
auto proto = SerializeAsString(data_empty_);
entry.ParseFromString(proto);
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_FALSE(entry.elements(0).has_id());
ASSERT_FALSE(entry.elements(0).has_value());
}
}
TEST_F(RwProtoTest, SetBytes_InitializesEmptyField) {
auto cursor = data_empty_.GetRoot();
auto proto = SamplePackets{}.TraceEntryWithTwoElements().SerializeAsString();
ASSERT_TRUE(cursor.SetBytes(AsConstBytes(proto)).IsOk());
CheckProtoWithTwoElements(SerializeAsString(data_empty_));
}
TEST_F(RwProtoTest, SetBytes_UpdatesExistingField) {
auto root = data_empty_.GetRoot();
// elements[0]
{
auto element = root;
element.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
// id field
{
auto id = element;
id.EnterField(protos::Element::kIdFieldNumber);
id.SetScalar(Scalar::VarInt(0xdeadbeef));
}
// overwrite existing id field
auto proto = SamplePackets::TraceEntryWithTwoElements()
.elements(0)
.SerializeAsString();
element.SetBytes(AsConstBytes(proto));
}
// elements[1]
{
auto element = root;
element.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 1);
// value field
{
auto value = element;
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(0xdeadbeef));
}
// overwrite existing id field
auto proto = SamplePackets::TraceEntryWithTwoElements()
.elements(1)
.SerializeAsString();
element.SetBytes(AsConstBytes(proto));
}
CheckProtoWithTwoElements(SerializeAsString(data_empty_));
}
TEST_F(RwProtoTest, SetScalar_IncompatibleWireType) {
auto cursor = data_trace_entry_with_two_elements_.GetRoot();
ASSERT_TRUE(cursor.SetScalar(Scalar::VarInt(0)).IsAbort());
cursor.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
ASSERT_TRUE(cursor.SetScalar(Scalar::VarInt(0)).IsAbort());
}
TEST_F(RwProtoTest, SetScalar_Success) {
auto cursor = data_empty_.GetRoot();
auto element0 = cursor;
element0.EnterRepeatedFieldAt(protos::TraceEntry::kElementsFieldNumber, 0);
// element[0].value = 10
auto value = element0;
value.EnterField(protos::Element::kValueFieldNumber);
value.SetScalar(Scalar::VarInt(10));
// element[0].value_fixed32 = 32
auto value_fixed32 = element0;
value_fixed32.EnterField(protos::Element::kValueFixed32FieldNumber);
value_fixed32.SetScalar(Scalar::Fixed32(32));
// element[0].value_fixed64 = 64
auto value_fixed64 = element0;
value_fixed64.EnterField(protos::Element::kValueFixed64FieldNumber);
value_fixed64.SetScalar(Scalar::Fixed64(64));
protos::TraceEntry entry;
entry.ParseFromString(SerializeAsString(data_empty_));
ASSERT_EQ(entry.elements_size(), 1);
ASSERT_EQ(entry.elements(0).value(), 10);
ASSERT_EQ(entry.elements(0).value_fixed32(), static_cast<uint32_t>(32));
ASSERT_EQ(entry.elements(0).value_fixed64(), static_cast<uint64_t>(64));
}
TEST_F(RwProtoTest, SerializeAsString) {
CheckProtoWithTwoElements(
SerializeAsString(data_trace_entry_with_two_elements_));
}
TEST_F(RwProtoTest, AccessNonRootFieldAfterDeleteAborts) {
auto cursor = data_empty_.GetRoot();
ASSERT_TRUE(cursor.EnterField(1).IsOk());
ASSERT_TRUE(cursor.Delete().IsOk());
ASSERT_TRUE(cursor.IsRoot().IsAbort());
ASSERT_TRUE(cursor.HasField(1).IsAbort());
ASSERT_TRUE(cursor.EnterField(1).IsAbort());
ASSERT_TRUE(cursor.EnterRepeatedFieldAt(1, 0).IsAbort());
ASSERT_TRUE(cursor.IterateRepeatedField(1).IsAbort());
ASSERT_TRUE(cursor.EnterRepeatedFieldByKey(1, 1, 0).IsAbort());
ASSERT_TRUE(cursor.GetScalar().IsAbort());
ASSERT_TRUE(cursor.SetBytes(protozero::ConstBytes{}).IsAbort());
ASSERT_TRUE(cursor.SetScalar(Scalar::Fixed32(0)).IsAbort());
ASSERT_TRUE(cursor.Merge(protozero::ConstBytes{}, 0).IsAbort());
ASSERT_TRUE(cursor.Delete().IsAbort());
}
} // namespace test
} // namespace protovm
} // namespace perfetto