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