Apply clang-format to source tree
[alexxy/gromacs.git] / src / gromacs / utility / tests / stringutil.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Tests for string utility functions and classes.
38  *
39  * For development, the tests can be run with a '-stdout' command-line option
40  * to print out the help to stdout instead of using the XML reference
41  * framework.
42  *
43  * \author Teemu Murtola <teemu.murtola@gmail.com>
44  * \ingroup module_utility
45  */
46 #include "gmxpre.h"
47
48 #include "gromacs/utility/stringutil.h"
49
50 #include <string>
51 #include <vector>
52
53 #include <gmock/gmock.h>
54 #include <gtest/gtest.h>
55
56 #include "gromacs/utility/arrayref.h"
57 #include "gromacs/utility/exceptions.h"
58
59 #include "testutils/refdata.h"
60 #include "testutils/stringtest.h"
61
62 namespace gmx
63 {
64 namespace test
65 {
66 namespace
67 {
68
69 /********************************************************************
70  * Tests for simple string utilities
71  */
72
73 TEST(StringUtilityTest, StartsWith)
74 {
75     EXPECT_TRUE(gmx::startsWith("foobar", "foo"));
76     EXPECT_TRUE(gmx::startsWith("foobar", ""));
77     EXPECT_TRUE(gmx::startsWith("", ""));
78     EXPECT_FALSE(gmx::startsWith("", "foobar"));
79     EXPECT_FALSE(gmx::startsWith("foo", "foobar"));
80     EXPECT_FALSE(gmx::startsWith("foobar", "oob"));
81     EXPECT_TRUE(gmx::startsWith(std::string("foobar"), "foo"));
82     EXPECT_TRUE(gmx::startsWith(std::string("foobar"), ""));
83     EXPECT_TRUE(gmx::startsWith(std::string(""), ""));
84     EXPECT_FALSE(gmx::startsWith(std::string(""), "foobar"));
85     EXPECT_FALSE(gmx::startsWith(std::string("foo"), "foobar"));
86     EXPECT_FALSE(gmx::startsWith(std::string("foobar"), "oob"));
87 }
88
89 TEST(StringUtilityTest, EndsWith)
90 {
91     EXPECT_TRUE(gmx::endsWith("foobar", "bar"));
92     EXPECT_TRUE(gmx::endsWith("foobar", nullptr));
93     EXPECT_TRUE(gmx::endsWith("foobar", ""));
94     EXPECT_TRUE(gmx::endsWith("", ""));
95     EXPECT_FALSE(gmx::endsWith("", "foobar"));
96     EXPECT_FALSE(gmx::endsWith("foobar", "bbar"));
97     EXPECT_FALSE(gmx::endsWith("foobar", "barr"));
98     EXPECT_FALSE(gmx::endsWith("foobar", "foofoobar"));
99 }
100
101 TEST(StringUtilityTest, StripSuffixIfPresent)
102 {
103     EXPECT_EQ("foo", gmx::stripSuffixIfPresent("foobar", "bar"));
104     EXPECT_EQ("foobar", gmx::stripSuffixIfPresent("foobar", nullptr));
105     EXPECT_EQ("foobar", gmx::stripSuffixIfPresent("foobar", ""));
106     EXPECT_EQ("foobar", gmx::stripSuffixIfPresent("foobar", "bbar"));
107     EXPECT_EQ("foobar", gmx::stripSuffixIfPresent("foobar", "barr"));
108     EXPECT_EQ("foobar", gmx::stripSuffixIfPresent("foobar", "foofoobar"));
109 }
110
111 TEST(StringUtilityTest, StripString)
112 {
113     EXPECT_EQ("", gmx::stripString(""));
114     EXPECT_EQ("foo", gmx::stripString("foo"));
115     EXPECT_EQ("foo", gmx::stripString("  foo"));
116     EXPECT_EQ("foo", gmx::stripString("foo "));
117     EXPECT_EQ("f o o", gmx::stripString(" f o o  "));
118 }
119
120 TEST(StringUtilityTest, SplitString)
121 {
122     using ::testing::ElementsAre;
123     using ::testing::IsEmpty;
124     using ::testing::Matcher;
125     Matcher<std::vector<std::string>> matcher = ElementsAre("foo", "bar");
126     EXPECT_THAT(gmx::splitString("foo bar"), matcher);
127     EXPECT_THAT(gmx::splitString("  foo bar"), matcher);
128     EXPECT_THAT(gmx::splitString("foo bar  "), matcher);
129     EXPECT_THAT(gmx::splitString(" foo \t bar  "), matcher);
130     EXPECT_THAT(gmx::splitString(""), IsEmpty());
131     EXPECT_THAT(gmx::splitString("   "), IsEmpty());
132 }
133
134 TEST(StringUtilityTest, SplitDelimitedString)
135 {
136     using ::testing::ElementsAre;
137     using ::testing::IsEmpty;
138     EXPECT_THAT(gmx::splitDelimitedString("foo;bar", ';'), ElementsAre("foo", "bar"));
139     EXPECT_THAT(gmx::splitDelimitedString(";foo;bar;", ';'), ElementsAre("", "foo", "bar", ""));
140     EXPECT_THAT(gmx::splitDelimitedString("foo;;bar", ';'), ElementsAre("foo", "", "bar"));
141     EXPECT_THAT(gmx::splitDelimitedString("foo", ';'), ElementsAre("foo"));
142     EXPECT_THAT(gmx::splitDelimitedString(";", ';'), ElementsAre("", ""));
143     EXPECT_THAT(gmx::splitDelimitedString("", ';'), IsEmpty());
144 }
145
146 TEST(StringUtilityTest, SplitAndTrimDelimitedString)
147 {
148     using ::testing::ElementsAre;
149     using ::testing::IsEmpty;
150     EXPECT_THAT(splitAndTrimDelimitedString("", ';'), IsEmpty());
151     EXPECT_THAT(splitAndTrimDelimitedString(" \t\n ", ';'), ElementsAre(""));
152     EXPECT_THAT(splitAndTrimDelimitedString("foo", ';'), ElementsAre("foo"));
153     EXPECT_THAT(splitAndTrimDelimitedString(" foo ", ';'), ElementsAre("foo"));
154     EXPECT_THAT(splitAndTrimDelimitedString("foo;bar", ';'), ElementsAre("foo", "bar"));
155     EXPECT_THAT(splitAndTrimDelimitedString(";foo;bar", ';'), ElementsAre("", "foo", "bar"));
156     EXPECT_THAT(splitAndTrimDelimitedString("foo;bar;", ';'), ElementsAre("foo", "bar", ""));
157     EXPECT_THAT(splitAndTrimDelimitedString(";foo;bar;", ';'), ElementsAre("", "foo", "bar", ""));
158     EXPECT_THAT(splitAndTrimDelimitedString("foo;;bar", ';'), ElementsAre("foo", "", "bar"));
159     EXPECT_THAT(splitAndTrimDelimitedString("foo  ;  bar ", ';'), ElementsAre("foo", "bar"));
160     EXPECT_THAT(splitAndTrimDelimitedString("  ; foo ;  bar ", ';'), ElementsAre("", "foo", "bar"));
161     EXPECT_THAT(splitAndTrimDelimitedString(" foo  ;  bar ; ", ';'), ElementsAre("foo", "bar", ""));
162     EXPECT_THAT(splitAndTrimDelimitedString(" ;  foo\n ;  bar ;  ", ';'),
163                 ElementsAre("", "foo", "bar", ""));
164     EXPECT_THAT(splitAndTrimDelimitedString(" foo  ; ; \tbar", ';'), ElementsAre("foo", "", "bar"));
165 }
166
167 TEST(StringUtilityTest, CanCompareCaseInsensitive)
168 {
169     EXPECT_TRUE(equalCaseInsensitive("foo", "foo"));
170     EXPECT_FALSE(equalCaseInsensitive("foo", "bar"));
171     EXPECT_TRUE(equalCaseInsensitive("foo", "FOO"));
172     EXPECT_FALSE(equalCaseInsensitive("foo", "foobar"));
173     EXPECT_FALSE(equalCaseInsensitive("foobar", "foo"));
174 }
175
176 /*! \brief
177  * Helper to test that string comparison works with switched input positions.
178  *
179  * \param[in] foo First string to check.
180  * \param[in] bar Second string to check.
181  * \param[in] length Max comparison length to use.
182  * \param[in] expectedResult If we expect the result be a match between the strings or not.
183  */
184 void checkEqualCaseInsensitive(const std::string& foo, const std::string& bar, int length, bool expectedResult)
185 {
186     EXPECT_EQ(equalCaseInsensitive(foo, bar, length), expectedResult);
187     EXPECT_EQ(equalCaseInsensitive(bar, foo, length), expectedResult);
188 }
189
190 TEST(StringUtilityTest, CanCompareCaseInsensitiveInLength)
191 {
192     checkEqualCaseInsensitive("foo", "bar", 0, true);
193     checkEqualCaseInsensitive("foo", "foo", 3, true);
194     checkEqualCaseInsensitive("foo", "bar", 3, false);
195     checkEqualCaseInsensitive("foo", "FOO", 3, true);
196     checkEqualCaseInsensitive("foo", "foobar", 3, true);
197     checkEqualCaseInsensitive("foo", "foobar", 5, false);
198     checkEqualCaseInsensitive("foo", "foobar", 6, false);
199     checkEqualCaseInsensitive("foo", "FooBAR", 3, true);
200     checkEqualCaseInsensitive("foo", "FooBAR", 5, false);
201     checkEqualCaseInsensitive("foo", "FooBAR", 6, false);
202     checkEqualCaseInsensitive("fooo", "foo", 3, true);
203     checkEqualCaseInsensitive("fooo", "foo", 4, false);
204     checkEqualCaseInsensitive("foobar", "foo", 4, false);
205     checkEqualCaseInsensitive("foobar", "foob", 4, true);
206 }
207
208 /********************************************************************
209  * Tests for formatString()
210  */
211
212 TEST(FormatStringTest, HandlesBasicFormatting)
213 {
214     EXPECT_EQ("12 abc", gmx::formatString("%d %s", 12, "abc"));
215 }
216
217 TEST(FormatStringTest, HandlesLongStrings)
218 {
219     std::string longString = gmx::formatString("%*c%d", 2000, 'x', 10);
220     EXPECT_EQ(2002U, longString.length());
221     EXPECT_EQ("x10", longString.substr(1999));
222 }
223
224 /********************************************************************
225  * Tests for StringFormatter
226  */
227
228 TEST(StringFormatterTest, HandlesBasicFormatting)
229 {
230     int value = 103;
231     EXPECT_EQ("103", gmx::StringFormatter("%d")(value));
232     EXPECT_EQ("null", gmx::StringFormatter("null")(value));
233 }
234
235 /********************************************************************
236  * Tests for formatAndJoin
237  */
238
239 TEST(formatAndJoinTest, Works)
240 {
241     const char* const words[] = { "The", "quick", "brown", "fox" };
242     EXPECT_EQ("The       .quick     .brown     .fox       ",
243               gmx::formatAndJoin(gmx::ArrayRef<const char* const>(words), ".",
244                                  gmx::StringFormatter("%-10s")));
245
246     const int values[] = { 0, 1, 4 };
247     EXPECT_EQ("0,1,4",
248               gmx::formatAndJoin(gmx::ArrayRef<const int>(values), ",", gmx::StringFormatter("%d")));
249 }
250
251 /********************************************************************
252  * Tests for joinStrings
253  */
254
255 TEST(JoinStringsTest, Works)
256 {
257     const char* const                words[] = { "The", "quick", "brown", "fox" };
258     gmx::ArrayRef<const char* const> refToWords(words);
259     EXPECT_EQ("The; quick; brown; fox",
260               gmx::joinStrings(refToWords.begin(), refToWords.end(), "; "));
261     EXPECT_EQ("The-quick-brown-fox", gmx::joinStrings(refToWords, "-"));
262     EXPECT_EQ("The-quick-brown-fox", gmx::joinStrings(words, "-"));
263 }
264
265 /********************************************************************
266  * Tests for replaceAll() and replaceAllWords()
267  */
268
269 TEST(ReplaceAllTest, HandlesEmptyStrings)
270 {
271     EXPECT_EQ("", gmx::replaceAll("", "aaa", "bbbb"));
272     EXPECT_EQ("", gmx::replaceAllWords("", "aaa", "bbbb"));
273 }
274
275 TEST(ReplaceAllTest, HandlesNoMatches)
276 {
277     const std::string text("Text with no matches");
278     EXPECT_EQ(text, gmx::replaceAll(text, "aaa", "bbbb"));
279     EXPECT_EQ(text, gmx::replaceAllWords(text, "aaa", "bbbb"));
280 }
281
282 TEST(ReplaceAllTest, HandlesMatchesAtEnds)
283 {
284     EXPECT_EQ("bbbbtext", gmx::replaceAll("aaatext", "aaa", "bbbb"));
285     EXPECT_EQ("textbbbb", gmx::replaceAll("textaaa", "aaa", "bbbb"));
286     EXPECT_EQ("bbbb text", gmx::replaceAllWords("aaa text", "aaa", "bbbb"));
287     EXPECT_EQ("text bbbb", gmx::replaceAllWords("text aaa", "aaa", "bbbb"));
288 }
289
290 TEST(ReplaceAllTest, HandlesMultipleMatches)
291 {
292     const std::string text("Text aaa with multiple aaa matches");
293     EXPECT_EQ("Text bbbb with multiple bbbb matches", gmx::replaceAll(text, "aaa", "bbbb"));
294     EXPECT_EQ("Text bbbb with multiple bbbb matches", gmx::replaceAllWords(text, "aaa", "bbbb"));
295 }
296
297 TEST(ReplaceAllTest, HandlesWordBoundaries)
298 {
299     const std::string text("Text aaax with one word aaa match");
300     EXPECT_EQ("Text aaax with one word bbbb match", gmx::replaceAllWords(text, "aaa", "bbbb"));
301 }
302
303 TEST(ReplaceAllTest, HandlesPossibleRecursiveMatches)
304 {
305     const std::string text("Text with recursive aaabbbbbb matches");
306     EXPECT_EQ("Text with recursive aaaaaabbb matches", gmx::replaceAll(text, "aaabbb", "aaaaaa"));
307 }
308
309 /********************************************************************
310  * Tests for TextLineWrapper
311  */
312
313 //! Simple test string for wrapping.
314 const char g_wrapText[] = "A quick brown fox jumps over the lazy dog";
315 //! Test string for wrapping with embedded line breaks.
316 const char g_wrapText2[] = "A quick brown fox jumps\nover the lazy dog";
317 //! Test string for wrapping with embedded line breaks and an empty line.
318 const char g_wrapText3[] = "A quick brown fox jumps\n\nover the lazy dog";
319 //! Test string for wrapping with a long word.
320 const char g_wrapTextLongWord[] =
321         "A quick brown fox jumps awordthatoverflowsaline over the lazy dog";
322 //! Test string for wrapping with extra whitespace.
323 const char g_wrapTextWhitespace[] = " A quick brown   fox jumps  \n over the lazy dog";
324
325 //! Test fixture for gmx::TextLineWrapper.
326 typedef gmx::test::StringTestBase TextLineWrapperTest;
327
328 TEST_F(TextLineWrapperTest, HandlesEmptyStrings)
329 {
330     gmx::TextLineWrapper wrapper;
331
332     EXPECT_EQ("", wrapper.wrapToString(""));
333     EXPECT_EQ("", wrapper.wrapToString("   "));
334     EXPECT_TRUE(wrapper.wrapToVector("").empty());
335     {
336         std::vector<std::string> wrapped(wrapper.wrapToVector("   "));
337         ASSERT_EQ(1U, wrapped.size());
338         EXPECT_EQ("", wrapped[0]);
339     }
340 }
341
342 TEST_F(TextLineWrapperTest, HandlesTrailingWhitespace)
343 {
344     gmx::TextLineWrapper wrapper;
345
346     EXPECT_EQ("line", wrapper.wrapToString("line   "));
347     EXPECT_EQ("line\n", wrapper.wrapToString("line   \n"));
348
349     wrapper.settings().setKeepFinalSpaces(true);
350     EXPECT_EQ("line   ", wrapper.wrapToString("line   "));
351     EXPECT_EQ("line   \n", wrapper.wrapToString("line   \n"));
352 }
353
354 TEST_F(TextLineWrapperTest, HandlesTrailingNewlines)
355 {
356     gmx::TextLineWrapper wrapper;
357
358     EXPECT_EQ("line", wrapper.wrapToString("line"));
359     EXPECT_EQ("line\n", wrapper.wrapToString("line\n"));
360     EXPECT_EQ("line\n\n", wrapper.wrapToString("line\n\n"));
361     EXPECT_EQ("\n", wrapper.wrapToString("\n"));
362     EXPECT_EQ("\n\n", wrapper.wrapToString("\n\n"));
363     {
364         std::vector<std::string> wrapped(wrapper.wrapToVector("line"));
365         ASSERT_EQ(1U, wrapped.size());
366         EXPECT_EQ("line", wrapped[0]);
367     }
368     {
369         std::vector<std::string> wrapped(wrapper.wrapToVector("line\n"));
370         ASSERT_EQ(1U, wrapped.size());
371         EXPECT_EQ("line", wrapped[0]);
372     }
373     {
374         std::vector<std::string> wrapped(wrapper.wrapToVector("line\n\n"));
375         ASSERT_EQ(2U, wrapped.size());
376         EXPECT_EQ("line", wrapped[0]);
377         EXPECT_EQ("", wrapped[1]);
378     }
379     {
380         std::vector<std::string> wrapped(wrapper.wrapToVector("\n"));
381         ASSERT_EQ(1U, wrapped.size());
382         EXPECT_EQ("", wrapped[0]);
383     }
384     {
385         std::vector<std::string> wrapped(wrapper.wrapToVector("\n\n"));
386         ASSERT_EQ(2U, wrapped.size());
387         EXPECT_EQ("", wrapped[0]);
388         EXPECT_EQ("", wrapped[1]);
389     }
390 }
391
392 TEST_F(TextLineWrapperTest, WrapsCorrectly)
393 {
394     gmx::TextLineWrapper wrapper;
395
396     wrapper.settings().setLineLength(10);
397     checkText(wrapper.wrapToString(g_wrapText), "WrappedAt10");
398     std::vector<std::string> wrapped(wrapper.wrapToVector(g_wrapText));
399     checker().checkSequence(wrapped.begin(), wrapped.end(), "WrappedToVector");
400     wrapper.settings().setLineLength(13);
401     checkText(wrapper.wrapToString(g_wrapText), "WrappedAt13");
402     wrapper.settings().setLineLength(14);
403     checkText(wrapper.wrapToString(g_wrapText), "WrappedAt14");
404     checkText(wrapper.wrapToString(g_wrapTextLongWord), "WrappedWithLongWord");
405 }
406
407 TEST_F(TextLineWrapperTest, WrapsCorrectlyWithExistingBreaks)
408 {
409     gmx::TextLineWrapper wrapper;
410
411     checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
412     wrapper.settings().setLineLength(10);
413     checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt10");
414     wrapper.settings().setLineLength(14);
415     checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
416 }
417
418 TEST_F(TextLineWrapperTest, HandlesIndent)
419 {
420     gmx::TextLineWrapper wrapper;
421     wrapper.settings().setIndent(2);
422
423     checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
424     wrapper.settings().setLineLength(16);
425     checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14");
426 }
427
428 TEST_F(TextLineWrapperTest, HandlesIndentWithEmptyLines)
429 {
430     gmx::TextLineWrapper wrapper;
431     wrapper.settings().setIndent(2);
432
433     checkText(wrapper.wrapToString(g_wrapText3), "WrappedWithNoLimit");
434     wrapper.settings().setLineLength(16);
435     checkText(wrapper.wrapToString(g_wrapText3), "WrappedAt14");
436 }
437
438 TEST_F(TextLineWrapperTest, HandlesHangingIndent)
439 {
440     gmx::TextLineWrapper wrapper;
441     wrapper.settings().setFirstLineIndent(2);
442     wrapper.settings().setIndent(4);
443
444     checkText(wrapper.wrapToString(g_wrapText2), "WrappedWithNoLimit");
445     wrapper.settings().setLineLength(16);
446     checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
447 }
448
449 TEST_F(TextLineWrapperTest, HandlesContinuationCharacter)
450 {
451     gmx::TextLineWrapper wrapper;
452     wrapper.settings().setFirstLineIndent(2);
453     wrapper.settings().setIndent(4);
454     wrapper.settings().setContinuationChar('\\');
455
456     wrapper.settings().setLineLength(16);
457     checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14/12");
458 }
459
460 TEST_F(TextLineWrapperTest, WrapsCorrectlyWithExtraWhitespace)
461 {
462     gmx::TextLineWrapper wrapper;
463     wrapper.settings().setLineLength(14);
464
465     checkText(wrapper.wrapToString(g_wrapTextWhitespace), "WrappedAt14");
466
467     wrapper.settings().setKeepFinalSpaces(true);
468     checkText(wrapper.wrapToString(g_wrapTextWhitespace), "WrappedAt14WithTrailingWhitespace");
469 }
470
471 } // namespace
472 } // namespace test
473 } // namespace gmx