47d76d2bf33b30f211ead56ba1350524cadc3dfd
[folly.git] / folly / gen / test / StringTest.cpp
1 /*
2  * Copyright 2017 Facebook, Inc.
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 <iosfwd>
18 #include <map>
19 #include <vector>
20
21 #include <folly/ApplyTuple.h>
22 #include <folly/gen/String.h>
23 #include <folly/portability/GTest.h>
24
25 using namespace folly::gen;
26 using namespace folly;
27 using std::make_tuple;
28 using std::ostream;
29 using std::pair;
30 using std::string;
31 using std::tuple;
32 using std::unique_ptr;
33 using std::vector;
34
35 using vec = vector<string>;
36
37 static auto collect = eachTo<std::string>() | as<vector>();
38
39 TEST(StringGen, EmptySplit) {
40   {
41     auto input = "";
42     auto expected = vec{};
43     EXPECT_EQ(expected, split(input, ',') | collect);
44   }
45
46   // The last delimiter is eaten, just like std::getline
47   {
48     auto input = ",";
49     auto expected = vec{""};
50     EXPECT_EQ(expected, split(input, ',') | collect);
51   }
52
53   {
54     auto input = ",,";
55     auto expected = vec{"", ""};
56     EXPECT_EQ(expected, split(input, ',') | collect);
57   }
58
59   {
60     auto input = ",,";
61     auto expected = vec{""};
62     EXPECT_EQ(expected, split(input, ',') | take(1) | collect);
63   }
64 }
65
66 TEST(StringGen, Split) {
67   {
68     auto input = "hello,, world, goodbye, meow";
69     auto expected = vec{"hello", "", " world", " goodbye", " meow"};
70     EXPECT_EQ(expected, split(input, ',') | collect);
71   }
72
73   {
74     auto input = "hello,, world, goodbye, meow";
75     auto expected = vec{"hello", "", " world"};
76     EXPECT_EQ(expected, split(input, ',') | take(3) | collect);
77   }
78
79   {
80     auto input = "hello,, world, goodbye, meow";
81     auto expected = vec{"hello", "", " world", " goodbye", " meow"};
82     EXPECT_EQ(expected, split(input, ",") | take(5) | collect);
83   }
84
85   {
86     auto input = "hello,, world, goodbye, meow";
87     auto expected = vec{"hello,", "world", "goodbye", "meow"};
88     EXPECT_EQ(expected, split(input, ", ") | collect);
89   }
90 }
91
92 TEST(StringGen, SplitByNewLine) {
93   {
94     auto input = "hello\n\n world\r\n goodbye\r me\n\row";
95     auto expected = vec{"hello", "", " world", " goodbye", " me", "", "ow"};
96     EXPECT_EQ(expected, lines(input) | collect);
97   }
98 }
99
100 TEST(StringGen, EmptyResplit) {
101   {
102     auto input = vec{""};
103     auto expected = vec{};
104     EXPECT_EQ(expected, from(input) | resplit(',') | collect);
105   }
106
107   // The last delimiter is eaten, just like std::getline
108   {
109     auto input = vec{","};
110     auto expected = vec{""};
111     EXPECT_EQ(expected, from(input) | resplit(',') | collect);
112   }
113
114   {
115     auto input = vec{",,"};
116     auto expected = vec{"", ""};
117     EXPECT_EQ(expected, from(input) | resplit(',') | collect);
118   }
119 }
120
121 TEST(StringGen, Resplit) {
122   {
123     auto input = vec{"hello,, world, goodbye, meow"};
124     auto expected = vec{"hello", "", " world", " goodbye", " meow"};
125     EXPECT_EQ(expected, from(input) | resplit(',') | collect);
126   }
127
128   {
129     auto input = vec{"hel", "lo,", ", world", ", goodbye, m", "eow"};
130     auto expected = vec{"hello", "", " world", " goodbye", " meow"};
131     EXPECT_EQ(expected, from(input) | resplit(',') | collect);
132   }
133 }
134
135 TEST(StringGen, ResplitKeepDelimiter) {
136   {
137     auto input = vec{"hello,, world, goodbye, meow"};
138     auto expected = vec{"hello,", ",", " world,", " goodbye,", " meow"};
139     EXPECT_EQ(expected, from(input) | resplit(',', true) | collect);
140   }
141
142   {
143     auto input = vec{"hel", "lo,", ", world", ", goodbye, m", "eow"};
144     auto expected = vec{"hello,", ",", " world,", " goodbye,", " meow"};
145     EXPECT_EQ(expected, from(input) | resplit(',', true) | collect);
146   }
147 }
148
149 TEST(StringGen, EachToTuple) {
150   {
151     auto lines = "2:1.414:yo 3:1.732:hi";
152     auto actual
153       = split(lines, ' ')
154       | eachToTuple<int, double, std::string>(':')
155       | as<vector>();
156     vector<tuple<int, double, std::string>> expected {
157       make_tuple(2, 1.414, "yo"),
158       make_tuple(3, 1.732, "hi"),
159     };
160     EXPECT_EQ(expected, actual);
161   }
162   {
163     auto lines = "2 3";
164     auto actual
165       = split(lines, ' ')
166       | eachToTuple<int>(',')
167       | as<vector>();
168     vector<tuple<int>> expected {
169       make_tuple(2),
170       make_tuple(3),
171     };
172     EXPECT_EQ(expected, actual);
173   }
174   {
175     // StringPiece target
176     auto lines = "1:cat 2:dog";
177     auto actual
178       = split(lines, ' ')
179       | eachToTuple<int, StringPiece>(':')
180       | as<vector>();
181     vector<tuple<int, StringPiece>> expected {
182       make_tuple(1, "cat"),
183       make_tuple(2, "dog"),
184     };
185     EXPECT_EQ(expected, actual);
186   }
187   {
188     // Empty field
189     auto lines = "2:tjackson:4 3::5";
190     auto actual
191       = split(lines, ' ')
192       | eachToTuple<int, fbstring, int>(':')
193       | as<vector>();
194     vector<tuple<int, fbstring, int>> expected {
195       make_tuple(2, "tjackson", 4),
196       make_tuple(3, "", 5),
197     };
198     EXPECT_EQ(expected, actual);
199   }
200   {
201     // Excess fields
202     auto lines = "1:2 3:4:5";
203     EXPECT_THROW((split(lines, ' ')
204                     | eachToTuple<int, int>(':')
205                     | as<vector>()),
206                  std::runtime_error);
207   }
208   {
209     // Missing fields
210     auto lines = "1:2:3 4:5";
211     EXPECT_THROW((split(lines, ' ')
212                     | eachToTuple<int, int, int>(':')
213                     | as<vector>()),
214                  std::runtime_error);
215   }
216 }
217
218 TEST(StringGen, EachToPair) {
219   {
220     // char delimiters
221     auto lines = "2:1.414 3:1.732";
222     auto actual
223       = split(lines, ' ')
224       | eachToPair<int, double>(':')
225       | as<std::map<int, double>>();
226     std::map<int, double> expected {
227       { 3, 1.732 },
228       { 2, 1.414 },
229     };
230     EXPECT_EQ(expected, actual);
231   }
232   {
233     // string delimiters
234     auto lines = "ab=>cd ef=>gh";
235     auto actual
236       = split(lines, ' ')
237       | eachToPair<string, string>("=>")
238       | as<std::map<string, string>>();
239     std::map<string, string> expected {
240       { "ab", "cd" },
241       { "ef", "gh" },
242     };
243     EXPECT_EQ(expected, actual);
244   }
245 }
246
247 void checkResplitMaxLength(vector<string> ins,
248                            char delim,
249                            uint64_t maxLength,
250                            vector<string> outs) {
251   vector<std::string> pieces;
252   auto splitter = streamSplitter(delim, [&pieces](StringPiece s) {
253     pieces.push_back(string(s.begin(), s.end()));
254     return true;
255   }, maxLength);
256   for (const auto& in : ins) {
257     splitter(in);
258   }
259   splitter.flush();
260
261   EXPECT_EQ(outs.size(), pieces.size());
262   for (size_t i = 0; i < outs.size(); ++i) {
263     EXPECT_EQ(outs[i], pieces[i]);
264   }
265
266   // Also check the concatenated input against the same output
267   if (ins.size() > 1) {
268     checkResplitMaxLength({folly::join("", ins)}, delim, maxLength, outs);
269   }
270 }
271
272 TEST(StringGen, ResplitMaxLength) {
273   checkResplitMaxLength(
274     {"hel", "lo,", ", world", ", goodbye, m", "ew"}, ',', 5,
275     {"hello", ",", ",", " worl", "d,", " good", "bye,", " mew"}
276   );
277   // " meow" cannot be "end of stream", since it's maxLength long
278   checkResplitMaxLength(
279     {"hel", "lo,", ", world", ", goodbye, m", "eow"}, ',', 5,
280     {"hello", ",", ",", " worl", "d,", " good", "bye,", " meow", ""}
281   );
282   checkResplitMaxLength(
283     {"||", "", "", "", "|a|b", "cdefghijklmn", "|opqrst",
284      "uvwx|y|||", "z", "0123456789", "|", ""}, '|', 2,
285     {"|", "|", "|", "a|", "bc", "de", "fg", "hi", "jk", "lm", "n|", "op", "qr",
286      "st", "uv", "wx", "|", "y|", "|", "|", "z0", "12", "34", "56", "78", "9|",
287      ""}
288   );
289 }
290
291 template <typename F>
292 void runUnsplitSuite(F fn) {
293   fn("hello, world");
294   fn("hello,world,goodbye");
295   fn(" ");
296   fn("");
297   fn(", ");
298   fn(", a, b,c");
299 }
300
301 TEST(StringGen, Unsplit) {
302
303   auto basicFn = [](StringPiece s) {
304     EXPECT_EQ(split(s, ',') | unsplit(','), s);
305   };
306
307   auto existingBuffer = [](StringPiece s) {
308     folly::fbstring buffer("asdf");
309     split(s, ',') | unsplit(',', &buffer);
310     auto expected = folly::to<folly::fbstring>(
311         "asdf", s.empty() ? "" : ",", s);
312     EXPECT_EQ(expected, buffer);
313   };
314
315   auto emptyBuffer = [](StringPiece s) {
316     std::string buffer;
317     split(s, ',') | unsplit(',', &buffer);
318     EXPECT_EQ(s, buffer);
319   };
320
321   auto stringDelim = [](StringPiece s) {
322     EXPECT_EQ(s, split(s, ',') | unsplit(","));
323     std::string buffer;
324     split(s, ',') | unsplit(",", &buffer);
325     EXPECT_EQ(buffer, s);
326   };
327
328   runUnsplitSuite(basicFn);
329   runUnsplitSuite(existingBuffer);
330   runUnsplitSuite(emptyBuffer);
331   runUnsplitSuite(stringDelim);
332   EXPECT_EQ("1, 2, 3", seq(1, 3) | unsplit(", "));
333 }
334
335 TEST(StringGen, Batch) {
336   std::vector<std::string> chunks{
337       "on", "e\nt", "w", "o", "\nthr", "ee\nfo", "ur\n",
338   };
339   std::vector<std::string> lines{
340       "one", "two", "three", "four",
341   };
342   EXPECT_EQ(4, from(chunks) | resplit('\n') | count);
343   EXPECT_EQ(4, from(chunks) | resplit('\n') | batch(2) | rconcat | count);
344   EXPECT_EQ(4, from(chunks) | resplit('\n') | batch(3) | rconcat | count);
345   EXPECT_EQ(lines, from(chunks) | resplit('\n') | eachTo<std::string>() |
346                        batch(3) | rconcat | as<vector>());
347 }
348
349 TEST(StringGen, UncurryTuple) {
350   folly::StringPiece file = "1\t2\t3\n1\t4\t9";
351   auto rows = split(file, '\n') | eachToTuple<int, int, int>('\t');
352   auto productSum =
353       rows | map(uncurry([](int x, int y, int z) { return x * y * z; })) | sum;
354   EXPECT_EQ(42, productSum);
355 }
356
357 TEST(StringGen, UncurryPair) {
358   folly::StringPiece file = "2\t3\n4\t9";
359   auto rows = split(file, '\n') | eachToPair<int, int>('\t');
360   auto productSum =
361       rows | map(uncurry([](int x, int y) { return x * y; })) | sum;
362   EXPECT_EQ(42, productSum);
363 }