1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <folly/portability/GMock.h>
18 #include <folly/portability/GTest.h>
19 
20 #include <thrift/lib/cpp2/frozen/FrozenUtil.h>
21 #include <thrift/lib/cpp2/frozen/test/gen-cpp2/Example_layouts.h>
22 #include <thrift/lib/cpp2/frozen/test/gen-cpp2/Example_types_custom_protocol.h>
23 #include <thrift/lib/cpp2/protocol/DebugProtocol.h>
24 #include <thrift/lib/cpp2/protocol/Serializer.h>
25 
26 namespace {
27 using namespace apache::thrift;
28 using namespace apache::thrift::frozen;
29 using namespace apache::thrift::test;
30 using namespace apache::thrift::util;
31 using namespace testing;
32 using Fixed8 = apache::thrift::frozen::FixedSizeString<8>;
33 using Fixed2 = apache::thrift::frozen::FixedSizeString<2>;
34 
35 template <class T>
toString(const T & x)36 std::string toString(const T& x) {
37   return debugString(x);
38 }
39 template <class T>
toString(const Layout<T> & x)40 std::string toString(const Layout<T>& x) {
41   std::ostringstream xStr;
42   xStr << x;
43   return xStr.str();
44 }
45 
46 #define EXPECT_PRINTED_EQ(a, b) EXPECT_EQ(toString(a), toString(b))
47 
__anon45252c020202null48 EveryLayout stressValue2 = [] {
49   EveryLayout x;
50   *x.aBool_ref() = true;
51   *x.aInt_ref() = 2;
52   *x.aList_ref() = {3, 5};
53   *x.aSet_ref() = {7, 11};
54   *x.aHashSet_ref() = {13, 17};
55   *x.aMap_ref() = {{19, 23}, {29, 31}};
56   *x.aHashMap_ref() = {{37, 41}, {43, 47}};
57   x.optInt_ref() = 53;
58   *x.aFloat_ref() = 59.61;
59   x.optMap_ref() = {{2, 4}, {3, 9}};
60   return x;
61 }();
62 
63 template <class T>
layout(const T & x,Layout<T> && layout=Layout<T> ())64 Layout<T>&& layout(const T& x, Layout<T>&& layout = Layout<T>()) {
65   size_t size = LayoutRoot::layout(x, layout);
66   (void)size;
67   return std::move(layout);
68 }
69 
70 template <class T>
layout(const T & x,size_t & size)71 Layout<T> layout(const T& x, size_t& size) {
72   Layout<T> layout;
73   size = LayoutRoot::layout(x, layout);
74   return std::move(layout);
75 }
76 
__anon45252c020302null77 auto tom1 = [] {
78   Pet1 max;
79   *max.name_ref() = "max";
80   Pet1 ed;
81   *ed.name_ref() = "ed";
82   Person1 tom;
83   *tom.name_ref() = "tom";
84   *tom.height_ref() = 1.82f;
85   tom.age_ref() = 30;
86   tom.pets_ref()->push_back(max);
87   tom.pets_ref()->push_back(ed);
88   return tom;
89 }();
__anon45252c020402null90 auto tom2 = [] {
91   Pet2 max;
92   *max.name_ref() = "max";
93   Pet2 ed;
94   *ed.name_ref() = "ed";
95   Person2 tom;
96   *tom.name_ref() = "tom";
97   *tom.weight_ref() = 169;
98   tom.age_ref() = 30;
99   tom.pets_ref()->push_back(max);
100   tom.pets_ref()->push_back(ed);
101   return tom;
102 }();
103 
TEST(Frozen,EndToEnd)104 TEST(Frozen, EndToEnd) {
105   auto view = freeze(tom1);
106   EXPECT_EQ(*tom1.name_ref(), view.name());
107   ASSERT_TRUE(view.name_ref().has_value());
108   EXPECT_EQ(*tom1.name_ref(), *view.name_ref());
109   ASSERT_TRUE(view.age().has_value());
110   EXPECT_EQ(*tom1.age_ref(), view.age().value());
111   EXPECT_EQ(*tom1.height_ref(), view.height());
112   EXPECT_EQ(view.pets()[0].name(), *tom1.pets_ref()[0].name_ref());
113   auto& pets = *tom1.pets_ref();
114   auto fpets = view.pets();
115   ASSERT_EQ(pets.size(), fpets.size());
116   for (size_t i = 0; i < tom1.pets_ref()->size(); ++i) {
117     EXPECT_EQ(*pets[i].name_ref(), fpets[i].name());
118   }
119   Layout<Person1> layout;
120   LayoutRoot::layout(tom1, layout);
121   auto ttom = view.thaw();
122   EXPECT_TRUE(ttom.name_ref().has_value());
123   EXPECT_PRINTED_EQ(tom1, ttom);
124 }
125 
TEST(Frozen,Comparison)126 TEST(Frozen, Comparison) {
127   EXPECT_EQ(frozenSize(tom1), frozenSize(tom2));
128   auto view1 = freeze(tom1);
129   auto view2 = freeze(tom2);
130   // name is optional now, and was __isset=true, so we don't write it
131   ASSERT_TRUE(view2.age().has_value());
132   EXPECT_EQ(*tom2.age_ref(), view2.age().value());
133   EXPECT_EQ(*tom2.name_ref(), view2.name());
134   EXPECT_EQ(tom2.name_ref()->size(), view2.name().size());
135   auto ttom2 = view2.thaw();
136   EXPECT_EQ(tom2, ttom2) << debugString(tom2) << debugString(ttom2);
137 }
138 
TEST(Frozen,Compatibility)139 TEST(Frozen, Compatibility) {
140   // Make sure Person1 works well with Person2
141   schema::MemorySchema schema;
142   Layout<Person1> person1;
143   Layout<Person2> person2;
144 
145   size_t size = LayoutRoot::layout(tom1, person1);
146 
147   saveRoot(person1, schema);
148   loadRoot(person2, schema);
149 
150   std::string storage(size, 'X');
151   folly::MutableStringPiece charRange(&storage.front(), size);
152   const folly::MutableByteRange bytes(charRange);
153   folly::MutableByteRange freezeRange = bytes;
154 
155   ByteRangeFreezer::freeze(person1, tom1, freezeRange);
156   auto view12 = person1.view({bytes.begin(), 0});
157   auto view21 = person2.view({bytes.begin(), 0});
158   EXPECT_EQ(view12.name(), view21.name());
159   EXPECT_EQ(view12.age(), view21.age());
160   EXPECT_TRUE(view12.height());
161   EXPECT_FALSE(view21.weight());
162   ASSERT_GE(view12.pets().size(), 2);
163   EXPECT_EQ(view12.pets()[0].name(), view21.pets()[0].name());
164   EXPECT_EQ(view12.pets()[1].name(), view21.pets()[1].name());
165 }
166 
167 // It's important to make sure the hash function not change, other wise the
168 // existing indexed data will be messed up.
TEST(Frozen,HashCompatibility)169 TEST(Frozen, HashCompatibility) {
170   // int
171   std::hash<int64_t> intHash;
172   for (int64_t i = -10000; i < 10000; ++i) {
173     EXPECT_EQ(Layout<int64_t>::hash(i), intHash(i));
174   }
175 
176   // string
177   using StrLayout = Layout<std::string>;
178   using View = StrLayout::View;
179 
180   auto follyHash = [](const View& v) {
181     return folly::hash::fnv64_buf(v.begin(), v.size());
182   };
183 
184   std::vector<std::string> strs{
185       "hello", "WOrld", "luckylook", "facebook", "Let it go!!"};
186   for (auto&& s : strs) {
187     View v(s);
188     EXPECT_EQ(StrLayout::hash(v), follyHash(v));
189   }
190 }
191 
TEST(Frozen,EmbeddedSchema)192 TEST(Frozen, EmbeddedSchema) {
193   std::string storage;
194   {
195     schema::Schema schema;
196     schema::MemorySchema memSchema;
197 
198     Layout<Person1> person1a;
199 
200     size_t size;
201     size = LayoutRoot::layout(tom1, person1a);
202     saveRoot(person1a, memSchema);
203 
204     schema::convert(memSchema, schema);
205 
206     CompactSerializer::serialize(schema, &storage);
207     size_t start = storage.size();
208     storage.resize(size + storage.size());
209 
210     folly::MutableStringPiece charRange(&storage[start], size);
211     folly::MutableByteRange bytes(charRange);
212     ByteRangeFreezer::freeze(person1a, tom1, bytes);
213   }
214   {
215     schema::Schema schema;
216     schema::MemorySchema memSchema;
217     Layout<Person2> person2;
218 
219     size_t start = CompactSerializer::deserialize(storage, schema);
220 
221     schema::convert(std::move(schema), memSchema);
222 
223     loadRoot(person2, memSchema);
224 
225     folly::StringPiece charRange(&storage[start], storage.size() - start);
226     folly::ByteRange bytes(charRange);
227     auto view = person2.view({bytes.begin(), 0});
228     EXPECT_EQ(*tom1.name_ref(), view.name());
229     ASSERT_EQ(tom1.age_ref().has_value(), view.age().has_value());
230     if (auto age = tom1.age_ref()) {
231       EXPECT_EQ(*age, view.age().value());
232     }
233     EXPECT_EQ(*tom1.pets_ref()[0].name_ref(), view.pets()[0].name());
234     EXPECT_EQ(*tom1.pets_ref()[1].name_ref(), view.pets()[1].name());
235   }
236 }
237 
TEST(Frozen,NoLayout)238 TEST(Frozen, NoLayout) {
239   ViewPosition null{nullptr, 0};
240 
241   EXPECT_FALSE(Layout<bool>().view(null));
242   EXPECT_EQ(0, Layout<int>().view(null));
243   EXPECT_EQ(0.0f, Layout<float>().view(null));
244   EXPECT_EQ(
245       apache::thrift::frozen::OptionalFieldView<int>(),
246       Layout<folly::Optional<int>>().view(null));
247   EXPECT_EQ(std::string(), Layout<std::string>().view(null));
248   EXPECT_EQ(std::vector<int>(), Layout<std::vector<int>>().view(null).thaw());
249   EXPECT_EQ(Person1(), Layout<Person1>().view(null).thaw());
250   EXPECT_EQ(Pet1(), Layout<Pet1>().view(null).thaw());
251   EXPECT_EQ(std::set<int>(), Layout<std::set<int>>().view(null).thaw());
252   EXPECT_EQ(
253       (std::map<int, int>()), (Layout<std::map<int, int>>().view(null).thaw()));
254 
255   Layout<Person1> emptyPersonLayout;
256   std::array<uint8_t, 100> storage;
257   folly::MutableByteRange bytes(storage.begin(), storage.end());
258   EXPECT_THROW(
259       ByteRangeFreezer::freeze(emptyPersonLayout, tom1, bytes),
260       LayoutException);
261 }
262 
263 template <class T>
testMaxLayout(const T & value)264 void testMaxLayout(const T& value) {
265   auto minLayout = Layout<T>();
266   auto valLayout = minLayout;
267   auto maxLayout = maximumLayout<T>();
268   LayoutRoot::layout(value, valLayout);
269   EXPECT_GT(valLayout.size, 0);
270   ASSERT_GT(maxLayout.size, 0);
271   std::array<uint8_t, 1000> storage;
272   folly::MutableByteRange bytes(storage.begin(), storage.end());
273   EXPECT_THROW(
274       ByteRangeFreezer::freeze(minLayout, value, bytes), LayoutException);
275   auto f = ByteRangeFreezer::freeze(maxLayout, value, bytes);
276   auto check = f.thaw();
277   EXPECT_EQ(value, check);
278 }
279 
TEST(Frozen,MaxLayoutVector)280 TEST(Frozen, MaxLayoutVector) {
281   testMaxLayout(std::vector<int>{99, 24});
282 }
283 
TEST(Frozen,MaxLayoutPairTree)284 TEST(Frozen, MaxLayoutPairTree) {
285   using std::make_pair;
286   auto p1 = make_pair(5, 2.3);
287   auto p2 = make_pair(4, p1);
288   auto p3 = make_pair(3, p2);
289   auto p4 = make_pair(2, p3);
290   auto p5 = make_pair(1, p4);
291   auto p6 = make_pair(0, p5);
292   testMaxLayout(p6);
293 }
294 
TEST(Frozen,MaxLayoutStress)295 TEST(Frozen, MaxLayoutStress) {
296   testMaxLayout(stressValue2);
297 }
298 
TEST(Frozen,String)299 TEST(Frozen, String) {
300   std::string str = "Hello";
301   auto fstr = freeze(str);
302   EXPECT_EQ(str, folly::StringPiece(fstr));
303   EXPECT_EQ(std::string(), folly::StringPiece(freeze(std::string())));
304 }
305 
TEST(Frozen,VectorString)306 TEST(Frozen, VectorString) {
307   std::vector<std::string> strs{"hello", "sara"};
308   auto fstrs = freeze(strs);
309   EXPECT_EQ(strs[0], fstrs[0]);
310   EXPECT_EQ(strs[1], fstrs[1]);
311   EXPECT_EQ(strs.size(), fstrs.size());
312   std::vector<std::string> check;
313 }
314 
TEST(Frozen,BigMap)315 TEST(Frozen, BigMap) {
316   PlaceTest t;
317   for (int i = 0; i < 1000; ++i) {
318     auto& place = t.places_ref()[i * i * i % 757368944];
319     *place.name_ref() = folly::to<std::string>(i);
320     for (int j = 0; j < 200; ++j) {
321       ++place.popularityByHour_ref()[rand() % (24 * 7)];
322     }
323   }
324   folly::IOBufQueue bq(folly::IOBufQueue::cacheChainLength());
325   CompactSerializer::serialize(t, &bq);
326   auto compactSize = bq.chainLength();
327   auto frozenSize = ::frozenSize(t);
328   EXPECT_EQ(t, freeze(t).thaw());
329   EXPECT_LT(frozenSize, compactSize * 0.7);
330 }
__anon45252c020602null331 Tiny tiny1 = [] {
332   Tiny obj;
333   *obj.a_ref() = "just a";
334   return obj;
335 }();
__anon45252c020702null336 Tiny tiny2 = [] {
337   Tiny obj;
338   *obj.a_ref() = "two";
339   *obj.b_ref() = "set";
340   return obj;
341 }();
__anon45252c020802null342 Tiny tiny4 = [] {
343   Tiny obj;
344   *obj.a_ref() = "four";
345   *obj.b_ref() = "itty";
346   *obj.c_ref() = "bitty";
347   *obj.d_ref() = "strings";
348   return obj;
349 }();
350 
TEST(Frozen,Tiny)351 TEST(Frozen, Tiny) {
352   EXPECT_EQ(tiny4, freeze(tiny4).thaw());
353   EXPECT_EQ(24, frozenSize(tiny4));
354 }
355 
TEST(Frozen,SchemaSaving)356 TEST(Frozen, SchemaSaving) {
357   // calculate a layout
358   Layout<EveryLayout> stressLayoutCalculated;
359   CHECK(LayoutRoot::layout(stressValue2, stressLayoutCalculated));
360 
361   // save it
362   schema::MemorySchema schemaSaved;
363   saveRoot(stressLayoutCalculated, schemaSaved);
364 
365   // reload it
366   Layout<EveryLayout> stressLayoutLoaded;
367   loadRoot(stressLayoutLoaded, schemaSaved);
368 
369   // make sure the two layouts are identical (via printing)
370   EXPECT_PRINTED_EQ(stressLayoutCalculated, stressLayoutLoaded);
371 
372   // make sure layouts round-trip
373   schema::MemorySchema schemaLoaded;
374   saveRoot(stressLayoutLoaded, schemaLoaded);
375   EXPECT_EQ(schemaSaved, schemaLoaded);
376 }
377 
TEST(Frozen,Enum)378 TEST(Frozen, Enum) {
379   Person1 he, she;
380   *he.gender_ref() = Gender::Male;
381   *she.gender_ref() = Gender::Female;
382   EXPECT_EQ(he, freeze(he).thaw());
383   EXPECT_EQ(she, freeze(she).thaw());
384 }
385 
TEST(Frozen,EnumAsKey)386 TEST(Frozen, EnumAsKey) {
387   EnumAsKeyTest thriftObj;
388   thriftObj.enumSet_ref()->insert(Gender::Male);
389   thriftObj.enumMap_ref()->emplace(Gender::Female, 1219);
390   thriftObj.outsideEnumSet_ref()->insert(Animal::DOG);
391   thriftObj.outsideEnumMap_ref()->emplace(Animal::CAT, 7779);
392 
393   auto frozenObj = freeze(thriftObj);
394   EXPECT_THAT(frozenObj.enumSet(), Contains(Gender::Male));
395   EXPECT_THAT(frozenObj.outsideEnumSet(), Contains(Animal::DOG));
396   EXPECT_EQ(frozenObj.enumMap().at(Gender::Female), 1219);
397   EXPECT_EQ(frozenObj.outsideEnumMap().at(Animal::CAT), 7779);
398 }
399 
400 template <class T>
frozenBits(const T & value)401 size_t frozenBits(const T& value) {
402   Layout<T> layout;
403   LayoutRoot::layout(value, layout);
404   return layout.bits;
405 }
406 
TEST(Frozen,Bool)407 TEST(Frozen, Bool) {
408   Pet1 meat, vegan, dunno;
409   meat.vegan_ref() = false;
410   vegan.vegan_ref() = true;
411   // Always-empty optionals take 0 bits.
412   // Sometimes-full optionals take >=1 bits.
413   // Always-false bools take 0 bits.
414   // Sometimes-true bools take 1 bits.
415   // dunno => Nothing => 0 bits.
416   // meat => Just(False) => 1 bit.
417   // vegan => Just(True) => 2 bits.
418   EXPECT_LT(frozenBits(dunno), frozenBits(meat));
419   EXPECT_LT(frozenBits(meat), frozenBits(vegan));
420   EXPECT_FALSE(*freeze(meat).vegan());
421   EXPECT_TRUE(*freeze(vegan).vegan());
422   EXPECT_FALSE(freeze(dunno).vegan().has_value());
423   EXPECT_EQ(meat, freeze(meat).thaw());
424   EXPECT_EQ(vegan, freeze(vegan).thaw());
425   EXPECT_EQ(dunno, freeze(dunno).thaw());
426 }
427 
TEST(Frozen,ThawPart)428 TEST(Frozen, ThawPart) {
429   auto f = freeze(tom1);
430   EXPECT_EQ(f.pets()[0].name(), "max");
431   EXPECT_EQ(f.pets()[1].name(), "ed");
432 
433   auto max = f.pets()[0].thaw();
434   auto ed = f.pets()[1].thaw();
435   EXPECT_EQ(typeid(max), typeid(Pet1));
436   EXPECT_EQ(typeid(ed), typeid(Pet1));
437   EXPECT_EQ(*max.name_ref(), "max");
438   EXPECT_EQ(*ed.name_ref(), "ed");
439 }
440 
TEST(Frozen,SchemaConversion)441 TEST(Frozen, SchemaConversion) {
442   schema::MemorySchema memSchema;
443   schema::Schema schema;
444 
445   Layout<EveryLayout> stressLayoutCalculated;
446   CHECK(LayoutRoot::layout(stressValue2, stressLayoutCalculated));
447 
448   schema::MemorySchema schemaSaved;
449   saveRoot(stressLayoutCalculated, schemaSaved);
450 
451   schema::convert(schemaSaved, schema);
452   schema::convert(std::move(schema), memSchema);
453 
454   EXPECT_EQ(memSchema, schemaSaved);
455 }
456 
TEST(Frozen,SparseSchema)457 TEST(Frozen, SparseSchema) {
458   {
459     auto l = layout(tiny1);
460     schema::MemorySchema schema;
461     saveRoot(l, schema);
462     EXPECT_LE(schema.getLayouts().size(), 4);
463   }
464   {
465     auto l = layout(tiny2);
466     schema::MemorySchema schema;
467     saveRoot(l, schema);
468     EXPECT_LE(schema.getLayouts().size(), 7);
469   }
470 }
471 
TEST(Frozen,DedupedSchema)472 TEST(Frozen, DedupedSchema) {
473   {
474     auto l = layout(tiny4);
475     schema::MemorySchema schema;
476     saveRoot(l, schema);
477     EXPECT_LE(schema.getLayouts().size(), 7); // 13 layouts originally
478   }
479   {
480     auto l = layout(stressValue2);
481     schema::MemorySchema schema;
482     saveRoot(l, schema);
483     EXPECT_LE(schema.getLayouts().size(), 24); // 49 layouts originally
484   }
485 }
486 
TEST(Frozen,TypeHelpers)487 TEST(Frozen, TypeHelpers) {
488   auto f = freeze(tom1);
489   View<Pet1> m = f.pets()[0];
490   EXPECT_EQ(m.name(), "max");
491 }
492 
TEST(Frozen,RangeTrivialRange)493 TEST(Frozen, RangeTrivialRange) {
494   auto data = std::vector<float>{3.0, 4.0, 5.0};
495   auto view = freeze(data);
496   auto r = folly::Range<const float*>(view.range());
497   EXPECT_EQ(data, std::vector<float>(r.begin(), r.end()));
498 }
499 
TEST(Frozen,PaddingLayout)500 TEST(Frozen, PaddingLayout) {
501   using std::pair;
502   using std::vector;
503   // The 'distance' field of the vector<double> is small and sensitive to
504   // padding adjustments. If actual distances are returned in
505   // layoutBytesDistance instead of worst-case distances, the below structure
506   // will successfully freeze at offset zero but fail at later offsets.
507   vector<vector<vector<double>>> test(10);
508   test.push_back({{1.0}});
509   size_t size;
510   auto testLayout = layout(test, size);
511   for (size_t offset = 0; offset < 8; ++offset) {
512     std::unique_ptr<byte[]> store(new byte[size + offset + 16]);
513     folly::MutableByteRange bytes(store.get() + offset, size + 16);
514 
515     auto view = ByteRangeFreezer::freeze(testLayout, test, bytes);
516     auto range = view[10][0].range();
517     EXPECT_EQ(range[0], 1.0);
518     EXPECT_EQ(reinterpret_cast<intptr_t>(range.begin()) % alignof(double), 0);
519   }
520 }
521 
TEST(Frozen,Bundled)522 TEST(Frozen, Bundled) {
523   using String = Bundled<std::string>;
524   String s("Hello");
525 
526   EXPECT_EQ("Hello", s);
527   EXPECT_FALSE(s.empty());
528   EXPECT_EQ(nullptr, s.findFirstOfType<int>());
529 
530   s.hold(47);
531   s.hold(11);
532 
533   EXPECT_EQ(47, *s.findFirstOfType<int>());
534   EXPECT_EQ(nullptr, s.findFirstOfType<std::string>());
535 }
536 
TEST(Frozen,TrivialCopyable)537 TEST(Frozen, TrivialCopyable) {
538   TriviallyCopyableStruct s;
539   s.field_ref() = 42;
540   auto view = freeze(s);
541   EXPECT_EQ(view.field(), 42);
542 }
543 
544 MATCHER(PairStrEq, "") {
545   *result_listener << std::get<0>(arg).first << ", " << std::get<0>(arg).second
546                    << ", vs " << std::get<1>(arg).first << ", "
547                    << std::get<1>(arg).second;
548   return std::get<0>(arg).first == std::get<1>(arg).first &&
549       std::get<0>(arg).second == std::get<1>(arg).second;
550 }
551 
TEST(Frozen,FixedSizeString)552 TEST(Frozen, FixedSizeString) {
553   // Good example.
554   {
555     TestFixedSizeString s;
556     s.bytes8_ref() = "01234567";
557     // bytes4 field is optional and can be unset.
558     auto view = freeze(s);
559     ASSERT_TRUE(view.bytes8_ref().has_value());
560     EXPECT_EQ(view.bytes8_ref()->toString(), "01234567");
561   }
562   // Good example.
563   {
564     TestFixedSizeString s;
565     s.bytes8_ref() = "01234567";
566     s.bytes4_ref() = "0123";
567     auto view = freeze(s);
568     ASSERT_TRUE(view.bytes8_ref().has_value());
569     EXPECT_EQ(view.bytes8_ref()->toString(), "01234567");
570     ASSERT_TRUE(view.bytes4().has_value());
571     EXPECT_EQ(view.bytes4()->toString(), "0123");
572   }
573   // Throws if an unqualified FixedSizeString field is unset.
574   {
575     TestFixedSizeString s;
576     s.bytes4_ref() = "0123";
577     // bytes8 field is unqualified and must be set.
578     EXPECT_THROW(
579         [&s]() { auto view = freeze(s); }(),
580         apache::thrift::frozen::detail::FixedSizeMismatchException);
581   }
582   // Throws if a FixedSizeString field doesn't have the expected size.
583   {
584     EXPECT_THROW(
585         []() {
586           TestFixedSizeString s;
587           s.bytes8_ref() = "01234567";
588           s.bytes4_ref() = "0";
589           auto view = freeze(s);
590         }(),
591         apache::thrift::frozen::detail::FixedSizeMismatchException);
592     EXPECT_THROW(
593         []() {
594           TestFixedSizeString s;
595           s.bytes8_ref() = "0";
596           s.bytes4_ref() = "0123";
597           auto view = freeze(s);
598         }(),
599         apache::thrift::frozen::detail::FixedSizeMismatchException);
600   }
601   // Tests FixedSizeString as both the key_type and the value_type in a hashmap.
602   {
603     TestFixedSizeString s;
604     s.bytes8_ref() = "01234567";
605     std::unordered_map<std::string, std::string> rawMap = {
606         {"76543210", "ab"},
607         {"12345670", "cd"},
608         {"hellosnw", "ef"},
609         {"facebook", "hi"},
610     };
611     for (const auto& [key, val] : rawMap) {
612       s.aMapToFreeze_ref()[apache::thrift::frozen::FixedSizeString<8>(key)] =
613           val;
614       s.aMap_ref()[apache::thrift::frozen::FixedSizeString<8>(key)] = val;
615     }
616 
617     // Tests aMap before serialization.
618     {
619       EXPECT_THAT(
620           *s.aMap_ref(), testing::UnorderedPointwise(PairStrEq(), rawMap));
621       auto iter = s.aMap_ref()->find(Fixed8("hellosnw"));
622       ASSERT_NE(iter, s.aMap_ref()->end());
623       EXPECT_THAT(
624           *iter,
625           testing::Pair(
626               testing::Eq(Fixed8("hellosnw")), testing::Eq(Fixed2("ef"))));
627     }
628 
629     auto view = freeze(s);
630 
631     auto extractToUnorderedMap = [](const auto& frozenMap) {
632       std::unordered_map<std::string, std::string> result;
633       for (auto iter = frozenMap.begin(); iter != frozenMap.end(); ++iter) {
634         result[iter->first().toString()] = iter->second().toString();
635       }
636       return result;
637     };
638 
639     ASSERT_TRUE(view.aMap_ref().has_value());
640     EXPECT_THAT(
641         extractToUnorderedMap(*view.aMap_ref()),
642         testing::UnorderedElementsAreArray(rawMap.begin(), rawMap.end()));
643     ASSERT_TRUE(view.aMapToFreeze_ref().has_value());
644     EXPECT_THAT(
645         extractToUnorderedMap(*view.aMapToFreeze_ref()),
646         testing::UnorderedElementsAreArray(rawMap.begin(), rawMap.end()));
647 
648     {
649       std::string key = "hellosnw";
650       auto iter = view.aMap_ref()->find(folly::ByteRange{
651           reinterpret_cast<const uint8_t*>(key.data()), key.size()});
652       ASSERT_NE(iter, view.aMap_ref()->end());
653       EXPECT_EQ(iter->first().toString(), "hellosnw");
654       EXPECT_EQ(iter->second().toString(), "ef");
655     }
656   }
657 }
658 
TEST(Frozen,Empty)659 TEST(Frozen, Empty) {
660   Empty s;
661   auto view = freeze(s);
662   (void)view;
663 }
664 
TEST(Frozen,Excluded)665 TEST(Frozen, Excluded) {
666   ContainsExcluded excludedUnset;
667   ContainsExcluded excludedSet;
668   excludedSet.excluded_ref().ensure();
669   (void)freeze(excludedUnset);
670   EXPECT_THROW(freeze(excludedSet), LayoutExcludedException);
671 }
672 
673 } // namespace
674