]> git.lizzy.rs Git - dragonfireclient.git/blob - src/unittest/test_utilities.cpp
Merge pull request #35 from arydevy/patch-1
[dragonfireclient.git] / src / unittest / test_utilities.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "test.h"
21
22 #include <cmath>
23 #include "util/enriched_string.h"
24 #include "util/numeric.h"
25 #include "util/string.h"
26
27 class TestUtilities : public TestBase {
28 public:
29         TestUtilities() { TestManager::registerTestModule(this); }
30         const char *getName() { return "TestUtilities"; }
31
32         void runTests(IGameDef *gamedef);
33
34         void testAngleWrapAround();
35         void testWrapDegrees_0_360_v3f();
36         void testLowercase();
37         void testTrim();
38         void testIsYes();
39         void testRemoveStringEnd();
40         void testUrlEncode();
41         void testUrlDecode();
42         void testPadString();
43         void testStartsWith();
44         void testStrEqual();
45         void testStringTrim();
46         void testStrToIntConversion();
47         void testStringReplace();
48         void testStringAllowed();
49         void testAsciiPrintableHelper();
50         void testUTF8();
51         void testRemoveEscapes();
52         void testWrapRows();
53         void testEnrichedString();
54         void testIsNumber();
55         void testIsPowerOfTwo();
56         void testMyround();
57         void testStringJoin();
58         void testEulerConversion();
59 };
60
61 static TestUtilities g_test_instance;
62
63 void TestUtilities::runTests(IGameDef *gamedef)
64 {
65         TEST(testAngleWrapAround);
66         TEST(testWrapDegrees_0_360_v3f);
67         TEST(testLowercase);
68         TEST(testTrim);
69         TEST(testIsYes);
70         TEST(testRemoveStringEnd);
71         TEST(testUrlEncode);
72         TEST(testUrlDecode);
73         TEST(testPadString);
74         TEST(testStartsWith);
75         TEST(testStrEqual);
76         TEST(testStringTrim);
77         TEST(testStrToIntConversion);
78         TEST(testStringReplace);
79         TEST(testStringAllowed);
80         TEST(testAsciiPrintableHelper);
81         TEST(testUTF8);
82         TEST(testRemoveEscapes);
83         TEST(testWrapRows);
84         TEST(testEnrichedString);
85         TEST(testIsNumber);
86         TEST(testIsPowerOfTwo);
87         TEST(testMyround);
88         TEST(testStringJoin);
89         TEST(testEulerConversion);
90 }
91
92 ////////////////////////////////////////////////////////////////////////////////
93
94 inline float ref_WrapDegrees180(float f)
95 {
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);
99         if (value < 0)
100                 value += 360;
101         return value - 180;
102 }
103
104
105 inline float ref_WrapDegrees_0_360(float f)
106 {
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);
110         if (value < 0)
111                 value += 360;
112         return value < 0 ? value + 360 : value;
113 }
114
115
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);
121
122     for (float f = -720; f <= -360; f += 0.25) {
123         UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
124     }
125
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);
132     }
133
134 }
135
136 void TestUtilities::testWrapDegrees_0_360_v3f()
137 {
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)
142         UASSERT(r.Y == 0.0f)
143         UASSERT(r.Z == 0.0f)
144     }
145
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));
149         UASSERT(r.X == 0.0f)
150         UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
151         UASSERT(r.Z == 0.0f)
152     }
153
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));
157         UASSERT(r.X == 0.0f)
158         UASSERT(r.Y == 0.0f)
159         UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
160         }
161
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)
170             }
171         }
172     }
173 }
174
175
176 void TestUtilities::testLowercase()
177 {
178         UASSERT(lowercase("Foo bAR") == "foo bar");
179         UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
180         UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
181 }
182
183
184 void TestUtilities::testTrim()
185 {
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  ") == "");
190 }
191
192
193 void TestUtilities::testIsYes()
194 {
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);
202 }
203
204
205 void TestUtilities::testRemoveStringEnd()
206 {
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) == "");
212 }
213
214
215 void TestUtilities::testUrlEncode()
216 {
217         UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
218                         == "%22Aardvarks%20lurk%2C%20OK%3F%22");
219 }
220
221
222 void TestUtilities::testUrlDecode()
223 {
224         UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
225                         == "\"Aardvarks lurk, OK?\"");
226 }
227
228
229 void TestUtilities::testPadString()
230 {
231         UASSERT(padStringRight("hello", 8) == "hello   ");
232 }
233
234 void TestUtilities::testStartsWith()
235 {
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);
246 }
247
248 void TestUtilities::testStrEqual()
249 {
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));
252 }
253
254
255 void TestUtilities::testStringTrim()
256 {
257         UASSERT(trim("  a") == "a");
258         UASSERT(trim("   a  ") == "a");
259         UASSERT(trim("a   ") == "a");
260         UASSERT(trim("") == "");
261 }
262
263
264 void TestUtilities::testStrToIntConversion()
265 {
266         UASSERT(mystoi("123", 0, 1000) == 123);
267         UASSERT(mystoi("123", 0, 10) == 10);
268 }
269
270
271 void TestUtilities::testStringReplace()
272 {
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");
280 }
281
282
283 void TestUtilities::testStringAllowed()
284 {
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);
289 }
290
291 void TestUtilities::testAsciiPrintableHelper()
292 {
293         UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
294         UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
295
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.
299         int ch = 331;
300         UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
301 }
302
303 void TestUtilities::testUTF8()
304 {
305         UASSERT(utf8_to_wide("¤") == L"¤");
306
307         UASSERT(wide_to_utf8(L"¤") == "¤");
308
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("-ä-")),
313                 "-ä-");
314         UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("-\xF0\xA0\x80\x8B-")),
315                 "-\xF0\xA0\x80\x8B-");
316
317 }
318
319 void TestUtilities::testRemoveEscapes()
320 {
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");
334 }
335
336 void TestUtilities::testWrapRows()
337 {
338         UASSERT(wrap_rows("12345678",4) == "1234\n5678");
339         // test that wrap_rows doesn't wrap inside multibyte sequences
340         {
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>");
348         };
349         {
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>");
356         }
357 }
358
359 void TestUtilities::testEnrichedString()
360 {
361         EnrichedString str(L"Test bar");
362         irr::video::SColor color(0xFF, 0, 0, 0xFF);
363
364         UASSERT(str.substr(1, 3).getString() == L"est");
365         str += L" BUZZ";
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
375 }
376
377 void TestUtilities::testIsNumber()
378 {
379         UASSERT(is_number("123") == true);
380         UASSERT(is_number("") == false);
381         UASSERT(is_number("123a") == false);
382 }
383
384
385 void TestUtilities::testIsPowerOfTwo()
386 {
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);
395         }
396         UASSERT(is_power_of_two(U32_MAX) == false);
397 }
398
399 void TestUtilities::testMyround()
400 {
401         UASSERT(myround(4.6f) == 5);
402         UASSERT(myround(1.2f) == 1);
403         UASSERT(myround(-3.1f) == -3);
404         UASSERT(myround(-6.5f) == -7);
405 }
406
407 void TestUtilities::testStringJoin()
408 {
409         std::vector<std::string> input;
410         UASSERT(str_join(input, ",") == "");
411
412         input.emplace_back("one");
413         UASSERT(str_join(input, ",") == "one");
414
415         input.emplace_back("two");
416         UASSERT(str_join(input, ",") == "one,two");
417
418         input.emplace_back("three");
419         UASSERT(str_join(input, ",") == "one,two,three");
420
421         input[1] = "";
422         UASSERT(str_join(input, ",") == "one,,three");
423
424         input[1] = "two";
425         UASSERT(str_join(input, " and ") == "one and two and three");
426 }
427
428
429 static bool within(const f32 value1, const f32 value2, const f32 precision)
430 {
431         return std::fabs(value1 - value2) <= precision;
432 }
433
434 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
435 {
436         return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
437                 && within(v1.Z, v2.Z, precision);
438 }
439
440 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
441                 const f32 precision)
442 {
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))
447                         return false;
448         return true;
449 }
450
451 static bool roundTripsDeg(const v3f &v, const f32 precision)
452 {
453         core::matrix4 m;
454         setPitchYawRoll(m, v);
455         return within(v, getPitchYawRoll(m), precision);
456 }
457
458 void TestUtilities::testEulerConversion()
459 {
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;
467         v3f v1, v2;
468         core::matrix4 m1, m2;
469         const f32 *M1 = m1.pointer();
470         const f32 *M2 = m2.pointer();
471
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));
482
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));
493
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);
509
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));
517
518         UASSERT(within(M1[4], M2[9], tolL));
519         UASSERT(within(M1[5], M2[10], tolL));
520         UASSERT(within(M1[6], M2[8], tolL));
521
522         UASSERT(within(M1[8], M2[1], tolL));
523         UASSERT(within(M1[9], M2[2], tolL));
524         UASSERT(within(M1[10], M2[0], tolL));
525
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));
530
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));
539 }