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/GTest.h>
18 
19 #include <thrift/lib/cpp2/frozen/FrozenTestUtil.h>
20 #include <thrift/lib/cpp2/frozen/FrozenUtil.h>
21 #include <thrift/lib/cpp2/protocol/Serializer.h>
22 
23 using namespace apache::thrift;
24 using namespace frozen;
25 using namespace util;
26 
TEST(FrozenUtil,FreezeAndUse)27 TEST(FrozenUtil, FreezeAndUse) {
28   auto file = freezeToTempFile(std::string("hello"));
29   MappedFrozen<std::string> mapped;
30   mapped = mapFrozen<std::string>(folly::File(file.fd()));
31   EXPECT_EQ(folly::StringPiece(mapped), "hello");
32 }
33 
TEST(FrozenUtil,FreezeAndMap)34 TEST(FrozenUtil, FreezeAndMap) {
35   auto original = std::vector<std::string>{"hello", "world"};
36   folly::test::TemporaryFile tmp;
37 
38   freezeToFile(original, folly::File(tmp.fd()));
39 
40   MappedFrozen<std::vector<std::string>> mapped;
41   EXPECT_FALSE(mapped);
42   mapped = mapFrozen<std::vector<std::string>>(folly::File(tmp.fd()));
43   EXPECT_TRUE(mapped);
44 
45   auto thawed = mapped.thaw();
46   EXPECT_EQ(original, thawed);
47   original.push_back("different");
48   EXPECT_NE(original, thawed);
49 }
50 
TEST(FrozenUtil,FutureVersion)51 TEST(FrozenUtil, FutureVersion) {
52   folly::test::TemporaryFile tmp;
53 
54   {
55     schema::Schema schema;
56     *schema.fileVersion_ref() = 1000;
57     schema.fileVersion_ref().ensure();
58 
59     std::string schemaStr;
60     CompactSerializer::serialize(schema, &schemaStr);
61     write(tmp.fd(), schemaStr.data(), schemaStr.size());
62   }
63 
64   EXPECT_THROW(
65       mapFrozen<std::string>(folly::File(tmp.fd())),
66       FrozenFileForwardIncompatible);
67 }
68 
TEST(FrozenUtil,FileSize)69 TEST(FrozenUtil, FileSize) {
70   auto original = std::vector<std::string>{"hello", "world"};
71   folly::test::TemporaryFile tmp;
72   freezeToFile(original, folly::File(tmp.fd()));
73   struct stat stats;
74   fstat(tmp.fd(), &stats);
75   EXPECT_LT(stats.st_size, 500); // most of this is the schema
76 }
77 
TEST(FrozenUtil,FreezeToString)78 TEST(FrozenUtil, FreezeToString) {
79   // multiplication tables for first three primes
80   using TestType = std::map<int, std::map<int, int>>;
81   TestType m{
82       {2, {{2, 4}, {3, 6}, {5, 10}}},
83       {3, {{2, 6}, {3, 9}, {5, 15}}},
84       {5, {{2, 10}, {3, 15}, {5, 25}}},
85   };
86   MappedFrozen<TestType> frozen;
87   MappedFrozen<TestType> frozen2;
88   {
89     std::string store;
90     freezeToString(m, store);
91     std::string store2 = freezeToString(m);
92     // In this example, the schema is 101 bytes and the data is only 17 bytes!
93     // By default, this is stripped out by this overload.
94     frozen = mapFrozen<TestType>(std::move(store));
95     frozen2 = mapFrozen<TestType>(std::move(store2));
96   }
97   EXPECT_EQ(frozen.at(3).at(5), 15);
98   EXPECT_EQ(frozen2.at(3).at(5), 15);
99   {
100     std::string store;
101     freezeToString(m, store);
102     std::string store2 = freezeToString(m);
103     // false = don't trim the space for the schema
104     frozen = mapFrozen<TestType>(std::move(store), false);
105     frozen2 = mapFrozen<TestType>(std::move(store2), false);
106   }
107   EXPECT_EQ(frozen.at(3).at(5), 15);
108   EXPECT_EQ(frozen2.at(3).at(5), 15);
109   {
110     std::string store;
111     freezeToString(m, store);
112     std::string store2;
113     freezeToStringMalloc(m, store2);
114     // TODO(T44041774): Changes in alignment are leading to differences in these
115     // two freezes. They shouldn't differ in this case.
116     EXPECT_NEAR(store.size(), store2.size(), 8);
117     // EXPECT_EQ(store, store2);
118     // false = don't trim the space for the schema
119     frozen = mapFrozen<TestType>(std::move(store), false);
120   }
121   EXPECT_EQ(frozen.at(3).at(5), 15);
122 }
123 
TEST(Frozen,WorstCasePadding)124 TEST(Frozen, WorstCasePadding) {
125   using Doubles = std::vector<double>;
126   using Entry = std::pair<Doubles, std::string>;
127   using Table = std::vector<Entry>;
128   Table table;
129   for (int i = 0; i < 10; ++i) {
130     table.emplace_back();
131     auto& e = table.back();
132     e.first = {1.1, 2.2};
133     e.second.resize(1, 'a' + i);
134   }
135   std::string str;
136   freezeToString(table, str);
137 }
138 
TEST(Frozen,TailPadding)139 TEST(Frozen, TailPadding) {
140   using Entry = std::pair<size_t, size_t>;
141   using Table = std::vector<Entry>;
142   Table table;
143   for (int i = 0; i < 1000; ++i) {
144     table.emplace_back();
145     auto& e = table.back();
146     e.first = 3;
147     e.second = 7;
148     std::string str;
149     freezeToString(table, str);
150     auto view = mapFrozen<Table>(std::move(str), true);
151     EXPECT_EQ(view.back().second(), 7);
152   }
153 }
154 
TEST(Frozen,ConstLayout)155 TEST(Frozen, ConstLayout) {
156   using Table = std::vector<std::string>;
157   Layout<Table> layout;
158   LayoutRoot::layout(Table{"x"}, layout);
159   EXPECT_EQ(11, frozenSize(Table{"y"}, layout));
160   EXPECT_THROW(frozenSize(Table{"hello", "world"}, layout), LayoutException);
161   LayoutRoot::layout(Table{"hello", "world"}, layout);
162   EXPECT_EQ(21, frozenSize(Table{"hello", "world"}, layout));
163 }
164 
TEST(Frozen,FreezeToStringMalloc)165 TEST(Frozen, FreezeToStringMalloc) {
166   using Table = std::vector<std::string>;
167   std::string x, y;
168   Table value{"hello", "world"};
169   freezeToString(value, x);
170   freezeToStringMalloc(value, y);
171   EXPECT_EQ(x, y);
172   auto fx = mapFrozen<Table>(std::move(x));
173   auto fy = mapFrozen<Table>(std::move(y));
174   EXPECT_EQ(fx[0], fy[0]);
175   EXPECT_EQ(fx[1], fy[1]);
176 }
177 
TEST(Frozen,FreezeDataToString)178 TEST(Frozen, FreezeDataToString) {
179   using Table = std::vector<std::string>;
180   Layout<Table> layout;
181   LayoutRoot::layout(Table{"xxx", "yyy", "www"}, layout);
182   const Layout<Table> fixedLayout = layout;
183   EXPECT_THROW(
184       freezeDataToString(Table{"hello", "world"}, fixedLayout),
185       LayoutException);
186   auto str = freezeDataToString(Table{"abc", "123", "xyz"}, fixedLayout);
187   auto view = fixedLayout.view({reinterpret_cast<byte*>(&str[0]), 0});
188   EXPECT_EQ(view.size(), 3);
189   EXPECT_EQ(view[0], "abc");
190   EXPECT_EQ(view[1], "123");
191   EXPECT_EQ(view[2], "xyz");
192 }
193