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(utf8_to_wide("abc"), utf8_to_wide("abc")));
251 UASSERT(str_equal(utf8_to_wide("ABC"), utf8_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(utf8_to_wide("¤") == L"¤");
307 UASSERT(wide_to_utf8(L"¤") == "¤");
309 UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("")), "");
310 UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!")),
311 "the shovel dug a crumbly node!");
312 UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("-ä-")),
314 UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("-\xF0\xA0\x80\x8B-")),
315 "-\xF0\xA0\x80\x8B-");
319 void TestUtilities::testRemoveEscapes()
321 UASSERT(unescape_enriched<wchar_t>(
322 L"abc\x1bXdef") == L"abcdef");
323 UASSERT(unescape_enriched<wchar_t>(
324 L"abc\x1b(escaped)def") == L"abcdef");
325 UASSERT(unescape_enriched<wchar_t>(
326 L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
327 UASSERT(unescape_enriched<wchar_t>(
328 L"abc\x1b(incomplete") == L"abc");
329 UASSERT(unescape_enriched<wchar_t>(
330 L"escape at the end\x1b") == L"escape at the end");
331 // Nested escapes not supported
332 UASSERT(unescape_enriched<wchar_t>(
333 L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
336 void TestUtilities::testWrapRows()
338 UASSERT(wrap_rows("12345678",4) == "1234\n5678");
339 // test that wrap_rows doesn't wrap inside multibyte sequences
341 const unsigned char s[] = {
342 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
343 0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
344 0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
345 0x6e, 0x2f, 0x2e, 0x2e, 0};
346 std::string str((char *)s);
347 UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
350 const unsigned char s[] = {
351 0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
352 0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
353 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
354 std::string str((char *)s);
355 UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
359 void TestUtilities::testEnrichedString()
361 EnrichedString str(L"Test bar");
362 irr::video::SColor color(0xFF, 0, 0, 0xFF);
364 UASSERT(str.substr(1, 3).getString() == L"est");
366 UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ");
367 str.setDefaultColor(color); // Blue foreground
368 UASSERT(str.getColors()[5] == color);
369 // Green background, then white and yellow text
370 str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow";
371 UASSERT(str.getColors()[2] == 0xFFFFFFFF);
372 str.setDefaultColor(color); // Blue foreground
373 UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text
374 UASSERT(str.getBackground() == 0xFF00FF00); // Green background
377 void TestUtilities::testIsNumber()
379 UASSERT(is_number("123") == true);
380 UASSERT(is_number("") == false);
381 UASSERT(is_number("123a") == false);
385 void TestUtilities::testIsPowerOfTwo()
387 UASSERT(is_power_of_two(0) == false);
388 UASSERT(is_power_of_two(1) == true);
389 UASSERT(is_power_of_two(2) == true);
390 UASSERT(is_power_of_two(3) == false);
391 for (int exponent = 2; exponent <= 31; ++exponent) {
392 UASSERT(is_power_of_two((1 << exponent) - 1) == false);
393 UASSERT(is_power_of_two((1 << exponent)) == true);
394 UASSERT(is_power_of_two((1 << exponent) + 1) == false);
396 UASSERT(is_power_of_two(U32_MAX) == false);
399 void TestUtilities::testMyround()
401 UASSERT(myround(4.6f) == 5);
402 UASSERT(myround(1.2f) == 1);
403 UASSERT(myround(-3.1f) == -3);
404 UASSERT(myround(-6.5f) == -7);
407 void TestUtilities::testStringJoin()
409 std::vector<std::string> input;
410 UASSERT(str_join(input, ",") == "");
412 input.emplace_back("one");
413 UASSERT(str_join(input, ",") == "one");
415 input.emplace_back("two");
416 UASSERT(str_join(input, ",") == "one,two");
418 input.emplace_back("three");
419 UASSERT(str_join(input, ",") == "one,two,three");
422 UASSERT(str_join(input, ",") == "one,,three");
425 UASSERT(str_join(input, " and ") == "one and two and three");
429 static bool within(const f32 value1, const f32 value2, const f32 precision)
431 return std::fabs(value1 - value2) <= precision;
434 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
436 return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
437 && within(v1.Z, v2.Z, precision);
440 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
443 const f32 *M1 = m1.pointer();
444 const f32 *M2 = m2.pointer();
445 for (int i = 0; i < 16; i++)
446 if (! within(M1[i], M2[i], precision))
451 static bool roundTripsDeg(const v3f &v, const f32 precision)
454 setPitchYawRoll(m, v);
455 return within(v, getPitchYawRoll(m), precision);
458 void TestUtilities::testEulerConversion()
460 // This test may fail on non-IEEE systems.
461 // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
462 // (ulp = unit in the last place; ulp(1.0) = 2^-23).
463 const f32 tolL = 4.76837158203125e-7f;
464 // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
465 // ulp(180.0) = 2^-16
466 const f32 tolH = 3.0517578125e-5f;
468 core::matrix4 m1, m2;
469 const f32 *M1 = m1.pointer();
470 const f32 *M2 = m2.pointer();
472 // Check that the radians version and the degrees version
473 // produce the same results. Check also that the conversion
474 // works both ways for these values.
475 v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
476 v2 = v3f(60.0f, 36.0f, 45.0f);
477 setPitchYawRollRad(m1, v1);
478 setPitchYawRoll(m2, v2);
479 UASSERT(within(m1, m2, tolL));
480 UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
481 UASSERT(within(getPitchYawRoll(m2), v2, tolH));
483 // Check the rotation matrix produced.
484 UASSERT(within(M1[0], 0.932004869f, tolL));
485 UASSERT(within(M1[1], 0.353553385f, tolL));
486 UASSERT(within(M1[2], 0.0797927827f, tolL));
487 UASSERT(within(M1[4], -0.21211791f, tolL));
488 UASSERT(within(M1[5], 0.353553355f, tolL));
489 UASSERT(within(M1[6], 0.911046684f, tolL));
490 UASSERT(within(M1[8], 0.293892622f, tolL));
491 UASSERT(within(M1[9], -0.866025448f, tolL));
492 UASSERT(within(M1[10], 0.404508471f, tolL));
494 // Check that the matrix is still homogeneous with no translation
495 UASSERT(M1[3] == 0.0f);
496 UASSERT(M1[7] == 0.0f);
497 UASSERT(M1[11] == 0.0f);
498 UASSERT(M1[12] == 0.0f);
499 UASSERT(M1[13] == 0.0f);
500 UASSERT(M1[14] == 0.0f);
501 UASSERT(M1[15] == 1.0f);
502 UASSERT(M2[3] == 0.0f);
503 UASSERT(M2[7] == 0.0f);
504 UASSERT(M2[11] == 0.0f);
505 UASSERT(M2[12] == 0.0f);
506 UASSERT(M2[13] == 0.0f);
507 UASSERT(M2[14] == 0.0f);
508 UASSERT(M2[15] == 1.0f);
510 // Compare to Irrlicht's results. To be comparable, the
511 // angles must come in a different order and the matrix
512 // elements to compare are different too.
513 m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
514 UASSERT(within(M1[0], M2[5], tolL));
515 UASSERT(within(M1[1], M2[6], tolL));
516 UASSERT(within(M1[2], M2[4], tolL));
518 UASSERT(within(M1[4], M2[9], tolL));
519 UASSERT(within(M1[5], M2[10], tolL));
520 UASSERT(within(M1[6], M2[8], tolL));
522 UASSERT(within(M1[8], M2[1], tolL));
523 UASSERT(within(M1[9], M2[2], tolL));
524 UASSERT(within(M1[10], M2[0], tolL));
526 // Check that Eulers that produce near gimbal-lock still round-trip
527 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
528 UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
529 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
531 // Check that Eulers at an angle > 90 degrees may not round-trip...
532 v1 = v3f(90.00001f, 1.f, 1.f);
533 setPitchYawRoll(m1, v1);
534 v2 = getPitchYawRoll(m1);
535 //UASSERT(within(v1, v2, tolL)); // this is typically false
536 // ... however the rotation matrix is the same for both
537 setPitchYawRoll(m2, v2);
538 UASSERT(within(m1, m2, tolL));