65c79462013ec9053bbe8381f118bc1e5dc1221b
[folly.git] / folly / gen / test / StringTest.cpp
1 /*
2  * Copyright 2016 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 <glog/logging.h>
18 #include <gtest/gtest.h>
19 #include <iosfwd>
20 #include <map>
21 #include <vector>
22
23 #include <folly/gen/String.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 void checkResplitMaxLength(vector<string> ins,
264                            char delim,
265                            uint64_t maxLength,
266                            vector<string> outs) {
267   vector<std::string> pieces;
268   auto splitter = streamSplitter(delim, [&pieces](StringPiece s) {
269     pieces.push_back(string(s.begin(), s.end()));
270     return true;
271   }, maxLength);
272   for (const auto& in : ins) {
273     splitter(in);
274   }
275   splitter.flush();
276
277   EXPECT_EQ(outs.size(), pieces.size());
278   for (size_t i = 0; i < outs.size(); ++i) {
279     EXPECT_EQ(outs[i], pieces[i]);
280   }
281
282   // Also check the concatenated input against the same output
283   if (ins.size() > 1) {
284     checkResplitMaxLength({folly::join("", ins)}, delim, maxLength, outs);
285   }
286 }
287
288 TEST(StringGen, ResplitMaxLength) {
289   checkResplitMaxLength(
290     {"hel", "lo,", ", world", ", goodbye, m", "ew"}, ',', 5,
291     {"hello", ",", ",", " worl", "d,", " good", "bye,", " mew"}
292   );
293   // " meow" cannot be "end of stream", since it's maxLength long
294   checkResplitMaxLength(
295     {"hel", "lo,", ", world", ", goodbye, m", "eow"}, ',', 5,
296     {"hello", ",", ",", " worl", "d,", " good", "bye,", " meow", ""}
297   );
298   checkResplitMaxLength(
299     {"||", "", "", "", "|a|b", "cdefghijklmn", "|opqrst",
300      "uvwx|y|||", "z", "0123456789", "|", ""}, '|', 2,
301     {"|", "|", "|", "a|", "bc", "de", "fg", "hi", "jk", "lm", "n|", "op", "qr",
302      "st", "uv", "wx", "|", "y|", "|", "|", "z0", "12", "34", "56", "78", "9|",
303      ""}
304   );
305 }
306
307 template<typename F>
308 void runUnsplitSuite(F fn) {
309   fn("hello, world");
310   fn("hello,world,goodbye");
311   fn(" ");
312   fn("");
313   fn(", ");
314   fn(", a, b,c");
315 }
316
317 TEST(StringGen, Unsplit) {
318
319   auto basicFn = [](StringPiece s) {
320     EXPECT_EQ(split(s, ',') | unsplit(','), s);
321   };
322
323   auto existingBuffer = [](StringPiece s) {
324     folly::fbstring buffer("asdf");
325     split(s, ',') | unsplit(',', &buffer);
326     auto expected = folly::to<folly::fbstring>(
327         "asdf", s.empty() ? "" : ",", s);
328     EXPECT_EQ(expected, buffer);
329   };
330
331   auto emptyBuffer = [](StringPiece s) {
332     std::string buffer;
333     split(s, ',') | unsplit(',', &buffer);
334     EXPECT_EQ(s, buffer);
335   };
336
337   auto stringDelim = [](StringPiece s) {
338     EXPECT_EQ(s, split(s, ',') | unsplit(","));
339     std::string buffer;
340     split(s, ',') | unsplit(",", &buffer);
341     EXPECT_EQ(buffer, s);
342   };
343
344   runUnsplitSuite(basicFn);
345   runUnsplitSuite(existingBuffer);
346   runUnsplitSuite(emptyBuffer);
347   runUnsplitSuite(stringDelim);
348   EXPECT_EQ("1, 2, 3", seq(1, 3) | unsplit(", "));
349 }
350
351 TEST(StringGen, Batch) {
352   std::vector<std::string> chunks{
353       "on", "e\nt", "w", "o", "\nthr", "ee\nfo", "ur\n",
354   };
355   std::vector<std::string> lines{
356       "one", "two", "three", "four",
357   };
358   EXPECT_EQ(4, from(chunks) | resplit('\n') | count);
359   EXPECT_EQ(4, from(chunks) | resplit('\n') | batch(2) | rconcat | count);
360   EXPECT_EQ(4, from(chunks) | resplit('\n') | batch(3) | rconcat | count);
361   EXPECT_EQ(lines, from(chunks) | resplit('\n') | eachTo<std::string>() |
362                        batch(3) | rconcat | as<vector>());
363 }