| /* |
| * 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 |