OXYGENHUB
◆ main openmsx / src / unittest / StringOp_test.cc
12.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "catch.hpp"
2
#include "StringOp.hh"
3
4
#include "narrow.hh"
5
6
#include <type_traits>
7
8
using namespace StringOp;
9
10
static void checkTrimRight(const std::string& s, char c, const std::string& expected)
11
{
12
	std::string test = s;
13
	trimRight(test, c);
14
	CHECK(test == expected);
15
16
	std::string_view ref = s;
17
	trimRight(ref, c);
18
	CHECK(ref == expected);
19
}
20
static void checkTrimRight(const std::string& s, const char* chars, const std::string& expected)
21
{
22
	std::string test = s;
23
	trimRight(test, chars);
24
	CHECK(test == expected);
25
26
	std::string_view ref = s;
27
	trimRight(ref, chars);
28
	CHECK(ref == expected);
29
}
30
31
static void checkTrimLeft(const std::string& s, char c, const std::string& expected)
32
{
33
	std::string test = s;
34
	trimLeft(test, c);
35
	CHECK(test == expected);
36
37
	std::string_view ref = s;
38
	trimLeft(ref, c);
39
	CHECK(ref == expected);
40
}
41
static void checkTrimLeft(const std::string& s, const char* chars, const std::string& expected)
42
{
43
	std::string test = s;
44
	trimLeft(test, chars);
45
	CHECK(test == expected);
46
47
	std::string_view ref = s;
48
	trimLeft(ref, chars);
49
	CHECK(ref == expected);
50
}
51
52
static void checkSplitOnFirst(const std::string& s, const std::string& first, const std::string& last)
53
{
54
	auto [f1, l1] = splitOnFirst(s, '-');
55
	auto [f2, l2] = splitOnFirst(s, " -+");
56
	static_assert(std::is_same_v<decltype(f1), std::string_view>);
57
	static_assert(std::is_same_v<decltype(f2), std::string_view>);
58
	static_assert(std::is_same_v<decltype(l1), std::string_view>);
59
	static_assert(std::is_same_v<decltype(l2), std::string_view>);
60
	CHECK(f1 == first);
61
	CHECK(f2 == first);
62
	CHECK(l1 == last);
63
	CHECK(l2 == last);
64
}
65
66
static void checkSplitOnLast(const std::string& s, const std::string& first, const std::string& last)
67
{
68
	auto [f1, l1] = splitOnLast(s, '-');
69
	auto [f2, l2] = splitOnLast(s, " -+");
70
	static_assert(std::is_same_v<decltype(f1), std::string_view>);
71
	static_assert(std::is_same_v<decltype(f2), std::string_view>);
72
	static_assert(std::is_same_v<decltype(l1), std::string_view>);
73
	static_assert(std::is_same_v<decltype(l2), std::string_view>);
74
	CHECK(f1 == first);
75
	CHECK(f2 == first);
76
	CHECK(l1 == last);
77
	CHECK(l2 == last);
78
}
79
80
template<StringOp::EmptyParts keepOrRemove, typename Separators>
81
static void checkSplit(Separators separators, const std::string& s, const std::vector<std::string_view>& expected)
82
{
83
	//CHECK(split(s, '-') == expected);
84
85
	std::vector<std::string_view> result;
86
	for (const auto& ss : StringOp::split_view<keepOrRemove>(s, separators)) {
87
		result.push_back(ss);
88
	}
89
	CHECK(result == expected);
90
}
91
92
static void checkParseRange(const std::string& s, const std::vector<unsigned>& expected)
93
{
94
	auto parsed = parseRange(s, 0, 63);
95
	std::vector<unsigned> result;
96
	parsed.foreachSetBit([&](size_t i) { result.push_back(narrow<unsigned>(i)); });
97
	CHECK(result == expected);
98
}
99
100
101
TEST_CASE("StringOp")
102
{
103
	SECTION("stringTo<int>") {
104
		std::optional<int> NOK;
105
		using OK = std::optional<int>;
106
107
		// empty string is invalid
108
		CHECK(StringOp::stringTo<int>("") == NOK);
109
110
		// valid decimal values, positive ..
111
		CHECK(StringOp::stringTo<int>("0") == OK(0));
112
		CHECK(StringOp::stringTo<int>("03") == OK(3));
113
		CHECK(StringOp::stringTo<int>("097") == OK(97));
114
		CHECK(StringOp::stringTo<int>("12") == OK(12));
115
		// .. and negative
116
		CHECK(StringOp::stringTo<int>("-0") == OK(0));
117
		CHECK(StringOp::stringTo<int>("-11") == OK(-11));
118
119
		// invalid
120
		CHECK(StringOp::stringTo<int>("-") == NOK);
121
		CHECK(StringOp::stringTo<int>("zz") == NOK);
122
		CHECK(StringOp::stringTo<int>("+") == NOK);
123
		CHECK(StringOp::stringTo<int>("+12") == NOK);
124
125
		// leading whitespace is invalid, trailing stuff is invalid
126
		CHECK(StringOp::stringTo<int>(" 14") == NOK);
127
		CHECK(StringOp::stringTo<int>("15 ") == NOK);
128
		CHECK(StringOp::stringTo<int>("15bar") == NOK);
129
130
		// hexadecimal
131
		CHECK(StringOp::stringTo<int>("0x1a") == OK(26));
132
		CHECK(StringOp::stringTo<int>("0x1B") == OK(27));
133
		CHECK(StringOp::stringTo<int>("0X1c") == OK(28));
134
		CHECK(StringOp::stringTo<int>("0X1D") == OK(29));
135
		CHECK(StringOp::stringTo<int>("-0x100") == OK(-256));
136
		CHECK(StringOp::stringTo<int>("0x") == NOK);
137
		CHECK(StringOp::stringTo<int>("0x12g") == NOK);
138
		CHECK(StringOp::stringTo<int>("0x-123") == NOK);
139
140
		// binary
141
		CHECK(StringOp::stringTo<int>("0b") == NOK);
142
		CHECK(StringOp::stringTo<int>("0b2") == NOK);
143
		CHECK(StringOp::stringTo<int>("0b100") == OK(4));
144
		CHECK(StringOp::stringTo<int>("-0B1001") == OK(-9));
145
		CHECK(StringOp::stringTo<int>("0b-11") == NOK);
146
147
		// overflow
148
		CHECK(StringOp::stringTo<int>("-2147483649") == NOK);
149
		CHECK(StringOp::stringTo<int>("2147483648") == NOK);
150
		CHECK(StringOp::stringTo<int>("999999999999999") == NOK);
151
		CHECK(StringOp::stringTo<int>("-999999999999999") == NOK);
152
		// edge cases (no overflow)
153
		CHECK(StringOp::stringTo<int>("-2147483648") == OK(-2147483648));
154
		CHECK(StringOp::stringTo<int>("2147483647") == OK(2147483647));
155
		CHECK(StringOp::stringTo<int>("-0x80000000") == OK(-2147483648));
156
		CHECK(StringOp::stringTo<int>("0x7fffffff") == OK(2147483647));
157
	}
158
	SECTION("stringTo<unsigned>") {
159
		std::optional<unsigned> NOK;
160
		using OK = std::optional<unsigned>;
161
162
		// empty string is invalid
163
		CHECK(StringOp::stringTo<unsigned>("") == NOK);
164
165
		// valid decimal values, only positive ..
166
		CHECK(StringOp::stringTo<unsigned>("0") == OK(0));
167
		CHECK(StringOp::stringTo<unsigned>("08") == OK(8));
168
		CHECK(StringOp::stringTo<unsigned>("0123") == OK(123));
169
		CHECK(StringOp::stringTo<unsigned>("13") == OK(13));
170
		// negative is invalid
171
		CHECK(StringOp::stringTo<unsigned>("-0") == NOK);
172
		CHECK(StringOp::stringTo<unsigned>("-12") == NOK);
173
174
		// invalid
175
		CHECK(StringOp::stringTo<unsigned>("-") == NOK);
176
		CHECK(StringOp::stringTo<unsigned>("zz") == NOK);
177
		CHECK(StringOp::stringTo<unsigned>("+") == NOK);
178
		CHECK(StringOp::stringTo<unsigned>("+12") == NOK);
179
180
		// leading whitespace is invalid, trailing stuff is invalid
181
		CHECK(StringOp::stringTo<unsigned>(" 16") == NOK);
182
		CHECK(StringOp::stringTo<unsigned>("17 ") == NOK);
183
		CHECK(StringOp::stringTo<unsigned>("17qux") == NOK);
184
185
		// hexadecimal
186
		CHECK(StringOp::stringTo<unsigned>("0x2a") == OK(42));
187
		CHECK(StringOp::stringTo<unsigned>("0x2B") == OK(43));
188
		CHECK(StringOp::stringTo<unsigned>("0X2c") == OK(44));
189
		CHECK(StringOp::stringTo<unsigned>("0X2D") == OK(45));
190
		CHECK(StringOp::stringTo<unsigned>("0x") == NOK);
191
		CHECK(StringOp::stringTo<unsigned>("-0x456") == NOK);
192
		CHECK(StringOp::stringTo<unsigned>("0x-123") == NOK);
193
194
		// binary
195
		CHECK(StringOp::stringTo<unsigned>("0b1100") == OK(12));
196
		CHECK(StringOp::stringTo<unsigned>("0B1010") == OK(10));
197
		CHECK(StringOp::stringTo<unsigned>("0b") == NOK);
198
		CHECK(StringOp::stringTo<unsigned>("-0b101") == NOK);
199
		CHECK(StringOp::stringTo<unsigned>("0b2") == NOK);
200
		CHECK(StringOp::stringTo<unsigned>("0b-11") == NOK);
201
202
		// overflow
203
		CHECK(StringOp::stringTo<unsigned>("4294967296") == NOK);
204
		CHECK(StringOp::stringTo<unsigned>("999999999999999") == NOK);
205
		// edge case (no overflow)
206
		CHECK(StringOp::stringTo<unsigned>("4294967295") == OK(4294967295));
207
		CHECK(StringOp::stringTo<unsigned>("0xffffffff") == OK(4294967295));
208
	}
209
210
	SECTION("stringToBool") {
211
		CHECK(stringToBool("0") == false);
212
		CHECK(stringToBool("1") == true);
213
		CHECK(stringToBool("Yes") == true);
214
		CHECK(stringToBool("yes") == true);
215
		CHECK(stringToBool("YES") == true);
216
		CHECK(stringToBool("No") == false);
217
		CHECK(stringToBool("no") == false);
218
		CHECK(stringToBool("NO") == false);
219
		CHECK(stringToBool("True") == true);
220
		CHECK(stringToBool("true") == true);
221
		CHECK(stringToBool("TRUE") == true);
222
		CHECK(stringToBool("False") == false);
223
		CHECK(stringToBool("false") == false);
224
		CHECK(stringToBool("FALSE") == false);
225
		// These two behave different as Tcl
226
		CHECK(stringToBool("2") == false); // is true in Tcl
227
		CHECK(stringToBool("foobar") == false); // is error in Tcl
228
	}
229
	/*SECTION("toLower") {
230
		CHECK(toLower("") == "");
231
		CHECK(toLower("foo") == "foo");
232
		CHECK(toLower("FOO") == "foo");
233
		CHECK(toLower("fOo") == "foo");
234
		CHECK(toLower(std::string("FoO")) == "foo");
235
	}*/
236
	SECTION("trimRight") {
237
		checkTrimRight("", ' ', "");
238
		checkTrimRight("  ", ' ', "");
239
		checkTrimRight("foo", ' ', "foo");
240
		checkTrimRight("  foo", ' ', "  foo");
241
		checkTrimRight("foo  ", ' ', "foo");
242
243
		checkTrimRight("", "o ", "");
244
		checkTrimRight(" o ", "o ", "");
245
		checkTrimRight("foobar", "o ", "foobar");
246
		checkTrimRight("  foobar", "o ", "  foobar");
247
		checkTrimRight("foo  ", "o ", "f");
248
	}
249
	SECTION("trimLeft") {
250
		checkTrimLeft("", ' ', "");
251
		checkTrimLeft("  ", ' ', "");
252
		checkTrimLeft("foo", ' ', "foo");
253
		checkTrimLeft("foo  ", ' ', "foo  ");
254
		checkTrimLeft("  foo", ' ', "foo");
255
256
		checkTrimLeft("", "f ", "");
257
		checkTrimLeft(" f ", "f ", "");
258
		checkTrimLeft("foo", "f ", "oo");
259
		checkTrimLeft("barfoo  ", "f ", "barfoo  ");
260
		checkTrimLeft("  foo", "f ", "oo");
261
	}
262
	SECTION("splitOnFirst") {
263
		checkSplitOnFirst("", "", "");
264
		checkSplitOnFirst("-", "", "");
265
		checkSplitOnFirst("foo-", "foo", "");
266
		checkSplitOnFirst("-foo", "", "foo");
267
		checkSplitOnFirst("foo-bar", "foo", "bar");
268
		checkSplitOnFirst("foo-bar-qux", "foo", "bar-qux");
269
		checkSplitOnFirst("-bar-qux", "", "bar-qux");
270
		checkSplitOnFirst("foo-bar-", "foo", "bar-");
271
	}
272
	SECTION("splitOnLast") {
273
		checkSplitOnLast("", "", "");
274
		checkSplitOnLast("-", "", "");
275
		checkSplitOnLast("foo-", "foo", "");
276
		checkSplitOnLast("-foo", "", "foo");
277
		checkSplitOnLast("foo-bar", "foo", "bar");
278
		checkSplitOnLast("foo-bar-qux", "foo-bar", "qux");
279
		checkSplitOnLast("-bar-qux", "-bar", "qux");
280
		checkSplitOnLast("foo-bar-", "foo-bar", "");
281
	}
282
	SECTION("split") {
283
		using enum StringOp::EmptyParts;
284
		checkSplit<KEEP>('-', "", {});
285
		checkSplit<KEEP>('-', "-", {""});
286
		checkSplit<KEEP>('-', "foo-", {"foo"});
287
		checkSplit<KEEP>('-', "-foo", {"", "foo"});
288
		checkSplit<KEEP>('-', "foo-bar", {"foo", "bar"});
289
		checkSplit<KEEP>('-', "foo-bar-qux", {"foo", "bar", "qux"});
290
		checkSplit<KEEP>('-', "-bar-qux", {"", "bar", "qux"});
291
		checkSplit<KEEP>('-', "foo-bar-", {"foo", "bar"});
292
		checkSplit<KEEP>('-', "foo--bar", {"foo", "", "bar"});
293
		checkSplit<KEEP>('-', "--foo--bar--", {"", "", "foo", "", "bar", ""});
294
		checkSplit<KEEP>(" \t", "foo\t\t  bar", {"foo", "", "", "", "bar"});
295
296
		checkSplit<REMOVE>('-', "", {});
297
		checkSplit<REMOVE>('-', "-", {});
298
		checkSplit<REMOVE>('-', "foo-", {"foo"});
299
		checkSplit<REMOVE>('-', "-foo", {"foo"});
300
		checkSplit<REMOVE>('-', "foo-bar", {"foo", "bar"});
301
		checkSplit<REMOVE>('-', "foo-bar-qux", {"foo", "bar", "qux"});
302
		checkSplit<REMOVE>('-', "-bar-qux", {"bar", "qux"});
303
		checkSplit<REMOVE>('-', "foo-bar-", {"foo", "bar"});
304
		checkSplit<REMOVE>('-', "foo--bar", {"foo", "bar"});
305
		checkSplit<REMOVE>('-', "--foo--bar--", {"foo", "bar"});
306
		checkSplit<REMOVE>(" \t", "foo\t\t  bar", {"foo", "bar"});
307
	}
308
	SECTION("parseRange") {
309
		checkParseRange("", {});
310
		checkParseRange("5", {5});
311
		checkParseRange("5,8", {5,8});
312
		checkParseRange("5,5", {5});
313
		checkParseRange("5-7", {5,6,7});
314
		checkParseRange("7-5", {5,6,7});
315
		checkParseRange("5-7,19", {5,6,7,19});
316
		checkParseRange("15,5-7", {5,6,7,15});
317
		checkParseRange("6,5-7", {5,6,7});
318
		checkParseRange("5-8,10-12", {5,6,7,8,10,11,12});
319
		checkParseRange("5-9,6-10", {5,6,7,8,9,10});
320
321
		CHECK_THROWS (parseRange( "4", 5, 10));
322
		CHECK_NOTHROW(parseRange( "5", 5, 10));
323
		CHECK_NOTHROW(parseRange("10", 5, 10));
324
		CHECK_THROWS (parseRange("11", 5, 10));
325
	}
326
	SECTION("caseless") {
327
		caseless op;
328
		CHECK( op("abc", "xyz"));
329
		CHECK(!op("xyz", "abc"));
330
		CHECK(!op("abc", "abc"));
331
		CHECK( op("ABC", "xyz"));
332
		CHECK(!op("xyz", "ABC"));
333
		CHECK(!op("ABC", "abc"));
334
		CHECK( op("aBC", "Xyz"));
335
		CHECK(!op("xYz", "AbC"));
336
		CHECK(!op("ABc", "abC"));
337
338
		CHECK( op("abc", "ABCdef"));
339
		CHECK(!op("AbcDef", "AbC"));
340
	}
341
	SECTION("casecmp") {
342
		casecmp op;
343
		CHECK( op("abc", "abc"));
344
		CHECK( op("abc", "ABC"));
345
		CHECK(!op("abc", "xyz"));
346
		CHECK(!op("ab", "abc"));
347
		CHECK(!op("ab", "ABC"));
348
		CHECK(!op("abc", "ab"));
349
		CHECK(!op("abc", "AB"));
350
	}
351
	SECTION("containsCaseInsensitive") {
352
		CHECK( StringOp::containsCaseInsensitive("abc def", "abc"));
353
		CHECK( StringOp::containsCaseInsensitive("abc def", "def"));
354
		CHECK(!StringOp::containsCaseInsensitive("abc def", "xyz"));
355
		CHECK( StringOp::containsCaseInsensitive("ABC DEF", "abc"));
356
		CHECK( StringOp::containsCaseInsensitive("ABC DEF", "def"));
357
		CHECK(!StringOp::containsCaseInsensitive("ABC DEF", "xyz"));
358
		CHECK( StringOp::containsCaseInsensitive("abc def", "ABC"));
359
		CHECK( StringOp::containsCaseInsensitive("abc def", "DEF"));
360
		CHECK(!StringOp::containsCaseInsensitive("abc def", "XYZ"));
361
		CHECK( StringOp::containsCaseInsensitive("ABC DEF", "ABC"));
362
		CHECK( StringOp::containsCaseInsensitive("ABC DEF", "DEF"));
363
		CHECK(!StringOp::containsCaseInsensitive("ABC DEF", "XYZ"));
364
	}
365
}