OpenTTD Source 20250521-master-g82876c25e0
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#include "../strings_func.h"
16#include "../core/string_builder.hpp"
17#include "../core/string_consumer.hpp"
18#include "../table/control_codes.h"
19
20#include "table/strings.h"
21
22#include "../safeguards.h"
23
24/**** String compare/equals *****/
25
26TEST_CASE("StrCompareIgnoreCase - std::string")
27{
28 /* Same string, with different cases. */
29 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{""}) == 0);
30 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"a"}) == 0);
31 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"A"}) == 0);
32 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"a"}) == 0);
33 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"A"}) == 0);
34
35 /* Not the same string. */
36 CHECK(StrCompareIgnoreCase(std::string{""}, std::string{"b"}) < 0);
37 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{""}) > 0);
38
39 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"b"}) < 0);
40 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"a"}) > 0);
41 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"B"}) < 0);
42 CHECK(StrCompareIgnoreCase(std::string{"b"}, std::string{"A"}) > 0);
43 CHECK(StrCompareIgnoreCase(std::string{"A"}, std::string{"b"}) < 0);
44 CHECK(StrCompareIgnoreCase(std::string{"B"}, std::string{"a"}) > 0);
45
46 CHECK(StrCompareIgnoreCase(std::string{"a"}, std::string{"aa"}) < 0);
47 CHECK(StrCompareIgnoreCase(std::string{"aa"}, std::string{"a"}) > 0);
48}
49
50TEST_CASE("StrCompareIgnoreCase - std::string_view")
51{
52 /*
53 * With std::string_view the only way to access the data is via .data(),
54 * which does not guarantee the termination that would be required by
55 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
56 * or strcasecmp would fail if it does not account for the length of the
57 * view. Thus, contrary to the string tests, this uses the same base
58 * string but gets different sections to trigger these corner cases.
59 */
60 std::string_view base{"aaAbB"};
61
62 /* Same string, with different cases. */
63 CHECK(StrCompareIgnoreCase(base.substr(0, 0), base.substr(1, 0)) == 0); // Different positions
64 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(1, 1)) == 0); // Different positions
65 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(2, 1)) == 0);
66 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(1, 1)) == 0);
67 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(2, 1)) == 0);
68
69 /* Not the same string. */
70 CHECK(StrCompareIgnoreCase(base.substr(3, 0), base.substr(3, 1)) < 0); // Same position, different lengths
71 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 0)) > 0); // Same position, different lengths
72
73 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(3, 1)) < 0);
74 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(0, 1)) > 0);
75 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(4, 1)) < 0);
76 CHECK(StrCompareIgnoreCase(base.substr(3, 1), base.substr(2, 1)) > 0);
77 CHECK(StrCompareIgnoreCase(base.substr(2, 1), base.substr(3, 1)) < 0);
78 CHECK(StrCompareIgnoreCase(base.substr(4, 1), base.substr(0, 1)) > 0);
79
80 CHECK(StrCompareIgnoreCase(base.substr(0, 1), base.substr(0, 2)) < 0); // Same position, different lengths
81 CHECK(StrCompareIgnoreCase(base.substr(0, 2), base.substr(0, 1)) > 0); // Same position, different lengths
82}
83
84TEST_CASE("StrEqualsIgnoreCase - std::string")
85{
86 /* Same string, with different cases. */
87 CHECK(StrEqualsIgnoreCase(std::string{""}, std::string{""}));
88 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"a"}));
89 CHECK(StrEqualsIgnoreCase(std::string{"a"}, std::string{"A"}));
90 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"a"}));
91 CHECK(StrEqualsIgnoreCase(std::string{"A"}, std::string{"A"}));
92
93 /* Not the same string. */
94 CHECK(!StrEqualsIgnoreCase(std::string{""}, std::string{"b"}));
95 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{""}));
96 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"b"}));
97 CHECK(!StrEqualsIgnoreCase(std::string{"b"}, std::string{"a"}));
98 CHECK(!StrEqualsIgnoreCase(std::string{"a"}, std::string{"aa"}));
99 CHECK(!StrEqualsIgnoreCase(std::string{"aa"}, std::string{"a"}));
100}
101
102TEST_CASE("StrEqualsIgnoreCase - std::string_view")
103{
104 /*
105 * With std::string_view the only way to access the data is via .data(),
106 * which does not guarantee the termination that would be required by
107 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
108 * or strcasecmp would fail if it does not account for the length of the
109 * view. Thus, contrary to the string tests, this uses the same base
110 * string but gets different sections to trigger these corner cases.
111 */
112 std::string_view base{"aaAb"};
113
114 /* Same string, with different cases. */
115 CHECK(StrEqualsIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
116 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
117 CHECK(StrEqualsIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
118 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(1, 1)));
119 CHECK(StrEqualsIgnoreCase(base.substr(2, 1), base.substr(2, 1)));
120
121 /* Not the same string. */
122 CHECK(!StrEqualsIgnoreCase(base.substr(3, 0), base.substr(3, 1))); // Same position, different lengths
123 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
124 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
125 CHECK(!StrEqualsIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
126 CHECK(!StrEqualsIgnoreCase(base.substr(0, 1), base.substr(0, 2))); // Same position, different lengths
127 CHECK(!StrEqualsIgnoreCase(base.substr(0, 2), base.substr(0, 1))); // Same position, different lengths
128}
129
130/**** String starts with *****/
131
132TEST_CASE("StrStartsWithIgnoreCase - std::string")
133{
134 /* Everything starts with an empty prefix. */
135 CHECK(StrStartsWithIgnoreCase(std::string{""}, std::string{""}));
136 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{""}));
137
138 /* Equals string, ignoring case. */
139 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
140 CHECK(StrStartsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
141 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
142 CHECK(StrStartsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
143
144 /* Starts with same, ignoring case. */
145 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"a"}));
146 CHECK(StrStartsWithIgnoreCase(std::string{"ab"}, std::string{"A"}));
147 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"a"}));
148 CHECK(StrStartsWithIgnoreCase(std::string{"Ab"}, std::string{"A"}));
149
150 /* Does not start the same. */
151 CHECK(!StrStartsWithIgnoreCase(std::string{""}, std::string{"b"}));
152 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
153 CHECK(!StrStartsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
154 CHECK(!StrStartsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
155}
156
157TEST_CASE("StrStartsWithIgnoreCase - std::string_view")
158{
159 /*
160 * With std::string_view the only way to access the data is via .data(),
161 * which does not guarantee the termination that would be required by
162 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
163 * or strcasecmp would fail if it does not account for the length of the
164 * view. Thus, contrary to the string tests, this uses the same base
165 * string but gets different sections to trigger these corner cases.
166 */
167 std::string_view base{"aabAb"};
168
169 /* Everything starts with an empty prefix. */
170 CHECK(StrStartsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
171 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
172
173 /* Equals string, ignoring case. */
174 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
175 CHECK(StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
176 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
177 CHECK(StrStartsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
178
179 /* Starts with same, ignoring case. */
180 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(0, 1)));
181 CHECK(StrStartsWithIgnoreCase(base.substr(1, 2), base.substr(3, 1)));
182 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(0, 1)));
183 CHECK(StrStartsWithIgnoreCase(base.substr(3, 2), base.substr(3, 1)));
184
185 /* Does not start the same. */
186 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
187 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
188 CHECK(!StrStartsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
189 CHECK(!StrStartsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
190}
191
192/**** String ends with *****/
193
194TEST_CASE("StrEndsWithIgnoreCase - std::string")
195{
196 /* Everything ends with an empty prefix. */
197 CHECK(StrEndsWithIgnoreCase(std::string{""}, std::string{""}));
198 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{""}));
199
200 /* Equals string, ignoring case. */
201 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"a"}));
202 CHECK(StrEndsWithIgnoreCase(std::string{"a"}, std::string{"A"}));
203 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"a"}));
204 CHECK(StrEndsWithIgnoreCase(std::string{"A"}, std::string{"A"}));
205
206 /* Ends with same, ignoring case. */
207 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"a"}));
208 CHECK(StrEndsWithIgnoreCase(std::string{"ba"}, std::string{"A"}));
209 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"a"}));
210 CHECK(StrEndsWithIgnoreCase(std::string{"bA"}, std::string{"A"}));
211
212 /* Does not end the same. */
213 CHECK(!StrEndsWithIgnoreCase(std::string{""}, std::string{"b"}));
214 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"b"}));
215 CHECK(!StrEndsWithIgnoreCase(std::string{"b"}, std::string{"a"}));
216 CHECK(!StrEndsWithIgnoreCase(std::string{"a"}, std::string{"aa"}));
217}
218
219TEST_CASE("StrEndsWithIgnoreCase - std::string_view")
220{
221 /*
222 * With std::string_view the only way to access the data is via .data(),
223 * which does not guarantee the termination that would be required by
224 * things such as stricmp/strcasecmp. So, just passing .data() into stricmp
225 * or strcasecmp would fail if it does not account for the length of the
226 * view. Thus, contrary to the string tests, this uses the same base
227 * string but gets different sections to trigger these corner cases.
228 */
229 std::string_view base{"aabAba"};
230
231 /* Everything ends with an empty prefix. */
232 CHECK(StrEndsWithIgnoreCase(base.substr(0, 0), base.substr(1, 0))); // Different positions
233 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 0)));
234
235 /* Equals string, ignoring case. */
236 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(1, 1))); // Different positions
237 CHECK(StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(3, 1)));
238 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(0, 1)));
239 CHECK(StrEndsWithIgnoreCase(base.substr(3, 1), base.substr(3, 1)));
240
241 /* Ends with same, ignoring case. */
242 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(0, 1)));
243 CHECK(StrEndsWithIgnoreCase(base.substr(2, 2), base.substr(3, 1)));
244 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(0, 1)));
245 CHECK(StrEndsWithIgnoreCase(base.substr(4, 2), base.substr(3, 1)));
246
247 /* Does not end the same. */
248 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 0), base.substr(2, 1)));
249 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(2, 1)));
250 CHECK(!StrEndsWithIgnoreCase(base.substr(2, 1), base.substr(0, 1)));
251 CHECK(!StrEndsWithIgnoreCase(base.substr(0, 1), base.substr(0, 2)));
252}
253
254
255TEST_CASE("FormatArrayAsHex")
256{
257 CHECK(FormatArrayAsHex(std::array<uint8_t, 0>{}) == "");
258 CHECK(FormatArrayAsHex(std::array<uint8_t, 1>{0x12}) == "12");
259 CHECK(FormatArrayAsHex(std::array<uint8_t, 4>{0x13, 0x38, 0x42, 0xAF}) == "133842AF");
260}
261
262TEST_CASE("ConvertHexToBytes")
263{
264 CHECK(ConvertHexToBytes("", {}) == true);
265 CHECK(ConvertHexToBytes("1", {}) == false);
266 CHECK(ConvertHexToBytes("12", {}) == false);
267
268 std::array<uint8_t, 1> bytes1;
269 CHECK(ConvertHexToBytes("1", bytes1) == false);
270 CHECK(ConvertHexToBytes("12", bytes1) == true);
271 CHECK(bytes1[0] == 0x12);
272 CHECK(ConvertHexToBytes("123", bytes1) == false);
273 CHECK(ConvertHexToBytes("1g", bytes1) == false);
274 CHECK(ConvertHexToBytes("g1", bytes1) == false);
275
276 std::array<uint8_t, 2> bytes2;
277 CHECK(ConvertHexToBytes("12", bytes2) == false);
278 CHECK(ConvertHexToBytes("1234", bytes2) == true);
279 CHECK(bytes2[0] == 0x12);
280 CHECK(bytes2[1] == 0x34);
281
282 std::array<uint8_t, 8> bytes3;
283 CHECK(ConvertHexToBytes("123456789abcdef0", bytes3) == true);
284 CHECK(bytes3[0] == 0x12);
285 CHECK(bytes3[1] == 0x34);
286 CHECK(bytes3[2] == 0x56);
287 CHECK(bytes3[3] == 0x78);
288 CHECK(bytes3[4] == 0x9a);
289 CHECK(bytes3[5] == 0xbc);
290 CHECK(bytes3[6] == 0xde);
291 CHECK(bytes3[7] == 0xf0);
292
293 CHECK(ConvertHexToBytes("123456789ABCDEF0", bytes3) == true);
294 CHECK(bytes3[0] == 0x12);
295 CHECK(bytes3[1] == 0x34);
296 CHECK(bytes3[2] == 0x56);
297 CHECK(bytes3[3] == 0x78);
298 CHECK(bytes3[4] == 0x9a);
299 CHECK(bytes3[5] == 0xbc);
300 CHECK(bytes3[6] == 0xde);
301 CHECK(bytes3[7] == 0xf0);
302}
303
304static const std::vector<std::pair<std::string, std::string>> _str_trim_testcases = {
305 {"a", "a"},
306 {" a", "a"},
307 {"a ", "a"},
308 {" a ", "a"},
309 {" a b c ", "a b c"},
310 {" ", ""},
311 {" \r\f\t ", ""},
312};
313
314TEST_CASE("StrTrimInPlace")
315{
316 for (auto [input, expected] : _str_trim_testcases) {
317 StrTrimInPlace(input);
318 CHECK(input == expected);
319 }
320}
321
322TEST_CASE("StrTrimView") {
323 for (const auto& [input, expected] : _str_trim_testcases) {
324 CHECK(StrTrimView(input, StringConsumer::WHITESPACE_NO_NEWLINE) == expected);
325 }
326}
327
328extern void FixSCCEncoded(std::string &str, bool fix_code);
329
330/* Helper to call FixSCCEncoded and return the result in a new string. */
331static std::string FixSCCEncodedWrapper(const std::string &str, bool fix_code)
332{
333 std::string result = str;
334 FixSCCEncoded(result, fix_code);
335 return result;
336}
337
338/* Helper to compose a string part from a unicode character */
339static void ComposePart(StringBuilder &builder, char32_t c)
340{
341 builder.PutUtf8(c);
342}
343
344/* Helper to compose a string part from a string. */
345static void ComposePart(StringBuilder &builder, const std::string &value)
346{
347 builder += value;
348}
349
350/* Helper to compose a string from unicode or string parts. */
351template <typename... Args>
352static std::string Compose(Args &&... args)
353{
354 std::string result;
355 StringBuilder builder(result);
356 (ComposePart(builder, args), ...);
357 return result;
358}
359
360TEST_CASE("FixSCCEncoded")
361{
362 /* Test conversion of empty string. */
363 CHECK(FixSCCEncodedWrapper("", false) == "");
364
365 /* Test conversion of old code to new code. */
366 CHECK(FixSCCEncodedWrapper("\uE0280", true) == Compose(SCC_ENCODED, "0"));
367
368 /* Test conversion of two old codes to new codes. */
369 CHECK(FixSCCEncodedWrapper("\uE0280:\uE0281", true) == Compose(SCC_ENCODED, "0", SCC_RECORD_SEPARATOR, SCC_ENCODED, "1"));
370
371 /* Test conversion with no parameter. */
372 CHECK(FixSCCEncodedWrapper("\uE0001", false) == Compose(SCC_ENCODED, "1"));
373
374 /* Test conversion with one numeric parameter. */
375 CHECK(FixSCCEncodedWrapper("\uE00022:1", false) == Compose(SCC_ENCODED, "22", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "1"));
376
377 /* Test conversion with signed numeric parameter. */
378 CHECK(FixSCCEncodedWrapper("\uE00022:-1", false) == Compose(SCC_ENCODED, "22", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "-1"));
379
380 /* Test conversion with two numeric parameters. */
381 CHECK(FixSCCEncodedWrapper("\uE0003:12:2", false) == Compose(SCC_ENCODED, "3", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "12", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "2"));
382
383 /* Test conversion with one string parameter. */
384 CHECK(FixSCCEncodedWrapper("\uE0004:\"Foo\"", false) == Compose(SCC_ENCODED, "4", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo"));
385
386 /* Test conversion with two string parameters. */
387 CHECK(FixSCCEncodedWrapper("\uE00055:\"Foo\":\"Bar\"", false) == Compose(SCC_ENCODED, "55", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Bar"));
388
389 /* Test conversion with two string parameters surrounding a numeric parameter. */
390 CHECK(FixSCCEncodedWrapper("\uE0006:\"Foo\":7CA:\"Bar\"", false) == Compose(SCC_ENCODED, "6", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "7CA", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Bar"));
391
392 /* Test conversion with one sub-string and two string parameters. */
393 CHECK(FixSCCEncodedWrapper("\uE000777:\uE0008888:\"Foo\":\"BarBaz\"", false) == Compose(SCC_ENCODED, "777", SCC_RECORD_SEPARATOR, SCC_ENCODED, "8888", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "Foo", SCC_RECORD_SEPARATOR, SCC_ENCODED_STRING, "BarBaz"));
394}
395
396extern void FixSCCEncodedNegative(std::string &str);
397
398/* Helper to call FixSCCEncodedNegative and return the result in a new string. */
399static std::string FixSCCEncodedNegativeWrapper(const std::string &str)
400{
401 std::string result = str;
402 FixSCCEncodedNegative(result);
403 return result;
404}
405
406TEST_CASE("FixSCCEncodedNegative")
407{
408 auto positive = Compose(SCC_ENCODED, "777", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "ffffffffffffffff");
409 auto negative = Compose(SCC_ENCODED, "777", SCC_RECORD_SEPARATOR, SCC_ENCODED_NUMERIC, "-1");
410
411 CHECK(FixSCCEncodedNegativeWrapper("") == "");
412 CHECK(FixSCCEncodedNegativeWrapper(positive) == positive);
413 CHECK(FixSCCEncodedNegativeWrapper(negative) == positive);
414}
415
416TEST_CASE("EncodedString::ReplaceParam - positive")
417{
418 /* Test that two encoded strings with different parameters are not the same. */
419 EncodedString string1 = GetEncodedString(STR_NULL, "Foo"sv, 10, "Bar"sv);
420 EncodedString string2 = GetEncodedString(STR_NULL, "Foo"sv, 15, "Bar"sv);
421 CHECK(string1 != string2);
422
423 /* Test that replacing parameter results in the same string. */
424 EncodedString string3 = string1.ReplaceParam(1, 15);
425 CHECK(string2 == string3);
426}
427
428TEST_CASE("EncodedString::ReplaceParam - negative")
429{
430 EncodedString string1 = GetEncodedString(STR_NULL, "Foo"sv, -1, "Bar"sv);
431 EncodedString string2 = GetEncodedString(STR_NULL, "Foo"sv, -2, "Bar"sv);
432 EncodedString string3 = GetEncodedString(STR_NULL, "Foo"sv, 0xFFFF'FFFF'FFFF'FFFF, "Bar"sv);
433 /* Test that two encoded strings with different parameters are not the same. */
434 CHECK(string1 != string2);
435 /* Test that signed values are stored as unsigned. */
436 CHECK(string1 == string3);
437
438 /* Test that replacing parameter results in the same string. */
439 EncodedString string4 = string1.ReplaceParam(1, -2);
440 CHECK(string2 == string4);
441}
void PutUtf8(char32_t c)
Append UTF.8 char.
Container for an encoded string, created by GetEncodedString.
EncodedString ReplaceParam(size_t param, StringParameter &&value) const
Replace a parameter of this EncodedString.
Definition strings.cpp:151
Compose data into a growing std::string.
static const std::string_view WHITESPACE_NO_NEWLINE
ASCII whitespace characters, excluding new-line.
@ SCC_ENCODED
Encoded string marker and sub-string parameter.
@ SCC_ENCODED_NUMERIC
Encoded numeric parameter.
@ SCC_ENCODED_STRING
Encoded string parameter.
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:570
std::string FormatArrayAsHex(std::span< const uint8_t > data)
Format a byte array into a continuous hex string.
Definition string.cpp:75
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:321
bool StrEndsWithIgnoreCase(std::string_view str, std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
Definition string.cpp:295
void StrTrimInPlace(std::string &str)
Trim the spaces from given string in place, i.e.
Definition string.cpp:226
bool StrStartsWithIgnoreCase(std::string_view str, std::string_view prefix)
Check whether the given string starts with the given prefix, ignoring case.
Definition string.cpp:255
int StrCompareIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s, while ignoring the case of the characters.
Definition string.cpp:308
void FixSCCEncodedNegative(std::string &str)
Scan the string for SCC_ENCODED_NUMERIC with negative values, and reencode them as uint64_t.
Definition saveload.cpp:988
void FixSCCEncoded(std::string &str, bool fix_code)
Scan the string for old values of SCC_ENCODED and fix it to it's new, value.
Definition saveload.cpp:921
EncodedString GetEncodedString(StringID str)
Encode a string with no parameters into an encoded string.
Definition strings.cpp:91