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