blob: 4291e0f6092731fb01931320d62dad9ab3f2f058 [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 <memory>
#include <string>
#include <unordered_map>
#include "test/gtest_and_gmock.h"
#include "src/protovm/compiler/compiler.h"
#include "src/protovm/compiler/trace.descriptor.h"
#include "src/protovm/compiler/vm_program.descriptor.h"
#include "src/protovm/compiler/winscope.descriptor.h"
#include "src/protozero/text_to_proto/text_to_proto.h"
#include "src/trace_processor/util/descriptors.h"
#include "src/trace_processor/util/protozero_to_text.h"
namespace perfetto::protovm {
namespace {
using perfetto::trace_processor::DescriptorPool;
using namespace perfetto::trace_processor::protozero_to_text;
class CompilerTest : public ::testing::Test {
protected:
void SetUp() override {
pool_ = std::make_unique<DescriptorPool>();
auto status =
pool_->AddFromFileDescriptorSet(perfetto::kVmProgramDescriptor.data(),
perfetto::kVmProgramDescriptor.size());
EXPECT_TRUE(status.ok());
}
void CheckCompilation(std::string_view config_textproto,
std::string_view expected_instructions_textproto) {
Compiler compiler;
std::string combined_descriptors =
std::string(
reinterpret_cast<const char*>(perfetto::kTraceDescriptor.data()),
perfetto::kTraceDescriptor.size()) +
std::string(
reinterpret_cast<const char*>(perfetto::kWinscopeDescriptor.data()),
perfetto::kWinscopeDescriptor.size());
base::StatusOr<std::string> actual_instructions_binary =
compiler.Compile(config_textproto, combined_descriptors);
EXPECT_TRUE(actual_instructions_binary.ok())
<< actual_instructions_binary.status().message();
std::string actual_instructions_textproto = ProtozeroToText(
*pool_, ".perfetto.protos.VmProgram",
protozero::ConstBytes{reinterpret_cast<const uint8_t*>(
actual_instructions_binary->data()),
actual_instructions_binary->size()});
EXPECT_EQ(actual_instructions_textproto,
NormalizeProgramTextProto(expected_instructions_textproto));
}
private:
std::string NormalizeProgramTextProto(std::string_view textproto) {
auto status_or_binary = protozero::TextToProto(
perfetto::kVmProgramDescriptor.data(),
perfetto::kVmProgramDescriptor.size(), ".perfetto.protos.VmProgram",
"program.textproto", textproto);
EXPECT_TRUE(status_or_binary.ok()) << status_or_binary.status().message();
return ProtozeroToText(*pool_, ".perfetto.protos.VmProgram",
protozero::ConstBytes{status_or_binary->data(),
status_or_binary->size()});
}
std::unique_ptr<DescriptorPool> pool_;
};
TEST_F(CompilerTest, Set) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
set {
src: "for_testing"
src: "protovm_patch"
src: "string_to_merge"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "string_merged"
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 1
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
relative_path {
field_id: 1
}
}
nested_instructions {
set {
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, DelIfSrcPresent) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
del {
src: "for_testing"
src: "protovm_patch"
src: "single_message"
dst: "for_testing"
dst: "protovm_incremental_state"
if_src_present: true
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 3
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
}
nested_instructions {
del {}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, DelByKey) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
del {
src: "for_testing"
src: "protovm_patch"
src: "delete_message_ids"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "messages"
dst_key_field: "id"
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 5
is_repeated: true
}
}
nested_instructions {
reg_load {
dst_register: 0
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
relative_path {
field_id: 4
}
relative_path {
map_key_field_id: 1
register_to_match: 0
}
}
nested_instructions {
del {
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, Merge) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
merge {
src: "for_testing"
src: "protovm_patch"
src: "single_message"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "single_message"
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 3
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
relative_path {
field_id: 3
}
}
nested_instructions {
merge {
del_if_src_empty: true
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, MergeRecursive) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
merge {
src: "for_testing"
src: "protovm_patch"
src: "single_message"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "single_message"
recursive: true
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 3
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
relative_path {
field_id: 3
}
}
nested_instructions {
abort_level: SKIP_CURRENT_INSTRUCTION
select {
relative_path {
field_id: 2
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 2
}
}
nested_instructions {
merge {
del_if_src_empty: true
skip_submessages: true
}
}
}
}
nested_instructions {
merge {
del_if_src_empty: true
skip_submessages: true
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, MergeByKey) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
merge {
src: "for_testing"
src: "protovm_patch"
src: "messages"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "messages"
key_field: "id"
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
relative_path {
field_id: 4
is_repeated: true
}
}
nested_instructions {
abort_level: ABORT
select {
relative_path {
field_id: 1
}
}
nested_instructions {
reg_load {
dst_register: 0
}
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
relative_path {
field_id: 4
}
relative_path {
map_key_field_id: 1
register_to_match: 0
}
}
nested_instructions {
merge {
del_if_src_empty: true
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, EnterScope) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
enter_scope {
src: "for_testing"
src: "protovm_patch"
dst: "for_testing"
dst: "protovm_incremental_state"
commands {
set {}
}
}
abort_level: ABORT
})";
std::string expected_instructions = R"(
instructions {
abort_level: ABORT
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
relative_path {
field_id: 7
}
}
nested_instructions {
set {
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, AbortLevelTranslation) {
std::unordered_map<std::string, std::string>
command_to_instruction_abort_level{
{"", ""},
{"abort_level: SKIP_CURRENT_COMMAND",
"abort_level: SKIP_CURRENT_INSTRUCTION"},
{"abort_level: SKIP_CURRENT_COMMAND_AND_BREAK_OUTER", ""},
{"abort_level: ABORT", "abort_level: ABORT"},
};
for (auto [command_abort, instruction_abort] :
command_to_instruction_abort_level) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
set {
src: "for_testing"
dst: "for_testing"
}
)" + command_abort +
R"(
})";
std::string expected_instructions = R"(
instructions {
)" + instruction_abort + R"(
select {
relative_path {
field_id: 900
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
}
nested_instructions {
set {
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
}
TEST_F(CompilerTest, ErrorInvalidFieldName) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
set {
src: "for_testing"
src: "protovm_patch"
src: "single_message"
dst: "for_testing"
dst: "protovm_incremental_state"
dst: "invalid_field_name"
}
})";
auto compiler = Compiler{};
std::string combined_descriptors =
std::string(
reinterpret_cast<const char*>(perfetto::kTraceDescriptor.data()),
perfetto::kTraceDescriptor.size()) +
std::string(
reinterpret_cast<const char*>(perfetto::kWinscopeDescriptor.data()),
perfetto::kWinscopeDescriptor.size());
auto status_or = compiler.Compile(config, combined_descriptors);
EXPECT_FALSE(status_or.ok());
EXPECT_THAT(
status_or.status().message(),
::testing::HasSubstr("Failed to lookup field name: invalid_field_name"));
}
TEST_F(CompilerTest, EmptyPath) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
set {}
}
commands {
set {
src: "for_testing"
}
}
commands {
set {
dst: "for_testing"
}
}
)";
std::string expected_instructions = R"(
instructions {
set {}
}
instructions {
select {
relative_path {
field_id: 900
}
}
nested_instructions {
set {}
}
}
instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 900
}
}
nested_instructions {
set {}
}
})";
CheckCompilation(config, expected_instructions);
}
TEST_F(CompilerTest, CanResolveWinscopeExtensionsProtos) {
std::string config = R"(
root_message: "perfetto.protos.TracePacket"
commands {
set {
src: "for_testing"
src: "protovm_patch"
dst: "winscope_extensions"
dst: "windowmanager"
dst: "window_manager_service"
}
})";
std::string expected_instructions = R"(
instructions {
select {
relative_path {
field_id: 900
}
relative_path {
field_id: 6
}
}
nested_instructions {
select {
cursor: VM_CURSOR_DST
create_if_not_exist: true
relative_path {
field_id: 112
}
relative_path {
field_id: 6
}
relative_path {
field_id: 3
}
}
nested_instructions {
set {
}
}
}
})";
CheckCompilation(config, expected_instructions);
}
} // namespace
} // namespace perfetto::protovm