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