OpenTTD Source 20241224-master-gee860a5c8e
string_func.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11
12#include "../3rdparty/catch2/catch.hpp"
13
14#include "../string_func.h"
15
16/**** String compare/equals *****/
17
18TEST_CASE("StrCompareIgnoreCase - std::string")
19{
20 /* Same string, with different cases. */
21 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{""}) == 0);
22 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"a"}) == 0);
23 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"A"}) == 0);
24 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"a"}) == 0);
25 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"A"}) == 0);
26
27 /* Not the same string. */
28 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{"b"}) < 0);
29 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{""}) > 0);
30
31 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"b"}) < 0);
32 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"a"}) > 0);
33 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"B"}) < 0);
34 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"A"}) > 0);
35 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"b"}) < 0);
36 CHECK(StrCompareIgnoreCase(std::string{"B"}, std::string{"a"}) > 0);
37
38 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"aa"}) < 0);
39 CHECK(StrCompareIgnoreCase(std::string{"aa"}, std::string{"a"}) > 0);
40}
41
42TEST_CASE("StrCompareIgnoreCase - char pointer")
43{
44 /* Same string, with different cases. */
45 CHECK(StrCompareIgnoreCase("", "") == 0);
46 CHECK(StrCompareIgnoreCase("a", "a") == 0);
47 CHECK(StrCompareIgnoreCase("a", "A") == 0);
48 CHECK(StrCompareIgnoreCase("A", "a") == 0);
49 CHECK(StrCompareIgnoreCase("A", "A") == 0);
50
51 /* Not the same string. */
52 CHECK(StrCompareIgnoreCase("", "b") < 0);
53 CHECK(StrCompareIgnoreCase("a", "") > 0);
54
55 CHECK(StrCompareIgnoreCase("a", "b") < 0);
56 CHECK(StrCompareIgnoreCase("b", "a") > 0);
57 CHECK(StrCompareIgnoreCase("a", "B") < 0);
58 CHECK(StrCompareIgnoreCase("b", "A") > 0);
59 CHECK(StrCompareIgnoreCase("A", "b") < 0);
60 CHECK(StrCompareIgnoreCase("B", "a") > 0);
61
62 CHECK(StrCompareIgnoreCase("a", "aa") < 0);
63 CHECK(StrCompareIgnoreCase("aa", "a") > 0);
64}
65
66TEST_CASE("StrCompareIgnoreCase - std::string_view")
67{
68 /*
69 * With std::string_view the only way to access the data is via .data(),
70 * which does not guarantee the termination that would be required by
71 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
72 * or strcasecmp would fail if it does not account for the length of the
73 * view. Thus, contrary to the string/char* tests, this uses the same base
74 * string but gets different sections to trigger these corner cases.
75 */
76 std::string_view base{"aaAbB"};
77
78 /* Same string, with different cases. */
79 CHECK(StrCompareIgnoreCase(base.substr(0, 0), base.substr(1, 0)) == 0); // Different positions
80 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(1, 1)) == 0); // Different positions
81 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(2, 1)) == 0);
82 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(1, 1)) == 0);
83 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(2, 1)) == 0);
84
85 /* Not the same string. */
86 CHECK(StrCompareIgnoreCase(base.substr(3, 0), base.substr(3, 1)) < 0); // Same position, different lengths
87 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 0)) > 0); // Same position, different lengths
88
89 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(3, 1)) < 0);
90 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(0, 1)) > 0);
91 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(4, 1)) < 0);
92 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(2, 1)) > 0);
93 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(3, 1)) < 0);
94 CHECK(StrCompareIgnoreCase(base.substr(4, 1), base.substr(0, 1)) > 0);
95
96 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 2)) < 0); // Same position, different lengths
97 CHECK(StrCompareIgnoreCase(base.substr(0, 2), base.substr(0, 1)) > 0); // Same position, different lengths
98}
99
100TEST_CASE("StrEqualsIgnoreCase - std::string")
101{
102 /* Same string, with different cases. */
103 CHECK(StrEqualsIgnoreCase(std::string{""}, std::string{""}));
104 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"a"}));
105 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"A"}));
106 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"a"}));
107 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"A"}));
108
109 /* Not the same string. */
110 CHECK(!StrEqualsIgnoreCase(std::string{""}, std::string{"b"}));
111 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{""}));
112 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"b"}));
113 CHECK(!StrEqualsIgnoreCase(std::string{"b"}, std::string{"a"}));
114 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"aa"}));
115 CHECK(!StrEqualsIgnoreCase(std::string{"aa"}, std::string{"a"}));
116}
117
118TEST_CASE("StrEqualsIgnoreCase - char pointer")
119{
120 /* Same string, with different cases. */
121 CHECK(StrEqualsIgnoreCase("", ""));
122 CHECK(StrEqualsIgnoreCase("a", "a"));
123 CHECK(StrEqualsIgnoreCase("a", "A"));
124 CHECK(StrEqualsIgnoreCase("A", "a"));
125 CHECK(StrEqualsIgnoreCase("A", "A"));
126
127 /* Not the same string. */
128 CHECK(!StrEqualsIgnoreCase("", "b"));
129 CHECK(!StrEqualsIgnoreCase("a", ""));
130 CHECK(!StrEqualsIgnoreCase("a", "b"));
131 CHECK(!StrEqualsIgnoreCase("b", "a"));
132 CHECK(!StrEqualsIgnoreCase("a", "aa"));
133 CHECK(!StrEqualsIgnoreCase("aa", "a"));
134}
135
136TEST_CASE("StrEqualsIgnoreCase - std::string_view")
137{
138 /*
139 * With std::string_view the only way to access the data is via .data(),
140 * which does not guarantee the termination that would be required by
141 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
142 * or strcasecmp would fail if it does not account for the length of the
143 * view. Thus, contrary to the string/char* tests, this uses the same base
144 * string but gets different sections to trigger these corner cases.
145 */
146 std::string_view base{"aaAb"};
147
148 /* Same string, with different cases. */
149 CHECK(StrEqualsIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
150 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
151 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
152 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(1, 1)));
153 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(2, 1)));
154
155 /* Not the same string. */
156 CHECK(!StrEqualsIgnoreCase(base.substr(3, 0), base.substr(3, 1))); // Same position, different lengths
157 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
158 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
159 CHECK(!StrEqualsIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
160 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 2))); // Same position, different lengths
161 CHECK(!StrEqualsIgnoreCase(base.substr(0, 2), base.substr(0, 1))); // Same position, different lengths
162}
163
164/**** String starts with *****/
165
166TEST_CASE("StrStartsWithIgnoreCase - std::string")
167{
168 /* Everything starts with an empty prefix. */
169 CHECK(StrStartsWithIgnoreCase(std::string{""}, std::string{""}));
170 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{""}));
171
172 /* Equals string, ignoring case. */
173 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
174 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
175 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
176 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
177
178 /* Starts with same, ignoring case. */
179 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"a"}));
180 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"A"}));
181 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"a"}));
182 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"A"}));
183
184 /* Does not start the same. */
185 CHECK(!StrStartsWithIgnoreCase(std::string{""}, std::string{"b"}));
186 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
187 CHECK(!StrStartsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
188 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
189}
190
191TEST_CASE("StrStartsWithIgnoreCase - char pointer")
192{
193 /* Everything starts with an empty prefix. */
194 CHECK(StrStartsWithIgnoreCase("", ""));
195 CHECK(StrStartsWithIgnoreCase("a", ""));
196
197 /* Equals string, ignoring case. */
198 CHECK(StrStartsWithIgnoreCase("a", "a"));
199 CHECK(StrStartsWithIgnoreCase("a", "A"));
200 CHECK(StrStartsWithIgnoreCase("A", "a"));
201 CHECK(StrStartsWithIgnoreCase("A", "A"));
202
203 /* Starts with same, ignoring case. */
204 CHECK(StrStartsWithIgnoreCase("ab", "a"));
205 CHECK(StrStartsWithIgnoreCase("ab", "A"));
206 CHECK(StrStartsWithIgnoreCase("Ab", "a"));
207 CHECK(StrStartsWithIgnoreCase("Ab", "A"));
208
209 /* Does not start the same. */
210 CHECK(!StrStartsWithIgnoreCase("", "b"));
211 CHECK(!StrStartsWithIgnoreCase("a", "b"));
212 CHECK(!StrStartsWithIgnoreCase("b", "a"));
213 CHECK(!StrStartsWithIgnoreCase("a", "aa"));
214}
215
216TEST_CASE("StrStartsWithIgnoreCase - std::string_view")
217{
218 /*
219 * With std::string_view the only way to access the data is via .data(),
220 * which does not guarantee the termination that would be required by
221 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
222 * or strcasecmp would fail if it does not account for the length of the
223 * view. Thus, contrary to the string/char* tests, this uses the same base
224 * string but gets different sections to trigger these corner cases.
225 */
226 std::string_view base{"aabAb"};
227
228 /* Everything starts with an empty prefix. */
229 CHECK(StrStartsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
230 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
231
232 /* Equals string, ignoring case. */
233 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
234 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
235 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
236 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
237
238 /* Starts with same, ignoring case. */
239 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(0, 1)));
240 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(3, 1)));
241 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(0, 1)));
242 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(3, 1)));
243
244 /* Does not start the same. */
245 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
246 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
247 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
248 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
249}
250
251/**** String ends with *****/
252
253TEST_CASE("StrEndsWithIgnoreCase - std::string")
254{
255 /* Everything ends with an empty prefix. */
256 CHECK(StrEndsWithIgnoreCase(std::string{""}, std::string{""}));
257 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{""}));
258
259 /* Equals string, ignoring case. */
260 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
261 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
262 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
263 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
264
265 /* Ends with same, ignoring case. */
266 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"a"}));
267 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"A"}));
268 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"a"}));
269 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"A"}));
270
271 /* Does not end the same. */
272 CHECK(!StrEndsWithIgnoreCase(std::string{""}, std::string{"b"}));
273 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
274 CHECK(!StrEndsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
275 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
276}
277
278TEST_CASE("StrEndsWithIgnoreCase - char pointer")
279{
280 /* Everything ends with an empty prefix. */
281 CHECK(StrEndsWithIgnoreCase("", ""));
282 CHECK(StrEndsWithIgnoreCase("a", ""));
283
284 /* Equals string, ignoring case. */
285 CHECK(StrEndsWithIgnoreCase("a", "a"));
286 CHECK(StrEndsWithIgnoreCase("a", "A"));
287 CHECK(StrEndsWithIgnoreCase("A", "a"));
288 CHECK(StrEndsWithIgnoreCase("A", "A"));
289
290 /* Ends with same, ignoring case. */
291 CHECK(StrEndsWithIgnoreCase("ba", "a"));
292 CHECK(StrEndsWithIgnoreCase("ba", "A"));
293 CHECK(StrEndsWithIgnoreCase("bA", "a"));
294 CHECK(StrEndsWithIgnoreCase("bA", "A"));
295
296 /* Does not end the same. */
297 CHECK(!StrEndsWithIgnoreCase("", "b"));
298 CHECK(!StrEndsWithIgnoreCase("a", "b"));
299 CHECK(!StrEndsWithIgnoreCase("b", "a"));
300 CHECK(!StrEndsWithIgnoreCase("a", "aa"));
301}
302
303TEST_CASE("StrEndsWithIgnoreCase - std::string_view")
304{
305 /*
306 * With std::string_view the only way to access the data is via .data(),
307 * which does not guarantee the termination that would be required by
308 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
309 * or strcasecmp would fail if it does not account for the length of the
310 * view. Thus, contrary to the string/char* tests, this uses the same base
311 * string but gets different sections to trigger these corner cases.
312 */
313 std::string_view base{"aabAba"};
314
315 /* Everything ends with an empty prefix. */
316 CHECK(StrEndsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
317 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
318
319 /* Equals string, ignoring case. */
320 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
321 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
322 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
323 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
324
325 /* Ends with same, ignoring case. */
326 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(0, 1)));
327 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(3, 1)));
328 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(0, 1)));
329 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(3, 1)));
330
331 /* Does not end the same. */
332 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
333 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
334 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
335 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
336}
337
338
339TEST_CASE("FormatArrayAsHex")
340{
341 CHECK(FormatArrayAsHex(std::array<uint8_t, 0>{}) == "");
342 CHECK(FormatArrayAsHex(std::array<uint8_t, 1>{0x12}) == "12");
343 CHECK(FormatArrayAsHex(std::array<uint8_t, 4>{0x13, 0x38, 0x42, 0xAF}) == "133842AF");
344}
345
346TEST_CASE("ConvertHexToBytes")
347{
348 CHECK(ConvertHexToBytes("", {}) == true);
349 CHECK(ConvertHexToBytes("1", {}) == false);
350 CHECK(ConvertHexToBytes("12", {}) == false);
351
352 std::array<uint8_t, 1> bytes1;
353 CHECK(ConvertHexToBytes("1", bytes1) == false);
354 CHECK(ConvertHexToBytes("12", bytes1) == true);
355 CHECK(bytes1[0] == 0x12);
356 CHECK(ConvertHexToBytes("123", bytes1) == false);
357 CHECK(ConvertHexToBytes("1g", bytes1) == false);
358 CHECK(ConvertHexToBytes("g1", bytes1) == false);
359
360 std::array<uint8_t, 2> bytes2;
361 CHECK(ConvertHexToBytes("12", bytes2) == false);
362 CHECK(ConvertHexToBytes("1234", bytes2) == true);
363 CHECK(bytes2[0] == 0x12);
364 CHECK(bytes2[1] == 0x34);
365
366 std::array<uint8_t, 8> bytes3;
367 CHECK(ConvertHexToBytes("123456789abcdef0", bytes3) == true);
368 CHECK(bytes3[0] == 0x12);
369 CHECK(bytes3[1] == 0x34);
370 CHECK(bytes3[2] == 0x56);
371 CHECK(bytes3[3] == 0x78);
372 CHECK(bytes3[4] == 0x9a);
373 CHECK(bytes3[5] == 0xbc);
374 CHECK(bytes3[6] == 0xde);
375 CHECK(bytes3[7] == 0xf0);
376
377 CHECK(ConvertHexToBytes("123456789ABCDEF0", bytes3) == true);
378 CHECK(bytes3[0] == 0x12);
379 CHECK(bytes3[1] == 0x34);
380 CHECK(bytes3[2] == 0x56);
381 CHECK(bytes3[3] == 0x78);
382 CHECK(bytes3[4] == 0x9a);
383 CHECK(bytes3[5] == 0xbc);
384 CHECK(bytes3[6] == 0xde);
385 CHECK(bytes3[7] == 0xf0);
386}
387
388static const std::vector<std::pair<std::string, std::string>> _str_trim_testcases = {
389 {"a", "a"},
390 {" a", "a"},
391 {"a ", "a"},
392 {" a ", "a"},
393 {" a b c ", "a b c"},
394 {" ", ""}
395};
396
397TEST_CASE("StrTrimInPlace")
398{
399 for (auto [input, expected] : _str_trim_testcases) {
400 StrTrimInPlace(input);
401 CHECK(input == expected);
402 }
403}
404
405TEST_CASE("StrTrimView") {
406 for (const auto& [input, expected] : _str_trim_testcases) {
407 CHECK(StrTrimView(input) == expected);
408 }
409}
410
bool ConvertHexToBytes(std::string_view hex, std::span< uint8_t > bytes)
Convert a hex-string to a byte-array, while validating it was actually hex.
Definition string.cpp:734
bool StrEqualsIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:347
std::string FormatArrayAsHex(std::span< const uint8_t > data)
Format a byte array into a continuous hex string.
Definition string.cpp:81
bool StrStartsWithIgnoreCase(std::string_view str, const std::string_view prefix)
Check whether the given string starts with the given prefix, ignoring case.
Definition string.cpp:281
int StrCompareIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
Definition string.cpp:334
bool StrEndsWithIgnoreCase(std::string_view str, const std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
Definition string.cpp:321
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
Definition string.cpp:260