#ifdef API_TESTS_FOR_RUNNER

#define TEST(_callback, _timeoutSeconds) { .cName = #_callback, .timeoutSeconds = _timeoutSeconds }
typedef struct Test { const char *cName; int timeoutSeconds; } Test;

#else

#define ES_PRIVATE_APIS
#include <essence.h>
#include <shared/crc.h>
#include <shared/array.cpp>
#include <shared/arena.cpp>
#include <shared/range_set.cpp>
#undef EsUTF8IsValid
#include <shared/unicode.cpp>

#define TEST(_callback, _timeoutSeconds) { .callback = _callback }
struct Test { bool (*callback)(); };

#define CHECK(x) do { if ((x)) { checkIndex++; } else { EsPrint("Failed check %d: " #x, checkIndex); return false; } } while (0)

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

bool AreFloatsRoughlyEqual(float a, float b) { return a - b < 0.0001f && a - b > -0.0001f; }
bool AreDoublesRoughlyEqual(double a, double b) { return a - b < 0.00000000001 && a - b > -0.00000000001; }
bool AreFloatsRoughlyEqual2(float a, float b) { return a - b < 0.01f && a - b > -0.01f; }

int CompareU8(const void *_a, const void *_b) {
	uint8_t a = *(const uint8_t *) _a, b = *(const uint8_t *) _b;
	return a < b ? -1 : a > b;
}

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

bool BasicFileOperationsDoByteCount(size_t byteCount) {
	uint8_t *buffer = (uint8_t *) EsHeapAllocate(byteCount, false);

	for (uintptr_t i = 0; i < byteCount; i++) {
		buffer[i] = EsRandomU8();
	}

	EsError error = EsFileWriteAll(EsLiteral("|Settings:/temp.dat"), buffer, byteCount); 

	if (error != ES_SUCCESS) {
		EsPrint("Error %d writing file of size %d.\n", error, byteCount);
		return false;
	}

	size_t readSize;
	uint8_t *read = (uint8_t *) EsFileReadAll(EsLiteral("|Settings:/temp.dat"), &readSize, &error);

	if (error != ES_SUCCESS) {
		EsPrint("Error %d reading file of size %d.\n", error, byteCount);
		return false;
	}

	if (readSize != byteCount) {
		EsPrint("Read size mismatch: got %d, expected %d.\n", readSize, byteCount);
		return false;
	}

	if (EsMemoryCompare(buffer, read, byteCount)) {
		EsPrint("Read data mismatch.\n");
		return false;
	}

	EsHeapFree(buffer);
	EsHeapFree(read);

	return true;
}

bool BasicFileOperations() {
	int checkIndex = 0;

	for (uintptr_t i = 0; i < 24; i += 2) {
		CHECK(BasicFileOperationsDoByteCount(1 << i));
	}

	for (uintptr_t i = 18; i > 0; i -= 3) {
		CHECK(BasicFileOperationsDoByteCount(1 << i));
	}

	EsError error = EsPathDelete(EsLiteral("|Settings:/temp.dat"));

	if (error != ES_SUCCESS) {
		EsPrint("Error %d deleting file.\n", error);
		CHECK(false);
	}

	EsFileReadAll(EsLiteral("|Settings:/temp.dat"), nullptr, &error);

	if (error != ES_ERROR_FILE_DOES_NOT_EXIST) {
		EsPrint("Checking file does not exist after deleting, instead got error %d.\n", error);
		CHECK(false);
	}

	return true;
}

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

bool CRTMathFunctions() {
	int checkIndex = 0;

	for (int i = 0; i <= 5; i++) CHECK(EsCRTabs(i) == i);
	for (int i = -5; i <= 0; i++) CHECK(EsCRTabs(i) == -i);

	for (float i = -1.0f; i <= 1.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTcosf(EsCRTacosf(i))));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTcosf(i), EsCRTcosf(-i)));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTcosf(i), EsCRTcosf(i + 2 * ES_PI)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTcos(i), EsCRTcos(-i)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTcos(i), EsCRTcos(i + 2 * ES_PI)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreFloatsRoughlyEqual(EsCRTcos(i), EsCRTcosf(i)));
	CHECK(AreFloatsRoughlyEqual(EsCRTacosf(-1.0f), ES_PI));
	CHECK(AreFloatsRoughlyEqual(EsCRTacosf(0.0f), ES_PI / 2.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTacosf(1.0f), 0.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTcosf(0.0f), 1.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTcosf(ES_PI / 2.0f), 0.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTcosf(ES_PI), -1.0f));
	CHECK(AreDoublesRoughlyEqual(EsCRTcos(0.0), 1.0));
	CHECK(AreDoublesRoughlyEqual(EsCRTcos(ES_PI / 2.0), 0.0));
	CHECK(AreDoublesRoughlyEqual(EsCRTcos(ES_PI), -1.0));

	for (float i = -1.0f; i <= 1.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTsinf(EsCRTasinf(i))));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTsinf(i), -EsCRTsinf(-i)));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTsinf(i), EsCRTsinf(i + 2 * ES_PI)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTsin(i), -EsCRTsin(-i)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTsin(i), EsCRTsin(i + 2 * ES_PI)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreFloatsRoughlyEqual(EsCRTsin(i), EsCRTsinf(i)));
	CHECK(AreFloatsRoughlyEqual(EsCRTasinf(-1.0f), ES_PI / -2.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTasinf(0.0f), 0.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTasinf(1.0f), ES_PI / 2.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTsinf(0.0f), 0.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTsinf(ES_PI / 2.0f), 1.0f));
	CHECK(AreFloatsRoughlyEqual(EsCRTsinf(ES_PI), 0.0f));
	CHECK(AreDoublesRoughlyEqual(EsCRTsin(0.0), 0.0));
	CHECK(AreDoublesRoughlyEqual(EsCRTsin(ES_PI / 2.0), 1.0));
	CHECK(AreDoublesRoughlyEqual(EsCRTsin(ES_PI), 0.0));

	for (float i = 0.0f; i <= 5.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTsqrtf(i) * EsCRTsqrtf(i), i));
	for (float i = -5.0f; i <= 5.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTcbrtf(i) * EsCRTcbrtf(i) * EsCRTcbrtf(i), i));
	for (double i = 0.0; i <= 5.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTsqrt(i) * EsCRTsqrt(i), i));
	for (double i = -5.0; i <= 5.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTcbrt(i) * EsCRTcbrt(i) * EsCRTcbrt(i), i));

	for (float i = -5.0f; i <= 0.0f; i += 0.01f) CHECK(-i == EsCRTfabsf(i));
	for (float i = 0.0f; i <= 5.0f; i += 0.01f) CHECK(i == EsCRTfabsf(i));
	for (double i = -5.0; i <= 0.0; i += 0.01) CHECK(-i == EsCRTfabs(i));
	for (double i = 0.0; i <= 5.0; i += 0.01) CHECK(i == EsCRTfabs(i));

	// This tests avoid angles near the y axis with atan2, because y/x blows up there. TODO Is this acceptable behaviour?
	for (float i = -ES_PI / 4.0f; i < ES_PI / 4.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTatan2f(EsCRTsinf(i), EsCRTcosf(i))));
	for (float i = -ES_PI / 4.0f; i < ES_PI / 4.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTatanf(EsCRTsinf(i) / EsCRTcosf(i))));
	for (float i = -ES_PI * 7.0f / 8.0f; i < -ES_PI * 5.0f / 8.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTatan2f(EsCRTsinf(i), EsCRTcosf(i))));
	for (float i = ES_PI * 5.0f / 8.0f; i < ES_PI * 7.0f / 8.0f; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i, EsCRTatan2f(EsCRTsinf(i), EsCRTcosf(i))));
	for (float i = -2.25f * ES_PI; i < -1.75f * ES_PI; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i + 2 * ES_PI, EsCRTatan2f(EsCRTsinf(i), EsCRTcosf(i))));
	for (float i = 1.75f * ES_PI; i < 2.25f * ES_PI; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i - 2 * ES_PI, EsCRTatan2f(EsCRTsinf(i), EsCRTcosf(i))));
	for (float i = -2.25f * ES_PI; i < -1.75f * ES_PI; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i + 2 * ES_PI, EsCRTatanf(EsCRTsinf(i) / EsCRTcosf(i))));
	for (float i = 1.75f * ES_PI; i < 2.25f * ES_PI; i += 0.01f) CHECK(AreFloatsRoughlyEqual(i - 2 * ES_PI, EsCRTatanf(EsCRTsinf(i) / EsCRTcosf(i))));
	for (double i = -ES_PI / 4.0; i < ES_PI / 4.0; i += 0.01) CHECK(AreDoublesRoughlyEqual(i, EsCRTatan2(EsCRTsin(i), EsCRTcos(i))));
	for (double i = -ES_PI * 7.0 / 8.0; i < -ES_PI * 5.0 / 8.0; i += 0.01) CHECK(AreDoublesRoughlyEqual(i, EsCRTatan2(EsCRTsin(i), EsCRTcos(i))));
	for (double i = ES_PI * 5.0 / 8.0; i < ES_PI * 7.0 / 8.0; i += 0.01) CHECK(AreDoublesRoughlyEqual(i, EsCRTatan2(EsCRTsin(i), EsCRTcos(i))));
	for (double i = -2.25 * ES_PI; i < -1.75 * ES_PI; i += 0.01) CHECK(AreDoublesRoughlyEqual(i + 2 * ES_PI, EsCRTatan2(EsCRTsin(i), EsCRTcos(i))));
	for (double i = 1.75 * ES_PI; i < 2.25 * ES_PI; i += 0.01) CHECK(AreDoublesRoughlyEqual(i - 2 * ES_PI, EsCRTatan2(EsCRTsin(i), EsCRTcos(i))));

	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i - 0.000001f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i + 0.000001f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i - 0.1f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i + 0.1f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i - 0.5f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i + 0.5f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i - 0.9f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i + 0.9f) == i);
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTceilf(i - 0.1f) == EsCRTceilf(i - 0.9f));
	for (float i = -1000.0f; i <= 1000.0f; i += 1.0f) CHECK(EsCRTfloorf(i + 0.1f) == EsCRTfloorf(i + 0.9f));
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i - 0.00000000001) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i + 0.00000000001) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i - 0.1) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i + 0.1) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i - 0.5) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i + 0.5) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i - 0.9) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i + 0.9) == i);
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTceil(i - 0.1) == EsCRTceil(i - 0.9));
	for (double i = -1000.0; i <= 1000.0; i += 1.0) CHECK(EsCRTfloor(i + 0.1) == EsCRTfloor(i + 0.9));

	for (float x = -10.0f; x <= 10.0f; x += 0.1f) for (float y = -10.0f; y <= 10.0f; y += 0.1f) CHECK(AreFloatsRoughlyEqual(x - (int64_t) (x / y) * y, EsCRTfmodf(x, y)));
	for (double x = -10.0; x <= 10.0; x += 0.1) for (double y = -10.0; y <= 10.0; y += 0.1) CHECK(AreDoublesRoughlyEqual(x - (int64_t) (x / y) * y, EsCRTfmod(x, y)));

	CHECK(EsCRTisnanf(0.0f / 0.0f));
	CHECK(!EsCRTisnanf(0.0f / 1.0f));
	CHECK(!EsCRTisnanf(1.0f / 0.0f));
	CHECK(!EsCRTisnanf(-1.0f / 0.0f));

	// TODO The precision of powf is really bad!! Get it to the point where it can use AreFloatsRoughlyEqual.
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTsqrtf(i), EsCRTpowf(i, 1.0f / 2.0f)));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTcbrtf(i), EsCRTpowf(i, 1.0f / 3.0f)));
	for (float i = 0.0f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual2(i * i, EsCRTpowf(i, 2.0f)));
	for (float i = 0.1f; i <= 10.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual2(1.0f / (i * i), EsCRTpowf(i, -2.0f)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTsqrt(i), EsCRTpow(i, 1.0 / 2.0)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTcbrt(i), EsCRTpow(i, 1.0 / 3.0)));
	for (double i = 0.0; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(i * i, EsCRTpow(i, 2.0)));
	for (double i = 0.1; i <= 10.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(1.0 / (i * i), EsCRTpow(i, -2.0)));

	CHECK(AreFloatsRoughlyEqual(EsCRTexpf(1.0f), 2.718281828459f));
	for (float i = -10.0f; i <= 4.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTexpf(i), EsCRTpowf(2.718281828459f, i)));
	CHECK(AreDoublesRoughlyEqual(EsCRTexp(1.0), 2.718281828459));
	for (double i = -10.0; i <= 4.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTexp(i), EsCRTpow(2.718281828459, i)));
	CHECK(AreFloatsRoughlyEqual(EsCRTexp2f(1.0f), 2.0f));
	for (float i = -10.0f; i <= 4.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTexp2f(i), EsCRTpowf(2.0f, i)));
	CHECK(AreDoublesRoughlyEqual(EsCRTexp2(1.0), 2.0));
	for (double i = -10.0; i <= 4.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTexp2(i), EsCRTpow(2.0, i)));
	CHECK(AreFloatsRoughlyEqual(EsCRTlog2f(2.0f), 1.0f));
	for (float i = -10.0f; i <= 4.0f; i += 0.1f) CHECK(AreFloatsRoughlyEqual(EsCRTlog2f(EsCRTexp2f(i)), i));
	CHECK(AreDoublesRoughlyEqual(EsCRTlog2(2.0), 1.0));
	for (double i = -10.0; i <= 4.0; i += 0.1) CHECK(AreDoublesRoughlyEqual(EsCRTlog2(EsCRTexp2(i)), i));

	return true;
}

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

bool CRTStringFunctions() {
	// TODO strncmp, strncpy, strnlen, atod, atoi, atof, strtod, strtof, strtol, strtoul.

	int checkIndex = 0;

	for (int i = 0; i < 256; i++) CHECK(EsCRTisalpha(i) == ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z')));
	for (int i = 0; i < 256; i++) CHECK(EsCRTisdigit(i) == (i >= '0' && i <= '9'));
	for (int i = 0; i < 256; i++) CHECK(EsCRTisupper(i) == (i >= 'A' && i <= 'Z'));
	for (int i = 0; i < 256; i++) CHECK(EsCRTisxdigit(i) == ((i >= '0' && i <= '9') || (i >= 'A' && i <= 'F') || (i >= 'a' && i <= 'f')));
	for (int i = 0; i <= 256; i++)  CHECK((EsCRTtolower(i) != i) == (i >= 'A' && i <= 'Z'));

	CHECK(0 > EsCRTstrcmp("a", "ab"));
	CHECK(0 < EsCRTstrcmp("ab", "a"));
	CHECK(0 > EsCRTstrcmp("ab", "ac"));
	CHECK(0 > EsCRTstrcmp("ac", "bc"));
	CHECK(0 > EsCRTstrcmp("", "a"));
	CHECK(0 < EsCRTstrcmp("a", ""));
	CHECK(0 < EsCRTstrcmp("a", "A"));
	CHECK(0 == EsCRTstrcmp("", ""));
	CHECK(0 == EsCRTstrcmp("a", "a"));
	CHECK(0 == EsCRTstrcmp("ab", "ab"));

	char x[10];
	EsCRTstrcpy(x, "hello");
	CHECK(0 == EsCRTstrcmp(x, "hello"));
	EsCRTstrcat(x, "!");
	CHECK(0 == EsCRTstrcmp(x, "hello!"));

	CHECK(!EsCRTstrchr(x, '.'));
	CHECK(x + 0 == EsCRTstrchr(x, 'h'));
	CHECK(x + 1 == EsCRTstrchr(x, 'e'));
	CHECK(x + 2 == EsCRTstrchr(x, 'l'));
	CHECK(x + 4 == EsCRTstrchr(x, 'o'));
	CHECK(x + 6 == EsCRTstrchr(x, 0));
	CHECK(!EsCRTstrstr(x, "."));
	CHECK(!EsCRTstrstr(x, "le"));
	CHECK(!EsCRTstrstr(x, "oo"));
	CHECK(!EsCRTstrstr(x, "ah"));
	CHECK(!EsCRTstrstr(x, "lle"));
	CHECK(x + 0 == EsCRTstrstr(x, ""));
	CHECK(x + 0 == EsCRTstrstr(x, "h"));
	CHECK(x + 0 == EsCRTstrstr(x, "he"));
	CHECK(x + 0 == EsCRTstrstr(x, "hello"));
	CHECK(x + 0 == EsCRTstrstr(x, "hello!"));
	CHECK(x + 1 == EsCRTstrstr(x, "ell"));
	CHECK(x + 1 == EsCRTstrstr(x, "ello!"));
	CHECK(x + 3 == EsCRTstrstr(x, "lo"));

	CHECK(0 == EsCRTstrlen(""));
	CHECK(6 == EsCRTstrlen(x));

	char *copy = EsCRTstrdup(x);
	CHECK(0 == EsCRTstrcmp(copy, x));
	EsCRTfree(copy);

	return true;
}

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

bool CRTOtherFunctions() {
	// TODO setjmp, longjmp.
	// Note that malloc, free and realloc are assumed to be working if EsHeapAllocate, EsHeapFree and EsHeapReallocate are.

	int checkIndex = 0;

	uint8_t x[4] = { 1, 2, 3, 4 };
	uint8_t y[4] = { 1, 3, 3, 4 };
	uint8_t z[4] = { 6, 7, 8, 9 };

	CHECK(0 > EsCRTmemcmp(x, y, 4));
	CHECK(0 > EsCRTmemcmp(x, y, 3));
	CHECK(0 > EsCRTmemcmp(x, y, 2));
	CHECK(0 < EsCRTmemcmp(y, x, 4));
	CHECK(0 < EsCRTmemcmp(y, x, 3));
	CHECK(0 < EsCRTmemcmp(y, x, 2));
	CHECK(0 == EsCRTmemcmp(x, y, 1));
	CHECK(0 == EsCRTmemcmp(x, y, 0));
	CHECK(0 == EsCRTmemcmp(y, x, 1));
	CHECK(0 == EsCRTmemcmp(y, x, 0));

	for (int i = 0; i < 4; i++) CHECK(x + i == EsCRTmemchr(x, i + 1, 4));
	for (int i = 4; i < 8; i++) CHECK(!EsCRTmemchr(x, i + 1, 4));

	y[3] = 5;
	EsCRTmemcpy(x, y, 2);
	CHECK(x[0] == 1 && x[1] == 3 && x[2] == 3 && x[3] == 4);
	CHECK(y[0] == 1 && y[1] == 3 && y[2] == 3 && y[3] == 5);
	EsCRTmemcpy(x, y, 4);
	CHECK(x[0] == 1 && x[1] == 3 && x[2] == 3 && x[3] == 5);
	CHECK(y[0] == 1 && y[1] == 3 && y[2] == 3 && y[3] == 5);
	EsCRTmemset(x, 0xCC, 4);
	for (int i = 0; i < 4; i++) CHECK(x[i] == 0xCC);
	CHECK(y[0] == 1 && y[1] == 3 && y[2] == 3 && y[3] == 5);
	EsCRTmemset(y, 0x00, 4);
	for (int i = 0; i < 4; i++) CHECK(y[i] == 0x00);
	for (int i = 0; i < 4; i++) CHECK(x[i] == 0xCC);
	CHECK(z[0] == 6 && z[1] == 7 && z[2] == 8 && z[3] == 9);

	y[0] = 0, y[1] = 1, y[2] = 2, y[3] = 3;
	EsCRTmemmove(y + 1, y + 2, 2);
	CHECK(y[0] == 0 && y[1] == 2 && y[2] == 3 && y[3] == 3);
	y[0] = 0, y[1] = 1, y[2] = 2, y[3] = 3;
	EsCRTmemmove(y + 2, y + 1, 2);
	CHECK(y[0] == 0 && y[1] == 1 && y[2] == 1 && y[3] == 2);

	for (int i = 0; i < 100000; i++) {
		uint8_t *p = (uint8_t *) EsCRTcalloc(i, 1);
		uint8_t *q = (uint8_t *) EsCRTmalloc(i);
		if (i == 0) { CHECK(!p && !q); continue; }
		CHECK(p != q);
		CHECK(p && q);
		for (int j = 0; j < i; j++) CHECK(!p[j]);
		EsCRTfree(p);
		EsCRTfree(q);
		if (i > 1000) i += 100;
		if (i > 10000) i += 1000;
	}

	uint8_t *array = (uint8_t *) EsCRTmalloc(10000);
	size_t *count = (size_t *) EsCRTcalloc(256, sizeof(size_t));
	EsRandomSeed(15); 
	array[0] = 100;
	count[100]++;
	for (int i = 1; i < 10000; i++) { array[i] = (EsRandomU8() & ~1) ?: 2; count[array[i]]++; }
	EsCRTqsort(array, 10000, 1, CompareU8);
	int p = 0;
	for (uintptr_t i = 0; i < 256; i++) for (uintptr_t j = 0; j < count[i]; j++, p++) CHECK(array[p] == i);
	CHECK(p == 10000);
	uint8_t n = 100;
	uint8_t *q = (uint8_t *) EsCRTbsearch(&n, array, 10000, 1, CompareU8);
	CHECK(q && q[0] == 100);
	n = 0;
	CHECK(!EsCRTbsearch(&n, array, 10000, 1, CompareU8));
	n = 255;
	CHECK(!EsCRTbsearch(&n, array, 10000, 1, CompareU8));

	for (n = 0; n < 255; n++) {
		q = (uint8_t *) EsCRTbsearch(&n, array, 10000, 1, CompareU8);
		if (count[n]) CHECK(q && *q == n);
		else CHECK(!q);
	}

	EsCRTfree(array);
	EsCRTfree(count);

#ifdef ES_BITS_32
	CHECK(!EsCRTcalloc(0x7FFFFFFF, 0x7FFFFFFF));
	CHECK(!EsCRTcalloc(0xFFFFFFFF, 0xFFFFFFFF));
#endif
#ifdef ES_BITS_64
	CHECK(!EsCRTcalloc(0x7FFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF));
	CHECK(!EsCRTcalloc(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF));
#endif

	return true;
}

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

bool PerformanceTimerDrift() {
	int checkIndex = 0;

	EsDateComponents start, end;

	EsDateNowUTC(&start);
	EsPerformanceTimerPush();

	for (uintptr_t i = 0; i < 50000000; i++) {
		EsCStringLength("Test"); 
	}

	double performanceTime = EsPerformanceTimerPop();
	EsDateNowUTC(&end);
	double mainTime = (DateToLinear(&end) - DateToLinear(&start)) / 1000.0;

	EsPrint("Performance timer: %F s.\n", performanceTime);
	EsPrint("Main timer: %F s.\n", mainTime);

	// TODO Improve the quality of the performance timer, if better timer sources are available, like the HPET.
	CHECK(EsCRTfabs(performanceTime - mainTime) / mainTime < 0.2); // Less than a 20% drift.
	return true;
}

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

EsTextbox *textbox;

Array<char> master;
Array<Array<char>> undo;

void OffsetToLineAndByte(uintptr_t offset, int32_t *_line, int32_t *_byte) {
	int32_t line = 0, byte = 0;

	for (uintptr_t i = 0; i < offset; i++) {
		if (master[i] == '\n') {
			line++;
			byte = 0;
		} else {
			byte++;
		}
	}

	*_line = line;
	*_byte = byte;
}

bool Compare() {
	size_t bytes;
	char *contents = EsTextboxGetContents(textbox, &bytes);
	// EsPrint("\tContents: '%e'\n\tMaster:   '%e'\n", bytes, contents, arrlenu(master), master);
	if (bytes != master.Length()) return false;
	if (EsMemoryCompare(&master[0], contents, bytes)) return false;
	EsHeapFree(contents);
	return true;
}

void FakeUndoItem(const void *, EsUndoManager *manager, EsMessage *message) {
	if (message->type == ES_MSG_UNDO_INVOKE) {
		EsUndoPush(manager, FakeUndoItem, nullptr, 0); 
	}
}

void AddUndoItem() {
	Array<char> copy = {};
	copy.SetLength(master.Length());
	if (copy.Length()) EsMemoryCopy(&copy[0], &master[0], copy.Length());
	undo.Add(copy);
}

void Complete() {
	EsUndoPush(textbox->instance->undoManager, FakeUndoItem, nullptr, 0); 
	EsUndoEndGroup(textbox->instance->undoManager);
}

bool Insert(uintptr_t offset, const char *string, size_t stringBytes) {
	if (!stringBytes) return true;
	AddUndoItem();
	// EsPrint("Insert '%e' at %d.\n", stringBytes, string, offset);
	int32_t line, byte;
	OffsetToLineAndByte(offset, &line, &byte);
	EsTextboxSetSelection(textbox, line, byte, line, byte);
	EsTextboxInsert(textbox, string, stringBytes);
	master.InsertMany(offset, stringBytes);
	EsMemoryCopy(&master[offset], string, stringBytes);
	if (!Compare()) return false;
	Complete();
	return true;
}

bool Delete(uintptr_t from, uintptr_t to) {
	if (from == to) return true;
	AddUndoItem();
	// EsPrint("Delete from %d to %d.\n", from, to);
	int32_t fromLine, fromByte, toLine, toByte;
	OffsetToLineAndByte(from, &fromLine, &fromByte);
	OffsetToLineAndByte(to, &toLine, &toByte);
	EsTextboxSetSelection(textbox, fromLine, fromByte, toLine, toByte);
	EsTextboxInsert(textbox, 0, 0);
	if (to > from) master.DeleteMany(from, to - from);
	else master.DeleteMany(to, from - to);
	if (!Compare()) return false;
	Complete();
	return true;
}

bool TextboxEditOperations() {
	int checkIndex = 0;
	textbox = EsTextboxCreate(EsWindowCreate(_EsInstanceCreate(sizeof(EsInstance), nullptr), ES_WINDOW_PLAIN), ES_TEXTBOX_ALLOW_TABS | ES_TEXTBOX_MULTILINE);
	EsRandomSeed(10); 
	EsTextboxSetUndoManager(textbox, textbox->instance->undoManager);

	CHECK(Insert(0, EsLiteral("hello\n\t\t\ttest\n\t\t\tworld")));
	CHECK(Insert(13, EsLiteral("\n")));
	CHECK(Insert(14, EsLiteral("\t\t\t")));
	CHECK(Insert(26, EsLiteral("\n")));
	CHECK(Insert(27, EsLiteral("\t\t\t")));
	CHECK(Insert(30, EsLiteral("\n")));
	CHECK(Insert(31, EsLiteral("\t\t\t")));

	char *initialText = (char *) EsHeapAllocate(100000, false);
	for (uintptr_t i = 0; i < 100000; i++) initialText[i] = EsRandomU8() < 0x40 ? '\n' : ((EsRandomU8() % 26) + 'a');
	CHECK(Insert(0, initialText, 100000));
	EsHeapFree(initialText);

	for (uintptr_t i = 0; i < 10000; i++) {
		uint8_t action = EsRandomU8();

		if (action < 0x70) {
			size_t stringBytes = EsRandomU8() & 0x1F;
			char string[0x20];

			for (uintptr_t i = 0; i < stringBytes; i++) {
				string[i] = EsRandomU8() < 0x40 ? '\n' : ((EsRandomU8() % 26) + 'a');
			}

			CHECK(Insert(EsRandomU64() % (master.Length() + 1), string, stringBytes));
		} else if (action < 0xE0) {
			if (master.Length()) {
				CHECK(Delete(EsRandomU64() % master.Length(), EsRandomU64() % master.Length()));
			}
		} else {
			if (!EsUndoIsEmpty(textbox->instance->undoManager, false)) {
				// EsPrint("Undo.\n");
				EsUndoInvokeGroup(textbox->instance->undoManager, false);
				master.Free();
				master = undo.Last();
				undo.Pop();
				CHECK(Compare());
			}
		}
	}

	return true;
}

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

struct {
	int a, b;
} testStruct = {
	.a = 1, .b = 2,
};

const int testVariable = 3;

bool DirectoryEnumerateRecursive(const char *path, size_t pathBytes) {
	size_t count;
	EsError error;
	EsDirectoryChild *buffer = EsDirectoryEnumerate(path, pathBytes, &count, &error);

	if (error != ES_SUCCESS) {
		EsPrint("Error %i enumerating at path \"%s\".\n", error, pathBytes, path);
		return false;
	}

	for (uintptr_t i = 0; i < count; i++) {
		char *childPath = (char *) EsHeapAllocate(pathBytes + 1 + buffer[i].nameBytes, false);
		size_t childPathBytes = EsStringFormat(childPath, ES_STRING_FORMAT_ENOUGH_SPACE, "%s/%s", pathBytes, path, buffer[i].nameBytes, buffer[i].name);

		if (buffer[i].type == ES_NODE_FILE) {
			size_t dataBytes;
			EsError error;
			void *data = EsFileReadAll(childPath, childPathBytes, &dataBytes, &error);

			if (error != ES_SUCCESS) {
				EsPrint("Error %i reading path \"%s\".\n", (EsError) count, childPathBytes, childPath);
				return false;
			}

			if (dataBytes != (size_t) buffer[i].fileSize) {
				EsPrint("File size mismatch reading path \"%s\" (got %d from EsFileReadAll, got %d from EsDirectoryEnumerate).\n", 
						childPathBytes, childPath, dataBytes, buffer[i].fileSize);
				return false;
			}

			EsHeapFree(data);
		} else if (buffer[i].type == ES_NODE_DIRECTORY) {
			if (!DirectoryEnumerateRecursive(childPath, childPathBytes)) {
				return false;
			}
		}
	}

	EsHeapFree(buffer);
	return true;
}

bool OldTests2018() {
	int checkIndex = 0;

	CHECK(testStruct.a == 1);
	CHECK(testStruct.b == 2);
	CHECK(testVariable == 3);
	testStruct.a += 3;
	CHECK(testStruct.a == 4);
	CHECK(testStruct.b == 2);
	CHECK(testVariable == 3);

	CHECK(DirectoryEnumerateRecursive(EsLiteral("|Settings:")));

	for (int count = 16; count < 100; count += 30) {
		EsHandle handles[100];

		for (int i = 0; i < count; i++) {
			char buffer[256];
			size_t length = EsStringFormat(buffer, 256, "|Settings:/TestFolder/%d", i);
			EsFileInformation node = EsFileOpen(buffer, length, ES_NODE_CREATE_DIRECTORIES | ES_NODE_FAIL_IF_FOUND | ES_FILE_WRITE);
			CHECK(node.error == ES_SUCCESS);
			handles[i] = node.handle;
		}

		for (int i = 0; i < count; i++) {
			CHECK(ES_SUCCESS == EsFileDelete(handles[i]));
		}

		for (int i = 0; i < count; i++) {
			EsHandleClose(handles[i]);
		}
	}

	{
		EsFileWriteAll(EsLiteral("|Settings:/TestFolder/a.txt"), EsLiteral("hello"));
		EsFileWriteAll(EsLiteral("|Settings:/b.txt"), EsLiteral("world"));
		CHECK(EsPathExists(EsLiteral("|Settings:/TestFolder/a.txt")));
		CHECK(EsPathExists(EsLiteral("|Settings:/b.txt")));
		CHECK(!EsPathExists(EsLiteral("|Settings:/TestFolder/b.txt")));
		CHECK(!EsPathExists(EsLiteral("|Settings:/a.txt")));
		CHECK(ES_SUCCESS == EsPathMove(EsLiteral("|Settings:/TestFolder/a.txt"), EsLiteral("|Settings:/a.txt"), ES_FLAGS_DEFAULT));
		CHECK(!EsPathExists(EsLiteral("|Settings:/TestFolder/a.txt")));
		CHECK(EsPathExists(EsLiteral("|Settings:/b.txt")));
		CHECK(!EsPathExists(EsLiteral("|Settings:/TestFolder/b.txt")));
		CHECK(EsPathExists(EsLiteral("|Settings:/a.txt")));
		CHECK(ES_ERROR_FILE_DOES_NOT_EXIST == EsPathMove(EsLiteral("|Settings:/TestFolder/a.txt"), EsLiteral("|Settings:/a.txt"), ES_FLAGS_DEFAULT));
		CHECK(ES_ERROR_ALREADY_EXISTS == EsPathMove(EsLiteral("|Settings:/a.txt"), EsLiteral("|Settings:/b.txt"), ES_FLAGS_DEFAULT));
		CHECK(ES_ERROR_ALREADY_EXISTS == EsPathMove(EsLiteral("|Settings:/a.txt"), EsLiteral("|Settings:/a.txt"), ES_FLAGS_DEFAULT));
		CHECK(ES_ERROR_TARGET_WITHIN_SOURCE == EsPathMove(EsLiteral("|Settings:/"), EsLiteral("|Settings:/TestFolder/TargetWithinSource"), ES_FLAGS_DEFAULT));
		CHECK(!EsPathExists(EsLiteral("|Settings:/TestFolder/a.txt")));
		CHECK(EsPathExists(EsLiteral("|Settings:/b.txt")));
		CHECK(!EsPathExists(EsLiteral("|Settings:/TestFolder/b.txt")));
		CHECK(EsPathExists(EsLiteral("|Settings:/a.txt")));
	}

	CHECK(DirectoryEnumerateRecursive(EsLiteral("|Settings:")));

	{
		void *a = EsCRTmalloc(0x100000);
		CHECK(a);
		void *b = EsCRTrealloc(a, 0x1000);
		CHECK(b);
		void *c = EsCRTrealloc(b, 0x100000);
		CHECK(c);
		EsCRTfree(c);
	}

	{
		char b[] = "abcdef";
		CHECK(EsCRTstrlen(b) == 6);
		CHECK(EsCRTstrnlen(b, 3) == 3);
		CHECK(EsCRTstrnlen(b, 10) == 6);
	}

	{
		CHECK(EsCRTstrtol("\n\f\n -0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAAAaaaaaa", nullptr, 0) == LONG_MIN);
		char *x = (char *) "+03", *y;
		CHECK(EsCRTstrtol(x, &y, 4) == 3 && y == x + 3);
	}

	{
		EsFileInformation node = EsFileOpen(EsLiteral("|Settings:/ResizeFileTest.txt"), ES_FILE_WRITE | ES_NODE_FAIL_IF_FOUND);
		CHECK(node.error == ES_SUCCESS);

		// TODO Failing large file resizes.
#if 0
		EsFileResize(node.handle, (uint64_t) 0xFFFFFFFFFFFF);
#endif

		uint8_t buffer[512];

		for (uintptr_t i = 1; i < 128; i++) {
			for (uintptr_t j = 0; j < 512; j++) {
				buffer[j] = i;
			}

			// OSPrint("Resizing file to %d\n", i * 512);
			EsFileResize(node.handle, i * 512);
			// OSPrint("Write to %d\n", (i - 1) * 512);
			EsFileWriteSync(node.handle, (i - 1) * 512, 512, buffer);
		}

		for (uintptr_t i = 1; i < 128; i++) {
			// OSPrint("Read from %d\n", (i - 1) * 512);
			EsFileReadSync(node.handle, (i - 1) * 512, 512, buffer);

			for (uintptr_t j = 0; j < 512; j++) {
				CHECK(buffer[j] == i);
			}
		}

		for (uintptr_t i = 126; i > 0; i--) {
			// OSPrint("Resizing file to %d\n", i * 512);
			EsFileResize(node.handle, i * 512);
		}

		for (uintptr_t i = 1; i < 2; i++) {
			// OSPrint("Read from %d\n", (i - 1) * 512);
			EsFileReadSync(node.handle, (i - 1) * 512, 512, buffer);

			for (uintptr_t j = 0; j < 512; j++) {
				CHECK(buffer[j] == i);
			}
		}

		EsHandleClose(node.handle);
	}

	{
		EsFileInformation node = EsFileOpen(EsLiteral("|Settings:/MapFile.txt"), ES_FILE_WRITE_SHARED);
		CHECK(node.error == ES_SUCCESS);
		EsFileResize(node.handle, 1048576);
		uint32_t *buffer = (uint32_t *) EsHeapAllocate(1048576, false);
		for (int i = 0; i < 262144; i++) buffer[i] = i;
		EsFileWriteSync(node.handle, 0, 1048576, buffer);
		EsFileReadSync(node.handle, 0, 1048576, buffer);
		for (uintptr_t i = 0; i < 262144; i++) CHECK(buffer[i] == i);
		EsFileInformation node2 = EsFileOpen(EsLiteral("|Settings:/MapFile.txt"), ES_FILE_READ_SHARED);
		CHECK(node.error == ES_SUCCESS);
		uint32_t *pointer = (uint32_t *) EsMemoryMapObject(node2.handle, 0, ES_MEMORY_MAP_OBJECT_ALL, ES_MEMORY_MAP_OBJECT_READ_ONLY);
		CHECK(pointer);
		uint32_t *pointer2 = (uint32_t *) EsMemoryMapObject(node2.handle, 0, ES_MEMORY_MAP_OBJECT_ALL, ES_MEMORY_MAP_OBJECT_READ_ONLY);
		CHECK(pointer2);
		for (uintptr_t i = 4096; i < 262144; i++) CHECK(pointer[i] == buffer[i]);
		for (int i = 0; i < 262144; i++) buffer[i] = i + 100;
		EsFileWriteSync(node.handle, 0, 1048576, buffer);
		EsFileReadSync(node.handle, 0, 1048576, buffer);
		for (uintptr_t i = 0; i < 262144; i++) CHECK(buffer[i] == i + 100);
		for (uintptr_t i = 4096; i < 262144; i++) CHECK(pointer[i] == buffer[i]);
		for (uintptr_t i = 4096; i < 262144; i++) CHECK(pointer2[i] == buffer[i]);
		EsMemoryUnreserve(pointer);
		EsHandleClose(node.handle);
		EsHandleClose(node2.handle);
		EsMemoryUnreserve(pointer2);
	}

	{
		const char *path = "|Settings:/OS/new_dir/test2.txt";
		EsFileInformation node = EsFileOpen(path, EsCStringLength(path), ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES);
		CHECK(node.error == ES_SUCCESS);
		CHECK(ES_SUCCESS == EsFileResize(node.handle, 8));
		char buffer[8];
		buffer[0] = 'a';
		buffer[1] = 'b';
		EsFileWriteSync(node.handle, 0, 1, buffer);
		buffer[0] = 'b';
		buffer[1] = 'c';
		size_t bytesRead = EsFileReadSync(node.handle, 0, 8, buffer);
		CHECK(bytesRead == 8);
		CHECK(buffer[0] == 'a' && buffer[1] == 0 && buffer[2] == 0);
		CHECK(EsFileGetSize(node.handle) == 8);
		EsHandleClose(node.handle);
	}
	
	return true;
}

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

bool HeapReallocate() {
	void *a = EsHeapReallocate(nullptr, 128, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 256, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 128, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 65536, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 128, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 128, true);
	EsHeapValidate();
	void *b = EsHeapReallocate(nullptr, 64, true);
	EsHeapValidate();
	void *c = EsHeapReallocate(nullptr, 64, true);
	EsHeapValidate();
	EsHeapReallocate(b, 0, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 128 + 88, true);
	EsHeapValidate();
	a = EsHeapReallocate(a, 128, true);
	EsHeapValidate();
	EsHeapReallocate(a, 0, true);
	EsHeapValidate();
	EsHeapReallocate(c, 0, true);
	EsHeapValidate();
	return true;
}

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

bool ArenaRandomAllocations() {
	int checkIndex = 0;
	Arena arena = {};
	ArenaInitialise(&arena, 3, sizeof(int));
	Array<void *> allocations = {};
	EsRandomSeed(20);

	for (uintptr_t i = 0; i < 500000; i++) {
		if ((EsRandomU8() & 1) || !allocations.Length()) {
			void *allocation = ArenaAllocate(&arena, false);
			for (uintptr_t i = 0; i < allocations.Length(); i++) CHECK(allocations[i] != allocation);
			allocations.Add(allocation);
		} else {
			int index = EsRandomU64() % allocations.Length();
			ArenaFree(&arena, allocations[index]);
			allocations.DeleteSwap(index);
		}
	}

	return true;
}

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

bool rangeSetCheck[1000];
RangeSet rangeSet = {};

bool RangeSetModify(bool set, int x, int y) {
	for (int i = x; i < y; i++) {
		rangeSetCheck[i] = set;
	}

	if (set) {
		if (!rangeSet.Set(x, y, nullptr, true)) {
			return false;
		}
	} else {
		if (!rangeSet.Clear(x, y, nullptr, true)) {
			return false;
		}
	}

	for (uintptr_t i = 0; i < sizeof(rangeSetCheck); i++) {
		if (rangeSetCheck[i]) {
			if (!rangeSet.Find(i, false)) {
				return false;
			}
		} else {
			if (rangeSet.Find(i, false)) {
				return false;
			}
		}
	}

	return true;
}

bool RangeSetTests() {
	int checkIndex = 0;

	CHECK(RangeSetModify(true, 2, 3));
	CHECK(RangeSetModify(true, 4, 5));
	CHECK(RangeSetModify(true, 0, 1));
	CHECK(RangeSetModify(true, 1, 2));
	CHECK(RangeSetModify(true, 3, 4));
	CHECK(RangeSetModify(true, 10, 15));
	CHECK(RangeSetModify(true, 4, 10));
	CHECK(RangeSetModify(true, 20, 30));
	CHECK(RangeSetModify(true, 15, 21));
	CHECK(RangeSetModify(true, 50, 55));
	CHECK(RangeSetModify(true, 60, 65));
	CHECK(RangeSetModify(true, 40, 70));
	CHECK(RangeSetModify(true, 0, 100));

	CHECK(RangeSetModify(false, 50, 60));
	CHECK(RangeSetModify(false, 55, 56));
	CHECK(RangeSetModify(false, 50, 55));
	CHECK(RangeSetModify(false, 55, 60));
	CHECK(RangeSetModify(false, 50, 60));
	CHECK(RangeSetModify(false, 49, 60));
	CHECK(RangeSetModify(false, 49, 61));

	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(false, 48, 62));
	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(false, 48, 62));
	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(true, 52, 53));
	CHECK(RangeSetModify(false, 48, 62));
	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(true, 52, 53));
	CHECK(RangeSetModify(false, 47, 62));
	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(true, 52, 53));
	CHECK(RangeSetModify(false, 47, 63));
	CHECK(RangeSetModify(true, 50, 51));
	CHECK(RangeSetModify(true, 52, 53));
	CHECK(RangeSetModify(false, 46, 64));

	EsRandomSeed(20);

	for (uintptr_t i = 0; i < 100000; i++) {
		int a = EsRandomU64() % 1000, b = EsRandomU64() % 1000;
		if (b <= a) continue;
		CHECK(RangeSetModify(EsRandomU8() & 1, a, b));
	}

	return true;
}

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

bool UTF8Tests() {
	int checkIndex = 0;

	// Strings taken from https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt under CC BY 4.0.

	const char *goodStrings[] = {
		"\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5",
		"\x01",
		"\xC2\x80",
		"\xE0\xA0\x80",
		"\xF0\x90\x80\x80",
		"\x7F",
		"\xDF\xBF",
		"\xEF\xBF\xBF", 
		"\xF7\xBF\xBF\xBF", 
		"\xED\x9F\xBF", 
		"\xEE\x80\x80", 
		"\xEF\xBF\xBD", 
		"\xF4\x8F\xBF\xBF", 
		"\xF4\x90\x80\x80",

		// Overlong sequences for non-ASCII characters are allowed.
		"\xE0\x9F\xBF",
		"\xF0\x8F\xBF\xBF",

		// Surrogate characters are allowed (for compatability with things like NTFS).
		"\xED\xA0\x80",
		"\xED\xAD\xBF",
		"\xED\xAE\x80",
		"\xED\xAF\xBF",
		"\xED\xB0\x80",
		"\xED\xBE\x80",
		"\xED\xBF\xBF",
		"\xED\xA0\x80\xED\xB0\x80",
		"\xED\xA0\x80\xED\xBF\xBF",
		"\xED\xAD\xBF\xED\xB0\x80",
		"\xED\xAD\xBF\xED\xBF\xBF",
		"\xED\xAE\x80\xED\xB0\x80",
		"\xED\xAE\x80\xED\xBF\xBF",
		"\xED\xAF\xBF\xED\xB0\x80",
		"\xED\xAF\xBF\xED\xBF\xBF",
	};

	const char *badStrings[] = {
		// We don't support 5 and 6 byte characters, as they shouldn't appear in Unicode text.
		"\xF8\x88\x80\x80\x80",
		"\xFC\x84\x80\x80\x80\x80",
		"\xFB\xBF\xBF\xBF\xBF", 
		"\xFD\xBF\xBF\xBF\xBF\xBF",

		"\x80",
		"\xBF",
		"\x80\xBF",
		"\x80\xBF\x80",
		"\x80\xBF\x80\xBF",
		"\x80\xBF\x80\xBF\x80",
		"\x80\xBF\x80\xBF\x80\xBF\x80",
		"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96"
			"\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB"
			"\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF",
		"\xC0\x20\xC1\x20\xC2\x20\xC3\x20\xC4\x20\xC5\x20\xC6\x20\xC7\x20\xC8\x20\xC9\x20\xCA\x20\xCB"
			"\x20\xCC\x20\xCD\x20\xCE\x20\xCF\x20\xD0\x20\xD1\x20\xD2\x20\xD3\x20\xD4\x20\xD5\x20"
			"\xD6\x20\xD7\x20\xD8\x20\xD9\x20\xDA\x20\xDB\x20\xDC\x20\xDD\x20\xDE\x20\xDF\x20",
		"\xE0\x20\xE1\x20\xE2\x20\xE3\x20\xE4\x20\xE5\x20\xE6\x20\xE7\x20\xE8\x20\xE9\x20\xEA\x20\xEB"
			"\x20\xEC\x20\xED\x20\xEE\x20\xEF\x20",
		"\xF0\x20\xF1\x20\xF2\x20\xF3\x20\xF4\x20\xF5\x20\xF6\x20\xF7\x20",
		"\xF8\x20\xF9\x20\xFA\x20\xFB\x20",
		"\xFC\x20\xFD\x20",
		"\xC0",
		"\xE0\x80",
		"\xF0\x80\x80",
		"\xF8\x80\x80\x80",
		"\xFC\x80\x80\x80\x80",
		"\xDF",
		"\xEF\xBF",
		"\xF7\xBF\xBF",
		"\xFB\xBF\xBF\xBF",
		"\xFD\xBF\xBF\xBF\xBF",
		"\xC0\xE0\x80\xF0\x80\x80\xF8\x80\x80\x80\xFC\x80\x80\x80\x80\xDF\xEF\xBF\xF7\xBF\xBF\xFB\xBF"
			"\xBF\xBF\xFD\xBF\xBF\xBF\xBF",
		"\xFE",
		"\xFF",
		"\xFE\xFE\xFF\xFF",
		"\xC0\xAF",
		"\xE0\x80\xAF",
		"\xF0\x80\x80\xAF",
		"\xF8\x80\x80\x80\xAF",
		"\xFC\x80\x80\x80\x80\xAF",
		"\xC1\xBF",
		"\xC0\x80",
		"\xE0\x80\x80",
		"\xF0\x80\x80\x80",
		"\xF8\x80\x80\x80\x80",
		"\xFC\x80\x80\x80\x80\x80",
	};

	for (uintptr_t i = 0; i < sizeof(goodStrings) / sizeof(goodStrings[0]); i++) {
		CHECK(EsUTF8IsValid(goodStrings[i], -1));

		const char *position = goodStrings[i];
		
		while (*position) {
			CHECK(utf8_value(position));
			position = utf8_advance(position);
			CHECK(position);
		}

		while (position != goodStrings[i]) {
			position = utf8_retreat(position);
			CHECK(position);
		}
	}

	for (uintptr_t i = 0; i < sizeof(badStrings) / sizeof(badStrings[0]); i++) {
		CHECK(!EsUTF8IsValid(badStrings[i], -1));
	}

	return true;
}

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

EsHandle pipeRead, pipeWrite;

void PipeTestsThread2(EsGeneric) {
	for (uint16_t i = 0; i < 1000; i++) {
		EsPipeWrite(pipeWrite, &i, sizeof(i));
	}

	uint16_t *buffer = (uint16_t *) EsHeapAllocate(10000, false);

	for (uint16_t i = 0; i < 1000; i++) {
		for (uintptr_t i = 0; i < 5000; i++) buffer[i] = i;
		EsPipeWrite(pipeWrite, buffer, 10000);
	}

	uint16_t s = 0x1234;
	EsPipeWrite(pipeWrite, &s, sizeof(s));
	EsSleep(2000);
	s = 0xFEDC;
	EsPipeWrite(pipeWrite, &s, sizeof(s));
	EsHandleClose(pipeWrite);

	EsHeapFree(buffer);
}

void PipeTestsThread3(EsGeneric) {
	uint8_t data[200];
	EsPipeRead(pipeRead, data, sizeof(data), false);
	EsHandleClose(pipeRead);
}

bool PipeTests() {
	EsPipeCreate(&pipeRead, &pipeWrite);

	int checkIndex = 0;
	EsThreadInformation information;
	CHECK(EsThreadCreate(PipeTestsThread2, &information, nullptr) == ES_SUCCESS);
	EsHandleClose(information.handle);

	for (uint16_t i = 0; i < 1000; i++) {
		uint16_t j;
		CHECK(sizeof(j) == EsPipeRead(pipeRead, &j, sizeof(j), true));
		CHECK(i == j);
	}

	uint16_t *buffer = (uint16_t *) EsHeapAllocate(10000, false);

	for (uint16_t i = 0; i < 1000; i++) {
		EsMemoryZero(buffer, 10000);
		uintptr_t position = 0;
		
		while (position < 10000) {
			size_t read = EsPipeRead(pipeRead, (uint8_t *) buffer + position, 10000 - position, i >= 500);
			if (i < 500) CHECK(read == 10000);
			CHECK(read);
			position += read;
		}

		CHECK(position == 10000);

		for (uintptr_t i = 0; i < 5000; i++) CHECK(buffer[i] == i);
	}

	EsSleep(1000);

	uint32_t s = 0x5678ABCD;
	CHECK(2 == EsPipeRead(pipeRead, &s, sizeof(s), true));
	CHECK(s == 0x56781234); // TODO Big endian support.
	s = 0x5678ABCD;
	CHECK(2 == EsPipeRead(pipeRead, &s, sizeof(s), false));
	CHECK(s == 0x5678FEDC); // TODO Big endian support.
	CHECK(0 == EsPipeRead(pipeRead, &s, sizeof(s), false));

	EsHandleClose(pipeRead);

	EsPipeCreate(&pipeRead, &pipeWrite);
	CHECK(EsThreadCreate(PipeTestsThread3, &information, nullptr) == ES_SUCCESS);
	EsHandleClose(information.handle);
	size_t written = EsPipeWrite(pipeWrite, buffer, 10000);
	CHECK(written > 0 && written < 10000); // The actual amountn written depends on the size of the internal pipe buffer, and whether the read happens in time.
	CHECK(0 == EsPipeWrite(pipeWrite, buffer, 10000));
	EsHandleClose(pipeWrite);

	EsHeapFree(buffer);

	return true;
}

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

#include <bits/syscall.h>

#define _exit(x)          EsPOSIXSystemCall(SYS_exit_group, (intptr_t) x, 0, 0, 0, 0, 0)
#define close(x)          EsPOSIXSystemCall(SYS_close, (intptr_t) x, 0, 0, 0, 0, 0)
#define dup2(x, y)        EsPOSIXSystemCall(SYS_dup2, (intptr_t) x, (intptr_t) y, 0, 0, 0, 0)
#define execve(x, y, z)   EsPOSIXSystemCall(SYS_execve, (intptr_t) x, (intptr_t) y, (intptr_t) z, 0, 0, 0)
#define exit(x)           EsPOSIXSystemCall(SYS_exit_group, (intptr_t) x, 0, 0, 0, 0, 0)
#define pipe(x)           EsPOSIXSystemCall(SYS_pipe, (intptr_t) x, 0, 0, 0, 0, 0)
#define read(x, y, z)     EsPOSIXSystemCall(SYS_read, (intptr_t) x, (intptr_t) y, (intptr_t) z, 0, 0, 0)
#define rename(x, y)      EsPOSIXSystemCall(SYS_rename, (intptr_t) x, (intptr_t) y, 0, 0, 0, 0)
#define truncate(x, y)    EsPOSIXSystemCall(SYS_truncate, (intptr_t) x, (intptr_t) y, 0, 0, 0, 0)
#define unlink(x)         EsPOSIXSystemCall(SYS_unlink, (intptr_t) x, 0, 0, 0, 0, 0)
#define vfork()           EsPOSIXSystemCall(SYS_vfork, 0, 0, 0, 0, 0, 0)
#define wait4(x, y, z, w) EsPOSIXSystemCall(SYS_wait4, (intptr_t) x, (intptr_t) y, (intptr_t) z, (intptr_t) w, 0, 0)

bool POSIXSubsystemRunCommandAndCheckOutput(const char **executeEnvironment, const char **argv, 
		const char *executable, const char *expectedOutput) {
	int stdoutPipe[2];
	pipe(stdoutPipe);

	long pid = vfork();

	if (pid == 0) {
		dup2(stdoutPipe[1], 1);
		dup2(stdoutPipe[1], 2);
		close(stdoutPipe[1]);
		execve(executable, argv, executeEnvironment);
		EsPrint("Could not execve.\n");
		return false;
	} else if (pid > 0) {
		close(stdoutPipe[1]);
		char readData[4096];
		int readPosition = 0;

		while (true) {
			intptr_t bytesRead = read(stdoutPipe[0], readData + readPosition, sizeof(readData) - readPosition);

			if (bytesRead <= 0) {
				break;
			} else {
				readPosition += bytesRead;
			}
		}

		if (readPosition != (int) EsCStringLength(expectedOutput) 
				|| EsMemoryCompare(readData, expectedOutput, readPosition)) {
			EsPrint("Incorrect output: '%s'.\n", readPosition, readData);
			return false;
		}

		int status;
		wait4(-1, &status, 0, NULL);

		if (status) {
			EsPrint("status = %d.\n", status);
			return false;
		}
	} else {
		EsPrint("Could not vfork.\n");
		return false;
	}

	close(stdoutPipe[0]);
	return true;
}

bool POSIXSubsystemTest() {
	int checkIndex = 0;

	const char *executeEnvironment[] = {
		"PATH=/Applications/POSIX/bin",
		"TMPDIR=/Applications/POSIX/tmp",
		NULL,
	};

	int _argc; 
	char **_argv;
	EsPOSIXInitialise(&_argc, &_argv);

	const char *executable = "/Applications/POSIX/bin/busybox";
	const char *argv[] = { "busybox", "sh", "test.sh", NULL, };

	EsFileWriteAll(EsLiteral("|POSIX:/test.sh"), EsLiteral("echo hello")); 
	CHECK(POSIXSubsystemRunCommandAndCheckOutput(executeEnvironment, argv, executable, "hello\n"));
	EsFileWriteAll(EsLiteral("|POSIX:/test.sh"), EsLiteral("echo world")); 
	CHECK(POSIXSubsystemRunCommandAndCheckOutput(executeEnvironment, argv, executable, "world\n"));
	EsFileWriteAll(EsLiteral("|POSIX:/test.sh"), EsLiteral("find . | grep Kernel.esx")); 
	CHECK(POSIXSubsystemRunCommandAndCheckOutput(executeEnvironment, argv, executable, "./Essence/Kernel.esx\n"));

	EsFileWriteAll(EsLiteral("|POSIX:/test.sh"), EsLiteral("exit")); 

	for (uintptr_t i = 0; i < 1000; i++) {
		CHECK(POSIXSubsystemRunCommandAndCheckOutput(executeEnvironment, argv, executable, ""));
	}

	return true;
}

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

bool RestartTest() {
	size_t fileSize;
	uint32_t *fileData = (uint32_t *) EsFileReadAll(EsLiteral("|Settings:/restart_test.txt"), &fileSize);
	uint32_t index = fileSize == sizeof(uint32_t) ? *fileData : 0;
#ifdef DEBUG_BUILD
	if (index == 30) return true;
#else
	if (index == 200) return true;
#endif
	index++;
	if (ES_SUCCESS != EsFileWriteAll(EsLiteral("|Settings:/restart_test.txt"), &index, sizeof(uint32_t))) return false;
	EsPrint("Restart %d...\n", index);
	EsSyscall(ES_SYSCALL_SHUTDOWN, ES_SHUTDOWN_ACTION_RESTART, 0, 0, 0);
	while (EsMessageReceive());
	return false;
}

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

bool ResizeFileTest() {
	int checkIndex = 0;

	size_t dataBytes = 2 + 2 * 4096 + 2 * 262144;
	uint8_t *data = (uint8_t *) EsHeapAllocate(dataBytes, false);
	uint8_t *compare = (uint8_t *) EsHeapAllocate(dataBytes, false);
	EsFileOffset currentSize = 0;

	// Make a list of all the file sizes that should cover the interesting cases.

	EsFileOffset sizes[125];
	size_t sizeCount = 0;

	for (int i = -2; i <= 2; i++) {
		for (int j = -2; j <= 2; j++) {
			for (int k = -2; k <= 2; k++) {
				EsFileOffsetDifference size = k + j * 4096 /* page size */ + i * 262144 /* active section size */;
				if (size < 0) continue;
				EsAssert(sizeCount != sizeof(sizes) / sizeof(sizes[0]));
				EsAssert((uintptr_t) size <= dataBytes);
				sizes[sizeCount++] = size;
			}
		}
	}

	EsFileInformation file = EsFileOpen(EsLiteral("|Settings:/resize.txt"), ES_FILE_WRITE | ES_NODE_FAIL_IF_FOUND);
	CHECK(file.error == ES_SUCCESS);

	for (uintptr_t i = 0; i < sizeCount; i++) {
		for (uintptr_t j = 0; j < sizeCount; j++) {
			EsPrint("i %x, j %x\n", sizes[i], sizes[j]);

			CHECK(ES_SUCCESS == EsFileResize(file.handle, sizes[i]));
			if (sizes[i] > currentSize) EsMemoryZero(data + currentSize, sizes[i] - currentSize);
			currentSize = sizes[i];
			CHECK(currentSize == EsFileReadSync(file.handle, 0, dataBytes, compare));
			CHECK(0 == EsMemoryCompare(data, compare, currentSize));

			CHECK(ES_SUCCESS == EsFileResize(file.handle, sizes[j]));
			if (sizes[j] > currentSize) EsMemoryZero(data + currentSize, sizes[j] - currentSize);
			currentSize = sizes[j];
			CHECK(currentSize == EsFileReadSync(file.handle, 0, dataBytes, compare));
			CHECK(0 == EsMemoryCompare(data, compare, currentSize));

			for (uintptr_t k = 0; k < currentSize; k++) {
				data[k] = EsRandomU8();
			}

			EsFileWriteSync(file.handle, 0, currentSize, data);
		}
	}

	EsHandleClose(file.handle);
	EsHeapFree(data);
	EsHeapFree(compare);
	return true;
}

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

bool FileContentTypeTest() {
	int checkIndex = 0;
	
	EsUniqueIdentifier identifier1, identifier2;
	for (uintptr_t i = 0; i < 16; i++) identifier1.d[i] = i;
	for (uintptr_t i = 0; i < 16; i++) identifier2.d[i] = 0;

	size_t fileSize;
	uint32_t *fileData = (uint32_t *) EsFileReadAll(EsLiteral("|Settings:/continue.txt"), &fileSize);

	if (fileData) {
		size_t count;
		EsError error;
		EsDirectoryChild *array = EsDirectoryEnumerate(EsLiteral("|Settings:/test"), &count, &error);
		CHECK(error == ES_SUCCESS);
		CHECK(count == 1);
		CHECK(0 == EsMemoryCompare(&identifier1, &array[0].contentType, sizeof(EsUniqueIdentifier)));
		EsFileInformation information = EsFileOpen(EsLiteral("|Settings:/test/a"), ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES);
		CHECK(information.error == ES_SUCCESS);
		CHECK(0 == EsMemoryCompare(&identifier1, &information.contentType, sizeof(EsUniqueIdentifier)));
	} else {
		uint32_t c = 1;
		CHECK(ES_SUCCESS == EsFileWriteAll(EsLiteral("|Settings:/continue.txt"), &c, sizeof(uint32_t)));

		EsFileInformation information = EsFileOpen(EsLiteral("|Settings:/test/a"), ES_FILE_WRITE | ES_NODE_CREATE_DIRECTORIES);
		CHECK(information.error == ES_SUCCESS);
		CHECK(0 == EsMemoryCompare(&identifier2, &information.contentType, sizeof(EsUniqueIdentifier)));
		CHECK(ES_SUCCESS == EsFileControl(information.handle, ES_FILE_CONTROL_SET_CONTENT_TYPE, &identifier1, sizeof(identifier1)));

		EsSyscall(ES_SYSCALL_SHUTDOWN, ES_SHUTDOWN_ACTION_RESTART, 0, 0, 0);
		while (EsMessageReceive());
	}

	return true;
}

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

#endif

const Test tests[] = {
	TEST(BasicFileOperations, 60),
	TEST(CRTMathFunctions, 60),
	TEST(CRTStringFunctions, 60),
	TEST(CRTOtherFunctions, 60),
	TEST(PerformanceTimerDrift, 60),
	TEST(TextboxEditOperations, 240),
	TEST(OldTests2018, 60),
	TEST(HeapReallocate, 60),
	TEST(ArenaRandomAllocations, 60),
	TEST(RangeSetTests, 60),
	TEST(UTF8Tests, 60),
	TEST(PipeTests, 60),
	TEST(POSIXSubsystemTest, 120),
	TEST(RestartTest, 1200),
	TEST(ResizeFileTest, 600),
	TEST(FileContentTypeTest, 60),
};

#ifndef API_TESTS_FOR_RUNNER

void RunTests() {
	size_t fileSize;
	EsError error;
	void *fileData = EsFileReadAll(EsLiteral("|Settings:/test.dat"), &fileSize, &error); 

	if (error == ES_ERROR_FILE_DOES_NOT_EXIST) {
		return; // Not in test mode.
	} else if (error != ES_SUCCESS) {
		EsPrint("Could not read test.dat (error %d).\n", error);
	} else if (fileSize != sizeof(uint32_t) * 2) {
		EsPrint("test.dat is the wrong size (got %d, expected %d).\n", fileSize, sizeof(uint32_t));
	} else {
		uint32_t index, mode;
		EsMemoryCopy(&index, fileData, sizeof(uint32_t));
		EsMemoryCopy(&mode, (uint8_t *) fileData + sizeof(uint32_t), sizeof(uint32_t));
		EsHeapFree(fileData);
		
		if (index >= sizeof(tests) / sizeof(tests[0])) {
			EsPrint("Test index out of bounds.\n");
		} else if (tests[index].callback()) {
			EsPrint("[APITests-Success]\n");
		} else {
			EsPrint("[APITests-Failure]\n");
			if (~mode & 1) EsSyscall(ES_SYSCALL_DEBUG_COMMAND, 2, 0, 0, 0);
		}
	}

	EsSyscall(ES_SYSCALL_SHUTDOWN, ES_SHUTDOWN_ACTION_POWER_OFF, 0, 0, 0);
	EsProcessTerminateCurrent();
}

void _start() {
	_init();

	while (true) {
		EsMessage *message = EsMessageReceive();

		if (message->type == ES_MSG_INSTANCE_CREATE) {
			EsInstance *instance = EsInstanceCreate(message, EsLiteral("API Tests"));
			EsApplicationStartupRequest request = EsInstanceGetStartupRequest(instance);

			if (request.flags & ES_APPLICATION_STARTUP_BACKGROUND_SERVICE) {
				RunTests();
			}
		}
	}
}

#endif