blob: 1b2f1d0bd153077677a2716794846a32760d434f [file] [log] [blame]
/*
* Copyright (C) 2018 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/perfetto_cmd/pbtxt_to_pb.h"
#include <memory>
#include <string>
#include "test/gtest_and_gmock.h"
#include "perfetto/tracing/core/data_source_config.h"
#include "perfetto/tracing/core/trace_config.h"
#include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
#include "protos/perfetto/config/test_config.gen.h"
namespace perfetto {
namespace {
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::StrictMock;
class MockErrorReporter : public ErrorReporter {
public:
MockErrorReporter() {}
~MockErrorReporter() override = default;
MOCK_METHOD(void,
AddError,
(size_t line,
size_t column_start,
size_t column_end,
const std::string& message),
(override));
};
TraceConfig ToProto(const std::string& input) {
StrictMock<MockErrorReporter> reporter;
std::vector<uint8_t> output = PbtxtToPb(input, &reporter);
EXPECT_FALSE(output.empty());
TraceConfig config;
config.ParseFromArray(output.data(), output.size());
return config;
}
void ToErrors(const std::string& input, MockErrorReporter* reporter) {
std::vector<uint8_t> output = PbtxtToPb(input, reporter);
}
TEST(PbtxtToPb, OneField) {
TraceConfig config = ToProto(R"(
duration_ms: 1234
)");
EXPECT_EQ(config.duration_ms(), 1234u);
}
TEST(PbtxtToPb, TwoFields) {
TraceConfig config = ToProto(R"(
duration_ms: 1234
file_write_period_ms: 5678
)");
EXPECT_EQ(config.duration_ms(), 1234u);
EXPECT_EQ(config.file_write_period_ms(), 5678u);
}
TEST(PbtxtToPb, Enum) {
TraceConfig config = ToProto(R"(
compression_type: COMPRESSION_TYPE_DEFLATE
)");
EXPECT_EQ(config.compression_type(), 1);
}
TEST(PbtxtToPb, LastCharacters) {
EXPECT_EQ(ToProto(R"(
duration_ms: 123;)")
.duration_ms(),
123u);
EXPECT_EQ(ToProto(R"(
duration_ms: 123
)")
.duration_ms(),
123u);
EXPECT_EQ(ToProto(R"(
duration_ms: 123#)")
.duration_ms(),
123u);
EXPECT_EQ(ToProto(R"(
duration_ms: 123 )")
.duration_ms(),
123u);
EXPECT_EQ(ToProto(R"(
compression_type: COMPRESSION_TYPE_DEFLATE;)")
.compression_type(),
1);
EXPECT_EQ(ToProto(R"(
compression_type: COMPRESSION_TYPE_DEFLATE
)")
.compression_type(),
1);
EXPECT_EQ(ToProto(R"(
compression_type: COMPRESSION_TYPE_DEFLATE#)")
.compression_type(),
1);
EXPECT_EQ(ToProto(R"(
compression_type: COMPRESSION_TYPE_DEFLATE )")
.compression_type(),
1);
}
TEST(PbtxtToPb, Semicolons) {
TraceConfig config = ToProto(R"(
duration_ms: 1234;
file_write_period_ms: 5678;
)");
EXPECT_EQ(config.duration_ms(), 1234u);
EXPECT_EQ(config.file_write_period_ms(), 5678u);
}
TEST(PbtxtToPb, NestedMessage) {
TraceConfig config = ToProto(R"(
buffers: {
size_kb: 123
}
)");
ASSERT_EQ(config.buffers().size(), 1u);
EXPECT_EQ(config.buffers()[0].size_kb(), 123u);
}
TEST(PbtxtToPb, SplitNested) {
TraceConfig config = ToProto(R"(
buffers: {
size_kb: 1
}
duration_ms: 1000;
buffers: {
size_kb: 2
}
)");
ASSERT_EQ(config.buffers().size(), 2u);
EXPECT_EQ(config.buffers()[0].size_kb(), 1u);
EXPECT_EQ(config.buffers()[1].size_kb(), 2u);
EXPECT_EQ(config.duration_ms(), 1000u);
}
TEST(PbtxtToPb, MultipleNestedMessage) {
TraceConfig config = ToProto(R"(
buffers: {
size_kb: 1
}
buffers: {
size_kb: 2
}
)");
ASSERT_EQ(config.buffers().size(), 2u);
EXPECT_EQ(config.buffers()[0].size_kb(), 1u);
EXPECT_EQ(config.buffers()[1].size_kb(), 2u);
}
TEST(PbtxtToPb, NestedMessageCrossFile) {
TraceConfig config = ToProto(R"(
data_sources {
config {
ftrace_config {
drain_period_ms: 42
}
}
}
)");
protos::gen::FtraceConfig ftrace_config;
ASSERT_TRUE(ftrace_config.ParseFromString(
config.data_sources()[0].config().ftrace_config_raw()));
ASSERT_EQ(ftrace_config.drain_period_ms(), 42u);
}
TEST(PbtxtToPb, Booleans) {
TraceConfig config = ToProto(R"(
write_into_file: false; deferred_start: true;
)");
EXPECT_EQ(config.write_into_file(), false);
EXPECT_EQ(config.deferred_start(), true);
}
TEST(PbtxtToPb, Comments) {
TraceConfig config = ToProto(R"(
write_into_file: false # deferred_start: true;
buffers# 1
# 2
:# 3
# 4
{# 5
# 6
fill_policy# 7
# 8
:# 9
# 10
RING_BUFFER# 11
# 12
;# 13
# 14
} # 15
# 16
)");
EXPECT_EQ(config.write_into_file(), false);
EXPECT_EQ(config.deferred_start(), false);
}
TEST(PbtxtToPb, Enums) {
TraceConfig config = ToProto(R"(
buffers: {
fill_policy: RING_BUFFER
}
)");
const auto kRingBuffer = TraceConfig::BufferConfig::RING_BUFFER;
EXPECT_EQ(config.buffers()[0].fill_policy(), kRingBuffer);
}
TEST(PbtxtToPb, AllFieldTypes) {
TraceConfig config = ToProto(R"(
data_sources {
config {
for_testing {
dummy_fields {
field_uint32: 1;
field_uint64: 2;
field_int32: 3;
field_int64: 4;
field_fixed64: 5;
field_sfixed64: 6;
field_fixed32: 7;
field_sfixed32: 8;
field_double: 9.9;
field_float: 10.10;
field_sint64: 11;
field_sint32: 12;
field_string: "13";
field_bytes: "14";
}
}
}
}
)");
const auto& fields =
config.data_sources()[0].config().for_testing().dummy_fields();
ASSERT_EQ(fields.field_uint32(), 1u);
ASSERT_EQ(fields.field_uint64(), 2u);
ASSERT_EQ(fields.field_int32(), 3);
ASSERT_EQ(fields.field_int64(), 4);
ASSERT_EQ(fields.field_fixed64(), 5u);
ASSERT_EQ(fields.field_sfixed64(), 6);
ASSERT_EQ(fields.field_fixed32(), 7u);
ASSERT_EQ(fields.field_sfixed32(), 8);
ASSERT_DOUBLE_EQ(fields.field_double(), 9.9);
ASSERT_FLOAT_EQ(fields.field_float(), 10.10f);
ASSERT_EQ(fields.field_sint64(), 11);
ASSERT_EQ(fields.field_sint32(), 12);
ASSERT_EQ(fields.field_string(), "13");
ASSERT_EQ(fields.field_bytes(), "14");
}
TEST(PbtxtToPb, LeadingDots) {
TraceConfig config = ToProto(R"(
data_sources {
config {
for_testing {
dummy_fields {
field_double: .1;
field_float: .2;
}
}
}
}
)");
const auto& fields =
config.data_sources()[0].config().for_testing().dummy_fields();
ASSERT_DOUBLE_EQ(fields.field_double(), .1);
ASSERT_FLOAT_EQ(fields.field_float(), .2f);
}
TEST(PbtxtToPb, NegativeNumbers) {
TraceConfig config = ToProto(R"(
data_sources {
config {
for_testing {
dummy_fields {
field_int32: -1;
field_int64: -2;
field_fixed64: -3;
field_sfixed64: -4;
field_fixed32: -5;
field_sfixed32: -6;
field_double: -7.7;
field_float: -8.8;
field_sint64: -9;
field_sint32: -10;
}
}
}
}
)");
const auto& fields =
config.data_sources()[0].config().for_testing().dummy_fields();
ASSERT_EQ(fields.field_int32(), -1);
ASSERT_EQ(fields.field_int64(), -2);
ASSERT_EQ(fields.field_fixed64(), static_cast<uint64_t>(-3));
ASSERT_EQ(fields.field_sfixed64(), -4);
ASSERT_EQ(fields.field_fixed32(), static_cast<uint32_t>(-5));
ASSERT_EQ(fields.field_sfixed32(), -6);
ASSERT_DOUBLE_EQ(fields.field_double(), -7.7);
ASSERT_FLOAT_EQ(fields.field_float(), -8.8f);
ASSERT_EQ(fields.field_sint64(), -9);
ASSERT_EQ(fields.field_sint32(), -10);
}
TEST(PbtxtToPb, EofEndsNumeric) {
TraceConfig config = ToProto(R"(duration_ms: 1234)");
EXPECT_EQ(config.duration_ms(), 1234u);
}
TEST(PbtxtToPb, EofEndsIdentifier) {
TraceConfig config = ToProto(R"(enable_extra_guardrails: true)");
EXPECT_EQ(config.enable_extra_guardrails(), true);
}
TEST(PbtxtToPb, ExampleConfig) {
TraceConfig config = ToProto(R"(
buffers {
size_kb: 100024
fill_policy: RING_BUFFER
}
data_sources {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
buffer_size_kb: 512 # 4 (page size) * 128
drain_period_ms: 200
ftrace_events: "binder_lock"
ftrace_events: "binder_locked"
atrace_categories: "gfx"
}
}
}
data_sources {
config {
name: "linux.process_stats"
target_buffer: 0
}
}
data_sources {
config {
name: "linux.inode_file_map"
target_buffer: 0
inode_file_config {
scan_delay_ms: 1000
scan_interval_ms: 1000
scan_batch_size: 500
mount_point_mapping: {
mountpoint: "/data"
scan_roots: "/data/app"
}
}
}
}
producers {
producer_name: "perfetto.traced_probes"
shm_size_kb: 4096
page_size_kb: 4
}
duration_ms: 10000
)");
EXPECT_EQ(config.duration_ms(), 10000u);
EXPECT_EQ(config.buffers()[0].size_kb(), 100024u);
EXPECT_EQ(config.data_sources()[0].config().name(), "linux.ftrace");
EXPECT_EQ(config.data_sources()[0].config().target_buffer(), 0u);
EXPECT_EQ(config.producers()[0].producer_name(), "perfetto.traced_probes");
}
TEST(PbtxtToPb, Strings) {
TraceConfig config = ToProto(R"(
data_sources {
config {
ftrace_config {
ftrace_events: "binder_lock"
ftrace_events: "foo/bar"
ftrace_events: "foo\\bar"
ftrace_events: "newline\nnewline"
ftrace_events: "\"quoted\""
ftrace_events: "\a\b\f\n\r\t\v\\\'\"\?"
ftrace_events: "\0127_\03422.\177"
}
}
}
)");
protos::gen::FtraceConfig ftrace_config;
ASSERT_TRUE(ftrace_config.ParseFromString(
config.data_sources()[0].config().ftrace_config_raw()));
const auto& events = ftrace_config.ftrace_events();
EXPECT_THAT(events, Contains("binder_lock"));
EXPECT_THAT(events, Contains("foo/bar"));
EXPECT_THAT(events, Contains("foo\\bar"));
EXPECT_THAT(events, Contains("newline\nnewline"));
EXPECT_THAT(events, Contains("\"quoted\""));
EXPECT_THAT(events, Contains("\a\b\f\n\r\t\v\\\'\"\?"));
EXPECT_THAT(events, Contains("\0127_\03422.\177"));
}
TEST(PbtxtToPb, UnknownField) {
MockErrorReporter reporter;
EXPECT_CALL(reporter,
AddError(2, 5, 11,
"No field named \"not_a_label\" in proto TraceConfig"));
ToErrors(R"(
not_a_label: false
)",
&reporter);
}
TEST(PbtxtToPb, UnknownNestedField) {
MockErrorReporter reporter;
EXPECT_CALL(
reporter,
AddError(
4, 5, 16,
"No field named \"not_a_field_name\" in proto DataSourceConfig"));
ToErrors(R"(
data_sources {
config {
not_a_field_name {
}
}
}
)",
&reporter);
}
TEST(PbtxtToPb, BadBoolean) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(2, 22, 3,
"Expected 'true' or 'false' for boolean field "
"write_into_file in proto TraceConfig instead "
"saw 'foo'"));
ToErrors(R"(
write_into_file: foo;
)",
&reporter);
}
TEST(PbtxtToPb, MissingBoolean) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(3, 3, 0, "Unexpected end of input"));
ToErrors(R"(
write_into_file:
)",
&reporter);
}
TEST(PbtxtToPb, RootProtoMustNotEndWithBrace) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(2, 5, 0, "Unmatched closing brace"));
ToErrors(R"(
}
)",
&reporter);
}
TEST(PbtxtToPb, SawNonRepeatedFieldTwice) {
MockErrorReporter reporter;
EXPECT_CALL(
reporter,
AddError(3, 5, 15,
"Saw non-repeating field 'write_into_file' more than once"));
ToErrors(R"(
write_into_file: true;
write_into_file: true;
)",
&reporter);
}
TEST(PbtxtToPb, WrongTypeBoolean) {
MockErrorReporter reporter;
EXPECT_CALL(reporter,
AddError(2, 18, 4,
"Expected value of type uint32 for field duration_ms in "
"proto TraceConfig instead saw 'true'"));
ToErrors(R"(
duration_ms: true;
)",
&reporter);
}
TEST(PbtxtToPb, WrongTypeNumber) {
MockErrorReporter reporter;
EXPECT_CALL(reporter,
AddError(2, 14, 3,
"Expected value of type message for field buffers in "
"proto TraceConfig instead saw '100'"));
ToErrors(R"(
buffers: 100;
)",
&reporter);
}
TEST(PbtxtToPb, NestedMessageDidNotTerminate) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(2, 15, 0, "Nested message not closed"));
ToErrors(R"(
buffers: {)",
&reporter);
}
TEST(PbtxtToPb, BadEscape) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(5, 23, 2,
"Unknown string escape in ftrace_events in "
"proto FtraceConfig: '\\p'"));
ToErrors(R"(
data_sources {
config {
ftrace_config {
ftrace_events: "\p"
}
}
})",
&reporter);
}
TEST(PbtxtToPb, BadEnumValue) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(1, 18, 3,
"Unexpected value 'FOO' for enum field "
"compression_type in proto TraceConfig"));
ToErrors(R"(compression_type: FOO)", &reporter);
}
TEST(PbtxtToPb, UnexpectedBracket) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(1, 0, 0, "Unexpected character '{'"));
ToErrors(R"({)", &reporter);
}
TEST(PbtxtToPb, UnknownNested) {
MockErrorReporter reporter;
EXPECT_CALL(reporter, AddError(1, 0, 3,
"No field named \"foo\" in "
"proto TraceConfig"));
ToErrors(R"(foo {}; bar: 42)", &reporter);
}
// TODO(hjd): Add these tests.
// TEST(PbtxtToPb, WrongTypeString)
// TEST(PbtxtToPb, OverflowOnIntegers)
// TEST(PbtxtToPb, NegativeNumbersForUnsignedInt)
// TEST(PbtxtToPb, UnterminatedString) {
// TEST(PbtxtToPb, NumberIsEof)
// TEST(PbtxtToPb, OneOf)
} // namespace
} // namespace perfetto