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