openMSX
StringOp_test.cc
Go to the documentation of this file.
1#include "catch.hpp"
2#include "StringOp.hh"
3#include "narrow.hh"
4#include <type_traits>
5
6using namespace StringOp;
7using std::string;
8using std::string_view;
9
10static void checkTrimRight(const string& s, char c, const string& expected)
11{
12 string test = s;
13 trimRight(test, c);
14 CHECK(test == expected);
15
16 string_view ref = s;
17 trimRight(ref, c);
18 CHECK(ref == expected);
19}
20static void checkTrimRight(const string& s, const char* chars, const string& expected)
21{
22 string test = s;
23 trimRight(test, chars);
24 CHECK(test == expected);
25
26 string_view ref = s;
27 trimRight(ref, chars);
28 CHECK(ref == expected);
29}
30
31static void checkTrimLeft(const string& s, char c, const string& expected)
32{
33 string test = s;
34 trimLeft(test, c);
35 CHECK(test == expected);
36
37 string_view ref = s;
38 trimLeft(ref, c);
39 CHECK(ref == expected);
40}
41static void checkTrimLeft(const string& s, const char* chars, const string& expected)
42{
43 string test = s;
44 trimLeft(test, chars);
45 CHECK(test == expected);
46
47 string_view ref = s;
48 trimLeft(ref, chars);
49 CHECK(ref == expected);
50}
51
52static void checkSplitOnFirst(const string& s, const string& first, const 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
66static void checkSplitOnLast(const string& s, const string& first, const 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
80template<StringOp::EmptyParts keepOrRemove, typename Separators>
81static void checkSplit(Separators separators, const string& s, const std::vector<string_view>& expected)
82{
83 //CHECK(split(s, '-') == expected);
84
85 std::vector<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
92static void checkParseRange(const 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
101TEST_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(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}
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
TEST_CASE("StringOp")
CHECK(m3==m3)
IterableBitSet< 64 > parseRange(string_view str, unsigned min, unsigned max)
Definition StringOp.cc:181
bool stringToBool(string_view str)
Definition StringOp.cc:16
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
Definition StringOp.cc:112
void trimRight(string &str, const char *chars)
Definition StringOp.cc:33
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
Definition StringOp.cc:95
void trimLeft(string &str, const char *chars)
Definition StringOp.cc:62
bool containsCaseInsensitive(std::string_view haystack, std::string_view needle)
Definition StringOp.hh:181
auto split_view(std::string_view str, Separators separators)
Definition StringOp.hh:83