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 "test/gtest_and_gmock.h"
23
24 #include "perfetto/tracing/core/data_source_config.h"
25 #include "perfetto/tracing/core/trace_config.h"
26
27 #include "protos/perfetto/config/ftrace/ftrace_config.gen.h"
28 #include "protos/perfetto/config/test_config.gen.h"
29
30 namespace perfetto {
31 namespace {
32
33 using ::testing::Contains;
34 using ::testing::ElementsAre;
35 using ::testing::StrictMock;
36
37 class MockErrorReporter : public ErrorReporter {
38 public:
MockErrorReporter()39 MockErrorReporter() {}
40 ~MockErrorReporter() override = default;
41 MOCK_METHOD(void,
42 AddError,
43 (size_t line,
44 size_t column_start,
45 size_t column_end,
46 const std::string& message),
47 (override));
48 };
49
ToProto(const std::string & input)50 TraceConfig ToProto(const std::string& input) {
51 StrictMock<MockErrorReporter> reporter;
52 std::vector<uint8_t> output = PbtxtToPb(input, &reporter);
53 EXPECT_FALSE(output.empty());
54 TraceConfig config;
55 config.ParseFromArray(output.data(), output.size());
56 return config;
57 }
58
ToErrors(const std::string & input,MockErrorReporter * reporter)59 void ToErrors(const std::string& input, MockErrorReporter* reporter) {
60 std::vector<uint8_t> output = PbtxtToPb(input, reporter);
61 }
62
TEST(PbtxtToPb,OneField)63 TEST(PbtxtToPb, OneField) {
64 TraceConfig config = ToProto(R"(
65 duration_ms: 1234
66 )");
67 EXPECT_EQ(config.duration_ms(), 1234u);
68 }
69
TEST(PbtxtToPb,TwoFields)70 TEST(PbtxtToPb, TwoFields) {
71 TraceConfig config = ToProto(R"(
72 duration_ms: 1234
73 file_write_period_ms: 5678
74 )");
75 EXPECT_EQ(config.duration_ms(), 1234u);
76 EXPECT_EQ(config.file_write_period_ms(), 5678u);
77 }
78
TEST(PbtxtToPb,Enum)79 TEST(PbtxtToPb, Enum) {
80 TraceConfig config = ToProto(R"(
81 compression_type: COMPRESSION_TYPE_DEFLATE
82 )");
83 EXPECT_EQ(config.compression_type(), 1);
84 }
85
TEST(PbtxtToPb,LastCharacters)86 TEST(PbtxtToPb, LastCharacters) {
87 EXPECT_EQ(ToProto(R"(
88 duration_ms: 123;)")
89 .duration_ms(),
90 123u);
91 EXPECT_EQ(ToProto(R"(
92 duration_ms: 123
93 )")
94 .duration_ms(),
95 123u);
96 EXPECT_EQ(ToProto(R"(
97 duration_ms: 123#)")
98 .duration_ms(),
99 123u);
100 EXPECT_EQ(ToProto(R"(
101 duration_ms: 123 )")
102 .duration_ms(),
103 123u);
104
105 EXPECT_EQ(ToProto(R"(
106 compression_type: COMPRESSION_TYPE_DEFLATE;)")
107 .compression_type(),
108 1);
109 EXPECT_EQ(ToProto(R"(
110 compression_type: COMPRESSION_TYPE_DEFLATE
111 )")
112 .compression_type(),
113 1);
114 EXPECT_EQ(ToProto(R"(
115 compression_type: COMPRESSION_TYPE_DEFLATE#)")
116 .compression_type(),
117 1);
118 EXPECT_EQ(ToProto(R"(
119 compression_type: COMPRESSION_TYPE_DEFLATE )")
120 .compression_type(),
121 1);
122 }
123
TEST(PbtxtToPb,Semicolons)124 TEST(PbtxtToPb, Semicolons) {
125 TraceConfig config = ToProto(R"(
126 duration_ms: 1234;
127 file_write_period_ms: 5678;
128 )");
129 EXPECT_EQ(config.duration_ms(), 1234u);
130 EXPECT_EQ(config.file_write_period_ms(), 5678u);
131 }
132
TEST(PbtxtToPb,NestedMessage)133 TEST(PbtxtToPb, NestedMessage) {
134 TraceConfig config = ToProto(R"(
135 buffers: {
136 size_kb: 123
137 }
138 )");
139 ASSERT_EQ(config.buffers().size(), 1u);
140 EXPECT_EQ(config.buffers()[0].size_kb(), 123u);
141 }
142
TEST(PbtxtToPb,SplitNested)143 TEST(PbtxtToPb, SplitNested) {
144 TraceConfig config = ToProto(R"(
145 buffers: {
146 size_kb: 1
147 }
148 duration_ms: 1000;
149 buffers: {
150 size_kb: 2
151 }
152 )");
153 ASSERT_EQ(config.buffers().size(), 2u);
154 EXPECT_EQ(config.buffers()[0].size_kb(), 1u);
155 EXPECT_EQ(config.buffers()[1].size_kb(), 2u);
156 EXPECT_EQ(config.duration_ms(), 1000u);
157 }
158
TEST(PbtxtToPb,MultipleNestedMessage)159 TEST(PbtxtToPb, MultipleNestedMessage) {
160 TraceConfig config = ToProto(R"(
161 buffers: {
162 size_kb: 1
163 }
164 buffers: {
165 size_kb: 2
166 }
167 )");
168 ASSERT_EQ(config.buffers().size(), 2u);
169 EXPECT_EQ(config.buffers()[0].size_kb(), 1u);
170 EXPECT_EQ(config.buffers()[1].size_kb(), 2u);
171 }
172
TEST(PbtxtToPb,NestedMessageCrossFile)173 TEST(PbtxtToPb, NestedMessageCrossFile) {
174 TraceConfig config = ToProto(R"(
175 data_sources {
176 config {
177 ftrace_config {
178 drain_period_ms: 42
179 }
180 }
181 }
182 )");
183 protos::gen::FtraceConfig ftrace_config;
184 ASSERT_TRUE(ftrace_config.ParseFromString(
185 config.data_sources()[0].config().ftrace_config_raw()));
186 ASSERT_EQ(ftrace_config.drain_period_ms(), 42u);
187 }
188
TEST(PbtxtToPb,Booleans)189 TEST(PbtxtToPb, Booleans) {
190 TraceConfig config = ToProto(R"(
191 write_into_file: false; deferred_start: true;
192 )");
193 EXPECT_EQ(config.write_into_file(), false);
194 EXPECT_EQ(config.deferred_start(), true);
195 }
196
TEST(PbtxtToPb,Comments)197 TEST(PbtxtToPb, Comments) {
198 TraceConfig config = ToProto(R"(
199 write_into_file: false # deferred_start: true;
200 buffers# 1
201 # 2
202 :# 3
203 # 4
204 {# 5
205 # 6
206 fill_policy# 7
207 # 8
208 :# 9
209 # 10
210 RING_BUFFER# 11
211 # 12
212 ;# 13
213 # 14
214 } # 15
215 # 16
216 )");
217 EXPECT_EQ(config.write_into_file(), false);
218 EXPECT_EQ(config.deferred_start(), false);
219 }
220
TEST(PbtxtToPb,Enums)221 TEST(PbtxtToPb, Enums) {
222 TraceConfig config = ToProto(R"(
223 buffers: {
224 fill_policy: RING_BUFFER
225 }
226 )");
227 const auto kRingBuffer = TraceConfig::BufferConfig::RING_BUFFER;
228 EXPECT_EQ(config.buffers()[0].fill_policy(), kRingBuffer);
229 }
230
TEST(PbtxtToPb,AllFieldTypes)231 TEST(PbtxtToPb, AllFieldTypes) {
232 TraceConfig config = ToProto(R"(
233 data_sources {
234 config {
235 for_testing {
236 dummy_fields {
237 field_uint32: 1;
238 field_uint64: 2;
239 field_int32: 3;
240 field_int64: 4;
241 field_fixed64: 5;
242 field_sfixed64: 6;
243 field_fixed32: 7;
244 field_sfixed32: 8;
245 field_double: 9.9;
246 field_float: 10.10;
247 field_sint64: 11;
248 field_sint32: 12;
249 field_string: "13";
250 field_bytes: "14";
251 }
252 }
253 }
254 }
255 )");
256 const auto& fields =
257 config.data_sources()[0].config().for_testing().dummy_fields();
258 ASSERT_EQ(fields.field_uint32(), 1u);
259 ASSERT_EQ(fields.field_uint64(), 2u);
260 ASSERT_EQ(fields.field_int32(), 3);
261 ASSERT_EQ(fields.field_int64(), 4);
262 ASSERT_EQ(fields.field_fixed64(), 5u);
263 ASSERT_EQ(fields.field_sfixed64(), 6);
264 ASSERT_EQ(fields.field_fixed32(), 7u);
265 ASSERT_EQ(fields.field_sfixed32(), 8);
266 ASSERT_DOUBLE_EQ(fields.field_double(), 9.9);
267 ASSERT_FLOAT_EQ(fields.field_float(), 10.10f);
268 ASSERT_EQ(fields.field_sint64(), 11);
269 ASSERT_EQ(fields.field_sint32(), 12);
270 ASSERT_EQ(fields.field_string(), "13");
271 ASSERT_EQ(fields.field_bytes(), "14");
272 }
273
TEST(PbtxtToPb,LeadingDots)274 TEST(PbtxtToPb, LeadingDots) {
275 TraceConfig config = ToProto(R"(
276 data_sources {
277 config {
278 for_testing {
279 dummy_fields {
280 field_double: .1;
281 field_float: .2;
282 }
283 }
284 }
285 }
286 )");
287 const auto& fields =
288 config.data_sources()[0].config().for_testing().dummy_fields();
289 ASSERT_DOUBLE_EQ(fields.field_double(), .1);
290 ASSERT_FLOAT_EQ(fields.field_float(), .2f);
291 }
292
TEST(PbtxtToPb,NegativeNumbers)293 TEST(PbtxtToPb, NegativeNumbers) {
294 TraceConfig config = ToProto(R"(
295 data_sources {
296 config {
297 for_testing {
298 dummy_fields {
299 field_int32: -1;
300 field_int64: -2;
301 field_fixed64: -3;
302 field_sfixed64: -4;
303 field_fixed32: -5;
304 field_sfixed32: -6;
305 field_double: -7.7;
306 field_float: -8.8;
307 field_sint64: -9;
308 field_sint32: -10;
309 }
310 }
311 }
312 }
313 )");
314 const auto& fields =
315 config.data_sources()[0].config().for_testing().dummy_fields();
316 ASSERT_EQ(fields.field_int32(), -1);
317 ASSERT_EQ(fields.field_int64(), -2);
318 ASSERT_EQ(fields.field_fixed64(), static_cast<uint64_t>(-3));
319 ASSERT_EQ(fields.field_sfixed64(), -4);
320 ASSERT_EQ(fields.field_fixed32(), static_cast<uint32_t>(-5));
321 ASSERT_EQ(fields.field_sfixed32(), -6);
322 ASSERT_DOUBLE_EQ(fields.field_double(), -7.7);
323 ASSERT_FLOAT_EQ(fields.field_float(), -8.8f);
324 ASSERT_EQ(fields.field_sint64(), -9);
325 ASSERT_EQ(fields.field_sint32(), -10);
326 }
327
TEST(PbtxtToPb,EofEndsNumeric)328 TEST(PbtxtToPb, EofEndsNumeric) {
329 TraceConfig config = ToProto(R"(duration_ms: 1234)");
330 EXPECT_EQ(config.duration_ms(), 1234u);
331 }
332
TEST(PbtxtToPb,EofEndsIdentifier)333 TEST(PbtxtToPb, EofEndsIdentifier) {
334 TraceConfig config = ToProto(R"(enable_extra_guardrails: true)");
335 EXPECT_EQ(config.enable_extra_guardrails(), true);
336 }
337
TEST(PbtxtToPb,ExampleConfig)338 TEST(PbtxtToPb, ExampleConfig) {
339 TraceConfig config = ToProto(R"(
340 buffers {
341 size_kb: 100024
342 fill_policy: RING_BUFFER
343 }
344
345 data_sources {
346 config {
347 name: "linux.ftrace"
348 target_buffer: 0
349 ftrace_config {
350 buffer_size_kb: 512 # 4 (page size) * 128
351 drain_period_ms: 200
352 ftrace_events: "binder_lock"
353 ftrace_events: "binder_locked"
354 atrace_categories: "gfx"
355 }
356 }
357 }
358
359 data_sources {
360 config {
361 name: "linux.process_stats"
362 target_buffer: 0
363 }
364 }
365
366 data_sources {
367 config {
368 name: "linux.inode_file_map"
369 target_buffer: 0
370 inode_file_config {
371 scan_delay_ms: 1000
372 scan_interval_ms: 1000
373 scan_batch_size: 500
374 mount_point_mapping: {
375 mountpoint: "/data"
376 scan_roots: "/data/app"
377 }
378 }
379 }
380 }
381
382 producers {
383 producer_name: "perfetto.traced_probes"
384 shm_size_kb: 4096
385 page_size_kb: 4
386 }
387
388 duration_ms: 10000
389 )");
390 EXPECT_EQ(config.duration_ms(), 10000u);
391 EXPECT_EQ(config.buffers()[0].size_kb(), 100024u);
392 EXPECT_EQ(config.data_sources()[0].config().name(), "linux.ftrace");
393 EXPECT_EQ(config.data_sources()[0].config().target_buffer(), 0u);
394 EXPECT_EQ(config.producers()[0].producer_name(), "perfetto.traced_probes");
395 }
396
TEST(PbtxtToPb,Strings)397 TEST(PbtxtToPb, Strings) {
398 TraceConfig config = ToProto(R"(
399 data_sources {
400 config {
401 ftrace_config {
402 ftrace_events: "binder_lock"
403 ftrace_events: "foo/bar"
404 ftrace_events: "foo\\bar"
405 ftrace_events: "newline\nnewline"
406 ftrace_events: "\"quoted\""
407 ftrace_events: "\a\b\f\n\r\t\v\\\'\"\?"
408 ftrace_events: "\0127_\03422.\177"
409 }
410 }
411 }
412 )");
413 protos::gen::FtraceConfig ftrace_config;
414 ASSERT_TRUE(ftrace_config.ParseFromString(
415 config.data_sources()[0].config().ftrace_config_raw()));
416 const auto& events = ftrace_config.ftrace_events();
417 EXPECT_THAT(events, Contains("binder_lock"));
418 EXPECT_THAT(events, Contains("foo/bar"));
419 EXPECT_THAT(events, Contains("foo\\bar"));
420 EXPECT_THAT(events, Contains("newline\nnewline"));
421 EXPECT_THAT(events, Contains("\"quoted\""));
422 EXPECT_THAT(events, Contains("\a\b\f\n\r\t\v\\\'\"\?"));
423 EXPECT_THAT(events, Contains("\0127_\03422.\177"));
424 }
425
TEST(PbtxtToPb,UnknownField)426 TEST(PbtxtToPb, UnknownField) {
427 MockErrorReporter reporter;
428 EXPECT_CALL(reporter,
429 AddError(2, 5, 11,
430 "No field named \"not_a_label\" in proto TraceConfig"));
431 ToErrors(R"(
432 not_a_label: false
433 )",
434 &reporter);
435 }
436
TEST(PbtxtToPb,UnknownNestedField)437 TEST(PbtxtToPb, UnknownNestedField) {
438 MockErrorReporter reporter;
439 EXPECT_CALL(
440 reporter,
441 AddError(
442 4, 5, 16,
443 "No field named \"not_a_field_name\" in proto DataSourceConfig"));
444 ToErrors(R"(
445 data_sources {
446 config {
447 not_a_field_name {
448 }
449 }
450 }
451 )",
452 &reporter);
453 }
454
TEST(PbtxtToPb,BadBoolean)455 TEST(PbtxtToPb, BadBoolean) {
456 MockErrorReporter reporter;
457 EXPECT_CALL(reporter, AddError(2, 22, 3,
458 "Expected 'true' or 'false' for boolean field "
459 "write_into_file in proto TraceConfig instead "
460 "saw 'foo'"));
461 ToErrors(R"(
462 write_into_file: foo;
463 )",
464 &reporter);
465 }
466
TEST(PbtxtToPb,MissingBoolean)467 TEST(PbtxtToPb, MissingBoolean) {
468 MockErrorReporter reporter;
469 EXPECT_CALL(reporter, AddError(3, 3, 0, "Unexpected end of input"));
470 ToErrors(R"(
471 write_into_file:
472 )",
473 &reporter);
474 }
475
TEST(PbtxtToPb,RootProtoMustNotEndWithBrace)476 TEST(PbtxtToPb, RootProtoMustNotEndWithBrace) {
477 MockErrorReporter reporter;
478 EXPECT_CALL(reporter, AddError(2, 5, 0, "Unmatched closing brace"));
479 ToErrors(R"(
480 }
481 )",
482 &reporter);
483 }
484
TEST(PbtxtToPb,SawNonRepeatedFieldTwice)485 TEST(PbtxtToPb, SawNonRepeatedFieldTwice) {
486 MockErrorReporter reporter;
487 EXPECT_CALL(
488 reporter,
489 AddError(3, 5, 15,
490 "Saw non-repeating field 'write_into_file' more than once"));
491 ToErrors(R"(
492 write_into_file: true;
493 write_into_file: true;
494 )",
495 &reporter);
496 }
497
TEST(PbtxtToPb,WrongTypeBoolean)498 TEST(PbtxtToPb, WrongTypeBoolean) {
499 MockErrorReporter reporter;
500 EXPECT_CALL(reporter,
501 AddError(2, 18, 4,
502 "Expected value of type uint32 for field duration_ms in "
503 "proto TraceConfig instead saw 'true'"));
504 ToErrors(R"(
505 duration_ms: true;
506 )",
507 &reporter);
508 }
509
TEST(PbtxtToPb,WrongTypeNumber)510 TEST(PbtxtToPb, WrongTypeNumber) {
511 MockErrorReporter reporter;
512 EXPECT_CALL(reporter,
513 AddError(2, 14, 3,
514 "Expected value of type message for field buffers in "
515 "proto TraceConfig instead saw '100'"));
516 ToErrors(R"(
517 buffers: 100;
518 )",
519 &reporter);
520 }
521
TEST(PbtxtToPb,NestedMessageDidNotTerminate)522 TEST(PbtxtToPb, NestedMessageDidNotTerminate) {
523 MockErrorReporter reporter;
524 EXPECT_CALL(reporter, AddError(2, 15, 0, "Nested message not closed"));
525 ToErrors(R"(
526 buffers: {)",
527 &reporter);
528 }
529
TEST(PbtxtToPb,BadEscape)530 TEST(PbtxtToPb, BadEscape) {
531 MockErrorReporter reporter;
532 EXPECT_CALL(reporter, AddError(5, 23, 2,
533 "Unknown string escape in ftrace_events in "
534 "proto FtraceConfig: '\\p'"));
535 ToErrors(R"(
536 data_sources {
537 config {
538 ftrace_config {
539 ftrace_events: "\p"
540 }
541 }
542 })",
543 &reporter);
544 }
545
TEST(PbtxtToPb,BadEnumValue)546 TEST(PbtxtToPb, BadEnumValue) {
547 MockErrorReporter reporter;
548 EXPECT_CALL(reporter, AddError(1, 18, 3,
549 "Unexpected value 'FOO' for enum field "
550 "compression_type in proto TraceConfig"));
551 ToErrors(R"(compression_type: FOO)", &reporter);
552 }
553
TEST(PbtxtToPb,UnexpectedBracket)554 TEST(PbtxtToPb, UnexpectedBracket) {
555 MockErrorReporter reporter;
556 EXPECT_CALL(reporter, AddError(1, 0, 0, "Unexpected character '{'"));
557 ToErrors(R"({)", &reporter);
558 }
559
TEST(PbtxtToPb,UnknownNested)560 TEST(PbtxtToPb, UnknownNested) {
561 MockErrorReporter reporter;
562 EXPECT_CALL(reporter, AddError(1, 0, 3,
563 "No field named \"foo\" in "
564 "proto TraceConfig"));
565 ToErrors(R"(foo {}; bar: 42)", &reporter);
566 }
567
568 // TODO(hjd): Add these tests.
569 // TEST(PbtxtToPb, WrongTypeString)
570 // TEST(PbtxtToPb, OverflowOnIntegers)
571 // TEST(PbtxtToPb, NegativeNumbersForUnsignedInt)
572 // TEST(PbtxtToPb, UnterminatedString) {
573 // TEST(PbtxtToPb, NumberIsEof)
574 // TEST(PbtxtToPb, OneOf)
575
576 } // namespace
577 } // namespace perfetto
578