diff --git a/test/testqsort.c b/test/testqsort.c index 6230e74657952..33987cce8b5d2 100644 --- a/test/testqsort.c +++ b/test/testqsort.c @@ -14,10 +14,22 @@ #include #include +typedef struct { + Uint8 major; + Uint8 minor; + Uint8 micro; +} VersionTuple; + static int a_global_var = 77; +static int (SDLCALL * global_compare_cbfn)(const void *_a, const void *_b); + +static unsigned long arraylens[16] = { 12, 1024 * 100 }; +static unsigned int count_arraylens = 2; + +static bool slow_checks; static int SDLCALL -num_compare(const void *_a, const void *_b) +compare_int(const void *_a, const void *_b) { const int a = *((const int *)_a); const int b = *((const int *)_b); @@ -25,51 +37,338 @@ num_compare(const void *_a, const void *_b) } static int SDLCALL -num_compare_r(void *userdata, const void *a, const void *b) +compare_float(const void *_a, const void *_b) +{ + const float a = *((const float *)_a); + const float b = *((const float *)_b); + return (a < b) ? -1 : ((a > b) ? 1 : 0); +} + +static int SDLCALL +compare_double(const void *_a, const void *_b) +{ + const double a = *((const double *)_a); + const double b = *((const double *)_b); + return (a < b) ? -1 : ((a > b) ? 1 : 0); +} + +static int SDLCALL +compare_intptr(const void *_a, const void *_b) +{ + const int* a = *((const int **)_a); + const int* b = *((const int **)_b); + return compare_int(a, b); +} + +static int SDLCALL +compare_version(const void *_a, const void *_b) +{ + const VersionTuple *a = ((const VersionTuple *)_a); + const VersionTuple *b = ((const VersionTuple *)_b); + int d; + d = (int)a->major - (int)b->major; + if (d != 0) { + return d; + } + d = (int)a->minor - (int)b->minor; + if (d != 0) { + return d; + } + return (int)a->micro - (int)b->micro; +} + +static int SDLCALL +generic_compare_r(void *userdata, const void *a, const void *b) { if (userdata != &a_global_var) { - SDL_Log("Uhoh, invalid userdata during qsort!"); + SDLTest_AssertCheck(userdata == &a_global_var, "User data of callback must be identical to global data"); } - return num_compare(a, b); + return global_compare_cbfn(a, b); } -static void -test_sort(const char *desc, int *nums, const int arraylen) +#define STR2(S) "[" #S "] " +#define STR(S) STR2(S) + +#define TEST_ARRAY_IS_SORTED(TYPE, ARRAY, SIZE, IS_LE) \ + do { \ + size_t sorted_index; \ + Uint64 count_non_sorted = 0; \ + for (sorted_index = 0; sorted_index < (SIZE) - 1; sorted_index++) { \ + if (!IS_LE((ARRAY)[sorted_index], (ARRAY)[sorted_index + 1])) { \ + count_non_sorted += 1; \ + } \ + } \ + SDLTest_AssertCheck(count_non_sorted == 0, \ + STR(TYPE) "Array (size=%d) is sorted (bad count=%" SDL_PRIu64 ")", (SIZE), count_non_sorted); \ + } while (0) + +/* This test is O(n^2), so very slow (a hashmap can speed this up): + * - we cannot trust qsort + * - the arrays can contain duplicate items + * - the arrays are not int + */ +#define TEST_ARRAY_LOST_NO_ELEMENTS(TYPE, ORIGINAL, SORTED, SIZE, IS_SAME) \ + do { \ + size_t original_index; \ + bool *original_seen = SDL_calloc((SIZE), sizeof(bool)); \ + Uint64 lost_count = 0; \ + SDL_assert(original_seen != NULL); \ + for (original_index = 0; original_index < (SIZE); original_index++) { \ + size_t sorted_index; \ + for (sorted_index = 0; sorted_index < (SIZE); sorted_index++) { \ + if (IS_SAME((ORIGINAL)[original_index], (SORTED)[sorted_index])) { \ + original_seen[original_index] = true; \ + break; \ + } \ + } \ + } \ + for (original_index = 0; original_index < (SIZE); original_index++) { \ + if (!original_seen[original_index]) { \ + SDLTest_AssertCheck(original_seen[original_index], \ + STR(TYPE) "Element %d is missing in the sorted array", (int)original_index); \ + lost_count += 1; \ + } \ + } \ + SDLTest_AssertCheck(lost_count == 0, \ + STR(TYPE) "No elements were lost (lost count=%" SDL_PRIu64 ")", lost_count); \ + SDL_free(original_seen); \ + } while (0) + +#define TEST_QSORT_ARRAY_GENERIC(TYPE, ARRAY, SIZE, QSORT_CALL, IS_LE, IS_SAME) \ + SDL_memcpy(copy, (ARRAY), sizeof(TYPE) * (SIZE)); \ + SDLTest_AssertPass(STR(TYPE) "About to call SDL_qsort(%d, %d)", \ + (int)(SIZE), (int)sizeof(TYPE)); \ + QSORT_CALL; \ + SDLTest_AssertPass(STR(TYPE) "SDL_qsort() finished"); \ + TEST_ARRAY_IS_SORTED(TYPE, copy, SIZE, IS_LE); \ + if (slow_checks) { \ + TEST_ARRAY_LOST_NO_ELEMENTS(TYPE, ARRAY, copy, SIZE, IS_SAME); \ + } \ + +#define TEST_QSORT_ARRAY_QSORT(TYPE, ARRAY, SIZE, COMPARE_CBFN, IS_LE, IS_SAME) \ + do { \ + SDLTest_AssertPass(STR(TYPE) "Testing SDL_qsort of array with size %u", (unsigned)(SIZE)); \ + global_compare_cbfn = NULL; \ + TEST_QSORT_ARRAY_GENERIC(TYPE, ARRAY, SIZE, \ + SDL_qsort(copy, (SIZE), sizeof(TYPE), COMPARE_CBFN), \ + IS_LE, IS_SAME); \ + } while (0); + +#define TEST_QSORT_ARRAY_QSORT_R(TYPE, ARRAY, SIZE, COMPARE_CBFN, IS_LE, IS_SAME) \ + do { \ + SDLTest_AssertPass(STR(TYPE) "Testing SDL_qsort_r of array with size %u", (unsigned)(SIZE)); \ + global_compare_cbfn = (COMPARE_CBFN); \ + TEST_QSORT_ARRAY_GENERIC(TYPE, ARRAY, SIZE, \ + SDL_qsort_r(copy, (SIZE), sizeof(TYPE), generic_compare_r, &a_global_var), \ + IS_LE, IS_SAME); \ + } while (0); + +#define TEST_QSORT_ARRAY(TYPE, ARRAY, SIZE, COMPARE_CBFN, IS_LE, IS_SAME) \ + do { \ + TYPE *copy = SDL_calloc((SIZE), sizeof(TYPE)); \ + SDL_assert(copy != NULL); \ + \ + TEST_QSORT_ARRAY_QSORT(TYPE, ARRAY, SIZE, COMPARE_CBFN, IS_LE, IS_SAME); \ + \ + TEST_QSORT_ARRAY_QSORT_R(TYPE, ARRAY, SIZE, COMPARE_CBFN, IS_LE, IS_SAME); \ + \ + SDL_free(copy); \ + } while (0) + +#define INT_ISLE(A, B) ((A) <= (B)) +#define INT_ISSAME(A, B) ((A) == (B)) + +#define INTPTR_ISLE(A, B) (*(A) <= *(B)) +#define INTPTR_ISSAME(A, B) ((uintptr_t)(A) == (uintptr_t)(B)) + +#define FLOAT_ISLE(A, B) ((A) <= (B)) +static bool FLOAT_ISSAME(float a, float b) { + Uint32 repr_a = *(Uint32*)&a; + Uint32 repr_b = *(Uint32*)&b; + return repr_a == repr_b; +} + +#define DOUBLE_ISLE(A, B) ((A) <= (B)) +static bool DOUBLE_ISSAME(double a, double b) { + Uint64 repr_a = *(Uint64*)&a; + Uint64 repr_b = *(Uint64*)&b; + return repr_a == repr_b; +} + +#define VERSION_ISLE(A, B) (compare_version(&(A), &(B)) <= 0) +#define VERSION_ISSAME(A, B) (compare_version(&(A), &(B)) == 0) + +static int SDLCALL qsort_testAlreadySorted(void *arg) { - static int nums_copy[1024 * 100]; - int i; - int prev; + unsigned int iteration; + (void)arg; - SDL_assert(SDL_arraysize(nums_copy) >= arraylen); + for (iteration = 0; iteration < count_arraylens; iteration++) { + const unsigned int arraylen = arraylens[iteration]; + unsigned int i; + int *ints = SDL_malloc(sizeof(int) * arraylen); + int **intptrs = SDL_malloc(sizeof(int *) * arraylen); + double *doubles = SDL_malloc(sizeof(double) * arraylen); - SDL_Log("test: %s arraylen=%d", desc, arraylen); + for (i = 0; i < arraylen; i++) { + ints[i] = i; + intptrs[i] = &ints[i]; + doubles[i] = (double)i * SDL_PI_D; + } + TEST_QSORT_ARRAY(int, ints, arraylen, compare_int, INT_ISLE, INT_ISSAME); + TEST_QSORT_ARRAY(int *, intptrs, arraylen, compare_intptr, INTPTR_ISLE, INTPTR_ISSAME); + TEST_QSORT_ARRAY(double, doubles, arraylen, compare_double, DOUBLE_ISLE, DOUBLE_ISSAME); - SDL_memcpy(nums_copy, nums, arraylen * sizeof (*nums)); + SDL_free(ints); + SDL_free(intptrs); + SDL_free(doubles); + } + return TEST_COMPLETED; +} - SDL_qsort(nums, arraylen, sizeof(nums[0]), num_compare); - SDL_qsort_r(nums_copy, arraylen, sizeof(nums[0]), num_compare_r, &a_global_var); +static int SDLCALL qsort_testAlreadySortedExceptLast(void *arg) +{ + unsigned int iteration; + (void)arg; - prev = nums[0]; - for (i = 1; i < arraylen; i++) { - const int val = nums[i]; - const int val2 = nums_copy[i]; - if ((val < prev) || (val != val2)) { - SDL_Log("sort is broken!"); - return; + for (iteration = 0; iteration < count_arraylens; iteration++) { + const unsigned int arraylen = arraylens[iteration]; + unsigned int i; + int *ints = SDL_malloc(sizeof(int) * arraylen); + int **intptrs = SDL_malloc(sizeof(int *) * arraylen); + double *doubles = SDL_malloc(sizeof(double) * arraylen); + VersionTuple *versions = SDL_malloc(sizeof(VersionTuple) * arraylen); + + for (i = 0; i < arraylen; i++) { + ints[i] = i; + intptrs[i] = &ints[i]; + doubles[i] = (double)i * SDL_PI_D; + versions[i].micro = i % 256; + versions[i].minor = i % (256 * 256) / 256; + versions[i].major = i % (256 * 256 * 256) / 256 / 256; } - prev = val; + ints[arraylen - 1] = -1; + doubles[arraylen - 1] = -1.; + TEST_QSORT_ARRAY(int, ints, arraylen, compare_int, INT_ISLE, INT_ISSAME); + TEST_QSORT_ARRAY(int *, intptrs, arraylen, compare_intptr, INTPTR_ISLE, INTPTR_ISSAME); + TEST_QSORT_ARRAY(double, doubles, arraylen, compare_double, DOUBLE_ISLE, DOUBLE_ISSAME); + TEST_QSORT_ARRAY(VersionTuple, versions, arraylen, compare_version, VERSION_ISLE, VERSION_ISSAME); + + SDL_free(ints); + SDL_free(intptrs); + SDL_free(doubles); + SDL_free(versions); + } + return TEST_COMPLETED; +} + +static int SDLCALL qsort_testReverseSorted(void *arg) +{ + unsigned int iteration; + (void)arg; + + for (iteration = 0; iteration < count_arraylens; iteration++) { + const unsigned int arraylen = arraylens[iteration]; + unsigned int i; + int *ints = SDL_malloc(sizeof(int) * arraylen); + int **intptrs = SDL_malloc(sizeof(int *) * arraylen); + double *doubles = SDL_malloc(sizeof(double) * arraylen); + VersionTuple *versions = SDL_malloc(sizeof(VersionTuple) * arraylen); + + for (i = 0; i < arraylen; i++) { + ints[i] = (arraylen - 1) - i; + intptrs[i] = &ints[i]; + doubles[i] = (double)((arraylen - 1) - i) * SDL_PI_D; + versions[i].micro = ints[i] % 256; + versions[i].minor = ints[i] % (256 * 256) / 256; + versions[i].major = ints[i] % (256 * 256 * 256) / 256 / 256; + } + TEST_QSORT_ARRAY(int, ints, arraylen, compare_int, INT_ISLE, INT_ISSAME); + TEST_QSORT_ARRAY(int *, intptrs, arraylen, compare_intptr, INTPTR_ISLE, INTPTR_ISSAME); + TEST_QSORT_ARRAY(double, doubles, arraylen, compare_double, DOUBLE_ISLE, DOUBLE_ISSAME); + TEST_QSORT_ARRAY(VersionTuple, versions, arraylen, compare_version, VERSION_ISLE, VERSION_ISSAME); + + SDL_free(ints); + SDL_free(intptrs); + SDL_free(doubles); + SDL_free(versions); + } + return TEST_COMPLETED; +} + +static int SDLCALL qsort_testRandomSorted(void *arg) +{ + unsigned int iteration; + (void)arg; + + for (iteration = 0; iteration < count_arraylens; iteration++) { + const unsigned int arraylen = arraylens[iteration]; + unsigned int i; + int *ints = SDL_malloc(sizeof(int) * arraylen); + float *floats = SDL_malloc(sizeof(float) * arraylen); + VersionTuple *versions = SDL_malloc(sizeof(VersionTuple) * arraylen); + + for (i = 0; i < arraylen; i++) { + ints[i] = SDLTest_RandomIntegerInRange(0, 1000000); + floats[i] = SDLTest_RandomFloat() * SDL_PI_F; + versions[i].micro = SDLTest_RandomIntegerInRange(0, 256); + versions[i].minor = SDLTest_RandomIntegerInRange(0, 256); + versions[i].major = SDLTest_RandomIntegerInRange(0, 256); + } + TEST_QSORT_ARRAY(int, ints, arraylen, compare_int, INT_ISLE, INT_ISSAME); + TEST_QSORT_ARRAY(float, floats, arraylen, compare_float, FLOAT_ISLE, FLOAT_ISSAME); + TEST_QSORT_ARRAY(VersionTuple, versions, arraylen, compare_version, VERSION_ISLE, VERSION_ISSAME); + + SDL_free(ints); + SDL_free(floats); + SDL_free(versions); } + return TEST_COMPLETED; } +static const SDLTest_TestCaseReference qsortTestAlreadySorted = { + qsort_testAlreadySorted, "qsort_testAlreadySorted", "Test sorting already sorted array", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference qsortTestAlreadySortedExceptLast = { + qsort_testAlreadySortedExceptLast, "qsort_testAlreadySortedExceptLast", "Test sorting nearly sorted array (last item is not in order)", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference qsortTestReverseSorted = { + qsort_testReverseSorted, "qsort_testReverseSorted", "Test sorting an array in reverse order", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference qsortTestRandomSorted = { + qsort_testRandomSorted, "qsort_testRandomSorted", "Test sorting a random array", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference *qsortTests[] = { + &qsortTestAlreadySorted, + &qsortTestAlreadySortedExceptLast, + &qsortTestReverseSorted, + &qsortTestRandomSorted, + NULL +}; + +static SDLTest_TestSuiteReference qsortTestSuite = { + "qsort", + NULL, + qsortTests, + NULL +}; + +static SDLTest_TestSuiteReference *testSuites[] = { + &qsortTestSuite, + NULL +}; + int main(int argc, char *argv[]) { - static int nums[1024 * 100]; - static const int itervals[] = { SDL_arraysize(nums), 12 }; int i; - int iteration; + int result; SDLTest_CommonState *state; - Uint64 seed = 0; - int seed_seen = 0; + SDLTest_TestSuiteRunner *runner; + bool list = false; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -77,27 +376,51 @@ int main(int argc, char *argv[]) return 1; } + runner = SDLTest_CreateTestSuiteRunner(state, testSuites); + /* Parse commandline */ for (i = 1; i < argc;) { int consumed; consumed = SDLTest_CommonArg(state, i); if (!consumed) { - if (!seed_seen) { - char *endptr = NULL; - - seed = (Uint64)SDL_strtoull(argv[i], &endptr, 0); - if (endptr != argv[i] && *endptr == '\0') { - seed_seen = 1; - consumed = 1; - } else { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Invalid seed. Use a decimal or hexadecimal number."); - return 1; + + if (SDL_strcasecmp(argv[i], "--array-lengths") == 0) { + count_arraylens = 0; + consumed = 1; + while (argv[i + consumed] && argv[i + 1][0] != '-') { + char *endptr = NULL; + unsigned int arraylen = (unsigned int)SDL_strtoul(argv[i + 1], &endptr, 10); + if (*endptr != '\0') { + count_arraylens = 0; + break; + } + if (count_arraylens >= SDL_arraysize(arraylens)) { + SDL_LogWarn(SDL_LOG_CATEGORY_TEST, "Dropping array length %d", arraylen); + } else { + arraylens[count_arraylens] = arraylen; + } + count_arraylens++; + consumed++; } + if (consumed == 0) { + SDL_LogError(SDL_LOG_CATEGORY_TEST, "--array-lengths needs positive int numbers"); + } + } else if (SDL_strcasecmp(argv[i], "--list") == 0) { + consumed = 1; + list = true; + } else if (SDL_strcmp(argv[i], "--slow-checks") == 0) { + slow_checks = true; + consumed = 1; } } if (consumed <= 0) { - static const char *options[] = { "[seed]", NULL }; + static const char *options[] = { + "[--list]", + "[--array-lengths N1 [N2 [N3 [...]]]", + "[--slow-checks]", + NULL + }; SDLTest_CommonLogUsage(state, argv[0], options); return 1; } @@ -105,38 +428,25 @@ int main(int argc, char *argv[]) i += consumed; } - if (!seed_seen) { - seed = SDL_GetPerformanceCounter(); - } - SDL_Log("Using random seed 0x%" SDL_PRIx64, seed); - - for (iteration = 0; iteration < SDL_arraysize(itervals); iteration++) { - const int arraylen = itervals[iteration]; - - for (i = 0; i < arraylen; i++) { - nums[i] = i; - } - test_sort("already sorted", nums, arraylen); - - for (i = 0; i < arraylen; i++) { - nums[i] = i; - } - nums[arraylen - 1] = -1; - test_sort("already sorted except last element", nums, arraylen); - - for (i = 0; i < arraylen; i++) { - nums[i] = (arraylen - 1) - i; - } - test_sort("reverse sorted", nums, arraylen); - - for (i = 0; i < arraylen; i++) { - nums[i] = SDL_rand_r(&seed, 1000000); + /* List all suites. */ + if (list) { + int suiteCounter; + for (suiteCounter = 0; testSuites[suiteCounter]; ++suiteCounter) { + int testCounter; + SDLTest_TestSuiteReference *testSuite = testSuites[suiteCounter]; + SDL_Log("Test suite: %s", testSuite->name); + for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) { + const SDLTest_TestCaseReference *testCase = testSuite->testCases[testCounter]; + SDL_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)"); + } } - test_sort("random sorted", nums, arraylen); + result = 0; + } else { + result = SDLTest_ExecuteTestSuiteRunner(runner); } SDL_Quit(); + SDLTest_DestroyTestSuiteRunner(runner); SDLTest_CommonDestroyState(state); - - return 0; + return result; }