xref: /freebsd/contrib/kyua/utils/text/operations_test.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1 // Copyright 2012 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "utils/text/operations.ipp"
30 
31 #include <iostream>
32 #include <set>
33 #include <string>
34 #include <vector>
35 
36 #include <atf-c++.hpp>
37 
38 #include "utils/text/exceptions.hpp"
39 
40 namespace text = utils::text;
41 
42 
43 namespace {
44 
45 
46 /// Tests text::refill() on an input string with a range of widths.
47 ///
48 /// \param expected The expected refilled paragraph.
49 /// \param input The input paragraph to be refilled.
50 /// \param first_width The first width to validate.
51 /// \param last_width The last width to validate (inclusive).
52 static void
refill_test(const char * expected,const char * input,const std::size_t first_width,const std::size_t last_width)53 refill_test(const char* expected, const char* input,
54             const std::size_t first_width, const std::size_t last_width)
55 {
56     for (std::size_t width = first_width; width <= last_width; ++width) {
57         const std::vector< std::string > lines = text::split(expected, '\n');
58         std::cout << "Breaking at width " << width << '\n';
59         ATF_REQUIRE_EQ(expected, text::refill_as_string(input, width));
60         ATF_REQUIRE(lines == text::refill(input, width));
61     }
62 }
63 
64 
65 }  // anonymous namespace
66 
67 
68 ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__empty);
ATF_TEST_CASE_BODY(escape_xml__empty)69 ATF_TEST_CASE_BODY(escape_xml__empty)
70 {
71     ATF_REQUIRE_EQ("", text::escape_xml(""));
72 }
73 
74 
75 ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__no_escaping);
ATF_TEST_CASE_BODY(escape_xml__no_escaping)76 ATF_TEST_CASE_BODY(escape_xml__no_escaping)
77 {
78     ATF_REQUIRE_EQ("a", text::escape_xml("a"));
79     ATF_REQUIRE_EQ("Some text!", text::escape_xml("Some text!"));
80     ATF_REQUIRE_EQ("\n\t\r", text::escape_xml("\n\t\r"));
81 }
82 
83 
84 ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__some_escaping);
ATF_TEST_CASE_BODY(escape_xml__some_escaping)85 ATF_TEST_CASE_BODY(escape_xml__some_escaping)
86 {
87     ATF_REQUIRE_EQ("&apos;", text::escape_xml("'"));
88 
89     ATF_REQUIRE_EQ("foo &quot;bar&amp; &lt;tag&gt; yay&apos; baz",
90                    text::escape_xml("foo \"bar& <tag> yay' baz"));
91 
92     ATF_REQUIRE_EQ("&quot;&amp;&lt;&gt;&apos;", text::escape_xml("\"&<>'"));
93     ATF_REQUIRE_EQ("&amp;&amp;&amp;", text::escape_xml("&&&"));
94     ATF_REQUIRE_EQ("&amp;#8;&amp;#11;", text::escape_xml("\b\v"));
95     ATF_REQUIRE_EQ("\t&amp;#127;BAR&amp;", text::escape_xml("\t\x7f""BAR&"));
96 }
97 
98 
99 ATF_TEST_CASE_WITHOUT_HEAD(quote__empty);
ATF_TEST_CASE_BODY(quote__empty)100 ATF_TEST_CASE_BODY(quote__empty)
101 {
102     ATF_REQUIRE_EQ("''", text::quote("", '\''));
103     ATF_REQUIRE_EQ("##", text::quote("", '#'));
104 }
105 
106 
107 ATF_TEST_CASE_WITHOUT_HEAD(quote__no_escaping);
ATF_TEST_CASE_BODY(quote__no_escaping)108 ATF_TEST_CASE_BODY(quote__no_escaping)
109 {
110     ATF_REQUIRE_EQ("'Some text\"'", text::quote("Some text\"", '\''));
111     ATF_REQUIRE_EQ("#Another'string#", text::quote("Another'string", '#'));
112 }
113 
114 
115 ATF_TEST_CASE_WITHOUT_HEAD(quote__some_escaping);
ATF_TEST_CASE_BODY(quote__some_escaping)116 ATF_TEST_CASE_BODY(quote__some_escaping)
117 {
118     ATF_REQUIRE_EQ("'Some\\'text'", text::quote("Some'text", '\''));
119     ATF_REQUIRE_EQ("#Some\\#text#", text::quote("Some#text", '#'));
120 
121     ATF_REQUIRE_EQ("'More than one\\' quote\\''",
122                    text::quote("More than one' quote'", '\''));
123     ATF_REQUIRE_EQ("'Multiple quotes \\'\\'\\' together'",
124                    text::quote("Multiple quotes ''' together", '\''));
125 
126     ATF_REQUIRE_EQ("'\\'escape at the beginning'",
127                    text::quote("'escape at the beginning", '\''));
128     ATF_REQUIRE_EQ("'escape at the end\\''",
129                    text::quote("escape at the end'", '\''));
130 }
131 
132 
133 ATF_TEST_CASE_WITHOUT_HEAD(refill__empty);
ATF_TEST_CASE_BODY(refill__empty)134 ATF_TEST_CASE_BODY(refill__empty)
135 {
136     ATF_REQUIRE_EQ(1, text::refill("", 0).size());
137     ATF_REQUIRE(text::refill("", 0)[0].empty());
138     ATF_REQUIRE_EQ("", text::refill_as_string("", 0));
139 
140     ATF_REQUIRE_EQ(1, text::refill("", 10).size());
141     ATF_REQUIRE(text::refill("", 10)[0].empty());
142     ATF_REQUIRE_EQ("", text::refill_as_string("", 10));
143 }
144 
145 
146 ATF_TEST_CASE_WITHOUT_HEAD(refill__no_changes);
ATF_TEST_CASE_BODY(refill__no_changes)147 ATF_TEST_CASE_BODY(refill__no_changes)
148 {
149     std::vector< std::string > exp_lines;
150     exp_lines.push_back("foo bar\nbaz");
151 
152     ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 12));
153     ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 12));
154 
155     ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 18));
156     ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 80));
157 }
158 
159 
160 ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one);
ATF_TEST_CASE_BODY(refill__break_one)161 ATF_TEST_CASE_BODY(refill__break_one)
162 {
163     refill_test("only break the\nfirst line", "only break the first line",
164                 14, 19);
165 }
166 
167 
168 ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one__not_first_word);
ATF_TEST_CASE_BODY(refill__break_one__not_first_word)169 ATF_TEST_CASE_BODY(refill__break_one__not_first_word)
170 {
171     refill_test("first-long-word\nother\nwords", "first-long-word other words",
172                 6, 10);
173     refill_test("first-long-word\nother words", "first-long-word other words",
174                 11, 20);
175     refill_test("first-long-word other\nwords", "first-long-word other words",
176                 21, 26);
177     refill_test("first-long-word other words", "first-long-word other words",
178                 27, 28);
179 }
180 
181 
182 ATF_TEST_CASE_WITHOUT_HEAD(refill__break_many);
ATF_TEST_CASE_BODY(refill__break_many)183 ATF_TEST_CASE_BODY(refill__break_many)
184 {
185     refill_test("this is a long\nparagraph to be\nsplit into\npieces",
186                 "this is a long paragraph to be split into pieces",
187                 15, 15);
188 }
189 
190 
191 ATF_TEST_CASE_WITHOUT_HEAD(refill__cannot_break);
ATF_TEST_CASE_BODY(refill__cannot_break)192 ATF_TEST_CASE_BODY(refill__cannot_break)
193 {
194     refill_test("this-is-a-long-string", "this-is-a-long-string", 5, 5);
195 
196     refill_test("this is\na-string-with-long-words",
197                 "this is a-string-with-long-words", 10, 10);
198 }
199 
200 
201 ATF_TEST_CASE_WITHOUT_HEAD(refill__preserve_whitespace);
ATF_TEST_CASE_BODY(refill__preserve_whitespace)202 ATF_TEST_CASE_BODY(refill__preserve_whitespace)
203 {
204     refill_test("foo  bar baz  ", "foo  bar baz  ", 80, 80);
205     refill_test("foo  \n bar", "foo    bar", 5, 5);
206 
207     std::vector< std::string > exp_lines;
208     exp_lines.push_back("foo \n");
209     exp_lines.push_back(" bar");
210     ATF_REQUIRE(exp_lines == text::refill("foo \n  bar", 5));
211     ATF_REQUIRE_EQ("foo \n\n bar", text::refill_as_string("foo \n  bar", 5));
212 }
213 
214 
215 ATF_TEST_CASE_WITHOUT_HEAD(join__empty);
ATF_TEST_CASE_BODY(join__empty)216 ATF_TEST_CASE_BODY(join__empty)
217 {
218     std::vector< std::string > lines;
219     ATF_REQUIRE_EQ("", text::join(lines, " "));
220 }
221 
222 
223 ATF_TEST_CASE_WITHOUT_HEAD(join__one);
ATF_TEST_CASE_BODY(join__one)224 ATF_TEST_CASE_BODY(join__one)
225 {
226     std::vector< std::string > lines;
227     lines.push_back("first line");
228     ATF_REQUIRE_EQ("first line", text::join(lines, "*"));
229 }
230 
231 
232 ATF_TEST_CASE_WITHOUT_HEAD(join__several);
ATF_TEST_CASE_BODY(join__several)233 ATF_TEST_CASE_BODY(join__several)
234 {
235     std::vector< std::string > lines;
236     lines.push_back("first abc");
237     lines.push_back("second");
238     lines.push_back("and last line");
239     ATF_REQUIRE_EQ("first abc second and last line", text::join(lines, " "));
240     ATF_REQUIRE_EQ("first abc***second***and last line",
241                    text::join(lines, "***"));
242 }
243 
244 
245 ATF_TEST_CASE_WITHOUT_HEAD(join__unordered);
ATF_TEST_CASE_BODY(join__unordered)246 ATF_TEST_CASE_BODY(join__unordered)
247 {
248     std::set< std::string > lines;
249     lines.insert("first");
250     lines.insert("second");
251     const std::string joined = text::join(lines, " ");
252     ATF_REQUIRE(joined == "first second" || joined == "second first");
253 }
254 
255 
256 ATF_TEST_CASE_WITHOUT_HEAD(split__empty);
ATF_TEST_CASE_BODY(split__empty)257 ATF_TEST_CASE_BODY(split__empty)
258 {
259     std::vector< std::string > words = text::split("", ' ');
260     std::vector< std::string > exp_words;
261     ATF_REQUIRE(exp_words == words);
262 }
263 
264 
265 ATF_TEST_CASE_WITHOUT_HEAD(split__one);
ATF_TEST_CASE_BODY(split__one)266 ATF_TEST_CASE_BODY(split__one)
267 {
268     std::vector< std::string > words = text::split("foo", ' ');
269     std::vector< std::string > exp_words;
270     exp_words.push_back("foo");
271     ATF_REQUIRE(exp_words == words);
272 }
273 
274 
275 ATF_TEST_CASE_WITHOUT_HEAD(split__several__simple);
ATF_TEST_CASE_BODY(split__several__simple)276 ATF_TEST_CASE_BODY(split__several__simple)
277 {
278     std::vector< std::string > words = text::split("foo bar baz", ' ');
279     std::vector< std::string > exp_words;
280     exp_words.push_back("foo");
281     exp_words.push_back("bar");
282     exp_words.push_back("baz");
283     ATF_REQUIRE(exp_words == words);
284 }
285 
286 
287 ATF_TEST_CASE_WITHOUT_HEAD(split__several__delimiters);
ATF_TEST_CASE_BODY(split__several__delimiters)288 ATF_TEST_CASE_BODY(split__several__delimiters)
289 {
290     std::vector< std::string > words = text::split("XfooXXbarXXXbazXX", 'X');
291     std::vector< std::string > exp_words;
292     exp_words.push_back("");
293     exp_words.push_back("foo");
294     exp_words.push_back("");
295     exp_words.push_back("bar");
296     exp_words.push_back("");
297     exp_words.push_back("");
298     exp_words.push_back("baz");
299     exp_words.push_back("");
300     exp_words.push_back("");
301     ATF_REQUIRE(exp_words == words);
302 }
303 
304 
305 ATF_TEST_CASE_WITHOUT_HEAD(replace_all__empty);
ATF_TEST_CASE_BODY(replace_all__empty)306 ATF_TEST_CASE_BODY(replace_all__empty)
307 {
308     ATF_REQUIRE_EQ("", text::replace_all("", "search", "replacement"));
309 }
310 
311 
312 ATF_TEST_CASE_WITHOUT_HEAD(replace_all__none);
ATF_TEST_CASE_BODY(replace_all__none)313 ATF_TEST_CASE_BODY(replace_all__none)
314 {
315     ATF_REQUIRE_EQ("string without matches",
316                    text::replace_all("string without matches",
317                                      "WITHOUT", "replacement"));
318 }
319 
320 
321 ATF_TEST_CASE_WITHOUT_HEAD(replace_all__one);
ATF_TEST_CASE_BODY(replace_all__one)322 ATF_TEST_CASE_BODY(replace_all__one)
323 {
324     ATF_REQUIRE_EQ("string replacement matches",
325                    text::replace_all("string without matches",
326                                      "without", "replacement"));
327 }
328 
329 
330 ATF_TEST_CASE_WITHOUT_HEAD(replace_all__several);
ATF_TEST_CASE_BODY(replace_all__several)331 ATF_TEST_CASE_BODY(replace_all__several)
332 {
333     ATF_REQUIRE_EQ("OO fOO bar OOf baz OO",
334                    text::replace_all("oo foo bar oof baz oo",
335                                      "oo", "OO"));
336 }
337 
338 
339 ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__bool);
ATF_TEST_CASE_BODY(to_type__ok__bool)340 ATF_TEST_CASE_BODY(to_type__ok__bool)
341 {
342     ATF_REQUIRE( text::to_type< bool >("true"));
343     ATF_REQUIRE(!text::to_type< bool >("false"));
344 }
345 
346 
347 ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__numerical);
ATF_TEST_CASE_BODY(to_type__ok__numerical)348 ATF_TEST_CASE_BODY(to_type__ok__numerical)
349 {
350     ATF_REQUIRE_EQ(12, text::to_type< int >("12"));
351     ATF_REQUIRE_EQ(18745, text::to_type< int >("18745"));
352     ATF_REQUIRE_EQ(-12345, text::to_type< int >("-12345"));
353 
354     ATF_REQUIRE_EQ(12.0, text::to_type< double >("12"));
355     ATF_REQUIRE_EQ(12.5, text::to_type< double >("12.5"));
356 }
357 
358 
359 ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__string);
ATF_TEST_CASE_BODY(to_type__ok__string)360 ATF_TEST_CASE_BODY(to_type__ok__string)
361 {
362     // While this seems redundant, having this particular specialization that
363     // does nothing allows callers to delegate work to to_type without worrying
364     // about the particular type being converted.
365     ATF_REQUIRE_EQ("", text::to_type< std::string >(""));
366     ATF_REQUIRE_EQ("  abcd  ", text::to_type< std::string >("  abcd  "));
367 }
368 
369 
370 ATF_TEST_CASE_WITHOUT_HEAD(to_type__empty);
ATF_TEST_CASE_BODY(to_type__empty)371 ATF_TEST_CASE_BODY(to_type__empty)
372 {
373     ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(""));
374 }
375 
376 
377 ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__bool);
ATF_TEST_CASE_BODY(to_type__invalid__bool)378 ATF_TEST_CASE_BODY(to_type__invalid__bool)
379 {
380     ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >(""));
381     ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("true "));
382     ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("foo"));
383 }
384 
385 
386 ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__numerical);
ATF_TEST_CASE_BODY(to_type__invalid__numerical)387 ATF_TEST_CASE_BODY(to_type__invalid__numerical)
388 {
389     ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(" 3"));
390     ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3 "));
391     ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3a"));
392     ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("a3"));
393 }
394 
395 
ATF_INIT_TEST_CASES(tcs)396 ATF_INIT_TEST_CASES(tcs)
397 {
398     ATF_ADD_TEST_CASE(tcs, escape_xml__empty);
399     ATF_ADD_TEST_CASE(tcs, escape_xml__no_escaping);
400     ATF_ADD_TEST_CASE(tcs, escape_xml__some_escaping);
401 
402     ATF_ADD_TEST_CASE(tcs, quote__empty);
403     ATF_ADD_TEST_CASE(tcs, quote__no_escaping);
404     ATF_ADD_TEST_CASE(tcs, quote__some_escaping);
405 
406     ATF_ADD_TEST_CASE(tcs, refill__empty);
407     ATF_ADD_TEST_CASE(tcs, refill__no_changes);
408     ATF_ADD_TEST_CASE(tcs, refill__break_one);
409     ATF_ADD_TEST_CASE(tcs, refill__break_one__not_first_word);
410     ATF_ADD_TEST_CASE(tcs, refill__break_many);
411     ATF_ADD_TEST_CASE(tcs, refill__cannot_break);
412     ATF_ADD_TEST_CASE(tcs, refill__preserve_whitespace);
413 
414     ATF_ADD_TEST_CASE(tcs, join__empty);
415     ATF_ADD_TEST_CASE(tcs, join__one);
416     ATF_ADD_TEST_CASE(tcs, join__several);
417     ATF_ADD_TEST_CASE(tcs, join__unordered);
418 
419     ATF_ADD_TEST_CASE(tcs, split__empty);
420     ATF_ADD_TEST_CASE(tcs, split__one);
421     ATF_ADD_TEST_CASE(tcs, split__several__simple);
422     ATF_ADD_TEST_CASE(tcs, split__several__delimiters);
423 
424     ATF_ADD_TEST_CASE(tcs, replace_all__empty);
425     ATF_ADD_TEST_CASE(tcs, replace_all__none);
426     ATF_ADD_TEST_CASE(tcs, replace_all__one);
427     ATF_ADD_TEST_CASE(tcs, replace_all__several);
428 
429     ATF_ADD_TEST_CASE(tcs, to_type__ok__bool);
430     ATF_ADD_TEST_CASE(tcs, to_type__ok__numerical);
431     ATF_ADD_TEST_CASE(tcs, to_type__ok__string);
432     ATF_ADD_TEST_CASE(tcs, to_type__empty);
433     ATF_ADD_TEST_CASE(tcs, to_type__invalid__bool);
434     ATF_ADD_TEST_CASE(tcs, to_type__invalid__numerical);
435 }
436