Translated using Weblate (Russian)
[oweals/minetest.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(narrow_to_wide("abc"), narrow_to_wide("abc")));
251         UASSERT(str_equal(narrow_to_wide("ABC"), narrow_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(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!");
308 }
309
310 void TestUtilities::testRemoveEscapes()
311 {
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");
325 }
326
327 void TestUtilities::testWrapRows()
328 {
329         UASSERT(wrap_rows("12345678",4) == "1234\n5678");
330         // test that wrap_rows doesn't wrap inside multibyte sequences
331         {
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>");
339         };
340         {
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>");
347         }
348 }
349
350 void TestUtilities::testEnrichedString()
351 {
352         EnrichedString str(L"Test bar");
353         irr::video::SColor color(0xFF, 0, 0, 0xFF);
354
355         UASSERT(str.substr(1, 3).getString() == L"est");
356         str += L" BUZZ";
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
366 }
367
368 void TestUtilities::testIsNumber()
369 {
370         UASSERT(is_number("123") == true);
371         UASSERT(is_number("") == false);
372         UASSERT(is_number("123a") == false);
373 }
374
375
376 void TestUtilities::testIsPowerOfTwo()
377 {
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);
386         }
387         UASSERT(is_power_of_two(U32_MAX) == false);
388 }
389
390 void TestUtilities::testMyround()
391 {
392         UASSERT(myround(4.6f) == 5);
393         UASSERT(myround(1.2f) == 1);
394         UASSERT(myround(-3.1f) == -3);
395         UASSERT(myround(-6.5f) == -7);
396 }
397
398 void TestUtilities::testStringJoin()
399 {
400         std::vector<std::string> input;
401         UASSERT(str_join(input, ",") == "");
402
403         input.emplace_back("one");
404         UASSERT(str_join(input, ",") == "one");
405
406         input.emplace_back("two");
407         UASSERT(str_join(input, ",") == "one,two");
408
409         input.emplace_back("three");
410         UASSERT(str_join(input, ",") == "one,two,three");
411
412         input[1] = "";
413         UASSERT(str_join(input, ",") == "one,,three");
414
415         input[1] = "two";
416         UASSERT(str_join(input, " and ") == "one and two and three");
417 }
418
419
420 static bool within(const f32 value1, const f32 value2, const f32 precision)
421 {
422         return std::fabs(value1 - value2) <= precision;
423 }
424
425 static bool within(const v3f &v1, const v3f &v2, const f32 precision)
426 {
427         return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
428                 && within(v1.Z, v2.Z, precision);
429 }
430
431 static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
432                 const f32 precision)
433 {
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))
438                         return false;
439         return true;
440 }
441
442 static bool roundTripsDeg(const v3f &v, const f32 precision)
443 {
444         core::matrix4 m;
445         setPitchYawRoll(m, v);
446         return within(v, getPitchYawRoll(m), precision);
447 }
448
449 void TestUtilities::testEulerConversion()
450 {
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;
458         v3f v1, v2;
459         core::matrix4 m1, m2;
460         const f32 *M1 = m1.pointer();
461         const f32 *M2 = m2.pointer();
462
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));
473
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));
484
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);
500
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));
508
509         UASSERT(within(M1[4], M2[9], tolL));
510         UASSERT(within(M1[5], M2[10], tolL));
511         UASSERT(within(M1[6], M2[8], tolL));
512
513         UASSERT(within(M1[8], M2[1], tolL));
514         UASSERT(within(M1[9], M2[2], tolL));
515         UASSERT(within(M1[10], M2[0], tolL));
516
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));
521
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));
530 }