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