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