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/feature_resolver.h"
9
10 #include <utility>
11 #include <vector>
12
13 #include <gmock/gmock.h>
14 #include <gtest/gtest.h>
15 #include "absl/log/absl_check.h"
16 #include "absl/log/absl_log.h"
17 #include "absl/log/die_if_null.h"
18 #include "absl/memory/memory.h"
19 #include "absl/status/status.h"
20 #include "absl/status/statusor.h"
21 #include "absl/strings/string_view.h"
22 #include "absl/strings/substitute.h"
23 #include "google/protobuf/compiler/parser.h"
24 #include "google/protobuf/cpp_features.pb.h"
25 #include "google/protobuf/descriptor.h"
26 #include "google/protobuf/descriptor.pb.h"
27 #include "google/protobuf/io/tokenizer.h"
28 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
29 #include "google/protobuf/test_textproto.h"
30 #include "google/protobuf/text_format.h"
31 #include "google/protobuf/unittest_custom_options.pb.h"
32 #include "google/protobuf/unittest_features.pb.h"
33 #include "google/protobuf/stubs/status_macros.h"
34
35 // Must be included last.
36 #include "google/protobuf/port_def.inc"
37
38 namespace google {
39 namespace protobuf {
40 namespace {
41
42 using ::testing::AllOf;
43 using ::testing::ElementsAre;
44 using ::testing::ExplainMatchResult;
45 using ::testing::HasSubstr;
46 using ::testing::IsEmpty;
47 using ::testing::UnorderedElementsAre;
48
49 // TODO: Use the gtest versions once that's available in OSS.
50 template <typename T>
GetStatus(const absl::StatusOr<T> & s)51 absl::Status GetStatus(const absl::StatusOr<T>& s) {
52 return s.status();
53 }
54
55 MATCHER_P(HasError, msg_matcher, "") {
56 return GetStatus(arg).code() == absl::StatusCode::kFailedPrecondition &&
57 ExplainMatchResult(msg_matcher, GetStatus(arg).message(),
58 result_listener);
59 }
60
61 MATCHER_P(StatusIs, status,
62 absl::StrCat(".status() is ", testing::PrintToString(status))) {
63 return GetStatus(arg).code() == status;
64 }
65 #define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk))
66 #define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk))
67
68 template <typename ExtensionT>
GetExtension(const ExtensionT & ext,const Descriptor * descriptor=FeatureSet::descriptor ())69 const FieldDescriptor* GetExtension(
70 const ExtensionT& ext,
71 const Descriptor* descriptor = FeatureSet::descriptor()) {
72 return ABSL_DIE_IF_NULL(descriptor->file()->pool()->FindExtensionByNumber(
73 descriptor, ext.number()));
74 }
75
76 template <typename... Extensions>
SetupFeatureResolver(Edition edition,Extensions...extensions)77 absl::StatusOr<FeatureResolver> SetupFeatureResolver(Edition edition,
78 Extensions... extensions) {
79 absl::StatusOr<FeatureSetDefaults> defaults =
80 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
81 {GetExtension(extensions)...},
82 EDITION_2023, EDITION_99997_TEST_ONLY);
83 RETURN_IF_ERROR(defaults.status());
84 return FeatureResolver::Create(edition, *defaults);
85 }
86
GetDefaults(Edition edition,const FeatureSetDefaults & defaults)87 absl::StatusOr<FeatureSet> GetDefaults(Edition edition,
88 const FeatureSetDefaults& defaults) {
89 absl::StatusOr<FeatureResolver> resolver =
90 FeatureResolver::Create(edition, defaults);
91 RETURN_IF_ERROR(resolver.status());
92 FeatureSet parent, child;
93 return resolver->MergeFeatures(parent, child);
94 }
95
96 template <typename... Extensions>
GetDefaults(Edition edition,Extensions...extensions)97 absl::StatusOr<FeatureSet> GetDefaults(Edition edition,
98 Extensions... extensions) {
99 absl::StatusOr<FeatureSetDefaults> defaults =
100 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
101 {GetExtension(extensions)...},
102 EDITION_2023, EDITION_99999_TEST_ONLY);
103 RETURN_IF_ERROR(defaults.status());
104 return GetDefaults(edition, *defaults);
105 }
106
GetProto(const FileDescriptor * file)107 FileDescriptorProto GetProto(const FileDescriptor* file) {
108 FileDescriptorProto proto;
109 file->CopyTo(&proto);
110 return proto;
111 }
112
TEST(FeatureResolverTest,DefaultsCore2023)113 TEST(FeatureResolverTest, DefaultsCore2023) {
114 absl::StatusOr<FeatureSet> merged = GetDefaults(EDITION_2023);
115 ASSERT_OK(merged);
116
117 EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
118 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
119 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
120 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
121 EXPECT_FALSE(merged->HasExtension(pb::test));
122 }
123
TEST(FeatureResolverTest,DefaultsTest2023)124 TEST(FeatureResolverTest, DefaultsTest2023) {
125 absl::StatusOr<FeatureSet> merged = GetDefaults(EDITION_2023, pb::test);
126 ASSERT_OK(merged);
127
128 EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
129 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
130 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
131 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
132
133 const pb::TestFeatures& ext = merged->GetExtension(pb::test);
134 EXPECT_EQ(ext.file_feature(), pb::VALUE3);
135 EXPECT_EQ(ext.extension_range_feature(), pb::VALUE1);
136 EXPECT_EQ(ext.message_feature(), pb::VALUE1);
137 EXPECT_EQ(ext.field_feature(), pb::VALUE1);
138 EXPECT_EQ(ext.oneof_feature(), pb::VALUE1);
139 EXPECT_EQ(ext.enum_feature(), pb::VALUE1);
140 EXPECT_EQ(ext.enum_entry_feature(), pb::VALUE1);
141 EXPECT_EQ(ext.service_feature(), pb::VALUE1);
142 EXPECT_EQ(ext.method_feature(), pb::VALUE1);
143 EXPECT_FALSE(ext.bool_field_feature());
144 }
145
TEST(FeatureResolverTest,DefaultsTestMessageExtension)146 TEST(FeatureResolverTest, DefaultsTestMessageExtension) {
147 absl::StatusOr<FeatureSet> merged =
148 GetDefaults(EDITION_2023, pb::TestMessage::test_message);
149 ASSERT_OK(merged);
150
151 EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
152 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
153 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
154 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
155 EXPECT_FALSE(merged->HasExtension(pb::test));
156
157 const pb::TestFeatures& ext =
158 merged->GetExtension(pb::TestMessage::test_message);
159 EXPECT_EQ(ext.file_feature(), pb::VALUE3);
160 EXPECT_EQ(ext.extension_range_feature(), pb::VALUE1);
161 EXPECT_EQ(ext.message_feature(), pb::VALUE1);
162 EXPECT_EQ(ext.field_feature(), pb::VALUE1);
163 EXPECT_EQ(ext.oneof_feature(), pb::VALUE1);
164 EXPECT_EQ(ext.enum_feature(), pb::VALUE1);
165 EXPECT_EQ(ext.enum_entry_feature(), pb::VALUE1);
166 EXPECT_EQ(ext.service_feature(), pb::VALUE1);
167 EXPECT_EQ(ext.method_feature(), pb::VALUE1);
168 EXPECT_FALSE(ext.bool_field_feature());
169 }
170
TEST(FeatureResolverTest,DefaultsTestNestedExtension)171 TEST(FeatureResolverTest, DefaultsTestNestedExtension) {
172 absl::StatusOr<FeatureSet> merged =
173 GetDefaults(EDITION_2023, pb::TestMessage::Nested::test_nested);
174 ASSERT_OK(merged);
175
176 EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT);
177 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
178 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
179 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
180 EXPECT_FALSE(merged->HasExtension(pb::test));
181
182 const pb::TestFeatures& ext =
183 merged->GetExtension(pb::TestMessage::Nested::test_nested);
184 EXPECT_EQ(ext.file_feature(), pb::VALUE3);
185 EXPECT_EQ(ext.extension_range_feature(), pb::VALUE1);
186 EXPECT_EQ(ext.message_feature(), pb::VALUE1);
187 EXPECT_EQ(ext.field_feature(), pb::VALUE1);
188 EXPECT_EQ(ext.oneof_feature(), pb::VALUE1);
189 EXPECT_EQ(ext.enum_feature(), pb::VALUE1);
190 EXPECT_EQ(ext.enum_entry_feature(), pb::VALUE1);
191 EXPECT_EQ(ext.service_feature(), pb::VALUE1);
192 EXPECT_EQ(ext.method_feature(), pb::VALUE1);
193 EXPECT_FALSE(ext.bool_field_feature());
194 }
195
TEST(FeatureResolverTest,DefaultsGeneratedPoolCustom)196 TEST(FeatureResolverTest, DefaultsGeneratedPoolCustom) {
197 DescriptorPool pool;
198 ASSERT_NE(
199 pool.BuildFile(GetProto(google::protobuf::DescriptorProto::descriptor()->file())),
200 nullptr);
201 ASSERT_NE(pool.BuildFile(GetProto(pb::TestFeatures::descriptor()->file())),
202 nullptr);
203 absl::StatusOr<FeatureSetDefaults> defaults =
204 FeatureResolver::CompileDefaults(
205 pool.FindMessageTypeByName("google.protobuf.FeatureSet"),
206 {pool.FindExtensionByName("pb.test")}, EDITION_2023, EDITION_2023);
207 ASSERT_OK(defaults);
208 ASSERT_EQ(defaults->defaults().size(), 3);
209 ASSERT_EQ(defaults->defaults().at(2).edition(), EDITION_2023);
210 FeatureSet merged = defaults->defaults().at(2).overridable_features();
211
212 EXPECT_EQ(merged.field_presence(), FeatureSet::EXPLICIT);
213 EXPECT_TRUE(merged.HasExtension(pb::test));
214 EXPECT_EQ(merged.GetExtension(pb::test).file_feature(), pb::VALUE3);
215 EXPECT_FALSE(merged.HasExtension(pb::cpp));
216 }
217
TEST(FeatureResolverTest,DefaultsMergedFeatures)218 TEST(FeatureResolverTest, DefaultsMergedFeatures) {
219 absl::StatusOr<FeatureSetDefaults> defaults =
220 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
221 {GetExtension(pb::test)}, EDITION_2023,
222 EDITION_2023);
223 ASSERT_OK(defaults);
224 ASSERT_EQ(defaults->defaults_size(), 3);
225
226 defaults->mutable_defaults(2)
227 ->mutable_fixed_features()
228 ->MutableExtension(pb::test)
229 ->set_file_feature(pb::VALUE7);
230 defaults->mutable_defaults(2)
231 ->mutable_fixed_features()
232 ->MutableExtension(pb::test)
233 ->set_multiple_feature(pb::VALUE6);
234 defaults->mutable_defaults(2)
235 ->mutable_overridable_features()
236 ->MutableExtension(pb::test)
237 ->clear_file_feature();
238 defaults->mutable_defaults(2)
239 ->mutable_overridable_features()
240 ->MutableExtension(pb::test)
241 ->set_multiple_feature(pb::VALUE8);
242
243 absl::StatusOr<FeatureSet> features = GetDefaults(EDITION_2023, *defaults);
244 ASSERT_OK(features);
245
246 const pb::TestFeatures& ext = features->GetExtension(pb::test);
247 EXPECT_EQ(ext.file_feature(), pb::VALUE7);
248 EXPECT_EQ(ext.multiple_feature(), pb::VALUE8);
249 }
250
TEST(FeatureResolverTest,DefaultsTooEarly)251 TEST(FeatureResolverTest, DefaultsTooEarly) {
252 absl::StatusOr<FeatureSetDefaults> defaults =
253 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
254 {GetExtension(pb::test)}, EDITION_2023,
255 EDITION_2023);
256 ASSERT_OK(defaults);
257 defaults->set_minimum_edition(EDITION_1_TEST_ONLY);
258 absl::StatusOr<FeatureSet> merged =
259 GetDefaults(EDITION_1_TEST_ONLY, *defaults);
260 EXPECT_THAT(merged, HasError(AllOf(HasSubstr("No valid default found"),
261 HasSubstr("1_TEST_ONLY"))));
262 }
263
TEST(FeatureResolverTest,DefaultsFarFuture)264 TEST(FeatureResolverTest, DefaultsFarFuture) {
265 absl::StatusOr<FeatureSet> merged =
266 GetDefaults(EDITION_99999_TEST_ONLY, pb::test);
267 ASSERT_OK(merged);
268
269 pb::TestFeatures ext = merged->GetExtension(pb::test);
270 EXPECT_EQ(ext.file_feature(), pb::VALUE5);
271 EXPECT_TRUE(ext.bool_field_feature());
272 }
273
TEST(FeatureResolverTest,DefaultsMiddleEdition)274 TEST(FeatureResolverTest, DefaultsMiddleEdition) {
275 absl::StatusOr<FeatureSet> merged =
276 GetDefaults(EDITION_99997_TEST_ONLY, pb::test);
277 ASSERT_OK(merged);
278
279 pb::TestFeatures ext = merged->GetExtension(pb::test);
280 EXPECT_EQ(ext.file_feature(), pb::VALUE4);
281 EXPECT_TRUE(ext.bool_field_feature());
282 }
283
TEST(FeatureResolverTest,CompileDefaultsFixedFutureFeature)284 TEST(FeatureResolverTest, CompileDefaultsFixedFutureFeature) {
285 absl::StatusOr<FeatureSetDefaults> defaults =
286 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
287 {GetExtension(pb::test)}, EDITION_PROTO2,
288 EDITION_2023);
289 ASSERT_OK(defaults);
290 ASSERT_EQ(defaults->defaults_size(), 3);
291
292 const auto& edition_defaults = defaults->defaults(2);
293 ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
294
295 EXPECT_TRUE(edition_defaults.fixed_features()
296 .GetExtension(pb::test)
297 .has_future_feature());
298 EXPECT_EQ(
299 edition_defaults.fixed_features().GetExtension(pb::test).future_feature(),
300 pb::VALUE1);
301 EXPECT_FALSE(edition_defaults.overridable_features()
302 .GetExtension(pb::test)
303 .has_future_feature());
304 }
305
TEST(FeatureResolverTest,CompileDefaultsFixedRemovedFeature)306 TEST(FeatureResolverTest, CompileDefaultsFixedRemovedFeature) {
307 absl::StatusOr<FeatureSetDefaults> defaults =
308 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
309 {GetExtension(pb::test)}, EDITION_PROTO2,
310 EDITION_2024);
311 ASSERT_OK(defaults);
312 ASSERT_EQ(defaults->defaults_size(), 4);
313
314 const auto& edition_defaults = defaults->defaults(3);
315 ASSERT_EQ(edition_defaults.edition(), EDITION_2024);
316
317 EXPECT_TRUE(edition_defaults.fixed_features()
318 .GetExtension(pb::test)
319 .has_removed_feature());
320 EXPECT_EQ(edition_defaults.fixed_features()
321 .GetExtension(pb::test)
322 .removed_feature(),
323 pb::VALUE3);
324 EXPECT_FALSE(edition_defaults.overridable_features()
325 .GetExtension(pb::test)
326 .has_removed_feature());
327 }
328
TEST(FeatureResolverTest,CompileDefaultsOverridable)329 TEST(FeatureResolverTest, CompileDefaultsOverridable) {
330 absl::StatusOr<FeatureSetDefaults> defaults =
331 FeatureResolver::CompileDefaults(FeatureSet::descriptor(),
332 {GetExtension(pb::test)}, EDITION_PROTO2,
333 EDITION_2023);
334 ASSERT_OK(defaults);
335 ASSERT_EQ(defaults->defaults_size(), 3);
336
337 const auto& edition_defaults = defaults->defaults(2);
338 ASSERT_EQ(edition_defaults.edition(), EDITION_2023);
339
340 EXPECT_FALSE(edition_defaults.fixed_features()
341 .GetExtension(pb::test)
342 .has_removed_feature());
343 EXPECT_TRUE(edition_defaults.overridable_features()
344 .GetExtension(pb::test)
345 .has_removed_feature());
346 EXPECT_EQ(edition_defaults.overridable_features()
347 .GetExtension(pb::test)
348 .removed_feature(),
349 pb::VALUE2);
350 }
351
TEST(FeatureResolverTest,CreateFromUnsortedDefaults)352 TEST(FeatureResolverTest, CreateFromUnsortedDefaults) {
353 auto valid_defaults = FeatureResolver::CompileDefaults(
354 FeatureSet::descriptor(), {}, EDITION_LEGACY, EDITION_2023);
355 ASSERT_OK(valid_defaults);
356 FeatureSetDefaults defaults = *valid_defaults;
357
358 defaults.mutable_defaults()->SwapElements(0, 1);
359
360 EXPECT_THAT(FeatureResolver::Create(EDITION_2023, defaults),
361 HasError(AllOf(HasSubstr("not strictly increasing."),
362 HasSubstr("Edition PROTO3 is greater "
363 "than or equal to edition LEGACY"))));
364 }
365
TEST(FeatureResolverTest,CreateUnknownEdition)366 TEST(FeatureResolverTest, CreateUnknownEdition) {
367 FeatureSetDefaults defaults = ParseTextOrDie(R"pb(
368 minimum_edition: EDITION_UNKNOWN
369 maximum_edition: EDITION_99999_TEST_ONLY
370 defaults { edition: EDITION_UNKNOWN }
371 )pb");
372 EXPECT_THAT(FeatureResolver::Create(EDITION_2023, defaults),
373 HasError(HasSubstr("Invalid edition UNKNOWN")));
374 }
375
TEST(FeatureResolverTest,CreateMissingEdition)376 TEST(FeatureResolverTest, CreateMissingEdition) {
377 FeatureSetDefaults defaults = ParseTextOrDie(R"pb(
378 minimum_edition: EDITION_UNKNOWN
379 maximum_edition: EDITION_99999_TEST_ONLY
380 defaults {}
381 )pb");
382 EXPECT_THAT(FeatureResolver::Create(EDITION_2023, defaults),
383 HasError(HasSubstr("Invalid edition UNKNOWN")));
384 }
385
TEST(FeatureResolverTest,CreateUnknownEnumFeature)386 TEST(FeatureResolverTest, CreateUnknownEnumFeature) {
387 auto valid_defaults = FeatureResolver::CompileDefaults(
388 FeatureSet::descriptor(), {}, EDITION_2023, EDITION_2023);
389 ASSERT_OK(valid_defaults);
390
391 // Use reflection to make sure we validate every enum feature in FeatureSet.
392 const Descriptor& descriptor = *FeatureSet::descriptor();
393 for (int i = 0; i < descriptor.field_count(); ++i) {
394 const FieldDescriptor& field = *descriptor.field(i);
395
396 // Clear the feature, which should be invalid.
397 FeatureSetDefaults defaults = *valid_defaults;
398 FeatureSet* features =
399 defaults.mutable_defaults()->Mutable(0)->mutable_overridable_features();
400 features->GetReflection()->ClearField(features, &field);
401 features =
402 defaults.mutable_defaults()->Mutable(0)->mutable_fixed_features();
403 features->GetReflection()->ClearField(features, &field);
404
405 EXPECT_THAT(FeatureResolver::Create(EDITION_2023, defaults),
406 HasError(AllOf(HasSubstr(field.name()),
407 HasSubstr("must resolve to a known value"))));
408 }
409 }
410
TEST(FeatureResolverTest,CompileDefaultsMissingDescriptor)411 TEST(FeatureResolverTest, CompileDefaultsMissingDescriptor) {
412 EXPECT_THAT(
413 FeatureResolver::CompileDefaults(nullptr, {}, EDITION_2023, EDITION_2023),
414 HasError(HasSubstr("find definition of google.protobuf.FeatureSet")));
415 }
416
TEST(FeatureResolverTest,CompileDefaultsMissingExtension)417 TEST(FeatureResolverTest, CompileDefaultsMissingExtension) {
418 EXPECT_THAT(
419 FeatureResolver::CompileDefaults(FeatureSet::descriptor(), {nullptr},
420 EDITION_2023, EDITION_2023),
421 HasError(HasSubstr("Unknown extension")));
422 }
423
TEST(FeatureResolverTest,CompileDefaultsInvalidExtension)424 TEST(FeatureResolverTest, CompileDefaultsInvalidExtension) {
425 EXPECT_THAT(
426 FeatureResolver::CompileDefaults(
427 FeatureSet::descriptor(),
428 {GetExtension(protobuf_unittest::file_opt1, FileOptions::descriptor())},
429 EDITION_2023, EDITION_2023),
430 HasError(HasSubstr("is not an extension of")));
431 }
432
TEST(FeatureResolverTest,CompileDefaultsMinimumLaterThanMaximum)433 TEST(FeatureResolverTest, CompileDefaultsMinimumLaterThanMaximum) {
434 EXPECT_THAT(
435 FeatureResolver::CompileDefaults(FeatureSet::descriptor(), {},
436 EDITION_99999_TEST_ONLY, EDITION_2023),
437 HasError(AllOf(HasSubstr("Invalid edition range"),
438 HasSubstr("99999_TEST_ONLY is newer"),
439 HasSubstr("2023"))));
440 }
441
TEST(FeatureResolverTest,MergeFeaturesChildOverrideCore)442 TEST(FeatureResolverTest, MergeFeaturesChildOverrideCore) {
443 absl::StatusOr<FeatureResolver> resolver = SetupFeatureResolver(EDITION_2023);
444 ASSERT_OK(resolver);
445 FeatureSet child = ParseTextOrDie(R"pb(
446 field_presence: IMPLICIT
447 repeated_field_encoding: EXPANDED
448 )pb");
449 absl::StatusOr<FeatureSet> merged =
450 resolver->MergeFeatures(FeatureSet(), child);
451 ASSERT_OK(merged);
452
453 EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT);
454 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
455 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::EXPANDED);
456 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
457 }
458
TEST(FeatureResolverTest,MergeFeaturesChildOverrideComplex)459 TEST(FeatureResolverTest, MergeFeaturesChildOverrideComplex) {
460 absl::StatusOr<FeatureResolver> resolver =
461 SetupFeatureResolver(EDITION_2023, pb::test);
462 ASSERT_OK(resolver);
463 FeatureSet child = ParseTextOrDie(R"pb(
464 field_presence: IMPLICIT
465 repeated_field_encoding: EXPANDED
466 [pb.test] { field_feature: VALUE5 }
467 )pb");
468 absl::StatusOr<FeatureSet> merged =
469 resolver->MergeFeatures(FeatureSet(), child);
470 ASSERT_OK(merged);
471
472 EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT);
473 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
474 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::EXPANDED);
475 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
476
477 pb::TestFeatures ext = merged->GetExtension(pb::test);
478 EXPECT_EQ(ext.file_feature(), pb::VALUE3);
479 EXPECT_EQ(ext.field_feature(), pb::VALUE5);
480 }
481
TEST(FeatureResolverTest,MergeFeaturesParentOverrides)482 TEST(FeatureResolverTest, MergeFeaturesParentOverrides) {
483 absl::StatusOr<FeatureResolver> resolver =
484 SetupFeatureResolver(EDITION_2023, pb::test);
485 ASSERT_OK(resolver);
486 FeatureSet parent = ParseTextOrDie(R"pb(
487 field_presence: IMPLICIT
488 repeated_field_encoding: EXPANDED
489 [pb.test] { message_feature: VALUE2 field_feature: VALUE5 }
490 )pb");
491 FeatureSet child = ParseTextOrDie(R"pb(
492 repeated_field_encoding: PACKED
493 [pb.test] { field_feature: VALUE7 }
494 )pb");
495 absl::StatusOr<FeatureSet> merged = resolver->MergeFeatures(parent, child);
496 ASSERT_OK(merged);
497
498 EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT);
499 EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN);
500 EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED);
501 EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED);
502
503 pb::TestFeatures ext = merged->GetExtension(pb::test);
504 EXPECT_EQ(ext.file_feature(), pb::VALUE3);
505 EXPECT_EQ(ext.extension_range_feature(), pb::VALUE1);
506 EXPECT_EQ(ext.message_feature(), pb::VALUE2);
507 EXPECT_EQ(ext.field_feature(), pb::VALUE7);
508 EXPECT_EQ(ext.oneof_feature(), pb::VALUE1);
509 EXPECT_EQ(ext.enum_feature(), pb::VALUE1);
510 EXPECT_EQ(ext.enum_entry_feature(), pb::VALUE1);
511 EXPECT_EQ(ext.service_feature(), pb::VALUE1);
512 EXPECT_EQ(ext.method_feature(), pb::VALUE1);
513 EXPECT_FALSE(ext.bool_field_feature());
514 }
515
TEST(FeatureResolverTest,MergeFeaturesUnknownEnumFeature)516 TEST(FeatureResolverTest, MergeFeaturesUnknownEnumFeature) {
517 absl::StatusOr<FeatureResolver> resolver = SetupFeatureResolver(EDITION_2023);
518 ASSERT_OK(resolver);
519
520 // Use reflection to make sure we validate every enum feature in FeatureSet.
521 const Descriptor& descriptor = *FeatureSet::descriptor();
522 for (int i = 0; i < descriptor.field_count(); ++i) {
523 const FieldDescriptor& field = *descriptor.field(i);
524
525 FeatureSet features;
526 const Reflection& reflection = *features.GetReflection();
527
528 // Set the feature to a value of 0, which is unknown by convention.
529 reflection.SetEnumValue(&features, &field, 0);
530 EXPECT_THAT(
531 resolver->MergeFeatures(FeatureSet(), features),
532 HasError(AllOf(
533 HasSubstr(field.name()), HasSubstr("must resolve to a known value"),
534 HasSubstr(field.enum_type()->FindValueByNumber(0)->name()))));
535 }
536 }
537
TEST(FeatureResolverTest,MergeFeaturesExtensionEnumUnknown)538 TEST(FeatureResolverTest, MergeFeaturesExtensionEnumUnknown) {
539 absl::StatusOr<FeatureResolver> resolver =
540 SetupFeatureResolver(EDITION_2023, pb::test);
541 ASSERT_OK(resolver);
542 FeatureSet child = ParseTextOrDie(R"pb(
543 [pb.test] { field_feature: TEST_ENUM_FEATURE_UNKNOWN }
544 )pb");
545 absl::StatusOr<FeatureSet> merged =
546 resolver->MergeFeatures(FeatureSet(), child);
547 ASSERT_OK(merged);
548 EXPECT_EQ(merged->GetExtension(pb::test).field_feature(),
549 pb::TEST_ENUM_FEATURE_UNKNOWN);
550 }
551
TEST(FeatureResolverTest,MergeFeaturesDistantPast)552 TEST(FeatureResolverTest, MergeFeaturesDistantPast) {
553 EXPECT_THAT(SetupFeatureResolver(EDITION_1_TEST_ONLY),
554 HasError(AllOf(HasSubstr("Edition 1_TEST_ONLY"),
555 HasSubstr("minimum supported edition 2023"))));
556 }
557
TEST(FeatureResolverTest,MergeFeaturesDistantFuture)558 TEST(FeatureResolverTest, MergeFeaturesDistantFuture) {
559 EXPECT_THAT(
560 SetupFeatureResolver(EDITION_99998_TEST_ONLY),
561 HasError(AllOf(HasSubstr("Edition 99998_TEST_ONLY"),
562 HasSubstr("maximum supported edition 99997_TEST_ONLY"))));
563 }
564
TEST(FeatureResolverLifetimesTest,Valid)565 TEST(FeatureResolverLifetimesTest, Valid) {
566 FeatureSet features = ParseTextOrDie(R"pb(
567 [pb.test] { file_feature: VALUE1 }
568 )pb");
569 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
570 features, nullptr);
571 EXPECT_THAT(results.errors, IsEmpty());
572 EXPECT_THAT(results.warnings, IsEmpty());
573 }
574
TEST(FeatureResolverLifetimesTest,DeprecatedFeature)575 TEST(FeatureResolverLifetimesTest, DeprecatedFeature) {
576 FeatureSet features = ParseTextOrDie(R"pb(
577 [pb.test] { removed_feature: VALUE1 }
578 )pb");
579 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
580 features, nullptr);
581 EXPECT_THAT(results.errors, IsEmpty());
582 EXPECT_THAT(
583 results.warnings,
584 ElementsAre(AllOf(HasSubstr("pb.TestFeatures.removed_feature"),
585 HasSubstr("deprecated in edition 2023"),
586 HasSubstr("Custom feature deprecation warning"))));
587 }
588
TEST(FeatureResolverLifetimesTest,RemovedFeature)589 TEST(FeatureResolverLifetimesTest, RemovedFeature) {
590 FeatureSet features = ParseTextOrDie(R"pb(
591 [pb.test] { removed_feature: VALUE1 }
592 )pb");
593 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2024,
594 features, nullptr);
595 EXPECT_THAT(results.errors,
596 ElementsAre(AllOf(HasSubstr("pb.TestFeatures.removed_feature"),
597 HasSubstr("removed in edition 2024"))));
598 EXPECT_THAT(results.warnings, IsEmpty());
599 }
600
TEST(FeatureResolverLifetimesTest,NotIntroduced)601 TEST(FeatureResolverLifetimesTest, NotIntroduced) {
602 FeatureSet features = ParseTextOrDie(R"pb(
603 [pb.test] { future_feature: VALUE1 }
604 )pb");
605 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
606 features, nullptr);
607 EXPECT_THAT(results.errors,
608 ElementsAre(AllOf(HasSubstr("pb.TestFeatures.future_feature"),
609 HasSubstr("introduced until edition 2024"))));
610 EXPECT_THAT(results.warnings, IsEmpty());
611 }
612
TEST(FeatureResolverLifetimesTest,WarningsAndErrors)613 TEST(FeatureResolverLifetimesTest, WarningsAndErrors) {
614 FeatureSet features = ParseTextOrDie(R"pb(
615 [pb.test] { future_feature: VALUE1 removed_feature: VALUE1 }
616 )pb");
617 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
618 features, nullptr);
619 EXPECT_THAT(results.errors,
620 ElementsAre(HasSubstr("pb.TestFeatures.future_feature")));
621 EXPECT_THAT(results.warnings,
622 ElementsAre(HasSubstr("pb.TestFeatures.removed_feature")));
623 }
624
TEST(FeatureResolverLifetimesTest,MultipleErrors)625 TEST(FeatureResolverLifetimesTest, MultipleErrors) {
626 FeatureSet features = ParseTextOrDie(R"pb(
627 [pb.test] { future_feature: VALUE1 legacy_feature: VALUE1 }
628 )pb");
629 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
630 features, nullptr);
631 EXPECT_THAT(results.errors, UnorderedElementsAre(
632 HasSubstr("pb.TestFeatures.future_feature"),
633 HasSubstr("pb.TestFeatures.legacy_feature")));
634 EXPECT_THAT(results.warnings, IsEmpty());
635 }
636
TEST(FeatureResolverLifetimesTest,DynamicPool)637 TEST(FeatureResolverLifetimesTest, DynamicPool) {
638 DescriptorPool pool;
639 {
640 FileDescriptorProto file;
641 FileDescriptorProto::GetDescriptor()->file()->CopyTo(&file);
642 ASSERT_NE(pool.BuildFile(file), nullptr);
643 }
644 {
645 FileDescriptorProto file;
646 pb::TestFeatures::GetDescriptor()->file()->CopyTo(&file);
647 ASSERT_NE(pool.BuildFile(file), nullptr);
648 }
649 const Descriptor* feature_set =
650 pool.FindMessageTypeByName("google.protobuf.FeatureSet");
651 ASSERT_NE(feature_set, nullptr);
652
653 FeatureSet features = ParseTextOrDie(R"pb(
654 [pb.test] { future_feature: VALUE1 removed_feature: VALUE1 }
655 )pb");
656 auto results = FeatureResolver::ValidateFeatureLifetimes(
657 EDITION_2023, features, feature_set);
658 EXPECT_THAT(results.errors,
659 ElementsAre(HasSubstr("pb.TestFeatures.future_feature")));
660 EXPECT_THAT(results.warnings,
661 ElementsAre(HasSubstr("pb.TestFeatures.removed_feature")));
662 }
663
TEST(FeatureResolverLifetimesTest,EmptyValueSupportValid)664 TEST(FeatureResolverLifetimesTest, EmptyValueSupportValid) {
665 FeatureSet features = ParseTextOrDie(R"pb(
666 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_EMPTY_SUPPORT }
667 )pb");
668 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
669 features, nullptr);
670 EXPECT_THAT(results.errors, IsEmpty());
671 EXPECT_THAT(results.warnings, IsEmpty());
672 }
673
TEST(FeatureResolverLifetimesTest,ValueSupportValid)674 TEST(FeatureResolverLifetimesTest, ValueSupportValid) {
675 FeatureSet features = ParseTextOrDie(R"pb(
676 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_SUPPORT }
677 )pb");
678 auto results = FeatureResolver::ValidateFeatureLifetimes(
679 EDITION_99997_TEST_ONLY, features, nullptr);
680 EXPECT_THAT(results.errors, IsEmpty());
681 EXPECT_THAT(results.warnings, IsEmpty());
682 }
683
TEST(FeatureResolverLifetimesTest,ValueSupportBeforeIntroduced)684 TEST(FeatureResolverLifetimesTest, ValueSupportBeforeIntroduced) {
685 FeatureSet features = ParseTextOrDie(R"pb(
686 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_FUTURE }
687 )pb");
688 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
689 features, nullptr);
690 EXPECT_THAT(results.errors,
691 ElementsAre(AllOf(
692 HasSubstr("pb.VALUE_LIFETIME_FUTURE"),
693 HasSubstr("introduced until edition 99997_TEST_ONLY"))));
694 EXPECT_THAT(results.warnings, IsEmpty());
695 }
696
TEST(FeatureResolverLifetimesTest,ValueSupportAfterRemoved)697 TEST(FeatureResolverLifetimesTest, ValueSupportAfterRemoved) {
698 FeatureSet features = ParseTextOrDie(R"pb(
699 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_REMOVED }
700 )pb");
701 auto results = FeatureResolver::ValidateFeatureLifetimes(
702 EDITION_99997_TEST_ONLY, features, nullptr);
703 EXPECT_THAT(
704 results.errors,
705 ElementsAre(AllOf(HasSubstr("pb.VALUE_LIFETIME_REMOVED"),
706 HasSubstr("removed in edition 99997_TEST_ONLY"))));
707 EXPECT_THAT(results.warnings, IsEmpty());
708 }
709
TEST(FeatureResolverLifetimesTest,ValueSupportDeprecated)710 TEST(FeatureResolverLifetimesTest, ValueSupportDeprecated) {
711 FeatureSet features = ParseTextOrDie(R"pb(
712 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_DEPRECATED }
713 )pb");
714 auto results = FeatureResolver::ValidateFeatureLifetimes(
715 EDITION_99997_TEST_ONLY, features, nullptr);
716 EXPECT_THAT(results.errors, IsEmpty());
717 EXPECT_THAT(
718 results.warnings,
719 ElementsAre(AllOf(HasSubstr("pb.VALUE_LIFETIME_DEPRECATED"),
720 HasSubstr("deprecated in edition 99997_TEST_ONLY"),
721 HasSubstr("Custom feature deprecation warning"))));
722 }
723
TEST(FeatureResolverLifetimesTest,ValueAndFeatureSupportDeprecated)724 TEST(FeatureResolverLifetimesTest, ValueAndFeatureSupportDeprecated) {
725 FeatureSet features = ParseTextOrDie(R"pb(
726 [pb.test] { value_lifetime_feature: VALUE_LIFETIME_DEPRECATED }
727 )pb");
728 auto results = FeatureResolver::ValidateFeatureLifetimes(
729 EDITION_99998_TEST_ONLY, features, nullptr);
730 EXPECT_THAT(results.errors, IsEmpty());
731 EXPECT_THAT(results.warnings,
732 UnorderedElementsAre(
733 AllOf(HasSubstr("pb.VALUE_LIFETIME_DEPRECATED"),
734 HasSubstr("deprecated in edition 99997_TEST_ONLY"),
735 HasSubstr("Custom feature deprecation warning")),
736 AllOf(HasSubstr("pb.TestFeatures.value_lifetime_feature"),
737 HasSubstr("deprecated in edition 99998_TEST_ONLY"),
738 HasSubstr("Custom feature deprecation warning"))));
739 }
740
TEST(FeatureResolverLifetimesTest,ValueSupportInvalidNumber)741 TEST(FeatureResolverLifetimesTest, ValueSupportInvalidNumber) {
742 FeatureSet features;
743 features.MutableExtension(pb::test)->set_value_lifetime_feature(
744 static_cast<pb::ValueLifetimeFeature>(1234));
745 auto results = FeatureResolver::ValidateFeatureLifetimes(EDITION_2023,
746 features, nullptr);
747 EXPECT_THAT(
748 results.errors,
749 ElementsAre(AllOf(HasSubstr("pb.TestFeatures.value_lifetime_feature"),
750 HasSubstr("1234"))));
751 EXPECT_THAT(results.warnings, IsEmpty());
752 }
753
754 class FakeErrorCollector : public io::ErrorCollector {
755 public:
756 FakeErrorCollector() = default;
757 ~FakeErrorCollector() override = default;
RecordWarning(int line,int column,absl::string_view message)758 void RecordWarning(int line, int column, absl::string_view message) override {
759 ABSL_LOG(WARNING) << line << ":" << column << ": " << message;
760 }
RecordError(int line,int column,absl::string_view message)761 void RecordError(int line, int column, absl::string_view message) override {
762 ABSL_LOG(ERROR) << line << ":" << column << ": " << message;
763 }
764 };
765
766 class FeatureResolverPoolTest : public testing::Test {
767 protected:
SetUp()768 void SetUp() override {
769 FileDescriptorProto file;
770 FileDescriptorProto::GetDescriptor()->file()->CopyTo(&file);
771 ASSERT_NE(pool_.BuildFile(file), nullptr);
772 feature_set_ = pool_.FindMessageTypeByName("google.protobuf.FeatureSet");
773 ASSERT_NE(feature_set_, nullptr);
774 auto defaults = FeatureResolver::CompileDefaults(
775 feature_set_, {}, EDITION_2023, EDITION_2023);
776 ASSERT_OK(defaults);
777 defaults_ = std::move(defaults).value();
778 }
779
ParseSchema(absl::string_view schema)780 const FileDescriptor* ParseSchema(absl::string_view schema) {
781 FakeErrorCollector error_collector;
782 io::ArrayInputStream raw_input(schema.data(), schema.size());
783 io::Tokenizer input(&raw_input, &error_collector);
784 compiler::Parser parser;
785 parser.RecordErrorsTo(&error_collector);
786
787 FileDescriptorProto file;
788
789 ABSL_CHECK(parser.Parse(&input, &file));
790 file.set_name("foo.proto");
791 return pool_.BuildFile(file);
792 }
793
794 DescriptorPool pool_;
795 FileDescriptorProto file_proto_;
796 const Descriptor* feature_set_;
797 FeatureSetDefaults defaults_;
798 };
799
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidNonMessage)800 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidNonMessage) {
801 const FileDescriptor* file = ParseSchema(R"schema(
802 syntax = "proto2";
803 package test;
804 import "google/protobuf/descriptor.proto";
805
806 message Foo {}
807 extend google.protobuf.FeatureSet {
808 optional string bar = 9999;
809 }
810 )schema");
811 ASSERT_NE(file, nullptr);
812
813 const FieldDescriptor* ext = file->extension(0);
814 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
815 EDITION_2023, EDITION_2023),
816 HasError(AllOf(HasSubstr("test.bar"),
817 HasSubstr("is not of message type"))));
818 }
819
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidRepeated)820 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidRepeated) {
821 const FileDescriptor* file = ParseSchema(R"schema(
822 syntax = "proto2";
823 package test;
824 import "google/protobuf/descriptor.proto";
825
826 message Foo {}
827 extend google.protobuf.FeatureSet {
828 repeated Foo bar = 9999;
829 }
830 )schema");
831 ASSERT_NE(file, nullptr);
832
833 const FieldDescriptor* ext = file->extension(0);
834 EXPECT_THAT(
835 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
836 EDITION_2023),
837 HasError(AllOf(HasSubstr("test.bar"), HasSubstr("repeated extension"))));
838 }
839
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithExtensions)840 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithExtensions) {
841 const FileDescriptor* file = ParseSchema(R"schema(
842 syntax = "proto2";
843 package test;
844 import "google/protobuf/descriptor.proto";
845
846 message Foo {
847 extensions 1;
848 }
849 extend google.protobuf.FeatureSet {
850 optional Foo bar = 9999;
851 }
852 extend Foo {
853 optional Foo bar2 = 1 [
854 targets = TARGET_TYPE_FIELD,
855 feature_support.edition_introduced = EDITION_2023,
856 edition_defaults = { edition: EDITION_LEGACY, value: "" }
857 ];
858 }
859 )schema");
860 ASSERT_NE(file, nullptr);
861
862 const FieldDescriptor* ext = file->extension(0);
863 EXPECT_THAT(
864 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
865 EDITION_2023),
866 HasError(AllOf(HasSubstr("test.bar"), HasSubstr("Nested extensions"))));
867 }
868
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithOneof)869 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithOneof) {
870 const FileDescriptor* file = ParseSchema(R"schema(
871 syntax = "proto2";
872 package test;
873 import "google/protobuf/descriptor.proto";
874
875 extend google.protobuf.FeatureSet {
876 optional Foo bar = 9999;
877 }
878 message Foo {
879 oneof x {
880 int32 int_field = 1 [
881 targets = TARGET_TYPE_FIELD,
882 feature_support.edition_introduced = EDITION_2023,
883 edition_defaults = { edition: EDITION_LEGACY, value: "1" }
884 ];
885 string string_field = 2 [
886 targets = TARGET_TYPE_FIELD,
887 feature_support.edition_introduced = EDITION_2023,
888 edition_defaults = { edition: EDITION_LEGACY, value: "'hello'" }
889 ];
890 }
891 }
892 )schema");
893 ASSERT_NE(file, nullptr);
894
895 const FieldDescriptor* ext = file->extension(0);
896 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
897 EDITION_2023, EDITION_2023),
898 HasError(AllOf(HasSubstr("test.Foo"),
899 HasSubstr("oneof feature fields"))));
900 }
901
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithRequired)902 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRequired) {
903 const FileDescriptor* file = ParseSchema(R"schema(
904 syntax = "proto2";
905 package test;
906 import "google/protobuf/descriptor.proto";
907
908 extend google.protobuf.FeatureSet {
909 optional Foo bar = 9999;
910 }
911 message Foo {
912 required int32 required_field = 1 [
913 targets = TARGET_TYPE_FIELD,
914 feature_support.edition_introduced = EDITION_2023,
915 edition_defaults = { edition: EDITION_LEGACY, value: "" }
916 ];
917 }
918 )schema");
919 ASSERT_NE(file, nullptr);
920
921 const FieldDescriptor* ext = file->extension(0);
922 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
923 EDITION_2023, EDITION_2023),
924 HasError(AllOf(HasSubstr("test.Foo.required_field"),
925 HasSubstr("required field"))));
926 }
927
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithRepeated)928 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRepeated) {
929 const FileDescriptor* file = ParseSchema(R"schema(
930 syntax = "proto2";
931 package test;
932 import "google/protobuf/descriptor.proto";
933
934 extend google.protobuf.FeatureSet {
935 optional Foo bar = 9999;
936 }
937 message Foo {
938 repeated int32 repeated_field = 1 [
939 targets = TARGET_TYPE_FIELD,
940 feature_support.edition_introduced = EDITION_2023,
941 edition_defaults = { edition: EDITION_LEGACY, value: "1" }
942 ];
943 }
944 )schema");
945 ASSERT_NE(file, nullptr);
946
947 const FieldDescriptor* ext = file->extension(0);
948 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
949 EDITION_2023, EDITION_2023),
950 HasError(AllOf(HasSubstr("test.Foo.repeated_field"),
951 HasSubstr("repeated field"))));
952 }
953
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithMissingTarget)954 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingTarget) {
955 const FileDescriptor* file = ParseSchema(R"schema(
956 syntax = "proto2";
957 package test;
958 import "google/protobuf/descriptor.proto";
959
960 extend google.protobuf.FeatureSet {
961 optional Foo bar = 9999;
962 }
963 message Foo {
964 optional bool bool_field = 1 [
965 feature_support.edition_introduced = EDITION_2023,
966 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
967 ];
968 }
969 )schema");
970 ASSERT_NE(file, nullptr);
971
972 const FieldDescriptor* ext = file->extension(0);
973 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
974 EDITION_2023, EDITION_2023),
975 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
976 HasSubstr("no target specified"))));
977 }
978
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithMissingSupport)979 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingSupport) {
980 const FileDescriptor* file = ParseSchema(R"schema(
981 syntax = "proto2";
982 package test;
983 import "google/protobuf/descriptor.proto";
984
985 extend google.protobuf.FeatureSet {
986 optional Foo bar = 9999;
987 }
988 message Foo {
989 optional bool bool_field = 1 [
990 targets = TARGET_TYPE_FIELD,
991 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
992 ];
993 }
994 )schema");
995 ASSERT_NE(file, nullptr);
996
997 const FieldDescriptor* ext = file->extension(0);
998 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
999 EDITION_2023, EDITION_2023),
1000 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1001 HasSubstr("no feature support"))));
1002 }
1003
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithMissingEditionIntroduced)1004 TEST_F(FeatureResolverPoolTest,
1005 CompileDefaultsInvalidWithMissingEditionIntroduced) {
1006 const FileDescriptor* file = ParseSchema(R"schema(
1007 syntax = "proto2";
1008 package test;
1009 import "google/protobuf/descriptor.proto";
1010
1011 extend google.protobuf.FeatureSet {
1012 optional Foo bar = 9999;
1013 }
1014 message Foo {
1015 optional bool bool_field = 1 [
1016 targets = TARGET_TYPE_FIELD,
1017 feature_support = {},
1018 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1019 ];
1020 }
1021 )schema");
1022 ASSERT_NE(file, nullptr);
1023
1024 const FieldDescriptor* ext = file->extension(0);
1025 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1026 EDITION_2023, EDITION_2023),
1027 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1028 HasSubstr("it was introduced in"))));
1029 }
1030
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithMissingDeprecationWarning)1031 TEST_F(FeatureResolverPoolTest,
1032 CompileDefaultsInvalidWithMissingDeprecationWarning) {
1033 const FileDescriptor* file = ParseSchema(R"schema(
1034 syntax = "proto2";
1035 package test;
1036 import "google/protobuf/descriptor.proto";
1037
1038 extend google.protobuf.FeatureSet {
1039 optional Foo bar = 9999;
1040 }
1041 message Foo {
1042 optional bool bool_field = 1 [
1043 targets = TARGET_TYPE_FIELD,
1044 feature_support = {
1045 edition_introduced: EDITION_2023
1046 edition_deprecated: EDITION_2023
1047 },
1048 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1049 ];
1050 }
1051 )schema");
1052 ASSERT_NE(file, nullptr);
1053
1054 const FieldDescriptor* ext = file->extension(0);
1055 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1056 EDITION_2023, EDITION_2023),
1057 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1058 HasSubstr("deprecation warning"))));
1059 }
1060
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidWithMissingDeprecation)1061 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingDeprecation) {
1062 const FileDescriptor* file = ParseSchema(R"schema(
1063 syntax = "proto2";
1064 package test;
1065 import "google/protobuf/descriptor.proto";
1066
1067 extend google.protobuf.FeatureSet {
1068 optional Foo bar = 9999;
1069 }
1070 message Foo {
1071 optional bool bool_field = 1 [
1072 targets = TARGET_TYPE_FIELD,
1073 feature_support = {
1074 edition_introduced: EDITION_2023
1075 deprecation_warning: "some message"
1076 },
1077 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1078 ];
1079 }
1080 )schema");
1081 ASSERT_NE(file, nullptr);
1082
1083 const FieldDescriptor* ext = file->extension(0);
1084 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1085 EDITION_2023, EDITION_2023),
1086 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1087 HasSubstr("is not marked deprecated"))));
1088 }
1089
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDeprecatedBeforeIntroduced)1090 TEST_F(FeatureResolverPoolTest,
1091 CompileDefaultsInvalidDeprecatedBeforeIntroduced) {
1092 const FileDescriptor* file = ParseSchema(R"schema(
1093 syntax = "proto2";
1094 package test;
1095 import "google/protobuf/descriptor.proto";
1096
1097 extend google.protobuf.FeatureSet {
1098 optional Foo bar = 9999;
1099 }
1100 message Foo {
1101 optional bool bool_field = 1 [
1102 targets = TARGET_TYPE_FIELD,
1103 feature_support = {
1104 edition_introduced: EDITION_2024
1105 edition_deprecated: EDITION_2023
1106 deprecation_warning: "warning"
1107 },
1108 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1109 ];
1110 }
1111 )schema");
1112 ASSERT_NE(file, nullptr);
1113
1114 const FieldDescriptor* ext = file->extension(0);
1115 EXPECT_THAT(
1116 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1117 EDITION_2023),
1118 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1119 HasSubstr("deprecated before it was introduced"))));
1120 }
1121
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDeprecatedAfterRemoved)1122 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDeprecatedAfterRemoved) {
1123 const FileDescriptor* file = ParseSchema(R"schema(
1124 syntax = "proto2";
1125 package test;
1126 import "google/protobuf/descriptor.proto";
1127
1128 extend google.protobuf.FeatureSet {
1129 optional Foo bar = 9999;
1130 }
1131 message Foo {
1132 optional bool bool_field = 1 [
1133 targets = TARGET_TYPE_FIELD,
1134 feature_support = {
1135 edition_introduced: EDITION_2023
1136 edition_deprecated: EDITION_2024
1137 deprecation_warning: "warning"
1138 edition_removed: EDITION_2024
1139 },
1140 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1141 ];
1142 }
1143 )schema");
1144 ASSERT_NE(file, nullptr);
1145
1146 const FieldDescriptor* ext = file->extension(0);
1147 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1148 EDITION_2023, EDITION_2023),
1149 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1150 HasSubstr("deprecated after it was removed"))));
1151 }
1152
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidRemovedBeforeIntroduced)1153 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidRemovedBeforeIntroduced) {
1154 const FileDescriptor* file = ParseSchema(R"schema(
1155 syntax = "proto2";
1156 package test;
1157 import "google/protobuf/descriptor.proto";
1158
1159 extend google.protobuf.FeatureSet {
1160 optional Foo bar = 9999;
1161 }
1162 message Foo {
1163 optional bool bool_field = 1 [
1164 targets = TARGET_TYPE_FIELD,
1165 feature_support = {
1166 edition_introduced: EDITION_2024
1167 edition_removed: EDITION_2023
1168 },
1169 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1170 ];
1171 }
1172 )schema");
1173 ASSERT_NE(file, nullptr);
1174
1175 const FieldDescriptor* ext = file->extension(0);
1176 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1177 EDITION_2023, EDITION_2023),
1178 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1179 HasSubstr("removed before it was introduced"))));
1180 }
1181
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidMissingLegacyDefaults)1182 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidMissingLegacyDefaults) {
1183 const FileDescriptor* file = ParseSchema(R"schema(
1184 syntax = "proto2";
1185 package test;
1186 import "google/protobuf/descriptor.proto";
1187
1188 extend google.protobuf.FeatureSet {
1189 optional Foo bar = 9999;
1190 }
1191 message Foo {
1192 optional bool bool_field = 1 [
1193 targets = TARGET_TYPE_FIELD,
1194 feature_support = {
1195 edition_introduced: EDITION_2024
1196 },
1197 edition_defaults = { edition: EDITION_2024, value: "true" }
1198 ];
1199 }
1200 )schema");
1201 ASSERT_NE(file, nullptr);
1202
1203 const FieldDescriptor* ext = file->extension(0);
1204 EXPECT_THAT(
1205 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1206 EDITION_2023),
1207 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1208 HasSubstr("no default specified for EDITION_LEGACY"))));
1209 }
1210
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDefaultsBeforeIntroduced)1211 TEST_F(FeatureResolverPoolTest,
1212 CompileDefaultsInvalidDefaultsBeforeIntroduced) {
1213 const FileDescriptor* file = ParseSchema(R"schema(
1214 syntax = "proto2";
1215 package test;
1216 import "google/protobuf/descriptor.proto";
1217
1218 extend google.protobuf.FeatureSet {
1219 optional Foo bar = 9999;
1220 }
1221 message Foo {
1222 optional bool bool_field = 1 [
1223 targets = TARGET_TYPE_FIELD,
1224 feature_support = {
1225 edition_introduced: EDITION_2024
1226 },
1227 edition_defaults = { edition: EDITION_LEGACY, value: "true" },
1228 edition_defaults = { edition: EDITION_2023, value: "false" }
1229 ];
1230 }
1231 )schema");
1232 ASSERT_NE(file, nullptr);
1233
1234 const FieldDescriptor* ext = file->extension(0);
1235 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1236 EDITION_2023, EDITION_2023),
1237 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1238 HasSubstr("specified for edition 2023"),
1239 HasSubstr("before it was introduced"))));
1240 }
1241
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDefaultsAfterRemoved)1242 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsAfterRemoved) {
1243 const FileDescriptor* file = ParseSchema(R"schema(
1244 syntax = "proto2";
1245 package test;
1246 import "google/protobuf/descriptor.proto";
1247
1248 extend google.protobuf.FeatureSet {
1249 optional Foo bar = 9999;
1250 }
1251 message Foo {
1252 optional bool bool_field = 1 [
1253 targets = TARGET_TYPE_FIELD,
1254 feature_support = {
1255 edition_introduced: EDITION_PROTO2
1256 edition_removed: EDITION_2023
1257 },
1258 edition_defaults = { edition: EDITION_LEGACY, value: "true" },
1259 edition_defaults = { edition: EDITION_2024, value: "true" }
1260 ];
1261 }
1262 )schema");
1263 ASSERT_NE(file, nullptr);
1264
1265 const FieldDescriptor* ext = file->extension(0);
1266 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1267 EDITION_2023, EDITION_2023),
1268 HasError(AllOf(HasSubstr("test.Foo.bool_field"),
1269 HasSubstr("specified for edition 2024"),
1270 HasSubstr("after it was removed"))));
1271 }
1272
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDefaultsScalarParsingError)1273 TEST_F(FeatureResolverPoolTest,
1274 CompileDefaultsInvalidDefaultsScalarParsingError) {
1275 const FileDescriptor* file = ParseSchema(R"schema(
1276 syntax = "proto2";
1277 package test;
1278 import "google/protobuf/descriptor.proto";
1279
1280 extend google.protobuf.FeatureSet {
1281 optional Foo bar = 9999;
1282 }
1283 message Foo {
1284 optional bool field_feature = 12 [
1285 targets = TARGET_TYPE_FIELD,
1286 feature_support.edition_introduced = EDITION_2023,
1287 edition_defaults = { edition: EDITION_LEGACY, value: "1.23" }
1288 ];
1289 }
1290 )schema");
1291 ASSERT_NE(file, nullptr);
1292
1293 const FieldDescriptor* ext = file->extension(0);
1294 EXPECT_THAT(
1295 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1296 EDITION_2023),
1297 HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("1.23"))));
1298 }
1299
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDefaultsScalarParsingErrorSkipped)1300 TEST_F(FeatureResolverPoolTest,
1301 CompileDefaultsInvalidDefaultsScalarParsingErrorSkipped) {
1302 const FileDescriptor* file = ParseSchema(R"schema(
1303 syntax = "proto2";
1304 package test;
1305 import "google/protobuf/descriptor.proto";
1306
1307 extend google.protobuf.FeatureSet {
1308 optional Foo bar = 9999;
1309 }
1310 message Foo {
1311 optional bool field_feature = 12 [
1312 targets = TARGET_TYPE_FIELD,
1313 feature_support.edition_introduced = EDITION_2023,
1314 edition_defaults = { edition: EDITION_99997_TEST_ONLY, value: "1.5" },
1315 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1316 ];
1317 }
1318 )schema");
1319 ASSERT_NE(file, nullptr);
1320
1321 const FieldDescriptor* ext = file->extension(0);
1322 auto defaults = FeatureResolver::CompileDefaults(feature_set_, {ext},
1323 EDITION_2023, EDITION_2023);
1324 ASSERT_OK(defaults);
1325
1326 auto resolver = FeatureResolver::Create(EDITION_2023, *defaults);
1327 ASSERT_OK(resolver);
1328 FeatureSet parent, child;
1329 EXPECT_OK(resolver->MergeFeatures(parent, child));
1330 }
1331
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidDefaultsTooEarly)1332 TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsTooEarly) {
1333 const FileDescriptor* file = ParseSchema(R"schema(
1334 syntax = "proto2";
1335 package test;
1336 import "google/protobuf/descriptor.proto";
1337
1338 extend google.protobuf.FeatureSet {
1339 optional Foo bar = 9999;
1340 }
1341 message Foo {
1342 optional bool field_feature = 12 [
1343 targets = TARGET_TYPE_FIELD,
1344 feature_support.edition_introduced = EDITION_2023,
1345 edition_defaults = { edition: EDITION_2_TEST_ONLY, value: "true" },
1346 edition_defaults = { edition: EDITION_LEGACY, value: "false" }
1347 ];
1348 }
1349 )schema");
1350 ASSERT_NE(file, nullptr);
1351
1352 const FieldDescriptor* ext = file->extension(0);
1353 EXPECT_THAT(
1354 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1355 EDITION_2023),
1356 HasError(HasSubstr("Minimum edition 2_TEST_ONLY is not EDITION_LEGACY")));
1357 }
1358
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueWithMissingDeprecationWarning)1359 TEST_F(FeatureResolverPoolTest,
1360 CompileDefaultsInvalidValueWithMissingDeprecationWarning) {
1361 const FileDescriptor* file = ParseSchema(R"schema(
1362 syntax = "proto2";
1363 package test;
1364 import "google/protobuf/descriptor.proto";
1365
1366 extend google.protobuf.FeatureSet {
1367 optional Foo bar = 9999;
1368 }
1369 enum FooValues {
1370 UNKNOWN = 0;
1371 VALUE = 1 [feature_support.edition_deprecated = EDITION_2023];
1372 }
1373 message Foo {
1374 optional FooValues bool_field = 1 [
1375 targets = TARGET_TYPE_FIELD,
1376 feature_support.edition_introduced = EDITION_2023,
1377 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1378 ];
1379 }
1380 )schema");
1381 ASSERT_NE(file, nullptr);
1382
1383 const FieldDescriptor* ext = file->extension(0);
1384 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1385 EDITION_2023, EDITION_2023),
1386 HasError(AllOf(HasSubstr("test.VALUE"),
1387 HasSubstr("deprecation warning"))));
1388 }
1389
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueWithMissingDeprecation)1390 TEST_F(FeatureResolverPoolTest,
1391 CompileDefaultsInvalidValueWithMissingDeprecation) {
1392 const FileDescriptor* file = ParseSchema(R"schema(
1393 syntax = "proto2";
1394 package test;
1395 import "google/protobuf/descriptor.proto";
1396
1397 extend google.protobuf.FeatureSet {
1398 optional Foo bar = 9999;
1399 }
1400 enum FooValues {
1401 UNKNOWN = 0;
1402 VALUE = 1 [feature_support.deprecation_warning = "some message"];
1403 }
1404 message Foo {
1405 optional FooValues bool_field = 1 [
1406 targets = TARGET_TYPE_FIELD,
1407 feature_support.edition_introduced = EDITION_2023,
1408 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1409 ];
1410 }
1411 )schema");
1412 ASSERT_NE(file, nullptr);
1413
1414 const FieldDescriptor* ext = file->extension(0);
1415 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1416 EDITION_2023, EDITION_2023),
1417 HasError(AllOf(HasSubstr("test.VALUE"),
1418 HasSubstr("is not marked deprecated"))));
1419 }
1420
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueDeprecatedBeforeIntroduced)1421 TEST_F(FeatureResolverPoolTest,
1422 CompileDefaultsInvalidValueDeprecatedBeforeIntroduced) {
1423 const FileDescriptor* file = ParseSchema(R"schema(
1424 syntax = "proto2";
1425 package test;
1426 import "google/protobuf/descriptor.proto";
1427
1428 extend google.protobuf.FeatureSet {
1429 optional Foo bar = 9999;
1430 }
1431 enum FooValues {
1432 UNKNOWN = 0;
1433 VALUE = 1 [feature_support = {
1434 edition_introduced: EDITION_2024
1435 edition_deprecated: EDITION_2023
1436 deprecation_warning: "warning"
1437 }];
1438 }
1439 message Foo {
1440 optional FooValues bool_field = 1 [
1441 targets = TARGET_TYPE_FIELD,
1442 feature_support.edition_introduced = EDITION_2023,
1443 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1444 ];
1445 }
1446 )schema");
1447 ASSERT_NE(file, nullptr);
1448
1449 const FieldDescriptor* ext = file->extension(0);
1450 EXPECT_THAT(
1451 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1452 EDITION_2023),
1453 HasError(AllOf(HasSubstr("test.VALUE"),
1454 HasSubstr("deprecated before it was introduced"))));
1455 }
1456
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueDeprecatedBeforeIntroducedInherited)1457 TEST_F(FeatureResolverPoolTest,
1458 CompileDefaultsInvalidValueDeprecatedBeforeIntroducedInherited) {
1459 const FileDescriptor* file = ParseSchema(R"schema(
1460 syntax = "proto2";
1461 package test;
1462 import "google/protobuf/descriptor.proto";
1463
1464 extend google.protobuf.FeatureSet {
1465 optional Foo bar = 9999;
1466 }
1467 enum FooValues {
1468 UNKNOWN = 0;
1469 VALUE = 1 [feature_support = {
1470 edition_deprecated: EDITION_2023
1471 deprecation_warning: "warning"
1472 }];
1473 }
1474 message Foo {
1475 optional FooValues bool_field = 1 [
1476 targets = TARGET_TYPE_FIELD,
1477 feature_support.edition_introduced = EDITION_2024,
1478 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1479 ];
1480 }
1481 )schema");
1482 ASSERT_NE(file, nullptr);
1483
1484 const FieldDescriptor* ext = file->extension(0);
1485 EXPECT_THAT(
1486 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1487 EDITION_2023),
1488 HasError(AllOf(HasSubstr("test.VALUE"),
1489 HasSubstr("deprecated before it was introduced"))));
1490 }
1491
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueDeprecatedAfterRemoved)1492 TEST_F(FeatureResolverPoolTest,
1493 CompileDefaultsInvalidValueDeprecatedAfterRemoved) {
1494 const FileDescriptor* file = ParseSchema(R"schema(
1495 syntax = "proto2";
1496 package test;
1497 import "google/protobuf/descriptor.proto";
1498
1499 extend google.protobuf.FeatureSet {
1500 optional Foo bar = 9999;
1501 }
1502 enum FooValues {
1503 UNKNOWN = 0;
1504 VALUE = 1 [feature_support = {
1505 edition_introduced: EDITION_2023
1506 edition_deprecated: EDITION_2024
1507 deprecation_warning: "warning"
1508 edition_removed: EDITION_2024
1509 }];
1510 }
1511 message Foo {
1512 optional FooValues bool_field = 1 [
1513 targets = TARGET_TYPE_FIELD,
1514 feature_support.edition_introduced = EDITION_2023,
1515 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1516 ];
1517 }
1518 )schema");
1519 ASSERT_NE(file, nullptr);
1520
1521 const FieldDescriptor* ext = file->extension(0);
1522 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1523 EDITION_2023, EDITION_2023),
1524 HasError(AllOf(HasSubstr("test.VALUE"),
1525 HasSubstr("deprecated after it was removed"))));
1526 }
1527
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueRemovedBeforeIntroduced)1528 TEST_F(FeatureResolverPoolTest,
1529 CompileDefaultsInvalidValueRemovedBeforeIntroduced) {
1530 const FileDescriptor* file = ParseSchema(R"schema(
1531 syntax = "proto2";
1532 package test;
1533 import "google/protobuf/descriptor.proto";
1534
1535 extend google.protobuf.FeatureSet {
1536 optional Foo bar = 9999;
1537 }
1538 enum FooValues {
1539 UNKNOWN = 0;
1540 VALUE = 1 [feature_support = {
1541 edition_introduced: EDITION_2024
1542 edition_removed: EDITION_2023
1543 }];
1544 }
1545 message Foo {
1546 optional FooValues bool_field = 1 [
1547 targets = TARGET_TYPE_FIELD,
1548 feature_support.edition_introduced = EDITION_2023,
1549 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1550 ];
1551 }
1552 )schema");
1553 ASSERT_NE(file, nullptr);
1554
1555 const FieldDescriptor* ext = file->extension(0);
1556 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1557 EDITION_2023, EDITION_2023),
1558 HasError(AllOf(HasSubstr("test.VALUE"),
1559 HasSubstr("removed before it was introduced"))));
1560 }
1561
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueIntroducedBeforeFeature)1562 TEST_F(FeatureResolverPoolTest,
1563 CompileDefaultsInvalidValueIntroducedBeforeFeature) {
1564 const FileDescriptor* file = ParseSchema(R"schema(
1565 syntax = "proto2";
1566 package test;
1567 import "google/protobuf/descriptor.proto";
1568
1569 extend google.protobuf.FeatureSet {
1570 optional Foo bar = 9999;
1571 }
1572 enum FooValues {
1573 UNKNOWN = 0;
1574 VALUE = 1 [feature_support = {
1575 edition_introduced: EDITION_2023
1576 }];
1577 }
1578 message Foo {
1579 optional FooValues bool_field = 1 [
1580 targets = TARGET_TYPE_FIELD,
1581 feature_support.edition_introduced = EDITION_2024,
1582 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1583 ];
1584 }
1585 )schema");
1586 ASSERT_NE(file, nullptr);
1587
1588 const FieldDescriptor* ext = file->extension(0);
1589 EXPECT_THAT(
1590 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1591 EDITION_2023),
1592 HasError(AllOf(HasSubstr("test.VALUE"), HasSubstr("introduced before"),
1593 HasSubstr("test.Foo.bool_field"))));
1594 }
1595
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueIntroducedAfterFeatureRemoved)1596 TEST_F(FeatureResolverPoolTest,
1597 CompileDefaultsInvalidValueIntroducedAfterFeatureRemoved) {
1598 const FileDescriptor* file = ParseSchema(R"schema(
1599 syntax = "proto2";
1600 package test;
1601 import "google/protobuf/descriptor.proto";
1602
1603 extend google.protobuf.FeatureSet {
1604 optional Foo bar = 9999;
1605 }
1606 enum FooValues {
1607 UNKNOWN = 0;
1608 VALUE = 1 [feature_support = {
1609 edition_introduced: EDITION_99997_TEST_ONLY
1610 }];
1611 }
1612 message Foo {
1613 optional FooValues bool_field = 1 [
1614 targets = TARGET_TYPE_FIELD,
1615 feature_support.edition_introduced = EDITION_2023,
1616 feature_support.edition_removed = EDITION_2024,
1617 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1618 ];
1619 }
1620 )schema");
1621 ASSERT_NE(file, nullptr);
1622
1623 const FieldDescriptor* ext = file->extension(0);
1624 EXPECT_THAT(FeatureResolver::CompileDefaults(feature_set_, {ext},
1625 EDITION_2023, EDITION_2023),
1626 HasError(AllOf(HasSubstr("test.VALUE"),
1627 HasSubstr("removed before it was introduced"))));
1628 }
1629
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueRemovedAfterFeature)1630 TEST_F(FeatureResolverPoolTest,
1631 CompileDefaultsInvalidValueRemovedAfterFeature) {
1632 const FileDescriptor* file = ParseSchema(R"schema(
1633 syntax = "proto2";
1634 package test;
1635 import "google/protobuf/descriptor.proto";
1636
1637 extend google.protobuf.FeatureSet {
1638 optional Foo bar = 9999;
1639 }
1640 enum FooValues {
1641 UNKNOWN = 0;
1642 VALUE = 1 [feature_support = {
1643 edition_removed: EDITION_99997_TEST_ONLY
1644 }];
1645 }
1646 message Foo {
1647 optional FooValues bool_field = 1 [
1648 targets = TARGET_TYPE_FIELD,
1649 feature_support.edition_introduced = EDITION_2023,
1650 feature_support.edition_removed = EDITION_2024,
1651 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1652 ];
1653 }
1654 )schema");
1655 ASSERT_NE(file, nullptr);
1656
1657 const FieldDescriptor* ext = file->extension(0);
1658 EXPECT_THAT(
1659 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1660 EDITION_2023),
1661 HasError(AllOf(HasSubstr("test.VALUE"), HasSubstr("removed after"),
1662 HasSubstr("test.Foo.bool_field"))));
1663 }
1664
TEST_F(FeatureResolverPoolTest,CompileDefaultsInvalidValueDeprecatedAfterFeature)1665 TEST_F(FeatureResolverPoolTest,
1666 CompileDefaultsInvalidValueDeprecatedAfterFeature) {
1667 const FileDescriptor* file = ParseSchema(R"schema(
1668 syntax = "proto2";
1669 package test;
1670 import "google/protobuf/descriptor.proto";
1671
1672 extend google.protobuf.FeatureSet {
1673 optional Foo bar = 9999;
1674 }
1675 enum FooValues {
1676 UNKNOWN = 0;
1677 VALUE = 1 [feature_support = {
1678 edition_deprecated: EDITION_99997_TEST_ONLY
1679 deprecation_warning: "warning"
1680 }];
1681 }
1682 message Foo {
1683 optional FooValues bool_field = 1 [
1684 targets = TARGET_TYPE_FIELD,
1685 feature_support.edition_introduced = EDITION_2023,
1686 feature_support.edition_deprecated = EDITION_2024,
1687 feature_support.deprecation_warning = "warning",
1688 edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
1689 ];
1690 }
1691 )schema");
1692 ASSERT_NE(file, nullptr);
1693
1694 const FieldDescriptor* ext = file->extension(0);
1695 EXPECT_THAT(
1696 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
1697 EDITION_2023),
1698 HasError(AllOf(HasSubstr("test.VALUE"), HasSubstr("deprecated after"),
1699 HasSubstr("test.Foo.bool_field"))));
1700 }
1701
TEST_F(FeatureResolverPoolTest,CompileDefaultsMinimumTooEarly)1702 TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumTooEarly) {
1703 const FileDescriptor* file = ParseSchema(R"schema(
1704 syntax = "proto2";
1705 package test;
1706 import "google/protobuf/descriptor.proto";
1707
1708 extend google.protobuf.FeatureSet {
1709 optional Foo bar = 9999;
1710 }
1711 message Foo {
1712 optional bool field_feature = 12 [
1713 targets = TARGET_TYPE_FIELD,
1714 feature_support.edition_introduced = EDITION_2023,
1715 edition_defaults = { edition: EDITION_LEGACY, value: "true" }
1716 ];
1717 }
1718 )schema");
1719 ASSERT_NE(file, nullptr);
1720
1721 const FieldDescriptor* ext = file->extension(0);
1722 EXPECT_THAT(
1723 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_1_TEST_ONLY,
1724 EDITION_99997_TEST_ONLY),
1725 HasError(HasSubstr("edition 1_TEST_ONLY is earlier than the oldest")));
1726 }
1727
TEST_F(FeatureResolverPoolTest,CompileDefaultsRemovedOnly)1728 TEST_F(FeatureResolverPoolTest, CompileDefaultsRemovedOnly) {
1729 const FileDescriptor* file = ParseSchema(R"schema(
1730 syntax = "proto2";
1731 package test;
1732 import "google/protobuf/descriptor.proto";
1733
1734 extend google.protobuf.FeatureSet {
1735 optional Foo bar = 9999;
1736 }
1737 enum Bar {
1738 TEST_ENUM_FEATURE_UNKNOWN = 0;
1739 VALUE1 = 1;
1740 VALUE2 = 2;
1741 }
1742 message Foo {
1743 optional Bar file_feature = 1 [
1744 targets = TARGET_TYPE_FIELD,
1745 feature_support.edition_introduced = EDITION_2023,
1746 feature_support.edition_removed = EDITION_99998_TEST_ONLY,
1747 edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
1748 ];
1749 }
1750 )schema");
1751 ASSERT_NE(file, nullptr);
1752
1753 const FieldDescriptor* ext = file->extension(0);
1754 auto compiled_defaults = FeatureResolver::CompileDefaults(
1755 feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
1756 ASSERT_OK(compiled_defaults);
1757 const auto& defaults = *compiled_defaults->defaults().rbegin();
1758 EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
1759 EXPECT_THAT(defaults.fixed_features().GetExtension(pb::test).file_feature(),
1760 pb::VALUE1);
1761 EXPECT_FALSE(defaults.overridable_features()
1762 .GetExtension(pb::test)
1763 .has_file_feature());
1764 }
1765
TEST_F(FeatureResolverPoolTest,CompileDefaultsIntroducedOnly)1766 TEST_F(FeatureResolverPoolTest, CompileDefaultsIntroducedOnly) {
1767 const FileDescriptor* file = ParseSchema(R"schema(
1768 syntax = "proto2";
1769 package test;
1770 import "google/protobuf/descriptor.proto";
1771
1772 extend google.protobuf.FeatureSet {
1773 optional Foo bar = 9999;
1774 }
1775 enum Bar {
1776 TEST_ENUM_FEATURE_UNKNOWN = 0;
1777 VALUE1 = 1;
1778 VALUE2 = 2;
1779 }
1780 message Foo {
1781 optional Bar file_feature = 1 [
1782 targets = TARGET_TYPE_FIELD,
1783 feature_support.edition_introduced = EDITION_99998_TEST_ONLY,
1784 edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
1785 ];
1786 }
1787 )schema");
1788 ASSERT_NE(file, nullptr);
1789
1790 const FieldDescriptor* ext = file->extension(0);
1791 auto compiled_defaults = FeatureResolver::CompileDefaults(
1792 feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
1793 ASSERT_OK(compiled_defaults);
1794 const auto& defaults = *compiled_defaults->defaults().rbegin();
1795 EXPECT_THAT(defaults.edition(), EDITION_99998_TEST_ONLY);
1796 EXPECT_THAT(
1797 defaults.overridable_features().GetExtension(pb::test).file_feature(),
1798 pb::VALUE1);
1799 EXPECT_FALSE(
1800 defaults.fixed_features().GetExtension(pb::test).has_file_feature());
1801 }
1802
TEST_F(FeatureResolverPoolTest,CompileDefaultsMinimumCovered)1803 TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
1804 const FileDescriptor* file = ParseSchema(R"schema(
1805 syntax = "proto2";
1806 package test;
1807 import "google/protobuf/descriptor.proto";
1808
1809 extend google.protobuf.FeatureSet {
1810 optional Foo bar = 9999;
1811 }
1812 enum Bar {
1813 TEST_ENUM_FEATURE_UNKNOWN = 0;
1814 VALUE1 = 1;
1815 VALUE2 = 2;
1816 VALUE3 = 3;
1817 }
1818 message Foo {
1819 optional Bar file_feature = 1 [
1820 targets = TARGET_TYPE_FIELD,
1821 feature_support.edition_introduced = EDITION_2023,
1822 edition_defaults = { edition: EDITION_99998_TEST_ONLY, value: "VALUE3" },
1823 edition_defaults = { edition: EDITION_2023, value: "VALUE2" },
1824 edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
1825 ];
1826 }
1827 )schema");
1828 ASSERT_NE(file, nullptr);
1829
1830 const FieldDescriptor* ext = file->extension(0);
1831 auto defaults = FeatureResolver::CompileDefaults(
1832 feature_set_, {ext}, EDITION_99997_TEST_ONLY, EDITION_99999_TEST_ONLY);
1833 ASSERT_OK(defaults);
1834 EXPECT_THAT(*defaults, EqualsProto(R"pb(
1835 minimum_edition: EDITION_99997_TEST_ONLY
1836 maximum_edition: EDITION_99999_TEST_ONLY
1837 defaults {
1838 edition: EDITION_LEGACY
1839 overridable_features {
1840 [pb.test] {}
1841 }
1842 fixed_features {
1843 field_presence: EXPLICIT
1844 enum_type: CLOSED
1845 repeated_field_encoding: EXPANDED
1846 utf8_validation: NONE
1847 message_encoding: LENGTH_PREFIXED
1848 json_format: LEGACY_BEST_EFFORT
1849 [pb.test] { file_feature: VALUE1 }
1850 }
1851 }
1852 defaults {
1853 edition: EDITION_PROTO3
1854 overridable_features {
1855 [pb.test] {}
1856 }
1857 fixed_features {
1858 field_presence: IMPLICIT
1859 enum_type: OPEN
1860 repeated_field_encoding: PACKED
1861 utf8_validation: VERIFY
1862 message_encoding: LENGTH_PREFIXED
1863 json_format: ALLOW
1864 [pb.test] { file_feature: VALUE1 }
1865 }
1866 }
1867 defaults {
1868 edition: EDITION_2023
1869 overridable_features {
1870 field_presence: EXPLICIT
1871 enum_type: OPEN
1872 repeated_field_encoding: PACKED
1873 utf8_validation: VERIFY
1874 message_encoding: LENGTH_PREFIXED
1875 json_format: ALLOW
1876 [pb.test] { file_feature: VALUE2 }
1877 }
1878 fixed_features {
1879 [pb.test] {}
1880 }
1881 }
1882 defaults {
1883 edition: EDITION_99998_TEST_ONLY
1884 overridable_features {
1885 field_presence: EXPLICIT
1886 enum_type: OPEN
1887 repeated_field_encoding: PACKED
1888 utf8_validation: VERIFY
1889 message_encoding: LENGTH_PREFIXED
1890 json_format: ALLOW
1891 [pb.test] { file_feature: VALUE3 }
1892 }
1893 fixed_features {
1894 [pb.test] {}
1895 }
1896 }
1897 )pb"));
1898 }
1899
1900 class FeatureUnboundedTypeTest
1901 : public FeatureResolverPoolTest,
1902 public ::testing::WithParamInterface<absl::string_view> {};
1903
TEST_P(FeatureUnboundedTypeTest,CompileDefaults)1904 TEST_P(FeatureUnboundedTypeTest, CompileDefaults) {
1905 const FileDescriptor* file = ParseSchema(absl::Substitute(R"schema(
1906 syntax = "proto2";
1907 package test;
1908 import "google/protobuf/descriptor.proto";
1909
1910 extend google.protobuf.FeatureSet {
1911 optional Foo bar = 9999;
1912 }
1913 message SomeMessage {
1914 optional bool value = 1;
1915 }
1916 message Foo {
1917 optional $0 field_feature = 12 [
1918 targets = TARGET_TYPE_FIELD,
1919 feature_support.edition_introduced = EDITION_2023,
1920 edition_defaults = { edition: EDITION_LEGACY, value: "1" }
1921 ];
1922 }
1923 )schema",
1924 GetParam()));
1925 ASSERT_NE(file, nullptr);
1926
1927 const FieldDescriptor* ext = file->extension(0);
1928 EXPECT_THAT(
1929 FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_1_TEST_ONLY,
1930 EDITION_99997_TEST_ONLY),
1931 HasError(HasSubstr("is not an enum or boolean")));
1932 }
1933
1934 INSTANTIATE_TEST_SUITE_P(FeatureUnboundedTypeTestImpl, FeatureUnboundedTypeTest,
1935 testing::Values("int32", "int64", "uint32", "string",
1936 "bytes", "float", "double",
1937 "SomeMessage"));
1938
1939 } // namespace
1940 } // namespace protobuf
1941 } // namespace google
1942
1943 #include "google/protobuf/port_undef.inc"
1944