]> git.ipfire.org Git - thirdparty/git.git/commitdiff
test-mergesort: add test subcommand
authorRené Scharfe <l.s.r@web.de>
Fri, 1 Oct 2021 09:12:27 +0000 (11:12 +0200)
committerJunio C Hamano <gitster@pobox.com>
Fri, 1 Oct 2021 19:43:08 +0000 (12:43 -0700)
Adapt the qsort certification program from "Engineering a Sort Function"
by Bentley and McIlroy for testing our linked list sort function.  It
generates several lists with various distribution patterns and counts
the number of operations llist_mergesort() needs to order them.  It
compares the result to the output of a trusted sort function (qsort(1))
and also checks if the sort is stable.

Also add a test script that makes use of the new subcommand.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
t/helper/test-mergesort.c
t/t0071-sort.sh [new file with mode: 0755]

index 05be0d067ac8ca9f6266aae1a954f4801655b053..8006be8bf8a416395fb61ef4b857a2c7805590bb 100644 (file)
@@ -50,9 +50,239 @@ static int sort_stdin(void)
        return 0;
 }
 
+static void dist_sawtooth(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = i % m;
+}
+
+static void dist_rand(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = rand() % m;
+}
+
+static void dist_stagger(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = (i * m + i) % n;
+}
+
+static void dist_plateau(int *arr, int n, int m)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] = (i < m) ? i : m;
+}
+
+static void dist_shuffle(int *arr, int n, int m)
+{
+       int i, j, k;
+       for (i = j = 0, k = 1; i < n; i++)
+               arr[i] = (rand() % m) ? (j += 2) : (k += 2);
+}
+
+#define DIST(name) { #name, dist_##name }
+
+static struct dist {
+       const char *name;
+       void (*fn)(int *arr, int n, int m);
+} dist[] = {
+       DIST(sawtooth),
+       DIST(rand),
+       DIST(stagger),
+       DIST(plateau),
+       DIST(shuffle),
+};
+
+static void mode_copy(int *arr, int n)
+{
+       /* nothing */
+}
+
+static void mode_reverse(int *arr, int n)
+{
+       int i, j;
+       for (i = 0, j = n - 1; i < j; i++, j--)
+               SWAP(arr[i], arr[j]);
+}
+
+static void mode_reverse_1st_half(int *arr, int n)
+{
+       mode_reverse(arr, n / 2);
+}
+
+static void mode_reverse_2nd_half(int *arr, int n)
+{
+       int half = n / 2;
+       mode_reverse(arr + half, n - half);
+}
+
+static int compare_ints(const void *av, const void *bv)
+{
+       const int *ap = av, *bp = bv;
+       int a = *ap, b = *bp;
+       return (a > b) - (a < b);
+}
+
+static void mode_sort(int *arr, int n)
+{
+       QSORT(arr, n, compare_ints);
+}
+
+static void mode_dither(int *arr, int n)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               arr[i] += i % 5;
+}
+
+#define MODE(name) { #name, mode_##name }
+
+static struct mode {
+       const char *name;
+       void (*fn)(int *arr, int n);
+} mode[] = {
+       MODE(copy),
+       MODE(reverse),
+       MODE(reverse_1st_half),
+       MODE(reverse_2nd_half),
+       MODE(sort),
+       MODE(dither),
+};
+
+static struct stats {
+       int get_next, set_next, compare;
+} stats;
+
+struct number {
+       int value, rank;
+       struct number *next;
+};
+
+static void *get_next_number(const void *a)
+{
+       stats.get_next++;
+       return ((const struct number *)a)->next;
+}
+
+static void set_next_number(void *a, void *b)
+{
+       stats.set_next++;
+       ((struct number *)a)->next = b;
+}
+
+static int compare_numbers(const void *av, const void *bv)
+{
+       const struct number *an = av, *bn = bv;
+       int a = an->value, b = bn->value;
+       stats.compare++;
+       return (a > b) - (a < b);
+}
+
+static void clear_numbers(struct number *list)
+{
+       while (list) {
+               struct number *next = list->next;
+               free(list);
+               list = next;
+       }
+}
+
+static int test(const struct dist *dist, const struct mode *mode, int n, int m)
+{
+       int *arr;
+       size_t i;
+       struct number *curr, *list, **tail;
+       int is_sorted = 1;
+       int is_stable = 1;
+       const char *verdict;
+       int result = -1;
+
+       ALLOC_ARRAY(arr, n);
+       dist->fn(arr, n, m);
+       mode->fn(arr, n);
+       for (i = 0, tail = &list; i < n; i++) {
+               curr = xmalloc(sizeof(*curr));
+               curr->value = arr[i];
+               curr->rank = i;
+               *tail = curr;
+               tail = &curr->next;
+       }
+       *tail = NULL;
+
+       stats.get_next = stats.set_next = stats.compare = 0;
+       list = llist_mergesort(list, get_next_number, set_next_number,
+                              compare_numbers);
+
+       QSORT(arr, n, compare_ints);
+       for (i = 0, curr = list; i < n && curr; i++, curr = curr->next) {
+               if (arr[i] != curr->value)
+                       is_sorted = 0;
+               if (curr->next && curr->value == curr->next->value &&
+                   curr->rank >= curr->next->rank)
+                       is_stable = 0;
+       }
+       if (i < n) {
+               verdict = "too short";
+       } else if (curr) {
+               verdict = "too long";
+       } else if (!is_sorted) {
+               verdict = "not sorted";
+       } else if (!is_stable) {
+               verdict = "unstable";
+       } else {
+               verdict = "OK";
+               result = 0;
+       }
+
+       printf("%-9s %-16s %8d %8d %8d %8d %8d %s\n",
+              dist->name, mode->name, n, m, stats.get_next, stats.set_next,
+              stats.compare, verdict);
+
+       clear_numbers(list);
+       free(arr);
+
+       return result;
+}
+
+/*
+ * A version of the qsort certification program from "Engineering a Sort
+ * Function" by Bentley and McIlroy, Software—Practice and Experience,
+ * Volume 23, Issue 11, 1249–1265 (November 1993).
+ */
+static int run_tests(int argc, const char **argv)
+{
+       const char *argv_default[] = { "100", "1023", "1024", "1025" };
+       if (!argc)
+               return run_tests(ARRAY_SIZE(argv_default), argv_default);
+       printf("%-9s %-16s %8s %8s %8s %8s %8s %s\n",
+              "distribut", "mode", "n", "m", "get_next", "set_next",
+              "compare", "verdict");
+       while (argc--) {
+               int i, j, m, n = strtol(*argv++, NULL, 10);
+               for (i = 0; i < ARRAY_SIZE(dist); i++) {
+                       for (j = 0; j < ARRAY_SIZE(mode); j++) {
+                               for (m = 1; m < 2 * n; m *= 2) {
+                                       if (test(&dist[i], &mode[j], n, m))
+                                               return 1;
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
 int cmd__mergesort(int argc, const char **argv)
 {
        if (argc == 2 && !strcmp(argv[1], "sort"))
                return sort_stdin();
-       usage("test-tool mergesort sort");
+       if (argc > 1 && !strcmp(argv[1], "test"))
+               return run_tests(argc - 2, argv + 2);
+       fprintf(stderr, "usage: test-tool mergesort sort\n");
+       fprintf(stderr, "   or: test-tool mergesort test [<n>...]\n");
+       return 129;
 }
diff --git a/t/t0071-sort.sh b/t/t0071-sort.sh
new file mode 100755 (executable)
index 0000000..a8ab174
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+test_description='verify sort functions'
+
+. ./test-lib.sh
+
+test_expect_success 'llist_mergesort()' '
+       test-tool mergesort test
+'
+
+test_done