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