aboutsummaryrefslogtreecommitdiff
path: root/src/server.cpp
Commit message (Expand)AuthorAge
* Add player:set_sky() with simple skybox supportPerttu Ahola2014-02-01
* Add propper client initializationsapier2014-01-31
* New HUD element - waypoint.RealBadAngel2014-01-26
* Fix use of previously deallocated EmergeManagerkwolekr2014-01-26
* Pass pointed_thing to on_punch and minetest.register_on_punchnode callbacksShadowNinja2014-01-23
* Allow vertical axis particle rotation constraintkhonkhortisan2014-01-13
* Fixed minetest reliable udp implementation (compatible to old clients)sapier2014-01-10
* Revert "Improve (re)spawn, add cache_block_before_spawn and max_spawn_height ...ShadowNinja2014-01-06
* Send long announce as POST, show OS in useragentproller2014-01-07
* Initialize world before creating BanManager and RollbackManagerShadowNinja2013-12-30
* Replace SimpleThread by JThread now implementing same featuressapier2013-12-15
* Update mapgen params in ServerMap after Mapgen initkwolekr2013-12-14
* Rewrite client media download and support hash-based remote downloadKahrl2013-12-13
* Add 'on_prejoinplayer' callbackkaeza2013-12-12
* Improve (re)spawn, add cache_block_before_spawn and max_spawn_height settingssweetbomber2013-12-05
* Cleanup jthread and fix win32 buildsapier2013-12-01
* Add minetest.swap_nodeNovatux2013-11-30
* Add a callback: minetest.register_on_craft(itemstack, player,Novatux2013-11-01
* Masterserver updateproller2013-10-18
* Show git hash in version string at top left corner of windowKahrl2013-09-28
* Fix some warnings and other minor detailskwolekr2013-09-16
* Always use builtin JThread librarykwolekr2013-09-15
* Use player:set_hotbar_image() instead of hardcoded hotbar.pngPilzAdam2013-09-05
* Server::ProcessData(): call getBanName once instead of twice (#639)Ilya2013-09-03
* Send player damage to all clients and apply [brightenPilzAdam2013-08-17
* Omnicleanup: header cleanup, add ModApiUtil shared between game and mainmenuKahrl2013-08-14
* Don't freak out when a client sends multiple TOSERVER_INIT packets; also log ...Perttu Ahola2013-08-08
* Clean up server's log messages and give a better error to client when its pla...Perttu Ahola2013-08-06
* Add texture pack selection to main menuNovatux2013-08-04
* Fix server getting completely choked up on even a little of DoSPerttu Ahola2013-08-04
* Allow mods to listen to cheat detections using minetest.register_on_cheat()Perttu Ahola2013-08-04
* Fix anticheatPerttu Ahola2013-08-03
* Weather backward compatibilityproller2013-08-02
* Dont announce server in singleplayerproller2013-07-27
* Cosmetic player info changesproller2013-07-24
* Add set_breath and get_breath to lua API.RealBadAngel2013-07-20
* Masterserver mods announse, ipv6, better curl errorsproller2013-07-13
* Disallow the name 'singleplayer' in a multiplayer serverPilzAdam2013-07-12
* Add Lua on_mapgen_init callback, and minetest.set_mapgen_params APIkwolekr2013-06-27
* Math mapgen fix, ip show on connect, pathfinder segfault fixproller2013-06-23
* Add support for IPv6proller2013-06-23
* Tweak IDropAction restriction handling in server.cppKahrl2013-06-16
* Add ObjectRef.hud_set_hotbar_itemcount and add TOCLIENT_HUD_SET_PARAMKahrl2013-05-26
* Move scriptapi to separate folder (by sapier)sapier2013-05-25
* Dont load mods that have no entry in world.mtPilzAdam2013-05-19
* Clear custom player HUDs when emerging players. Fixes #711.Aaron Suen2013-05-10
* Optional dependencies and properly handle mod name conflicts againKahrl2013-05-03
* Server: force block send of pointed_pos_under after predicted node placeKahrl2013-05-03
* Generalize hud_builtin_enable into hud_set_flagskwolekr2013-04-25
* Added support to disable built-in HUD elementsDiego Martínez2013-04-24
5' href='#n445'>445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "test.h"

#include <cmath>
#include "util/enriched_string.h"
#include "util/numeric.h"
#include "util/string.h"
#include "util/base64.h"

class TestUtilities : public TestBase {
public:
	TestUtilities() { TestManager::registerTestModule(this); }
	const char *getName() { return "TestUtilities"; }

	void runTests(IGameDef *gamedef);

	void testAngleWrapAround();
	void testWrapDegrees_0_360_v3f();
	void testLowercase();
	void testTrim();
	void testIsYes();
	void testRemoveStringEnd();
	void testUrlEncode();
	void testUrlDecode();
	void testPadString();
	void testStartsWith();
	void testStrEqual();
	void testStrToIntConversion();
	void testStringReplace();
	void testStringAllowed();
	void testAsciiPrintableHelper();
	void testUTF8();
	void testRemoveEscapes();
	void testWrapRows();
	void testEnrichedString();
	void testIsNumber();
	void testIsPowerOfTwo();
	void testMyround();
	void testStringJoin();
	void testEulerConversion();
	void testBase64();
	void testSanitizeDirName();
};

static TestUtilities g_test_instance;

void TestUtilities::runTests(IGameDef *gamedef)
{
	TEST(testAngleWrapAround);
	TEST(testWrapDegrees_0_360_v3f);
	TEST(testLowercase);
	TEST(testTrim);
	TEST(testIsYes);
	TEST(testRemoveStringEnd);
	TEST(testUrlEncode);
	TEST(testUrlDecode);
	TEST(testPadString);
	TEST(testStartsWith);
	TEST(testStrEqual);
	TEST(testStrToIntConversion);
	TEST(testStringReplace);
	TEST(testStringAllowed);
	TEST(testAsciiPrintableHelper);
	TEST(testUTF8);
	TEST(testRemoveEscapes);
	TEST(testWrapRows);
	TEST(testEnrichedString);
	TEST(testIsNumber);
	TEST(testIsPowerOfTwo);
	TEST(testMyround);
	TEST(testStringJoin);
	TEST(testEulerConversion);
	TEST(testBase64);
	TEST(testSanitizeDirName);
}

////////////////////////////////////////////////////////////////////////////////

inline float ref_WrapDegrees180(float f)
{
	// This is a slower alternative to the wrapDegrees_180() function;
	// used as a reference for testing
	float value = fmodf(f + 180, 360);
	if (value < 0)
		value += 360;
	return value - 180;
}


inline float ref_WrapDegrees_0_360(float f)
{
	// This is a slower alternative to the wrapDegrees_0_360() function;
	// used as a reference for testing
	float value = fmodf(f, 360);
	if (value < 0)
		value += 360;
	return value < 0 ? value + 360 : value;
}


void TestUtilities::testAngleWrapAround() {
    UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
    UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
    UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
    UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);

    for (float f = -720; f <= -360; f += 0.25) {
        UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
    }

    for (float f = -1440; f <= 1440; f += 0.25) {
        UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
        UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
        UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
        UASSERT(wrapDegrees_0_360(
                std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
    }

}

void TestUtilities::testWrapDegrees_0_360_v3f()
{
    // only x test with little step
	for (float x = -720.f; x <= 720; x += 0.05) {
        v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
        UASSERT(r.X >= 0.0f && r.X < 360.0f)
        UASSERT(r.Y == 0.0f)
        UASSERT(r.Z == 0.0f)
    }

    // only y test with little step
    for (float y = -720.f; y <= 720; y += 0.05) {
        v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
        UASSERT(r.X == 0.0f)
        UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
        UASSERT(r.Z == 0.0f)
    }

    // only z test with little step
    for (float z = -720.f; z <= 720; z += 0.05) {
        v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
        UASSERT(r.X == 0.0f)
        UASSERT(r.Y == 0.0f)
        UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
	}

    // test the whole coordinate translation
    for (float x = -720.f; x <= 720; x += 2.5) {
        for (float y = -720.f; y <= 720; y += 2.5) {
            for (float z = -720.f; z <= 720; z += 2.5) {
                v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
                UASSERT(r.X >= 0.0f && r.X < 360.0f)
                UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
                UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
            }
        }
    }
}


void TestUtilities::testLowercase()
{
	UASSERT(lowercase("Foo bAR") == "foo bar");
	UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
	UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
}


void TestUtilities::testTrim()
{
	UASSERT(trim("") == "");
	UASSERT(trim("dirt_with_grass") == "dirt_with_grass");
	UASSERT(trim("\n \t\r  Foo bAR  \r\n\t\t  ") == "Foo bAR");
	UASSERT(trim("\n \t\r    \r\n\t\t  ") == "");
	UASSERT(trim("  a") == "a");
	UASSERT(trim("a   ") == "a");
}


void TestUtilities::testIsYes()
{
	UASSERT(is_yes("YeS") == true);
	UASSERT(is_yes("") == false);
	UASSERT(is_yes("FAlse") == false);
	UASSERT(is_yes("-1") == true);
	UASSERT(is_yes("0") == false);
	UASSERT(is_yes("1") == true);
	UASSERT(is_yes("2") == true);
}


void TestUtilities::testRemoveStringEnd()
{
	const char *ends[] = {"abc", "c", "bc", "", NULL};
	UASSERT(removeStringEnd("abc", ends) == "");
	UASSERT(removeStringEnd("bc", ends) == "b");
	UASSERT(removeStringEnd("12c", ends) == "12");
	UASSERT(removeStringEnd("foo", ends) == "");
}


void TestUtilities::testUrlEncode()
{
	UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
			== "%22Aardvarks%20lurk%2C%20OK%3F%22");
}


void TestUtilities::testUrlDecode()
{
	UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
			== "\"Aardvarks lurk, OK?\"");
}


void TestUtilities::testPadString()
{
	UASSERT(padStringRight("hello", 8) == "hello   ");
}

void TestUtilities::testStartsWith()
{
	UASSERT(str_starts_with(std::string(), std::string()) == true);
	UASSERT(str_starts_with(std::string("the sharp pickaxe"),
		std::string()) == true);
	UASSERT(str_starts_with(std::string("the sharp pickaxe"),
		std::string("the")) == true);
	UASSERT(str_starts_with(std::string("the sharp pickaxe"),
		std::string("The")) == false);
	UASSERT(str_starts_with(std::string("the sharp pickaxe"),
		std::string("The"), true) == true);
	UASSERT(str_starts_with(std::string("T"), std::string("The")) == false);
}

void TestUtilities::testStrEqual()
{
	UASSERT(str_equal(utf8_to_wide("abc"), utf8_to_wide("abc")));
	UASSERT(str_equal(utf8_to_wide("ABC"), utf8_to_wide("abc"), true));
}


void TestUtilities::testStrToIntConversion()
{
	UASSERT(mystoi("123", 0, 1000) == 123);
	UASSERT(mystoi("123", 0, 10) == 10);
}


void TestUtilities::testStringReplace()
{
	std::string test_str;
	test_str = "Hello there";
	str_replace(test_str, "there", "world");
	UASSERT(test_str == "Hello world");
	test_str = "ThisAisAaAtest";
	str_replace(test_str, 'A', ' ');
	UASSERT(test_str == "This is a test");
}


void TestUtilities::testStringAllowed()
{
	UASSERT(string_allowed("hello", "abcdefghijklmno") == true);
	UASSERT(string_allowed("123", "abcdefghijklmno") == false);
	UASSERT(string_allowed_blacklist("hello", "123") == true);
	UASSERT(string_allowed_blacklist("hello123", "123") == false);
}

void TestUtilities::testAsciiPrintableHelper()
{
	UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
	UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);

	// Ensures that there is no cutting off going on...
	// If there were, 331 would be cut to 75 in this example
	// and 73 is a valid ASCII char.
	int ch = 331;
	UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
}

void TestUtilities::testUTF8()
{
	UASSERT(utf8_to_wide("¤") == L"¤");

	UASSERT(wide_to_utf8(L"¤") == "¤");

	UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("")), "");
	UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!")),
		"the shovel dug a crumbly node!");
	UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("-ä-")),
		"-ä-");
	UASSERTEQ(std::string, wide_to_utf8(utf8_to_wide("-\xF0\xA0\x80\x8B-")),
		"-\xF0\xA0\x80\x8B-");

}

void TestUtilities::testRemoveEscapes()
{
	UASSERT(unescape_enriched<wchar_t>(
		L"abc\x1bXdef") == L"abcdef");
	UASSERT(unescape_enriched<wchar_t>(
		L"abc\x1b(escaped)def") == L"abcdef");
	UASSERT(unescape_enriched<wchar_t>(
		L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
	UASSERT(unescape_enriched<wchar_t>(
		L"abc\x1b(incomplete") == L"abc");
	UASSERT(unescape_enriched<wchar_t>(
		L"escape at the end\x1b") == L"escape at the end");
	// Nested escapes not supported
	UASSERT(unescape_enriched<wchar_t>(
		L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
}

void TestUtilities::testWrapRows()
{
	UASSERT(wrap_rows("12345678",4) == "1234\n5678");
	// test that wrap_rows doesn't wrap inside multibyte sequences
	{
		const unsigned char s[] = {
			0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
			0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
			0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
			0x6e, 0x2f, 0x2e, 0x2e, 0};
		std::string str((char *)s);
		UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
	};
	{
		const unsigned char s[] = {
			0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
			0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
			0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
		std::string str((char *)s);
		UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
	}
}

void TestUtilities::testEnrichedString()
{
	EnrichedString str(L"Test bar");
	irr::video::SColor color(0xFF, 0, 0, 0xFF);

	UASSERT(str.substr(1, 3).getString() == L"est");
	str += L" BUZZ";
	UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ");
	str.setDefaultColor(color); // Blue foreground
	UASSERT(str.getColors()[5] == color);
	// Green background, then white and yellow text
	str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow";
	UASSERT(str.getColors()[2] == 0xFFFFFFFF);
	str.setDefaultColor(color); // Blue foreground
	UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text
	UASSERT(str.getBackground() == 0xFF00FF00); // Green background
}

void TestUtilities::testIsNumber()
{
	UASSERT(is_number("123") == true);
	UASSERT(is_number("") == false);
	UASSERT(is_number("123a") == false);
}


void TestUtilities::testIsPowerOfTwo()
{
	UASSERT(is_power_of_two(0) == false);
	UASSERT(is_power_of_two(1) == true);
	UASSERT(is_power_of_two(2) == true);
	UASSERT(is_power_of_two(3) == false);
	for (int exponent = 2; exponent <= 31; ++exponent) {
		UASSERT(is_power_of_two((1U << exponent) - 1) == false);
		UASSERT(is_power_of_two((1U << exponent)) == true);
		UASSERT(is_power_of_two((1U << exponent) + 1) == false);
	}
	UASSERT(is_power_of_two(U32_MAX) == false);
}

void TestUtilities::testMyround()
{
	UASSERT(myround(4.6f) == 5);
	UASSERT(myround(1.2f) == 1);
	UASSERT(myround(-3.1f) == -3);
	UASSERT(myround(-6.5f) == -7);
}

void TestUtilities::testStringJoin()
{
	std::vector<std::string> input;
	UASSERT(str_join(input, ",") == "");

	input.emplace_back("one");
	UASSERT(str_join(input, ",") == "one");

	input.emplace_back("two");
	UASSERT(str_join(input, ",") == "one,two");

	input.emplace_back("three");
	UASSERT(str_join(input, ",") == "one,two,three");

	input[1] = "";
	UASSERT(str_join(input, ",") == "one,,three");

	input[1] = "two";
	UASSERT(str_join(input, " and ") == "one and two and three");
}


static bool within(const f32 value1, const f32 value2, const f32 precision)
{
	return std::fabs(value1 - value2) <= precision;
}

static bool within(const v3f &v1, const v3f &v2, const f32 precision)
{
	return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
		&& within(v1.Z, v2.Z, precision);
}

static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
		const f32 precision)
{
	const f32 *M1 = m1.pointer();
	const f32 *M2 = m2.pointer();
	for (int i = 0; i < 16; i++)
		if (! within(M1[i], M2[i], precision))
			return false;
	return true;
}

static bool roundTripsDeg(const v3f &v, const f32 precision)
{
	core::matrix4 m;
	setPitchYawRoll(m, v);
	return within(v, getPitchYawRoll(m), precision);
}

void TestUtilities::testEulerConversion()
{
	// This test may fail on non-IEEE systems.
	// Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
	// (ulp = unit in the last place; ulp(1.0) = 2^-23).
	const f32 tolL = 4.76837158203125e-7f;
	// High tolerance is 2 ulp(180.0), needed for numbers in degrees.
	// ulp(180.0) = 2^-16
	const f32 tolH = 3.0517578125e-5f;
	v3f v1, v2;
	core::matrix4 m1, m2;
	const f32 *M1 = m1.pointer();
	const f32 *M2 = m2.pointer();

	// Check that the radians version and the degrees version
	// produce the same results. Check also that the conversion
	// works both ways for these values.
	v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
	v2 = v3f(60.0f, 36.0f, 45.0f);
	setPitchYawRollRad(m1, v1);
	setPitchYawRoll(m2, v2);
	UASSERT(within(m1, m2, tolL));
	UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
	UASSERT(within(getPitchYawRoll(m2), v2, tolH));

	// Check the rotation matrix produced.
	UASSERT(within(M1[0], 0.932004869f, tolL));
	UASSERT(within(M1[1], 0.353553385f, tolL));
	UASSERT(within(M1[2], 0.0797927827f, tolL));
	UASSERT(within(M1[4], -0.21211791f, tolL));
	UASSERT(within(M1[5], 0.353553355f, tolL));
	UASSERT(within(M1[6], 0.911046684f, tolL));
	UASSERT(within(M1[8], 0.293892622f, tolL));
	UASSERT(within(M1[9], -0.866025448f, tolL));
	UASSERT(within(M1[10], 0.404508471f, tolL));

	// Check that the matrix is still homogeneous with no translation
	UASSERT(M1[3] == 0.0f);
	UASSERT(M1[7] == 0.0f);
	UASSERT(M1[11] == 0.0f);
	UASSERT(M1[12] == 0.0f);
	UASSERT(M1[13] == 0.0f);
	UASSERT(M1[14] == 0.0f);
	UASSERT(M1[15] == 1.0f);
	UASSERT(M2[3] == 0.0f);
	UASSERT(M2[7] == 0.0f);
	UASSERT(M2[11] == 0.0f);
	UASSERT(M2[12] == 0.0f);
	UASSERT(M2[13] == 0.0f);
	UASSERT(M2[14] == 0.0f);
	UASSERT(M2[15] == 1.0f);

	// Compare to Irrlicht's results. To be comparable, the
	// angles must come in a different order and the matrix
	// elements to compare are different too.
	m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
	UASSERT(within(M1[0], M2[5], tolL));
	UASSERT(within(M1[1], M2[6], tolL));
	UASSERT(within(M1[2], M2[4], tolL));

	UASSERT(within(M1[4], M2[9], tolL));
	UASSERT(within(M1[5], M2[10], tolL));
	UASSERT(within(M1[6], M2[8], tolL));

	UASSERT(within(M1[8], M2[1], tolL));
	UASSERT(within(M1[9], M2[2], tolL));
	UASSERT(within(M1[10], M2[0], tolL));

	// Check that Eulers that produce near gimbal-lock still round-trip
	UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
	UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
	UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));

	// Check that Eulers at an angle > 90 degrees may not round-trip...
	v1 = v3f(90.00001f, 1.f, 1.f);
	setPitchYawRoll(m1, v1);
	v2 = getPitchYawRoll(m1);
	//UASSERT(within(v1, v2, tolL)); // this is typically false
	// ... however the rotation matrix is the same for both
	setPitchYawRoll(m2, v2);
	UASSERT(within(m1, m2, tolL));
}

void TestUtilities::testBase64()
{
	// Test character set
	UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz"
		"0123456789+/") == true);
	UASSERT(base64_is_valid("/+9876543210"
		"zyxwvutsrqponmlkjihgfedcba"
		"ZYXWVUTSRQPONMLKJIHGFEDCBA") == true);
	UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz"
		"0123456789+.") == false);
	UASSERT(base64_is_valid("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		"abcdefghijklmnopqrstuvwxyz"
		"0123456789 /") == false);

	// Test empty string
	UASSERT(base64_is_valid("") == true);

	// Test different lengths, with and without padding,
	// with correct and incorrect padding
	UASSERT(base64_is_valid("A") == false);
	UASSERT(base64_is_valid("AA") == true);
	UASSERT(base64_is_valid("AAA") == true);
	UASSERT(base64_is_valid("AAAA") == true);
	UASSERT(base64_is_valid("AAAAA") == false);
	UASSERT(base64_is_valid("AAAAAA") == true);
	UASSERT(base64_is_valid("AAAAAAA") == true);
	UASSERT(base64_is_valid("AAAAAAAA") == true);
	UASSERT(base64_is_valid("A===") == false);
	UASSERT(base64_is_valid("AA==") == true);
	UASSERT(base64_is_valid("AAA=") == true);
	UASSERT(base64_is_valid("AAAA") == true);
	UASSERT(base64_is_valid("AAAA====") == false);
	UASSERT(base64_is_valid("AAAAA===") == false);
	UASSERT(base64_is_valid("AAAAAA==") == true);
	UASSERT(base64_is_valid("AAAAAAA=") == true);
	UASSERT(base64_is_valid("AAAAAAA==") == false);
	UASSERT(base64_is_valid("AAAAAAA===") == false);
	UASSERT(base64_is_valid("AAAAAAA====") == false);
	UASSERT(base64_is_valid("AAAAAAAA") == true);
	UASSERT(base64_is_valid("AAAAAAAA=") == false);
	UASSERT(base64_is_valid("AAAAAAAA==") == false);
	UASSERT(base64_is_valid("AAAAAAAA===") == false);
	UASSERT(base64_is_valid("AAAAAAAA====") == false);

	// Test if canonical encoding
	// Last character limitations, length % 4 == 3
	UASSERT(base64_is_valid("AAB") == false);
	UASSERT(base64_is_valid("AAE") == true);
	UASSERT(base64_is_valid("AAQ") == true);
	UASSERT(base64_is_valid("AAB=") == false);
	UASSERT(base64_is_valid("AAE=") == true);
	UASSERT(base64_is_valid("AAQ=") == true);
	UASSERT(base64_is_valid("AAAAAAB=") == false);
	UASSERT(base64_is_valid("AAAAAAE=") == true);
	UASSERT(base64_is_valid("AAAAAAQ=") == true);
	// Last character limitations, length % 4 == 2
	UASSERT(base64_is_valid("AB") == false);
	UASSERT(base64_is_valid("AE") == false);
	UASSERT(base64_is_valid("AQ") == true);
	UASSERT(base64_is_valid("AB==") == false);
	UASSERT(base64_is_valid("AE==") == false);
	UASSERT(base64_is_valid("AQ==") == true);
	UASSERT(base64_is_valid("AAAAAB==") == false);
	UASSERT(base64_is_valid("AAAAAE==") == false);
	UASSERT(base64_is_valid("AAAAAQ==") == true);

	// Extraneous character present
	UASSERT(base64_is_valid(".") == false);
	UASSERT(base64_is_valid("A.") == false);
	UASSERT(base64_is_valid("AA.") == false);
	UASSERT(base64_is_valid("AAA.") == false);
	UASSERT(base64_is_valid("AAAA.") == false);
	UASSERT(base64_is_valid("AAAAA.") == false);
	UASSERT(base64_is_valid("A.A") == false);
	UASSERT(base64_is_valid("AA.A") == false);
	UASSERT(base64_is_valid("AAA.A") == false);
	UASSERT(base64_is_valid("AAAA.A") == false);
	UASSERT(base64_is_valid("AAAAA.A") == false);
	UASSERT(base64_is_valid("\xE1""AAA") == false);

	// Padding in wrong position
	UASSERT(base64_is_valid("A=A") == false);
	UASSERT(base64_is_valid("AA=A") == false);
	UASSERT(base64_is_valid("AAA=A") == false);
	UASSERT(base64_is_valid("AAAA=A") == false);
	UASSERT(base64_is_valid("AAAAA=A") == false);
}


void TestUtilities::testSanitizeDirName()
{
	UASSERT(sanitizeDirName("a", "~") == "a");
	UASSERT(sanitizeDirName("  ", "~") == "__");
	UASSERT(sanitizeDirName(" a ", "~") == "_a_");
	UASSERT(sanitizeDirName("COM1", "~") == "~COM1");
	UASSERT(sanitizeDirName("COM1", ":") == "_COM1");
	UASSERT(sanitizeDirName("cOm\u00B2", "~") == "~cOm\u00B2");
	UASSERT(sanitizeDirName("cOnIn$", "~") == "~cOnIn$");
	UASSERT(sanitizeDirName(" cOnIn$ ", "~") == "_cOnIn$_");
}