• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google Inc.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "google/protobuf/compiler/retention.h"
9 
10 #include <memory>
11 #include <string>
12 
13 #include "google/protobuf/descriptor.pb.h"
14 #include <gmock/gmock.h>
15 #include <gtest/gtest.h>
16 #include "absl/log/absl_check.h"
17 #include "absl/log/die_if_null.h"
18 #include "absl/strings/substitute.h"
19 #include "google/protobuf/compiler/parser.h"
20 #include "google/protobuf/descriptor.h"
21 #include "google/protobuf/dynamic_message.h"
22 #include "google/protobuf/io/tokenizer.h"
23 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
24 #include "google/protobuf/text_format.h"
25 
26 
27 namespace google {
28 namespace protobuf {
29 namespace compiler {
30 namespace {
31 
32 MATCHER_P(EqualsProto, msg, "") {
33   return msg.DebugString() == arg.DebugString();
34 }
35 
36 class FakeErrorCollector : public io::ErrorCollector {
37  public:
38   FakeErrorCollector() = default;
39   ~FakeErrorCollector() override = default;
40 
RecordError(int line,io::ColumnNumber column,absl::string_view message)41   void RecordError(int line, io::ColumnNumber column,
42                    absl::string_view message) override {
43     ABSL_CHECK(false) << line << ":" << column << ": " << message;
44   }
45 
RecordWarning(int line,io::ColumnNumber column,absl::string_view message)46   void RecordWarning(int line, io::ColumnNumber column,
47                      absl::string_view message) override {
48     ABSL_CHECK(false) << line << ":" << column << ": " << message;
49   }
50 };
51 class RetentionStripTest : public testing::Test {
52  protected:
SetUp()53   void SetUp() override {
54     FileDescriptorProto descriptor_proto_descriptor;
55     FileDescriptorSet::descriptor()->file()->CopyTo(
56         &descriptor_proto_descriptor);
57     pool_.BuildFile(descriptor_proto_descriptor);
58   }
59 
ParseSchema(absl::string_view contents,absl::string_view file_name="foo.proto")60   const FileDescriptor* ParseSchema(absl::string_view contents,
61                                     absl::string_view file_name = "foo.proto") {
62     std::string proto_file = absl::Substitute(
63         R"schema(
64           syntax = "proto2";
65 
66           package google.protobuf.internal;
67 
68           import "$0";
69 
70           $1
71         )schema",
72         FileDescriptorSet::descriptor()->file()->name(), contents);
73     io::ArrayInputStream input_stream(proto_file.data(),
74                                       static_cast<int>(proto_file.size()));
75     FakeErrorCollector error_collector;
76     io::Tokenizer tokenizer(&input_stream, &error_collector);
77     Parser parser;
78     parser.RecordErrorsTo(&error_collector);
79     FileDescriptorProto file_descriptor;
80     ABSL_CHECK(parser.Parse(&tokenizer, &file_descriptor));
81     file_descriptor.set_name(file_name);
82 
83     return pool_.BuildFile(file_descriptor);
84   }
85 
86   template <typename ProtoType>
BuildDynamicProto(absl::string_view data)87   ProtoType BuildDynamicProto(absl::string_view data) {
88     // We use a dynamic message to generate the expected options proto. This
89     // lets us parse the custom options in text format.
90     const Descriptor* file_options_descriptor =
91         pool_.FindMessageTypeByName(ProtoType().GetTypeName());
92     DynamicMessageFactory factory;
93     std::unique_ptr<Message> dynamic_message(
94         factory.GetPrototype(file_options_descriptor)->New());
95     ABSL_CHECK(TextFormat::ParseFromString(data, dynamic_message.get()));
96     ProtoType ret;
97     ABSL_CHECK(ret.ParseFromString(dynamic_message->SerializeAsString()));
98     return ret;
99   }
100 
101   DescriptorPool pool_;
102 };
103 
TEST_F(RetentionStripTest,StripSourceRetentionFileOptions)104 TEST_F(RetentionStripTest, StripSourceRetentionFileOptions) {
105   const FileDescriptor* file = ParseSchema(R"schema(
106       option (source_retention_option) = 123;
107       option (options) = {
108         i1: 123
109         i2: 456
110         c { s: "abc" }
111         rc { s: "abc" }
112       };
113       option (repeated_options) = {
114         i1: 111 i2: 222
115       };
116 
117       message Options {
118         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
119         optional int32 i2 = 2;
120         message ChildMessage {
121           optional string s = 1 [retention = RETENTION_SOURCE];
122         }
123         optional ChildMessage c = 3;
124         repeated ChildMessage rc = 4;
125       }
126 
127       extend google.protobuf.FileOptions {
128         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
129         optional Options options = 50001;
130         repeated Options repeated_options = 50002;
131       })schema");
132 
133   FileOptions expected_options = BuildDynamicProto<FileOptions>(
134       R"pb(
135         [google.protobuf.internal.options] {
136           i2: 456
137           c {}
138           rc {}
139         }
140         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
141 
142   FileDescriptorProto stripped_file = StripSourceRetentionOptions(*file);
143 
144   EXPECT_THAT(StripSourceRetentionOptions(*file).options(),
145               EqualsProto(expected_options));
146   EXPECT_THAT(StripLocalSourceRetentionOptions(*file),
147               EqualsProto(expected_options));
148 }
149 
TEST_F(RetentionStripTest,StripSourceRetentionProtoFileOptions)150 TEST_F(RetentionStripTest, StripSourceRetentionProtoFileOptions) {
151   const FileDescriptor* file = ParseSchema(R"schema(
152       option (source_retention_option) = 123;
153       option (options) = {
154         i1: 123
155         i2: 456
156         c { s: "abc" }
157         rc { s: "abc" }
158       };
159       option (repeated_options) = {
160         i1: 111 i2: 222
161       };
162 
163       message Options {
164         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
165         optional int32 i2 = 2;
166         message ChildMessage {
167           optional string s = 1 [retention = RETENTION_SOURCE];
168         }
169         optional ChildMessage c = 3;
170         repeated ChildMessage rc = 4;
171       }
172 
173       extend google.protobuf.FileOptions {
174         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
175         optional Options options = 50001;
176         repeated Options repeated_options = 50002;
177       }
178   )schema");
179 
180   FileDescriptorProto proto;
181   file->CopyTo(&proto);
182 
183   ASSERT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
184                 [google.protobuf.internal.source_retention_option]: 123
185                 [google.protobuf.internal.options] {
186                   i1: 123
187                   i2: 456
188                   c { s: "abc" }
189                   rc { s: "abc" }
190                 }
191                 [google.protobuf.internal.repeated_options] { i1: 111 i2: 222 })pb")));
192 
193   StripSourceRetentionOptions(*file->pool(), proto);
194 
195   EXPECT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
196                 [google.protobuf.internal.options] {
197                   i2: 456
198                   c {}
199                   rc {}
200                 }
201                 [google.protobuf.internal.repeated_options] { i2: 222 })pb")));
202 }
203 
TEST_F(RetentionStripTest,StripSourceRetentionMessageOptions)204 TEST_F(RetentionStripTest, StripSourceRetentionMessageOptions) {
205   const FileDescriptor* file = ParseSchema(R"schema(
206       message TestMessage {
207         option (source_retention_option) = 123;
208         option (options) = {
209           i1: 123
210           i2: 456
211           c { s: "abc" }
212           rc { s: "abc" }
213         };
214         option (repeated_options) = {
215           i1: 111 i2: 222
216         };
217       }
218 
219       message Options {
220         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
221         optional int32 i2 = 2;
222         message ChildMessage {
223           optional string s = 1 [retention = RETENTION_SOURCE];
224         }
225         optional ChildMessage c = 3;
226         repeated ChildMessage rc = 4;
227       }
228 
229       extend google.protobuf.MessageOptions {
230         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
231         optional Options options = 50001;
232         repeated Options repeated_options = 50002;
233       })schema");
234 
235   MessageOptions expected_options = BuildDynamicProto<MessageOptions>(
236       R"pb(
237         [google.protobuf.internal.options] {
238           i2: 456
239           c {}
240           rc {}
241         }
242         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
243   const Descriptor* message =
244       ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
245 
246   EXPECT_THAT(StripSourceRetentionOptions(*file).message_type(0).options(),
247               EqualsProto(expected_options));
248   EXPECT_THAT(StripSourceRetentionOptions(*message).options(),
249               EqualsProto(expected_options));
250   EXPECT_THAT(StripLocalSourceRetentionOptions(*message),
251               EqualsProto(expected_options));
252 }
253 
TEST_F(RetentionStripTest,StripSourceRetentionEnumOptions)254 TEST_F(RetentionStripTest, StripSourceRetentionEnumOptions) {
255   const FileDescriptor* file = ParseSchema(R"schema(
256       enum TestEnum {
257         option (source_retention_option) = 123;
258         option (options) = {
259           i1: 123
260           i2: 456
261           c { s: "abc" }
262           rc { s: "abc" }
263         };
264         option (repeated_options) = {
265           i1: 111 i2: 222
266         };
267         VALUE1 = 0;
268       }
269 
270       message Options {
271         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
272         optional int32 i2 = 2;
273         message ChildMessage {
274           optional string s = 1 [retention = RETENTION_SOURCE];
275         }
276         optional ChildMessage c = 3;
277         repeated ChildMessage rc = 4;
278       }
279 
280       extend google.protobuf.EnumOptions {
281         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
282         optional Options options = 50001;
283         repeated Options repeated_options = 50002;
284       })schema");
285 
286   EnumOptions expected_options = BuildDynamicProto<EnumOptions>(
287       R"pb(
288         [google.protobuf.internal.options] {
289           i2: 456
290           c {}
291           rc {}
292         }
293         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
294   const EnumDescriptor* enm =
295       ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
296 
297   EXPECT_THAT(StripSourceRetentionOptions(*file).enum_type(0).options(),
298               EqualsProto(expected_options));
299   EXPECT_THAT(StripSourceRetentionOptions(*enm).options(),
300               EqualsProto(expected_options));
301   EXPECT_THAT(StripLocalSourceRetentionOptions(*enm),
302               EqualsProto(expected_options));
303 }
304 
TEST_F(RetentionStripTest,StripSourceRetentionEnumValueOptions)305 TEST_F(RetentionStripTest, StripSourceRetentionEnumValueOptions) {
306   const FileDescriptor* file = ParseSchema(R"schema(
307       enum TestEnum {
308         VALUE1 = 0 [(source_retention_option) = 123, (options) = {
309           i1: 123
310           i2: 456
311           c { s: "abc" }
312           rc { s: "abc" }
313         }, (repeated_options) = {
314           i1: 111 i2: 222
315         }];
316       }
317 
318       message Options {
319         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
320         optional int32 i2 = 2;
321         message ChildMessage {
322           optional string s = 1 [retention = RETENTION_SOURCE];
323         }
324         optional ChildMessage c = 3;
325         repeated ChildMessage rc = 4;
326       }
327 
328       extend google.protobuf.EnumValueOptions {
329         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
330         optional Options options = 50001;
331         repeated Options repeated_options = 50002;
332       })schema");
333 
334   EnumValueOptions expected_options = BuildDynamicProto<EnumValueOptions>(
335       R"pb(
336         [google.protobuf.internal.options] {
337           i2: 456
338           c {}
339           rc {}
340         }
341         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
342   const EnumDescriptor* enm =
343       ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
344   const EnumValueDescriptor* value = ABSL_DIE_IF_NULL(enm->value(0));
345 
346   EXPECT_THAT(
347       StripSourceRetentionOptions(*file).enum_type(0).value(0).options(),
348       EqualsProto(expected_options));
349   EXPECT_THAT(StripSourceRetentionOptions(*enm).value(0).options(),
350               EqualsProto(expected_options));
351   EXPECT_THAT(StripLocalSourceRetentionOptions(*value),
352               EqualsProto(expected_options));
353 }
354 
TEST_F(RetentionStripTest,StripSourceRetentionFieldOptions)355 TEST_F(RetentionStripTest, StripSourceRetentionFieldOptions) {
356   const FileDescriptor* file = ParseSchema(R"schema(
357       message TestMessage {
358         optional string test_field = 1 [(source_retention_option) = 123, (options) = {
359           i1: 123
360           i2: 456
361           c { s: "abc" }
362           rc { s: "abc" }
363         }, (repeated_options) = {
364           i1: 111 i2: 222
365         }];
366       }
367 
368       message Options {
369         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
370         optional int32 i2 = 2;
371         message ChildMessage {
372           optional string s = 1 [retention = RETENTION_SOURCE];
373         }
374         optional ChildMessage c = 3;
375         repeated ChildMessage rc = 4;
376       }
377 
378       extend google.protobuf.FieldOptions {
379         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
380         optional Options options = 50001;
381         repeated Options repeated_options = 50002;
382       })schema");
383 
384   FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
385       R"pb(
386         [google.protobuf.internal.options] {
387           i2: 456
388           c {}
389           rc {}
390         }
391         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
392   const Descriptor* message =
393       ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
394   const FieldDescriptor* field =
395       ABSL_DIE_IF_NULL(message->FindFieldByName("test_field"));
396 
397   EXPECT_THAT(
398       StripSourceRetentionOptions(*file).message_type(0).field(0).options(),
399       EqualsProto(expected_options));
400   EXPECT_THAT(StripSourceRetentionOptions(*message).field(0).options(),
401               EqualsProto(expected_options));
402   EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
403               EqualsProto(expected_options));
404   EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
405               EqualsProto(expected_options));
406 }
407 
TEST_F(RetentionStripTest,StripSourceRetentionExtensionOptions)408 TEST_F(RetentionStripTest, StripSourceRetentionExtensionOptions) {
409   const FileDescriptor* file = ParseSchema(R"schema(
410       message TestMessage {
411         extensions 1;
412       }
413 
414       extend TestMessage {
415         optional string test_field = 1 [(source_retention_option) = 123, (options) = {
416           i1: 123
417           i2: 456
418           c { s: "abc" }
419           rc { s: "abc" }
420         }, (repeated_options) = {
421           i1: 111 i2: 222
422         }];
423       }
424 
425       message Options {
426         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
427         optional int32 i2 = 2;
428         message ChildMessage {
429           optional string s = 1 [retention = RETENTION_SOURCE];
430         }
431         optional ChildMessage c = 3;
432         repeated ChildMessage rc = 4;
433       }
434 
435       extend google.protobuf.FieldOptions {
436         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
437         optional Options options = 50001;
438         repeated Options repeated_options = 50002;
439       })schema");
440 
441   FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
442       R"pb(
443         [google.protobuf.internal.options] {
444           i2: 456
445           c {}
446           rc {}
447         }
448         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
449   const FieldDescriptor* field =
450       ABSL_DIE_IF_NULL(file->FindExtensionByName("test_field"));
451 
452   EXPECT_THAT(StripSourceRetentionOptions(*file).extension(0).options(),
453               EqualsProto(expected_options));
454   EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
455               EqualsProto(expected_options));
456   EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
457               EqualsProto(expected_options));
458 }
459 
TEST_F(RetentionStripTest,StripSourceRetentionOneofOptions)460 TEST_F(RetentionStripTest, StripSourceRetentionOneofOptions) {
461   const FileDescriptor* file = ParseSchema(R"schema(
462       message TestMessage {
463         oneof test_oneof {
464         option (source_retention_option) = 123;
465         option (options) = {
466           i1: 123
467           i2: 456
468           c { s: "abc" }
469           rc { s: "abc" }
470         };
471         option (repeated_options) = {
472           i1: 111 i2: 222
473         };
474           string field1 = 1;
475           string field2 = 2;
476         };
477       }
478 
479       message Options {
480         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
481         optional int32 i2 = 2;
482         message ChildMessage {
483           optional string s = 1 [retention = RETENTION_SOURCE];
484         }
485         optional ChildMessage c = 3;
486         repeated ChildMessage rc = 4;
487       }
488 
489       extend google.protobuf.OneofOptions {
490         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
491         optional Options options = 50001;
492         repeated Options repeated_options = 50002;
493       })schema");
494 
495   OneofOptions expected_options = BuildDynamicProto<OneofOptions>(
496       R"pb(
497         [google.protobuf.internal.options] {
498           i2: 456
499           c {}
500           rc {}
501         }
502         [google.protobuf.internal.repeated_options] { i2: 222 })pb");
503   const Descriptor* message =
504       ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
505   const OneofDescriptor* oneof =
506       ABSL_DIE_IF_NULL(message->FindOneofByName("test_oneof"));
507 
508   EXPECT_THAT(StripSourceRetentionOptions(*file)
509                   .message_type(0)
510                   .oneof_decl(0)
511                   .options(),
512               EqualsProto(expected_options));
513   EXPECT_THAT(StripSourceRetentionOptions(*message).oneof_decl(0).options(),
514               EqualsProto(expected_options));
515   EXPECT_THAT(StripSourceRetentionOptions(*oneof).options(),
516               EqualsProto(expected_options));
517   EXPECT_THAT(StripLocalSourceRetentionOptions(*oneof),
518               EqualsProto(expected_options));
519 }
520 
TEST_F(RetentionStripTest,StripSourceRetentionExtensionRangeOptions)521 TEST_F(RetentionStripTest, StripSourceRetentionExtensionRangeOptions) {
522   const FileDescriptor* file = ParseSchema(R"schema(
523       message TestMessage {
524         extensions 1 to max [(source_retention_option) = 123, (options) = {
525           i1: 123
526           i2: 456
527           c { s: "abc" }
528           rc { s: "abc" }
529         }, (repeated_options) = {
530           i1: 111 i2: 222
531         }];
532       }
533 
534       message Options {
535         optional int32 i1 = 1 [retention = RETENTION_SOURCE];
536         optional int32 i2 = 2;
537         message ChildMessage {
538           optional string s = 1 [retention = RETENTION_SOURCE];
539         }
540         optional ChildMessage c = 3;
541         repeated ChildMessage rc = 4;
542       }
543 
544       extend google.protobuf.ExtensionRangeOptions {
545         optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
546         optional Options options = 50001;
547         repeated Options repeated_options = 50002;
548       })schema");
549 
550   ExtensionRangeOptions expected_options =
551       BuildDynamicProto<ExtensionRangeOptions>(
552           R"pb(
553             [google.protobuf.internal.options] {
554               i2: 456
555               c {}
556               rc {}
557             }
558             [google.protobuf.internal.repeated_options] { i2: 222 })pb");
559   const Descriptor* message =
560       ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
561   const Descriptor::ExtensionRange* range =
562       ABSL_DIE_IF_NULL(message->FindExtensionRangeContainingNumber(2));
563 
564   EXPECT_THAT(StripSourceRetentionOptions(*file)
565                   .message_type(0)
566                   .extension_range(0)
567                   .options(),
568               EqualsProto(expected_options));
569   EXPECT_THAT(
570       StripSourceRetentionOptions(*message).extension_range(0).options(),
571       EqualsProto(expected_options));
572   EXPECT_THAT(StripSourceRetentionOptions(*message, *range).options(),
573               EqualsProto(expected_options));
574   EXPECT_THAT(StripLocalSourceRetentionOptions(*message, *range),
575               EqualsProto(expected_options));
576 }
577 
578 
579 }  // namespace
580 }  // namespace compiler
581 }  // namespace protobuf
582 }  // namespace google
583