1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/perfetto_cmd/pbtxt_to_pb.h"
18
19 #include <memory>
20 #include <string>
21
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24
25 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
26 #include "perfetto/config/trace_config.pb.h"
27
28 namespace perfetto {
29 namespace {
30
31 using ::testing::StrictMock;
32 using ::testing::Contains;
33 using ::testing::ElementsAre;
34 using ::google::protobuf::io::ZeroCopyInputStream;
35 using ::google::protobuf::io::ArrayInputStream;
36
37 class MockErrorReporter : public ErrorReporter {
38 public:
MockErrorReporter()39 MockErrorReporter() {}
40 ~MockErrorReporter() = default;
41 MOCK_METHOD4(AddError,
42 void(size_t line,
43 size_t column_start,
44 size_t column_end,
45 const std::string& message));
46 };
47
ToProto(const std::string & input)48 protos::TraceConfig ToProto(const std::string& input) {
49 StrictMock<MockErrorReporter> reporter;
50 std::vector<uint8_t> output = PbtxtToPb(input, &reporter);
51 EXPECT_FALSE(output.empty());
52 protos::TraceConfig config;
53 config.ParseFromArray(output.data(), static_cast<int>(output.size()));
54 return config;
55 }
56
ToErrors(const std::string & input,MockErrorReporter * reporter)57 void ToErrors(const std::string& input, MockErrorReporter* reporter) {
58 std::vector<uint8_t> output = PbtxtToPb(input, reporter);
59 }
60
TEST(PbtxtToPb,OneField)61 TEST(PbtxtToPb, OneField) {
62 protos::TraceConfig config = ToProto(R"(
63 duration_ms: 1234
64 )");
65 EXPECT_EQ(config.duration_ms(), 1234);
66 }
67
TEST(PbtxtToPb,TwoFields)68 TEST(PbtxtToPb, TwoFields) {
69 protos::TraceConfig config = ToProto(R"(
70 duration_ms: 1234
71 file_write_period_ms: 5678
72 )");
73 EXPECT_EQ(config.duration_ms(), 1234);
74 EXPECT_EQ(config.file_write_period_ms(), 5678);
75 }
76
TEST(PbtxtToPb,Semicolons)77 TEST(PbtxtToPb, Semicolons) {
78 protos::TraceConfig config = ToProto(R"(
79 duration_ms: 1234;
80 file_write_period_ms: 5678;
81 )");
82 EXPECT_EQ(config.duration_ms(), 1234);
83 EXPECT_EQ(config.file_write_period_ms(), 5678);
84 }
85
TEST(PbtxtToPb,NestedMessage)86 TEST(PbtxtToPb, NestedMessage) {
87 protos::TraceConfig config = ToProto(R"(
88 buffers: {
89 size_kb: 123
90 }
91 )");
92 ASSERT_EQ(config.buffers().size(), 1);
93 EXPECT_EQ(config.buffers().Get(0).size_kb(), 123);
94 }
95
TEST(PbtxtToPb,SplitNested)96 TEST(PbtxtToPb, SplitNested) {
97 protos::TraceConfig config = ToProto(R"(
98 buffers: {
99 size_kb: 1
100 }
101 duration_ms: 1000;
102 buffers: {
103 size_kb: 2
104 }
105 )");
106 ASSERT_EQ(config.buffers().size(), 2);
107 EXPECT_EQ(config.buffers().Get(0).size_kb(), 1);
108 EXPECT_EQ(config.buffers().Get(1).size_kb(), 2);
109 EXPECT_EQ(config.duration_ms(), 1000);
110 }
111
TEST(PbtxtToPb,MultipleNestedMessage)112 TEST(PbtxtToPb, MultipleNestedMessage) {
113 protos::TraceConfig config = ToProto(R"(
114 buffers: {
115 size_kb: 1
116 }
117 buffers: {
118 size_kb: 2
119 }
120 )");
121 ASSERT_EQ(config.buffers().size(), 2);
122 EXPECT_EQ(config.buffers().Get(0).size_kb(), 1);
123 EXPECT_EQ(config.buffers().Get(1).size_kb(), 2);
124 }
125
TEST(PbtxtToPb,NestedMessageCrossFile)126 TEST(PbtxtToPb, NestedMessageCrossFile) {
127 protos::TraceConfig config = ToProto(R"(
128 data_sources {
129 config {
130 ftrace_config {
131 drain_period_ms: 42
132 }
133 }
134 }
135 )");
136 ASSERT_EQ(
137 config.data_sources().Get(0).config().ftrace_config().drain_period_ms(),
138 42);
139 }
140
TEST(PbtxtToPb,Booleans)141 TEST(PbtxtToPb, Booleans) {
142 protos::TraceConfig config = ToProto(R"(
143 write_into_file: false; deferred_start: true;
144 )");
145 EXPECT_EQ(config.write_into_file(), false);
146 EXPECT_EQ(config.deferred_start(), true);
147 }
148
TEST(PbtxtToPb,Comments)149 TEST(PbtxtToPb, Comments) {
150 protos::TraceConfig config = ToProto(R"(
151 write_into_file: false # deferred_start: true;
152 buffers# 1
153 # 2
154 :# 3
155 # 4
156 {# 5
157 # 6
158 fill_policy# 7
159 # 8
160 :# 9
161 # 10
162 RING_BUFFER# 11
163 # 12
164 ;# 13
165 # 14
166 } # 15
167 # 16
168 )");
169 EXPECT_EQ(config.write_into_file(), false);
170 EXPECT_EQ(config.deferred_start(), false);
171 }
172
TEST(PbtxtToPb,Enums)173 TEST(PbtxtToPb, Enums) {
174 protos::TraceConfig config = ToProto(R"(
175 buffers: {
176 fill_policy: RING_BUFFER
177 }
178 )");
179 EXPECT_EQ(config.buffers().Get(0).fill_policy(),
180 protos::TraceConfig::BufferConfig::RING_BUFFER);
181 }
182
TEST(PbtxtToPb,AllFieldTypes)183 TEST(PbtxtToPb, AllFieldTypes) {
184 protos::TraceConfig config = ToProto(R"(
185 data_sources {
186 config {
187 for_testing {
188 dummy_fields {
189 field_uint32: 1;
190 field_uint64: 2;
191 field_int32: 3;
192 field_int64: 4;
193 field_fixed64: 5;
194 field_sfixed64: 6;
195 field_fixed32: 7;
196 field_sfixed32: 8;
197 field_double: 9;
198 field_float: 10;
199 field_sint64: 11;
200 field_sint32: 12;
201 field_string: "13";
202 field_bytes: "14";
203 }
204 }
205 }
206 }
207 )");
208 const auto& fields =
209 config.data_sources().Get(0).config().for_testing().dummy_fields();
210 ASSERT_EQ(fields.field_uint32(), 1);
211 ASSERT_EQ(fields.field_uint64(), 2);
212 ASSERT_EQ(fields.field_int32(), 3);
213 ASSERT_EQ(fields.field_int64(), 4);
214 ASSERT_EQ(fields.field_fixed64(), 5);
215 ASSERT_EQ(fields.field_sfixed64(), 6);
216 ASSERT_EQ(fields.field_fixed32(), 7);
217 ASSERT_EQ(fields.field_sfixed32(), 8);
218 ASSERT_EQ(fields.field_double(), 9);
219 ASSERT_EQ(fields.field_float(), 10);
220 ASSERT_EQ(fields.field_sint64(), 11);
221 ASSERT_EQ(fields.field_sint32(), 12);
222 ASSERT_EQ(fields.field_string(), "13");
223 ASSERT_EQ(fields.field_bytes(), "14");
224 }
225
TEST(PbtxtToPb,NegativeNumbers)226 TEST(PbtxtToPb, NegativeNumbers) {
227 protos::TraceConfig config = ToProto(R"(
228 data_sources {
229 config {
230 for_testing {
231 dummy_fields {
232 field_int32: -1;
233 field_int64: -2;
234 field_fixed64: -3;
235 field_sfixed64: -4;
236 field_fixed32: -5;
237 field_sfixed32: -6;
238 field_double: -7;
239 field_float: -8;
240 field_sint64: -9;
241 field_sint32: -10;
242 }
243 }
244 }
245 }
246 )");
247 const auto& fields =
248 config.data_sources().Get(0).config().for_testing().dummy_fields();
249 ASSERT_EQ(fields.field_int32(), -1);
250 ASSERT_EQ(fields.field_int64(), -2);
251 ASSERT_EQ(fields.field_fixed64(), -3);
252 ASSERT_EQ(fields.field_sfixed64(), -4);
253 ASSERT_EQ(fields.field_fixed32(), -5);
254 ASSERT_EQ(fields.field_sfixed32(), -6);
255 ASSERT_EQ(fields.field_double(), -7);
256 ASSERT_EQ(fields.field_float(), -8);
257 ASSERT_EQ(fields.field_sint64(), -9);
258 ASSERT_EQ(fields.field_sint32(), -10);
259 }
260
TEST(PbtxtToPb,EofEndsNumeric)261 TEST(PbtxtToPb, EofEndsNumeric) {
262 protos::TraceConfig config = ToProto(R"(duration_ms: 1234)");
263 EXPECT_EQ(config.duration_ms(), 1234);
264 }
265
TEST(PbtxtToPb,EofEndsIdentifier)266 TEST(PbtxtToPb, EofEndsIdentifier) {
267 protos::TraceConfig config = ToProto(R"(enable_extra_guardrails: true)");
268 EXPECT_EQ(config.enable_extra_guardrails(), true);
269 }
270
TEST(PbtxtToPb,ExampleConfig)271 TEST(PbtxtToPb, ExampleConfig) {
272 protos::TraceConfig config = ToProto(R"(
273 buffers {
274 size_kb: 100024
275 fill_policy: RING_BUFFER
276 }
277
278 data_sources {
279 config {
280 name: "linux.ftrace"
281 target_buffer: 0
282 ftrace_config {
283 buffer_size_kb: 512 # 4 (page size) * 128
284 drain_period_ms: 200
285 ftrace_events: "binder_lock"
286 ftrace_events: "binder_locked"
287 atrace_categories: "gfx"
288 }
289 }
290 }
291
292 data_sources {
293 config {
294 name: "linux.process_stats"
295 target_buffer: 0
296 }
297 }
298
299 data_sources {
300 config {
301 name: "linux.inode_file_map"
302 target_buffer: 0
303 inode_file_config {
304 scan_delay_ms: 1000
305 scan_interval_ms: 1000
306 scan_batch_size: 500
307 mount_point_mapping: {
308 mountpoint: "/data"
309 scan_roots: "/data/app"
310 }
311 }
312 }
313 }
314
315 producers {
316 producer_name: "perfetto.traced_probes"
317 shm_size_kb: 4096
318 page_size_kb: 4
319 }
320
321 duration_ms: 10000
322 )");
323 EXPECT_EQ(config.duration_ms(), 10000);
324 EXPECT_EQ(config.buffers().Get(0).size_kb(), 100024);
325 EXPECT_EQ(config.data_sources().Get(0).config().name(), "linux.ftrace");
326 EXPECT_EQ(config.data_sources().Get(0).config().target_buffer(), 0);
327 EXPECT_EQ(config.producers().Get(0).producer_name(),
328 "perfetto.traced_probes");
329 }
330
TEST(PbtxtToPb,Strings)331 TEST(PbtxtToPb, Strings) {
332 protos::TraceConfig config = ToProto(R"(
333 data_sources {
334 config {
335 ftrace_config {
336 ftrace_events: "binder_lock"
337 ftrace_events: "foo/bar"
338 ftrace_events: "foo\\bar"
339 ftrace_events: "newline\nnewline"
340 ftrace_events: "\"quoted\""
341 ftrace_events: "\a\b\f\n\r\t\v\\\'\"\?"
342 }
343 }
344 }
345 )");
346 auto events =
347 config.data_sources().Get(0).config().ftrace_config().ftrace_events();
348 EXPECT_THAT(events, Contains("binder_lock"));
349 EXPECT_THAT(events, Contains("foo/bar"));
350 EXPECT_THAT(events, Contains("foo\\bar"));
351 EXPECT_THAT(events, Contains("newline\nnewline"));
352 EXPECT_THAT(events, Contains("\"quoted\""));
353 EXPECT_THAT(events, Contains("\a\b\f\n\r\t\v\\\'\"\?"));
354 }
355
TEST(PbtxtToPb,UnknownField)356 TEST(PbtxtToPb, UnknownField) {
357 MockErrorReporter reporter;
358 EXPECT_CALL(reporter,
359 AddError(2, 5, 11,
360 "No field named \"not_a_label\" in proto TraceConfig"));
361 ToErrors(R"(
362 not_a_label: false
363 )",
364 &reporter);
365 }
366
TEST(PbtxtToPb,UnknownNestedField)367 TEST(PbtxtToPb, UnknownNestedField) {
368 MockErrorReporter reporter;
369 EXPECT_CALL(
370 reporter,
371 AddError(
372 4, 5, 16,
373 "No field named \"not_a_field_name\" in proto DataSourceConfig"));
374 ToErrors(R"(
375 data_sources {
376 config {
377 not_a_field_name {
378 }
379 }
380 }
381 )",
382 &reporter);
383 }
384
TEST(PbtxtToPb,BadBoolean)385 TEST(PbtxtToPb, BadBoolean) {
386 MockErrorReporter reporter;
387 EXPECT_CALL(reporter, AddError(2, 22, 3,
388 "Expected 'true' or 'false' for boolean field "
389 "write_into_file in proto TraceConfig instead "
390 "saw 'foo'"));
391 ToErrors(R"(
392 write_into_file: foo;
393 )",
394 &reporter);
395 }
396
TEST(PbtxtToPb,MissingBoolean)397 TEST(PbtxtToPb, MissingBoolean) {
398 MockErrorReporter reporter;
399 EXPECT_CALL(reporter, AddError(3, 3, 0, "Unexpected end of input"));
400 ToErrors(R"(
401 write_into_file:
402 )",
403 &reporter);
404 }
405
TEST(PbtxtToPb,RootProtoMustNotEndWithBrace)406 TEST(PbtxtToPb, RootProtoMustNotEndWithBrace) {
407 MockErrorReporter reporter;
408 EXPECT_CALL(reporter, AddError(2, 5, 0, "Unmatched closing brace"));
409 ToErrors(R"(
410 }
411 )",
412 &reporter);
413 }
414
TEST(PbtxtToPb,SawNonRepeatedFieldTwice)415 TEST(PbtxtToPb, SawNonRepeatedFieldTwice) {
416 MockErrorReporter reporter;
417 EXPECT_CALL(
418 reporter,
419 AddError(3, 5, 15,
420 "Saw non-repeating field 'write_into_file' more than once"));
421 ToErrors(R"(
422 write_into_file: true;
423 write_into_file: true;
424 )",
425 &reporter);
426 }
427
TEST(PbtxtToPb,WrongTypeBoolean)428 TEST(PbtxtToPb, WrongTypeBoolean) {
429 MockErrorReporter reporter;
430 EXPECT_CALL(reporter,
431 AddError(2, 18, 4,
432 "Expected value of type uint32 for field duration_ms in "
433 "proto TraceConfig instead saw 'true'"));
434 ToErrors(R"(
435 duration_ms: true;
436 )",
437 &reporter);
438 }
439
TEST(PbtxtToPb,WrongTypeNumber)440 TEST(PbtxtToPb, WrongTypeNumber) {
441 MockErrorReporter reporter;
442 EXPECT_CALL(reporter,
443 AddError(2, 14, 3,
444 "Expected value of type message for field buffers in "
445 "proto TraceConfig instead saw '100'"));
446 ToErrors(R"(
447 buffers: 100;
448 )",
449 &reporter);
450 }
451
TEST(PbtxtToPb,NestedMessageDidNotTerminate)452 TEST(PbtxtToPb, NestedMessageDidNotTerminate) {
453 MockErrorReporter reporter;
454 EXPECT_CALL(reporter, AddError(2, 15, 0, "Nested message not closed"));
455 ToErrors(R"(
456 buffers: {)",
457 &reporter);
458 }
459
TEST(PbtxtToPb,BadEscape)460 TEST(PbtxtToPb, BadEscape) {
461 MockErrorReporter reporter;
462 EXPECT_CALL(reporter, AddError(5, 23, 2,
463 "Unknown string escape in ftrace_events in "
464 "proto FtraceConfig: '\\p'"));
465 ToErrors(R"(
466 data_sources {
467 config {
468 ftrace_config {
469 ftrace_events: "\p"
470 }
471 }
472 })",
473 &reporter);
474 }
475
476 // TODO(hjd): Add these tests.
477 // TEST(PbtxtToPb, WrongTypeString)
478 // TEST(PbtxtToPb, OverflowOnIntegers)
479 // TEST(PbtxtToPb, NegativeNumbersForUnsignedInt)
480 // TEST(PbtxtToPb, UnterminatedString) {
481 // TEST(PbtxtToPb, NumberIsEof)
482 // TEST(PbtxtToPb, OneOf)
483
484 } // namespace
485 } // namespace perfetto
486