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/numeric.h"
24 #include "util/string.h"
26 class TestUtilities : public TestBase {
28 TestUtilities() { TestManager::registerTestModule(this); }
29 const char *getName() { return "TestUtilities"; }
31 void runTests(IGameDef *gamedef);
33 void testAngleWrapAround();
34 void testWrapDegrees_0_360_v3f();
38 void testRemoveStringEnd();
42 void testStartsWith();
44 void testStringTrim();
45 void testStrToIntConversion();
46 void testStringReplace();
47 void testStringAllowed();
48 void testAsciiPrintableHelper();
50 void testRemoveEscapes();
53 void testIsPowerOfTwo();
55 void testStringJoin();
56 void testEulerConversion();
59 static TestUtilities g_test_instance;
61 void TestUtilities::runTests(IGameDef *gamedef)
63 TEST(testAngleWrapAround);
64 TEST(testWrapDegrees_0_360_v3f);
68 TEST(testRemoveStringEnd);
75 TEST(testStrToIntConversion);
76 TEST(testStringReplace);
77 TEST(testStringAllowed);
78 TEST(testAsciiPrintableHelper);
80 TEST(testRemoveEscapes);
83 TEST(testIsPowerOfTwo);
86 TEST(testEulerConversion);
89 ////////////////////////////////////////////////////////////////////////////////
91 inline float ref_WrapDegrees180(float f)
93 // This is a slower alternative to the wrapDegrees_180() function;
94 // used as a reference for testing
95 float value = fmodf(f + 180, 360);
102 inline float ref_WrapDegrees_0_360(float f)
104 // This is a slower alternative to the wrapDegrees_0_360() function;
105 // used as a reference for testing
106 float value = fmodf(f, 360);
109 return value < 0 ? value + 360 : value;
113 void TestUtilities::testAngleWrapAround() {
114 UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
115 UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
116 UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
117 UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
119 for (float f = -720; f <= -360; f += 0.25) {
120 UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
123 for (float f = -1440; f <= 1440; f += 0.25) {
124 UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
125 UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
126 UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
127 UASSERT(wrapDegrees_0_360(
128 std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
133 void TestUtilities::testWrapDegrees_0_360_v3f()
135 // only x test with little step
136 for (float x = -720.f; x <= 720; x += 0.05) {
137 v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
138 UASSERT(r.X >= 0.0f && r.X < 360.0f)
143 // only y test with little step
144 for (float y = -720.f; y <= 720; y += 0.05) {
145 v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
147 UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
151 // only z test with little step
152 for (float z = -720.f; z <= 720; z += 0.05) {
153 v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
156 UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
159 // test the whole coordinate translation
160 for (float x = -720.f; x <= 720; x += 2.5) {
161 for (float y = -720.f; y <= 720; y += 2.5) {
162 for (float z = -720.f; z <= 720; z += 2.5) {
163 v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
164 UASSERT(r.X >= 0.0f && r.X < 360.0f)
165 UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
166 UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
173 void TestUtilities::testLowercase()
175 UASSERT(lowercase("Foo bAR") == "foo bar");
176 UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
177 UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
181 void TestUtilities::testTrim()
183 UASSERT(trim("") == "");
184 UASSERT(trim("dirt_with_grass") == "dirt_with_grass");
185 UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR");
186 UASSERT(trim("\n \t\r \r\n\t\t ") == "");
190 void TestUtilities::testIsYes()
192 UASSERT(is_yes("YeS") == true);
193 UASSERT(is_yes("") == false);
194 UASSERT(is_yes("FAlse") == false);
195 UASSERT(is_yes("-1") == true);
196 UASSERT(is_yes("0") == false);
197 UASSERT(is_yes("1") == true);
198 UASSERT(is_yes("2") == true);
202 void TestUtilities::testRemoveStringEnd()
204 const char *ends[] = {"abc", "c", "bc", "", NULL};
205 UASSERT(removeStringEnd("abc", ends) == "");
206 UASSERT(removeStringEnd("bc", ends) == "b");
207 UASSERT(removeStringEnd("12c", ends) == "12");
208 UASSERT(removeStringEnd("foo", ends) == "");
212 void TestUtilities::testUrlEncode()
214 UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
215 == "%22Aardvarks%20lurk%2C%20OK%3F%22");
219 void TestUtilities::testUrlDecode()
221 UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
222 == "\"Aardvarks lurk, OK?\"");
226 void TestUtilities::testPadString()
228 UASSERT(padStringRight("hello", 8) == "hello ");
231 void TestUtilities::testStartsWith()
233 UASSERT(str_starts_with(std::string(), std::string()) == true);
234 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
235 std::string()) == true);
236 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
237 std::string("the")) == true);
238 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
239 std::string("The")) == false);
240 UASSERT(str_starts_with(std::string("the sharp pickaxe"),
241 std::string("The"), true) == true);
242 UASSERT(str_starts_with(std::string("T"), std::string("The")) == false);
245 void TestUtilities::testStrEqual()
247 UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc")));
248 UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true));
252 void TestUtilities::testStringTrim()
254 UASSERT(trim(" a") == "a");
255 UASSERT(trim(" a ") == "a");
256 UASSERT(trim("a ") == "a");
257 UASSERT(trim("") == "");
261 void TestUtilities::testStrToIntConversion()
263 UASSERT(mystoi("123", 0, 1000) == 123);
264 UASSERT(mystoi("123", 0, 10) == 10);
268 void TestUtilities::testStringReplace()
270 std::string test_str;
271 test_str = "Hello there";
272 str_replace(test_str, "there", "world");
273 UASSERT(test_str == "Hello world");
274 test_str = "ThisAisAaAtest";
275 str_replace(test_str, 'A', ' ');
276 UASSERT(test_str == "This is a test");
280 void TestUtilities::testStringAllowed()
282 UASSERT(string_allowed("hello", "abcdefghijklmno") == true);
283 UASSERT(string_allowed("123", "abcdefghijklmno") == false);
284 UASSERT(string_allowed_blacklist("hello", "123") == true);
285 UASSERT(string_allowed_blacklist("hello123", "123") == false);
288 void TestUtilities::testAsciiPrintableHelper()
290 UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
291 UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
293 // Ensures that there is no cutting off going on...
294 // If there were, 331 would be cut to 75 in this example
295 // and 73 is a valid ASCII char.
297 UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
300 void TestUtilities::testUTF8()
302 UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
303 UASSERT(wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!"))
304 == "the shovel dug a crumbly node!");
307 void TestUtilities::testRemoveEscapes()
309 UASSERT(unescape_enriched<wchar_t>(
310 L"abc\x1bXdef") == L"abcdef");
311 UASSERT(unescape_enriched<wchar_t>(
312 L"abc\x1b(escaped)def") == L"abcdef");
313 UASSERT(unescape_enriched<wchar_t>(
314 L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
315 UASSERT(unescape_enriched<wchar_t>(
316 L"abc\x1b(incomplete") == L"abc");
317 UASSERT(unescape_enriched<wchar_t>(
318 L"escape at the end\x1b") == L"escape at the end");
319 // Nested escapes not supported
320 UASSERT(unescape_enriched<wchar_t>(
321 L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
324 void TestUtilities::testWrapRows()
326 UASSERT(wrap_rows("12345678",4) == "1234\n5678");
327 // test that wrap_rows doesn't wrap inside multibyte sequences
329 const unsigned char s[] = {
330 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
331 0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
332 0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
333 0x6e, 0x2f, 0x2e, 0x2e, 0};
334 std::string str((char *)s);
335 UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
338 const unsigned char s[] = {
339 0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
340 0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
341 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
342 std::string str((char *)s);
343 UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
348 void TestUtilities::testIsNumber()
350 UASSERT(is_number("123") == true);
351 UASSERT(is_number("") == false);
352 UASSERT(is_number("123a") == false);
356 void TestUtilities::testIsPowerOfTwo()
358 UASSERT(is_power_of_two(0) == false);
359 UASSERT(is_power_of_two(1) == true);
360 UASSERT(is_power_of_two(2) == true);
361 UASSERT(is_power_of_two(3) == false);
362 for (int exponent = 2; exponent <= 31; ++exponent) {
363 UASSERT(is_power_of_two((1 << exponent) - 1) == false);
364 UASSERT(is_power_of_two((1 << exponent)) == true);
365 UASSERT(is_power_of_two((1 << exponent) + 1) == false);
367 UASSERT(is_power_of_two(U32_MAX) == false);
370 void TestUtilities::testMyround()
372 UASSERT(myround(4.6f) == 5);
373 UASSERT(myround(1.2f) == 1);
374 UASSERT(myround(-3.1f) == -3);
375 UASSERT(myround(-6.5f) == -7);
378 void TestUtilities::testStringJoin()
380 std::vector<std::string> input;
381 UASSERT(str_join(input, ",") == "");
383 input.emplace_back("one");
384 UASSERT(str_join(input, ",") == "one");
386 input.emplace_back("two");
387 UASSERT(str_join(input, ",") == "one,two");
389 input.emplace_back("three");
390 UASSERT(str_join(input, ",") == "one,two,three");
393 UASSERT(str_join(input, ",") == "one,,three");
396 UASSERT(str_join(input, " and ") == "one and two and three");
400 static bool within(const f32 value1, const f32 value2, const f32 precision)
402 return std::fabs(value1 - value2) <= precision;
405 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
407 return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
408 && within(v1.Z, v2.Z, precision);
411 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
414 const f32 *M1 = m1.pointer();
415 const f32 *M2 = m2.pointer();
416 for (int i = 0; i < 16; i++)
417 if (! within(M1[i], M2[i], precision))
422 static bool roundTripsDeg(const v3f &v, const f32 precision)
425 setPitchYawRoll(m, v);
426 return within(v, getPitchYawRoll(m), precision);
429 void TestUtilities::testEulerConversion()
431 // This test may fail on non-IEEE systems.
432 // Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
433 // (ulp = unit in the last place; ulp(1.0) = 2^-23).
434 const f32 tolL = 4.76837158203125e-7f;
435 // High tolerance is 2 ulp(180.0), needed for numbers in degrees.
436 // ulp(180.0) = 2^-16
437 const f32 tolH = 3.0517578125e-5f;
439 core::matrix4 m1, m2;
440 const f32 *M1 = m1.pointer();
441 const f32 *M2 = m2.pointer();
443 // Check that the radians version and the degrees version
444 // produce the same results. Check also that the conversion
445 // works both ways for these values.
446 v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
447 v2 = v3f(60.0f, 36.0f, 45.0f);
448 setPitchYawRollRad(m1, v1);
449 setPitchYawRoll(m2, v2);
450 UASSERT(within(m1, m2, tolL));
451 UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
452 UASSERT(within(getPitchYawRoll(m2), v2, tolH));
454 // Check the rotation matrix produced.
455 UASSERT(within(M1[0], 0.932004869f, tolL));
456 UASSERT(within(M1[1], 0.353553385f, tolL));
457 UASSERT(within(M1[2], 0.0797927827f, tolL));
458 UASSERT(within(M1[4], -0.21211791f, tolL));
459 UASSERT(within(M1[5], 0.353553355f, tolL));
460 UASSERT(within(M1[6], 0.911046684f, tolL));
461 UASSERT(within(M1[8], 0.293892622f, tolL));
462 UASSERT(within(M1[9], -0.866025448f, tolL));
463 UASSERT(within(M1[10], 0.404508471f, tolL));
465 // Check that the matrix is still homogeneous with no translation
466 UASSERT(M1[3] == 0.0f);
467 UASSERT(M1[7] == 0.0f);
468 UASSERT(M1[11] == 0.0f);
469 UASSERT(M1[12] == 0.0f);
470 UASSERT(M1[13] == 0.0f);
471 UASSERT(M1[14] == 0.0f);
472 UASSERT(M1[15] == 1.0f);
473 UASSERT(M2[3] == 0.0f);
474 UASSERT(M2[7] == 0.0f);
475 UASSERT(M2[11] == 0.0f);
476 UASSERT(M2[12] == 0.0f);
477 UASSERT(M2[13] == 0.0f);
478 UASSERT(M2[14] == 0.0f);
479 UASSERT(M2[15] == 1.0f);
481 // Compare to Irrlicht's results. To be comparable, the
482 // angles must come in a different order and the matrix
483 // elements to compare are different too.
484 m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
485 UASSERT(within(M1[0], M2[5], tolL));
486 UASSERT(within(M1[1], M2[6], tolL));
487 UASSERT(within(M1[2], M2[4], tolL));
489 UASSERT(within(M1[4], M2[9], tolL));
490 UASSERT(within(M1[5], M2[10], tolL));
491 UASSERT(within(M1[6], M2[8], tolL));
493 UASSERT(within(M1[8], M2[1], tolL));
494 UASSERT(within(M1[9], M2[2], tolL));
495 UASSERT(within(M1[10], M2[0], tolL));
497 // Check that Eulers that produce near gimbal-lock still round-trip
498 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
499 UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
500 UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
502 // Check that Eulers at an angle > 90 degrees may not round-trip...
503 v1 = v3f(90.00001f, 1.f, 1.f);
504 setPitchYawRoll(m1, v1);
505 v2 = getPitchYawRoll(m1);
506 //UASSERT(within(v1, v2, tolL)); // this is typically false
507 // ... however the rotation matrix is the same for both
508 setPitchYawRoll(m2, v2);
509 UASSERT(within(m1, m2, tolL));