3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "util/enriched_string.h"
24 #include "util/numeric.h"
25 #include "util/string.h"
27 class TestUtilities : public TestBase {
29 TestUtilities() { TestManager::registerTestModule(this); }
30 const char *getName() { return "TestUtilities"; }
32 void runTests(IGameDef *gamedef);
34 void testAngleWrapAround();
35 void testWrapDegrees_0_360_v3f();
39 void testRemoveStringEnd();
43 void testStartsWith();
45 void testStringTrim();
46 void testStrToIntConversion();
47 void testStringReplace();
48 void testStringAllowed();
49 void testAsciiPrintableHelper();
51 void testRemoveEscapes();
53 void testEnrichedString();
55 void testIsPowerOfTwo();
57 void testStringJoin();
58 void testEulerConversion();
61 static TestUtilities g_test_instance;
63 void TestUtilities::runTests(IGameDef *gamedef)
65 TEST(testAngleWrapAround);
66 TEST(testWrapDegrees_0_360_v3f);
70 TEST(testRemoveStringEnd);
77 TEST(testStrToIntConversion);
78 TEST(testStringReplace);
79 TEST(testStringAllowed);
80 TEST(testAsciiPrintableHelper);
82 TEST(testRemoveEscapes);
84 TEST(testEnrichedString);
86 TEST(testIsPowerOfTwo);
89 TEST(testEulerConversion);
92 ////////////////////////////////////////////////////////////////////////////////
94 inline float ref_WrapDegrees180(float f)
96 // This is a slower alternative to the wrapDegrees_180() function;
97 // used as a reference for testing
98 float value = fmodf(f + 180, 360);
105 inline float ref_WrapDegrees_0_360(float f)
107 // This is a slower alternative to the wrapDegrees_0_360() function;
108 // used as a reference for testing
109 float value = fmodf(f, 360);
112 return value < 0 ? value + 360 : value;
116 void TestUtilities::testAngleWrapAround() {
117 UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
118 UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
119 UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
120 UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
122 for (float f = -720; f <= -360; f += 0.25) {
123 UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
126 for (float f = -1440; f <= 1440; f += 0.25) {
127 UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
128 UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
129 UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
130 UASSERT(wrapDegrees_0_360(
131 std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
136 void TestUtilities::testWrapDegrees_0_360_v3f()
138 // only x test with little step
139 for (float x = -720.f; x <= 720; x += 0.05) {
140 v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
141 UASSERT(r.X >= 0.0f && r.X < 360.0f)
146 // only y test with little step
147 for (float y = -720.f; y <= 720; y += 0.05) {
148 v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
150 UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
154 // only z test with little step
155 for (float z = -720.f; z <= 720; z += 0.05) {
156 v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
159 UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
162 // test the whole coordinate translation
163 for (float x = -720.f; x <= 720; x += 2.5) {
164 for (float y = -720.f; y <= 720; y += 2.5) {
165 for (float z = -720.f; z <= 720; z += 2.5) {
166 v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
167 UASSERT(r.X >= 0.0f && r.X < 360.0f)
168 UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
169 UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
176 void TestUtilities::testLowercase()
178 UASSERT(lowercase("Foo bAR") == "foo bar");
179 UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
180 UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
184 void TestUtilities::testTrim()
186 UASSERT(trim("") == "");
187 UASSERT(trim("dirt_with_grass") == "dirt_with_grass");
188 UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR");
189 UASSERT(trim("\n \t\r \r\n\t\t ") == "");
193 void TestUtilities::testIsYes()
195 UASSERT(is_yes("YeS") == true);
196 UASSERT(is_yes("") == false);
197 UASSERT(is_yes("FAlse") == false);
198 UASSERT(is_yes("-1") == true);
199 UASSERT(is_yes("0") == false);
200 UASSERT(is_yes("1") == true);
201 UASSERT(is_yes("2") == true);
205 void TestUtilities::testRemoveStringEnd()
207 const char *ends[] = {"abc", "c", "bc", "", NULL};
208 UASSERT(removeStringEnd("abc", ends) == "");
209 UASSERT(removeStringEnd("bc", ends) == "b");
210 UASSERT(removeStringEnd("12c", ends) == "12");
211 UASSERT(removeStringEnd("foo", ends) == "");
215 void TestUtilities::testUrlEncode()
217 UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
218 == "%22Aardvarks%20lurk%2C%20OK%3F%22");
222 void TestUtilities::testUrlDecode()
224 UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
225 == "\"Aardvarks lurk, OK?\"");
229 void TestUtilities::testPadString()
231 UASSERT(padStringRight("hello", 8) == "hello ");
234 void TestUtilities::testStartsWith()
236 UASSERT(str_starts_with(std::string(), std::string()) == true);
237 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
238 std::string()) == true);
239 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
240 std::string("the")) == true);
241 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
242 std::string("The")) == false);
243 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
244 std::string("The"), true) == true);
245 UASSERT(str_starts_with(std::string("T"), std::string("The")) == false);
248 void TestUtilities::testStrEqual()
250 UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc")));
251 UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true));
255 void TestUtilities::testStringTrim()
257 UASSERT(trim(" a") == "a");
258 UASSERT(trim(" a ") == "a");
259 UASSERT(trim("a ") == "a");
260 UASSERT(trim("") == "");
264 void TestUtilities::testStrToIntConversion()
266 UASSERT(mystoi("123", 0, 1000) == 123);
267 UASSERT(mystoi("123", 0, 10) == 10);
271 void TestUtilities::testStringReplace()
273 std::string test_str;
274 test_str = "Hello there";
275 str_replace(test_str, "there", "world");
276 UASSERT(test_str == "Hello world");
277 test_str = "ThisAisAaAtest";
278 str_replace(test_str, 'A', ' ');
279 UASSERT(test_str == "This is a test");
283 void TestUtilities::testStringAllowed()
285 UASSERT(string_allowed("hello", "abcdefghijklmno") == true);
286 UASSERT(string_allowed("123", "abcdefghijklmno") == false);
287 UASSERT(string_allowed_blacklist("hello", "123") == true);
288 UASSERT(string_allowed_blacklist("hello123", "123") == false);
291 void TestUtilities::testAsciiPrintableHelper()
293 UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
294 UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
296 // Ensures that there is no cutting off going on...
297 // If there were, 331 would be cut to 75 in this example
298 // and 73 is a valid ASCII char.
300 UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
303 void TestUtilities::testUTF8()
305 UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
306 UASSERT(wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!"))
307 == "the shovel dug a crumbly node!");
310 void TestUtilities::testRemoveEscapes()
312 UASSERT(unescape_enriched<wchar_t>(
313 L"abc\x1bXdef") == L"abcdef");
314 UASSERT(unescape_enriched<wchar_t>(
315 L"abc\x1b(escaped)def") == L"abcdef");
316 UASSERT(unescape_enriched<wchar_t>(
317 L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
318 UASSERT(unescape_enriched<wchar_t>(
319 L"abc\x1b(incomplete") == L"abc");
320 UASSERT(unescape_enriched<wchar_t>(
321 L"escape at the end\x1b") == L"escape at the end");
322 // Nested escapes not supported
323 UASSERT(unescape_enriched<wchar_t>(
324 L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
327 void TestUtilities::testWrapRows()
329 UASSERT(wrap_rows("12345678",4) == "1234\n5678");
330 // test that wrap_rows doesn't wrap inside multibyte sequences
332 const unsigned char s[] = {
333 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
334 0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
335 0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
336 0x6e, 0x2f, 0x2e, 0x2e, 0};
337 std::string str((char *)s);
338 UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
341 const unsigned char s[] = {
342 0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
343 0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
344 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
345 std::string str((char *)s);
346 UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
350 void TestUtilities::testEnrichedString()
352 EnrichedString str(L"Test bar");
353 irr::video::SColor color(0xFF, 0, 0, 0xFF);
355 UASSERT(str.substr(1, 3).getString() == L"est");
357 UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ");
358 str.setDefaultColor(color); // Blue foreground
359 UASSERT(str.getColors()[5] == color);
360 // Green background, then white and yellow text
361 str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow";
362 UASSERT(str.getColors()[2] == 0xFFFFFFFF);
363 str.setDefaultColor(color); // Blue foreground
364 UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text
365 UASSERT(str.getBackground() == 0xFF00FF00); // Green background
368 void TestUtilities::testIsNumber()
370 UASSERT(is_number("123") == true);
371 UASSERT(is_number("") == false);
372 UASSERT(is_number("123a") == false);
376 void TestUtilities::testIsPowerOfTwo()
378 UASSERT(is_power_of_two(0) == false);
379 UASSERT(is_power_of_two(1) == true);
380 UASSERT(is_power_of_two(2) == true);
381 UASSERT(is_power_of_two(3) == false);
382 for (int exponent = 2; exponent <= 31; ++exponent) {
383 UASSERT(is_power_of_two((1 << exponent) - 1) == false);
384 UASSERT(is_power_of_two((1 << exponent)) == true);
385 UASSERT(is_power_of_two((1 << exponent) + 1) == false);
387 UASSERT(is_power_of_two(U32_MAX) == false);
390 void TestUtilities::testMyround()
392 UASSERT(myround(4.6f) == 5);
393 UASSERT(myround(1.2f) == 1);
394 UASSERT(myround(-3.1f) == -3);
395 UASSERT(myround(-6.5f) == -7);
398 void TestUtilities::testStringJoin()
400 std::vector<std::string> input;
401 UASSERT(str_join(input, ",") == "");
403 input.emplace_back("one");
404 UASSERT(str_join(input, ",") == "one");
406 input.emplace_back("two");
407 UASSERT(str_join(input, ",") == "one,two");
409 input.emplace_back("three");
410 UASSERT(str_join(input, ",") == "one,two,three");
413 UASSERT(str_join(input, ",") == "one,,three");
416 UASSERT(str_join(input, " and ") == "one and two and three");
420 static bool within(const f32 value1, const f32 value2, const f32 precision)
422 return std::fabs(value1 - value2) <= precision;
425 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
427 return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
428 && within(v1.Z, v2.Z, precision);
431 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
434 const f32 *M1 = m1.pointer();
435 const f32 *M2 = m2.pointer();
436 for (int i = 0; i < 16; i++)
437 if (! within(M1[i], M2[i], precision))
442 static bool roundTripsDeg(const v3f &v, const f32 precision)
445 setPitchYawRoll(m, v);
446 return within(v, getPitchYawRoll(m), precision);
449 void TestUtilities::testEulerConversion()
451 // This test may fail on non-IEEE systems.
452 // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
453 // (ulp = unit in the last place; ulp(1.0) = 2^-23).
454 const f32 tolL = 4.76837158203125e-7f;
455 // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
456 // ulp(180.0) = 2^-16
457 const f32 tolH = 3.0517578125e-5f;
459 core::matrix4 m1, m2;
460 const f32 *M1 = m1.pointer();
461 const f32 *M2 = m2.pointer();
463 // Check that the radians version and the degrees version
464 // produce the same results. Check also that the conversion
465 // works both ways for these values.
466 v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
467 v2 = v3f(60.0f, 36.0f, 45.0f);
468 setPitchYawRollRad(m1, v1);
469 setPitchYawRoll(m2, v2);
470 UASSERT(within(m1, m2, tolL));
471 UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
472 UASSERT(within(getPitchYawRoll(m2), v2, tolH));
474 // Check the rotation matrix produced.
475 UASSERT(within(M1[0], 0.932004869f, tolL));
476 UASSERT(within(M1[1], 0.353553385f, tolL));
477 UASSERT(within(M1[2], 0.0797927827f, tolL));
478 UASSERT(within(M1[4], -0.21211791f, tolL));
479 UASSERT(within(M1[5], 0.353553355f, tolL));
480 UASSERT(within(M1[6], 0.911046684f, tolL));
481 UASSERT(within(M1[8], 0.293892622f, tolL));
482 UASSERT(within(M1[9], -0.866025448f, tolL));
483 UASSERT(within(M1[10], 0.404508471f, tolL));
485 // Check that the matrix is still homogeneous with no translation
486 UASSERT(M1[3] == 0.0f);
487 UASSERT(M1[7] == 0.0f);
488 UASSERT(M1[11] == 0.0f);
489 UASSERT(M1[12] == 0.0f);
490 UASSERT(M1[13] == 0.0f);
491 UASSERT(M1[14] == 0.0f);
492 UASSERT(M1[15] == 1.0f);
493 UASSERT(M2[3] == 0.0f);
494 UASSERT(M2[7] == 0.0f);
495 UASSERT(M2[11] == 0.0f);
496 UASSERT(M2[12] == 0.0f);
497 UASSERT(M2[13] == 0.0f);
498 UASSERT(M2[14] == 0.0f);
499 UASSERT(M2[15] == 1.0f);
501 // Compare to Irrlicht's results. To be comparable, the
502 // angles must come in a different order and the matrix
503 // elements to compare are different too.
504 m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
505 UASSERT(within(M1[0], M2[5], tolL));
506 UASSERT(within(M1[1], M2[6], tolL));
507 UASSERT(within(M1[2], M2[4], tolL));
509 UASSERT(within(M1[4], M2[9], tolL));
510 UASSERT(within(M1[5], M2[10], tolL));
511 UASSERT(within(M1[6], M2[8], tolL));
513 UASSERT(within(M1[8], M2[1], tolL));
514 UASSERT(within(M1[9], M2[2], tolL));
515 UASSERT(within(M1[10], M2[0], tolL));
517 // Check that Eulers that produce near gimbal-lock still round-trip
518 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
519 UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
520 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
522 // Check that Eulers at an angle > 90 degrees may not round-trip...
523 v1 = v3f(90.00001f, 1.f, 1.f);
524 setPitchYawRoll(m1, v1);
525 v2 = getPitchYawRoll(m1);
526 //UASSERT(within(v1, v2, tolL)); // this is typically false
527 // ... however the rotation matrix is the same for both
528 setPitchYawRoll(m2, v2);
529 UASSERT(within(m1, m2, tolL));