Add linked list library to PSPP.
[pspp] / tests / libpspp / llx-test.c
diff --git a/tests/libpspp/llx-test.c b/tests/libpspp/llx-test.c
new file mode 100644 (file)
index 0000000..0de38b0
--- /dev/null
@@ -0,0 +1,2069 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2006 Free Software Foundation, Inc.
+   Written by Ben Pfaff <blp@gnu.org>.
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 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
+   General Public License for more details.
+
+   You should have received a copy of the GNU 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. */
+
+/* This is a test program for the llx_* routines defined in
+   ll.c.  This test program aims to be as comprehensive as
+   possible.  "gcov -b" should report 100% coverage of lines and
+   branches in llx.c and llx.h.  "valgrind --leak-check=yes
+   --show-reachable=yes" should give a clean report.
+   
+   This test program depends only on ll.c, llx.c, and the
+   standard C library.
+
+   See ll-test.c for a similar program for the ll_* routines. */
+
+#include <libpspp/llx.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+\f
+/* Support preliminaries. */
+#if __GNUC__ >= 2 && !defined UNUSED
+#define UNUSED __attribute__ ((unused))
+#else
+#define UNUSED
+#endif
+
+/* Exit with a failure code.
+   (Place a breakpoint on this function while debugging.) */
+static void
+check_die (void) 
+{
+  exit (EXIT_FAILURE);   
+}
+
+/* If OK is not true, prints a message about failure on the
+   current source file and the given LINE and terminates. */
+static void
+check_func (bool ok, int line) 
+{
+  if (!ok) 
+    {
+      printf ("check failed at %s, line %d\n", __FILE__, line);
+      check_die ();
+    }
+}
+
+/* Verifies that EXPR evaluates to true.
+   If not, prints a message citing the calling line number and
+   terminates. */
+#define check(EXPR) check_func ((EXPR), __LINE__)
+
+/* Prints a message about memory exhaustion and exits with a
+   failure code. */
+static void
+xalloc_die (void)
+{
+  printf ("virtual memory exhausted\n");
+  exit (EXIT_FAILURE);
+}
+
+/* Allocates and returns N bytes of memory. */
+static void *
+xmalloc (size_t n) 
+{
+  if (n != 0) 
+    {
+      void *p = malloc (n);
+      if (p == NULL)
+        xalloc_die ();
+
+      return p;
+    }
+  else
+    return NULL;
+}
+
+/* Allocates and returns N * M bytes of memory. */
+static void *
+xnmalloc (size_t n, size_t m) 
+{
+  if ((size_t) -1 / m <= n)
+    xalloc_die ();
+  return xmalloc (n * m);
+}
+\f
+/* Always returns a null pointer, failing allocation. */
+static struct llx *
+null_allocate_node (void *aux UNUSED)
+{
+  return NULL;
+}
+
+/* Does nothing. */
+static void
+null_release_node (struct llx *llx UNUSED, void *aux UNUSED) 
+{
+}
+
+/* Memory manager that fails all allocations and does nothing on
+   free. */
+static const struct llx_manager llx_null_mgr = 
+  {
+    null_allocate_node,
+    null_release_node,
+    NULL,
+  };
+\f
+/* List type and support routines. */
+
+/* Test data element. */
+struct element
+  {
+    int x;                      /* Primary value. */
+    int y;                      /* Secondary value. */
+  };
+
+int aux_data;
+
+/* Prints the elements in LIST. */
+static void UNUSED
+print_list (struct llx_list *list)
+{
+  struct llx *x;
+
+  printf ("list:");
+  for (x = llx_head (list); x != llx_null (list); x = llx_next (x)) 
+    {
+      const struct element *e = llx_data (x);
+      printf (" %d", e->x);
+    }
+  printf ("\n");
+}
+
+/* Prints the value returned by PREDICATE given auxiliary data
+   AUX for each element in LIST. */
+static void UNUSED
+print_pred (struct llx_list *list,
+            llx_predicate_func *predicate, void *aux UNUSED) 
+{
+  struct llx *x;
+
+  printf ("pred:");
+  for (x = llx_head (list); x != llx_null (list); x = llx_next (x)) 
+    printf (" %d", predicate (x, aux));
+  printf ("\n");
+}
+
+/* Prints the CNT numbers in VALUES. */
+static void UNUSED
+print_array (int values[], size_t cnt) 
+{
+  size_t i;
+
+  printf ("arry:");
+  for (i = 0; i < cnt; i++)
+    printf (" %d", values[i]);
+  printf ("\n");
+}
+
+/* Compares the `x' values in A and B and returns a strcmp-type
+   return value.  Verifies that AUX points to aux_data. */
+static int
+compare_elements (const void *a_, const void *b_, void *aux) 
+{
+  const struct element *a = a_;
+  const struct element *b = b_;
+
+  check (aux == &aux_data);
+  return a->x < b->x ? -1 : a->x > b->x;
+}
+
+/* Compares the `x' and `y' values in A and B and returns a
+   strcmp-type return value.  Verifies that AUX points to
+   aux_data. */
+static int
+compare_elements_x_y (const void *a_, const void *b_, void *aux) 
+{
+  const struct element *a = a_;
+  const struct element *b = b_;
+
+  check (aux == &aux_data);
+  if (a->x != b->x)
+    return a->x < b->x ? -1 : 1;
+  else if (a->y != b->y)
+    return a->y < b->y ? -1 : 1;
+  else
+    return 0;
+}
+
+/* Compares the `y' values in A and B and returns a strcmp-type
+   return value.  Verifies that AUX points to aux_data. */
+static int
+compare_elements_y (const void *a_, const void *b_, void *aux)
+{
+  const struct element *a = a_;
+  const struct element *b = b_;
+
+  check (aux == &aux_data);
+  return a->y < b->y ? -1 : a->y > b->y;
+}
+
+/* Returns true if the bit in *PATTERN indicated by `x in
+   *ELEMENT is set, false otherwise. */
+static bool
+pattern_pred (const void *element_, void *pattern_) 
+{
+  const struct element *element = element_;
+  unsigned *pattern = pattern_;
+
+  return (*pattern & (1u << element->x)) != 0;
+}
+
+/* Allocates N elements in *ELEMS.
+   Adds the elements to LIST, if it is nonnull.
+   Puts pointers to the elements' list elements in *ELEMP,
+   followed by a pointer to the list null element, if ELEMP is
+   nonnull.
+   Allocates space for N values in *VALUES, if VALUES is
+   nonnull. */
+static void
+allocate_elements (size_t n,
+                   struct llx_list *list,
+                   struct element ***elems,
+                   struct llx ***elemp,
+                   int **values) 
+{
+  size_t i;
+
+  if (list != NULL)
+    llx_init (list);
+
+  *elems = xnmalloc (n, sizeof **elems);
+  if (elemp != NULL) 
+    {
+      *elemp = xnmalloc (n + 1, sizeof *elemp);
+      (*elemp)[n] = llx_null (list);
+    }
+  
+  for (i = 0; i < n; i++) 
+    {
+      (*elems)[i] = xmalloc (sizeof ***elems);
+      if (list != NULL) 
+        {
+          struct llx *llx = llx_push_tail (list, (*elems)[i], &llx_malloc_mgr);
+          if (elemp != NULL)
+            (*elemp)[i] = llx; 
+        }
+    }
+  
+  if (values != NULL)
+    *values = xnmalloc (n, sizeof *values);
+}
+
+/* Copies the CNT values of `x' from LIST into VALUES[]. */
+static void
+extract_values (struct llx_list *list, int values[], size_t cnt) 
+{
+  struct llx *x;
+  
+  check (llx_count (list) == cnt);
+  for (x = llx_head (list); x != llx_null (list); x = llx_next (x)) 
+    {
+      struct element *e = llx_data (x);
+      *values++ = e->x;
+    }
+}
+
+/* As allocate_elements, but sets ascending values, starting
+   from 0, in `x' values in *ELEMS and in *VALUES (if
+   nonnull). */
+static void
+allocate_ascending (size_t n,
+                    struct llx_list *list,
+                    struct element ***elems,
+                    struct llx ***elemp,
+                    int **values) 
+{
+  size_t i;
+
+  allocate_elements (n, list, elems, elemp, values);
+  
+  for (i = 0; i < n; i++) 
+    (*elems)[i]->x = i;
+  if (values != NULL)
+    extract_values (list, *values, n); 
+}
+
+/* As allocate_elements, but sets binary values extracted from
+   successive bits in PATTERN in `x' values in *ELEMS and in
+   *VALUES (if nonnull). */
+static void
+allocate_pattern (size_t n,
+                  int pattern,
+                  struct llx_list *list,
+                  struct element ***elems,
+                  struct llx ***elemp,
+                  int **values) 
+{
+  size_t i;
+
+  allocate_elements (n, list, elems, elemp, values);
+  
+  for (i = 0; i < n; i++) 
+    (*elems)[i]->x = (pattern & (1 << i)) != 0;
+  if (values != NULL)
+    extract_values (list, *values, n); 
+}
+
+/* Randomly shuffles the CNT elements in ARRAY, each of which is
+   SIZE bytes in size. */
+static void
+random_shuffle (void *array_, size_t cnt, size_t size)
+{
+  char *array = array_;
+  char *tmp = xmalloc (size);
+  size_t i;
+
+  for (i = 0; i < cnt; i++) 
+    {
+      size_t j = rand () % (cnt - i) + i;
+      if (i != j) 
+        {
+          memcpy (tmp, array + j * size, size);
+          memcpy (array + j * size, array + i * size, size);
+          memcpy (array + i * size, tmp, size);
+        }
+    }
+
+  free (tmp);
+}
+
+/* As allocate_ascending, but orders the values randomly. */
+static void
+allocate_random (size_t n,
+                 struct llx_list *list,
+                 struct element ***elems,
+                 struct llx ***elemp,
+                 int **values) 
+{
+  size_t i;
+
+  allocate_elements (n, list, elems, elemp, values);
+  
+  for (i = 0; i < n; i++) 
+    (*elems)[i]->x = i;
+  random_shuffle (*elems, n, sizeof **elems);
+  if (values != NULL)
+    extract_values (list, *values, n); 
+}
+
+/* Frees LIST, the N elements of ELEMS, ELEMP, and VALUES. */
+static void
+free_elements (size_t n,
+               struct llx_list *list,
+               struct element **elems,
+               struct llx **elemp,
+               int *values) 
+{
+  size_t i;
+
+  if (list != NULL)
+    llx_destroy (list, NULL, NULL, &llx_malloc_mgr);
+  for (i = 0; i < n; i++)
+    free (elems[i]);
+  free (elems); 
+  free (elemp);
+  free (values);
+}
+
+/* Compares A and B and returns a strcmp-type return value. */
+static int
+compare_ints (const void *a_, const void *b_, void *aux UNUSED) 
+{
+  const int *a = a_;
+  const int *b = b_;
+
+  return *a < *b ? -1 : *a > *b;
+}
+
+/* Compares A and B and returns a strcmp-type return value. */
+static int
+compare_ints_noaux (const void *a_, const void *b_) 
+{
+  const int *a = a_;
+  const int *b = b_;
+
+  return *a < *b ? -1 : *a > *b;
+}
+
+/* Checks that LIST contains the CNT values in ELEMENTS. */
+static void
+check_list_contents (struct llx_list *list, int elements[], size_t cnt) 
+{
+  struct llx *llx;
+  size_t i;
+
+  check ((cnt == 0) == llx_is_empty (list));
+
+  /* Iterate in forward order. */
+  for (llx = llx_head (list), i = 0; i < cnt; llx = llx_next (llx), i++) 
+    {
+      struct element *e = llx_data (llx);
+      check (elements[i] == e->x);
+      check (llx != llx_null (list));
+    }
+  check (llx == llx_null (list));
+
+  /* Iterate in reverse order. */
+  for (llx = llx_tail (list), i = 0; i < cnt; llx = llx_prev (llx), i++)
+    {
+      struct element *e = llx_data (llx);
+      check (elements[cnt - i - 1] == e->x);
+      check (llx != llx_null (list));
+    }
+  check (llx == llx_null (list));
+
+  check (llx_count (list) == cnt);
+}
+
+/* Lexicographicallxy compares ARRAY1, which contains COUNT1
+   elements of SIZE bytes each, to ARRAY2, which contains COUNT2
+   elements of SIZE bytes, according to COMPARE.  Returns a
+   strcmp-type result.  AUX is passed to COMPARE as auxiliary
+   data. */
+static int
+lexicographical_compare_3way (const void *array1, size_t count1,
+                              const void *array2, size_t count2,
+                              size_t size,
+                              int (*compare) (const void *, const void *,
+                                              void *aux),
+                              void *aux) 
+{
+  const char *first1 = array1;
+  const char *first2 = array2;
+  size_t min_count = count1 < count2 ? count1 : count2;
+
+  while (min_count > 0)
+    {
+      int cmp = compare (first1, first2, aux);
+      if (cmp != 0)
+        return cmp;
+
+      first1 += size;
+      first2 += size;
+      min_count--;
+    }
+
+  return count1 < count2 ? -1 : count1 > count2;
+}
+\f
+/* Tests. */
+
+/* Tests list push and pop operations. */
+static void
+test_push_pop (void)
+{
+  const int max_elems = 1024;
+
+  struct llx_list list;
+  struct element **elems;
+  int *values;
+
+  int i;
+
+  allocate_elements (max_elems, NULL, &elems, NULL, &values);
+
+  /* Push on tail. */
+  llx_init (&list);
+  check_list_contents (&list, NULL, 0);
+  for (i = 0; i < max_elems; i++) 
+    {
+      values[i] = elems[i]->x = i;
+      llx_push_tail (&list, elems[i], &llx_malloc_mgr);
+      check_list_contents (&list, values, i + 1);
+    }
+
+  /* Remove from tail. */
+  for (i = 0; i < max_elems; i++) 
+    {
+      struct element *e = llx_pop_tail (&list, &llx_malloc_mgr);
+      check (e->x == max_elems - i - 1);
+      check_list_contents (&list, values, max_elems - i - 1);
+    }
+
+  /* Push at start. */
+  check_list_contents (&list, NULL, 0);
+  for (i = 0; i < max_elems; i++)
+    {
+      values[max_elems - i - 1] = elems[i]->x = max_elems - i - 1;
+      llx_push_head (&list, elems[i], &llx_malloc_mgr);
+      check_list_contents (&list, &values[max_elems - i - 1], i + 1);
+    }
+
+  /* Remove from start. */
+  for (i = 0; i < max_elems; i++) 
+    {
+      struct element *e = llx_pop_head (&list, &llx_malloc_mgr);
+      check (e->x == (int) i);
+      check_list_contents (&list, &values[i + 1], max_elems - i - 1);
+    }
+
+  free_elements (max_elems, &list, elems, NULL, values);
+}
+
+/* Tests insertion and removal at arbitrary positions. */
+static void
+test_insert_remove (void) 
+{
+  const int max_elems = 16;
+  int cnt;
+
+  for (cnt = 0; cnt < max_elems; cnt++) 
+    {
+      struct element **elems;
+      struct llx **elemp;
+      int *values = xnmalloc (cnt + 1, sizeof *values);
+
+      struct llx_list list;
+      struct element extra;
+      struct llx *extra_llx;
+      int pos;
+
+      allocate_ascending (cnt, &list, &elems, &elemp, NULL);
+      extra.x = -1;
+      for (pos = 0; pos <= cnt; pos++) 
+        {
+          int i, j;
+
+          extra_llx = llx_insert (elemp[pos], &extra, &llx_malloc_mgr);
+          check (extra_llx != NULL);
+
+          j = 0;
+          for (i = 0; i < pos; i++)
+            values[j++] = i;
+          values[j++] = -1;
+          for (; i < cnt; i++)
+            values[j++] = i;
+          check_list_contents (&list, values, cnt + 1);
+
+          llx_remove (extra_llx, &llx_malloc_mgr);
+        }
+      check_list_contents (&list, values, cnt);
+
+      free_elements (cnt, &list, elems, elemp, values);
+    }
+}
+
+/* Tests swapping individual elements. */
+static void
+test_swap (void) 
+{
+  const int max_elems = 8;
+  int cnt;
+
+  for (cnt = 0; cnt <= max_elems; cnt++) 
+    {
+      struct llx_list list;
+      struct element **elems;
+      struct llx **elemp;
+      int *values;
+
+      int i, j, k;
+
+      allocate_ascending (cnt, &list, &elems, &elemp, &values);
+      check_list_contents (&list, values, cnt);
+
+      for (i = 0; i < cnt; i++) 
+        for (j = 0; j < cnt; j++) 
+          for (k = 0; k < 2; k++) 
+            {
+              int t;
+
+              llx_swap (elemp[i], elemp[j]);
+              t = values[i];
+              values[i] = values[j];
+              values[j] = t;
+              check_list_contents (&list, values, cnt);
+            } 
+
+      free_elements (cnt, &list, elems, elemp, values);
+    }
+}
+
+/* Tests swapping ranges of list elements. */
+static void
+test_swap_range (void) 
+{
+  const int max_elems = 8;
+  int cnt, a0, a1, b0, b1, r;
+
+  for (cnt = 0; cnt <= max_elems; cnt++) 
+    for (a0 = 0; a0 <= cnt; a0++) 
+      for (a1 = a0; a1 <= cnt; a1++)
+        for (b0 = a1; b0 <= cnt; b0++) 
+          for (b1 = b0; b1 <= cnt; b1++)
+            for (r = 0; r < 2; r++)
+              {
+                struct llx_list list;
+                struct element **elems;
+                struct llx **elemp;
+                int *values;
+
+                int i, j;
+
+                allocate_ascending (cnt, &list, &elems, &elemp, &values);
+                check_list_contents (&list, values, cnt);
+
+                j = 0;
+                for (i = 0; i < a0; i++)
+                  values[j++] = i;
+                for (i = b0; i < b1; i++)
+                  values[j++] = i;
+                for (i = a1; i < b0; i++)
+                  values[j++] = i;
+                for (i = a0; i < a1; i++)
+                  values[j++] = i;
+                for (i = b1; i < cnt; i++)
+                  values[j++] = i;
+                check (j == cnt);
+
+                if (r == 0)
+                  llx_swap_range (elemp[a0], elemp[a1], elemp[b0], elemp[b1]);
+                else
+                  llx_swap_range (elemp[b0], elemp[b1], elemp[a0], elemp[a1]);
+                check_list_contents (&list, values, cnt);
+
+                free_elements (cnt, &list, elems, elemp, values);
+              }
+}
+
+/* Tests removing ranges of list elements. */
+static void
+test_remove_range (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++) 
+      for (r1 = r0; r1 <= cnt; r1++)
+        {
+          struct llx_list list;
+          struct element **elems;
+          struct llx **elemp;
+          int *values;
+
+          int i, j;
+
+          allocate_ascending (cnt, &list, &elems, &elemp, &values);
+          check_list_contents (&list, values, cnt);
+
+          j = 0;
+          for (i = 0; i < r0; i++)
+            values[j++] = i;
+          for (i = r1; i < cnt; i++)
+            values[j++] = i;
+
+          llx_remove_range (elemp[r0], elemp[r1], &llx_malloc_mgr);
+          check_list_contents (&list, values, j);
+
+          free_elements (cnt, &list, elems, elemp, values);
+        }
+}
+
+/* Tests llx_remove_equal. */
+static void
+test_remove_equal (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1, eq_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++)
+      for (r1 = r0; r1 <= cnt; r1++)
+        for (eq_pat = 0; eq_pat <= 1 << cnt; eq_pat++) 
+          {
+            struct llx_list list;
+            struct element **elems;
+            struct llx **elemp;
+            int *values;
+
+            struct element to_remove;
+            int remaining;
+            int i;
+
+            allocate_elements (cnt, &list, &elems, &elemp, &values);
+
+            remaining = 0;
+            for (i = 0; i < cnt; i++) 
+              {
+                int x = eq_pat & (1 << i) ? -1 : i;
+                bool delete = x == -1 && r0 <= i && i < r1;
+                elems[i]->x = x;
+                if (!delete)
+                  values[remaining++] = x; 
+              }
+
+            to_remove.x = -1;
+            check ((int) llx_remove_equal (elemp[r0], elemp[r1], &to_remove,
+                                           compare_elements, &aux_data,
+                                           &llx_malloc_mgr)
+                   == cnt - remaining);
+            check_list_contents (&list, values, remaining);
+
+            free_elements (cnt, &list, elems, elemp, values);
+          }
+}
+
+/* Tests llx_remove_if. */
+static void
+test_remove_if (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1, pattern;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++)
+      for (r1 = r0; r1 <= cnt; r1++)
+        for (pattern = 0; pattern <= 1 << cnt; pattern++) 
+          {
+            struct llx_list list;
+            struct element **elems;
+            struct llx **elemp;
+            int *values;
+
+            int remaining;
+            int i;
+
+            allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+            remaining = 0;
+            for (i = 0; i < cnt; i++) 
+              {
+                bool delete = (pattern & (1 << i))  && r0 <= i && i < r1;
+                if (!delete)
+                  values[remaining++] = i; 
+              }
+
+            check ((int) llx_remove_if (elemp[r0], elemp[r1], 
+                                        pattern_pred, &pattern,
+                                        &llx_malloc_mgr)
+                   == cnt - remaining);
+            check_list_contents (&list, values, remaining);
+
+            free_elements (cnt, &list, elems, elemp, values);
+          }
+}
+
+/* Tests, via HELPER, a function that looks at list elements
+   equal to some specified element. */
+static void
+test_examine_equal_range (void (*helper) (int r0, int r1, int eq_pat,
+                                          const void *to_find,
+                                          struct llx **elemp))
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1, eq_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (eq_pat = 0; eq_pat <= 1 << cnt; eq_pat++) 
+      {
+        struct llx_list list;
+        struct element **elems;
+        struct llx **elemp;
+        int *values;
+
+        struct element to_find;
+
+        int i;
+
+        allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+        for (i = 0; i < cnt; i++)
+          if (eq_pat & (1 << i))
+            values[i] = elems[i]->x = -1;
+
+        to_find.x = -1;
+        for (r0 = 0; r0 <= cnt; r0++)
+          for (r1 = r0; r1 <= cnt; r1++)
+            helper (r0, r1, eq_pat, &to_find, elemp);
+
+        check_list_contents (&list, values, cnt);
+
+        free_elements (cnt, &list, elems, elemp, values);
+      }
+}
+
+/* Tests, via HELPER, a function that looks at list elements for
+   which a given predicate returns true. */
+static void
+test_examine_if_range (void (*helper) (int r0, int r1, int eq_pat,
+                                       struct llx **elemp))
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1, eq_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (eq_pat = 0; eq_pat <= 1 << cnt; eq_pat++) 
+      {
+        struct llx_list list;
+        struct element **elems;
+        struct llx **elemp;
+        int *values;
+
+        allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+        for (r0 = 0; r0 <= cnt; r0++)
+          for (r1 = r0; r1 <= cnt; r1++)
+            helper (r0, r1, eq_pat, elemp);
+
+        check_list_contents (&list, values, cnt);
+
+        free_elements (cnt, &list, elems, elemp, values);
+      }
+}
+
+/* Helper function for testing llx_find_equal. */
+static void
+test_find_equal_helper (int r0, int r1, int eq_pat,
+                        const void *to_find, struct llx **elemp)
+{
+  struct llx *match;
+  int i;
+
+  match = llx_find_equal (elemp[r0], elemp[r1], to_find,
+                          compare_elements, &aux_data);
+  for (i = r0; i < r1; i++)
+    if (eq_pat & (1 << i))
+      break;
+
+  check (match == elemp[i]); 
+}
+
+/* Tests llx_find_equal. */
+static void
+test_find_equal (void) 
+{
+  test_examine_equal_range (test_find_equal_helper);
+}
+
+/* Helper function for testing llx_find_if. */
+static void
+test_find_if_helper (int r0, int r1, int eq_pat, struct llx **elemp)
+{
+  struct llx *match = llx_find_if (elemp[r0], elemp[r1],
+                                   pattern_pred, &eq_pat);
+  int i;
+
+  for (i = r0; i < r1; i++)
+    if (eq_pat & (1 << i))
+      break;
+
+  check (match == elemp[i]); 
+}
+
+/* Tests llx_find_if. */
+static void
+test_find_if (void) 
+{
+  test_examine_if_range (test_find_if_helper);
+}
+
+/* Tests llx_find_adjacent_equal. */
+static void
+test_find_adjacent_equal (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, eq_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (eq_pat = 0; eq_pat <= 1 << cnt; eq_pat++) 
+      {
+        struct llx_list list;
+        struct element **elems;
+        struct llx **elemp;
+        int *values;
+        int match;
+
+        int i;
+
+        allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+        match = -1;
+        for (i = 0; i < cnt - 1; i++) 
+          {
+            elems[i]->y = i;
+            if (eq_pat & (1 << i)) 
+              {
+                values[i] = elems[i]->x = match;
+                values[i + 1] = elems[i + 1]->x = match;
+              }
+            else
+              match--;
+          }
+        
+        for (i = 0; i <= cnt; i++)
+          {
+            struct llx *llx1 = llx_find_adjacent_equal (elemp[i], llx_null (&list),
+                                                        compare_elements,
+                                                        &aux_data);
+            struct llx *llx2;
+            int j;
+
+            llx2 = llx_null (&list);
+            for (j = i; j < cnt - 1; j++)
+              if (eq_pat & (1 << j)) 
+                {
+                  llx2 = elemp[j];
+                  break;
+                }
+            check (llx1 == llx2);
+          }
+        check_list_contents (&list, values, cnt);
+
+        free_elements (cnt, &list, elems, elemp, values);
+      }
+}
+
+/* Helper function for testing llx_count_range. */
+static void
+test_count_range_helper (int r0, int r1, int eq_pat UNUSED, struct llx **elemp)
+{
+  check ((int) llx_count_range (elemp[r0], elemp[r1]) == r1 - r0);
+}
+
+/* Tests llx_count_range. */
+static void
+test_count_range (void) 
+{
+  test_examine_if_range (test_count_range_helper);
+}
+
+/* Helper function for testing llx_count_equal. */
+static void
+test_count_equal_helper (int r0, int r1, int eq_pat,
+                         const void *to_find, struct llx **elemp)
+{
+  int count1, count2;
+  int i;
+
+  count1 = llx_count_equal (elemp[r0], elemp[r1], to_find,
+                            compare_elements, &aux_data);
+  count2 = 0;
+  for (i = r0; i < r1; i++)
+    if (eq_pat & (1 << i))
+      count2++;
+              
+  check (count1 == count2); 
+}
+
+/* Tests llx_count_equal. */
+static void
+test_count_equal (void) 
+{
+  test_examine_equal_range (test_count_equal_helper);
+}
+
+/* Helper function for testing llx_count_if. */
+static void
+test_count_if_helper (int r0, int r1, int eq_pat, struct llx **elemp) 
+{
+  int count1;
+  int count2;
+  int i;
+
+  count1 = llx_count_if (elemp[r0], elemp[r1], pattern_pred, &eq_pat);
+
+  count2 = 0;
+  for (i = r0; i < r1; i++)
+    if (eq_pat & (1 << i))
+      count2++;
+
+  check (count1 == count2); 
+}
+
+/* Tests llx_count_if. */
+static void
+test_count_if (void) 
+{
+  test_examine_if_range (test_count_if_helper);
+}
+
+/* Returns N!. */
+static unsigned
+factorial (unsigned n) 
+{
+  unsigned value = 1;
+  while (n > 1)
+    value *= n--;
+  return value;
+}
+
+/* Returns the number of permutations of the CNT values in
+   VALUES.  If VALUES contains duplicates, they must be
+   adjacent. */
+static unsigned
+expected_perms (int *values, size_t cnt) 
+{
+  size_t i, j;
+  unsigned perm_cnt;
+  
+  perm_cnt = factorial (cnt);
+  for (i = 0; i < cnt; i = j) 
+    {
+      for (j = i + 1; j < cnt; j++)
+        if (values[i] != values[j])
+          break;
+      perm_cnt /= factorial (j - i);
+    }
+  return perm_cnt;
+}
+
+/* Tests llx_min and llx_max. */
+static void
+test_min_max (void) 
+{
+  const int max_elems = 6;
+  int cnt;
+
+  for (cnt = 0; cnt <= max_elems; cnt++) 
+    {
+      struct llx_list list;
+      struct element **elems;
+      struct llx **elemp;
+      int *values;
+      int *new_values = xnmalloc (cnt, sizeof *values);
+
+      size_t perm_cnt;
+
+      allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+      perm_cnt = 1;
+      while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                   compare_elements, &aux_data)) 
+        {
+          int r0, r1;
+          struct llx *x;
+          int i;
+          
+          for (i = 0, x = llx_head (&list); x != llx_null (&list);
+               x = llx_next (x), i++) 
+            {
+              struct element *e = llx_data (x);
+              elemp[i] = x;
+              new_values[i] = e->x;
+            }
+          for (r0 = 0; r0 <= cnt; r0++)
+            for (r1 = r0; r1 <= cnt; r1++) 
+              {
+                struct llx *min = llx_min (elemp[r0], elemp[r1],
+                                           compare_elements, &aux_data);
+                struct llx *max = llx_max (elemp[r0], elemp[r1],
+                                           compare_elements, &aux_data);
+                if (r0 == r1) 
+                  {
+                    check (min == elemp[r1]);
+                    check (max == elemp[r1]); 
+                  }
+                else 
+                  {
+                    struct element *min_elem = llx_data (min);
+                    struct element *max_elem = llx_data (max);
+                    int min_int, max_int;
+                    int i;
+
+                    min_int = max_int = new_values[r0];
+                    for (i = r0; i < r1; i++)
+                      {
+                        int value = new_values[i];
+                        if (value < min_int)
+                          min_int = value;
+                        if (value > max_int)
+                          max_int = value;
+                      }
+                    check (min != elemp[r1] && min_elem->x == min_int);
+                    check (max != elemp[r1] && max_elem->x == max_int); 
+                  }
+              }
+          perm_cnt++;
+        }
+      check (perm_cnt == factorial (cnt));
+      check_list_contents (&list, values, cnt);
+
+      free_elements (cnt, &list, elems, elemp, values);
+      free (new_values);
+    }
+}
+
+/* Tests llx_lexicographical_compare_3way. */
+static void
+test_lexicographical_compare_3way (void) 
+{
+  const int max_elems = 4;
+
+  int cnt_a, pat_a, cnt_b, pat_b;
+
+  for (cnt_a = 0; cnt_a <= max_elems; cnt_a++)
+    for (pat_a = 0; pat_a <= 1 << cnt_a; pat_a++)
+      for (cnt_b = 0; cnt_b <= max_elems; cnt_b++)
+        for (pat_b = 0; pat_b <= 1 << cnt_b; pat_b++) 
+          {
+            struct llx_list list_a, list_b;
+            struct element **elems_a, **elems_b;
+            struct llx **elemp_a, **elemp_b;
+            int *values_a, *values_b;
+
+            int a0, a1, b0, b1;
+
+            allocate_pattern (cnt_a, pat_a,
+                              &list_a, &elems_a, &elemp_a, &values_a);
+            allocate_pattern (cnt_b, pat_b,
+                              &list_b, &elems_b, &elemp_b, &values_b);
+
+            for (a0 = 0; a0 <= cnt_a; a0++)
+              for (a1 = a0; a1 <= cnt_a; a1++)
+                for (b0 = 0; b0 <= cnt_b; b0++)
+                  for (b1 = b0; b1 <= cnt_b; b1++)
+                    {
+                      int a_ordering = lexicographical_compare_3way (
+                        values_a + a0, a1 - a0,
+                        values_b + b0, b1 - b0,
+                        sizeof *values_a,
+                        compare_ints, NULL);
+
+                      int b_ordering = llx_lexicographical_compare_3way (
+                        elemp_a[a0], elemp_a[a1],
+                        elemp_b[b0], elemp_b[b1],
+                        compare_elements, &aux_data);
+
+                      check (a_ordering == b_ordering);
+                    } 
+
+            free_elements (cnt_a, &list_a, elems_a, elemp_a, values_a);
+            free_elements (cnt_b, &list_b, elems_b, elemp_b, values_b);
+          }
+}
+
+/* Appends the `x' value in element E to the array pointed to by
+   NEXT_OUTPUT, and advances NEXT_OUTPUT to the next position. */
+static void
+apply_func (void *e_, void *next_output_) 
+{
+  struct element *e = e_;
+  int **next_output = next_output_;
+  
+  *(*next_output)++ = e->x;
+}
+
+/* Tests llx_apply. */
+static void
+test_apply (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++) 
+      for (r1 = r0; r1 <= cnt; r1++)
+        {
+          struct llx_list list;
+          struct element **elems;
+          struct llx **elemp;
+          int *values;
+
+          int *output;
+          int *next_output;
+          int j;
+
+          allocate_ascending (cnt, &list, &elems, &elemp, &values);
+          check_list_contents (&list, values, cnt);
+
+          output = next_output = xnmalloc (cnt, sizeof *output);
+          llx_apply (elemp[r0], elemp[r1], apply_func, &next_output);
+          check_list_contents (&list, values, cnt);
+          llx_destroy (&list, NULL, NULL, &llx_malloc_mgr);
+            
+          check (r1 - r0 == next_output - output);
+          for (j = 0; j < r1 - r0; j++)
+            check (output[j] == r0 + j);
+
+          free_elements (cnt, NULL, elems, elemp, values);
+          free (output);
+        }
+}
+
+/* Tests llx_destroy. */
+static void
+test_destroy (void) 
+{
+  const int max_elems = 8;
+
+  int cnt;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    {
+      struct llx_list list;
+      struct element **elems;
+      struct llx **elemp;
+      int *values;
+
+      int *output;
+      int *next_output;
+      int j;
+
+      allocate_ascending (cnt, &list, &elems, &elemp, &values);
+      check_list_contents (&list, values, cnt);
+
+      output = next_output = xnmalloc (cnt, sizeof *output);
+      llx_destroy (&list, apply_func, &next_output, &llx_malloc_mgr);
+            
+      check (cnt == next_output - output);
+      for (j = 0; j < cnt; j++)
+        check (output[j] == j);
+
+      free_elements (cnt, NULL, elems, elemp, values);
+      free (output);
+    }
+}
+
+/* Tests llx_reverse. */
+static void
+test_reverse (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++) 
+      for (r1 = r0; r1 <= cnt; r1++)
+        {
+          struct llx_list list;
+          struct element **elems;
+          struct llx **elemp;
+          int *values;
+
+          int i, j;
+
+          allocate_ascending (cnt, &list, &elems, &elemp, &values);
+          check_list_contents (&list, values, cnt);
+
+          j = 0;
+          for (i = 0; i < r0; i++)
+            values[j++] = i;
+          for (i = r1 - 1; i >= r0; i--)
+            values[j++] = i;
+          for (i = r1; i < cnt; i++)
+            values[j++] = i;
+
+          llx_reverse (elemp[r0], elemp[r1]);
+          check_list_contents (&list, values, cnt);
+
+          free_elements (cnt, &list, elems, elemp, values);
+        }
+}
+
+/* Tests llx_next_permutation and llx_prev_permutation when the
+   permuted values have no duplicates. */
+static void
+test_permutations_no_dups (void) 
+{
+  const int max_elems = 8;
+  int cnt;
+
+  for (cnt = 0; cnt <= max_elems; cnt++) 
+    {
+      struct llx_list list;
+      struct element **elems;
+      int *values;
+      int *old_values = xnmalloc (cnt, sizeof *values);
+      int *new_values = xnmalloc (cnt, sizeof *values);
+
+      size_t perm_cnt;
+
+      allocate_ascending (cnt, &list, &elems, NULL, &values);
+
+      perm_cnt = 1;
+      extract_values (&list, old_values, cnt);
+      while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                   compare_elements, &aux_data)) 
+        {
+          extract_values (&list, new_values, cnt);
+          check (lexicographical_compare_3way (new_values, cnt,
+                                               old_values, cnt,
+                                               sizeof *new_values,
+                                               compare_ints, NULL) > 0);
+          memcpy (old_values, new_values, (cnt) * sizeof *old_values);
+          perm_cnt++;
+        }
+      check (perm_cnt == factorial (cnt));
+      check_list_contents (&list, values, cnt);
+
+      perm_cnt = 1;
+      llx_reverse (llx_head (&list), llx_null (&list));
+      extract_values (&list, old_values, cnt);
+      while (llx_prev_permutation (llx_head (&list), llx_null (&list),
+                                   compare_elements, &aux_data)) 
+        {
+          extract_values (&list, new_values, cnt);
+          check (lexicographical_compare_3way (new_values, cnt,
+                                               old_values, cnt,
+                                               sizeof *new_values,
+                                               compare_ints, NULL) < 0);
+          memcpy (old_values, new_values, (cnt) * sizeof *old_values);
+          perm_cnt++;
+        }
+      check (perm_cnt == factorial (cnt));
+      llx_reverse (llx_head (&list), llx_null (&list));
+      check_list_contents (&list, values, cnt);
+
+      free_elements (cnt, &list, elems, NULL, values);
+      free (old_values);
+      free (new_values);
+    }
+}
+
+/* Tests llx_next_permutation and llx_prev_permutation when the
+   permuted values contain duplicates. */
+static void
+test_permutations_with_dups (void) 
+{
+  const int max_elems = 8;
+  const int max_dup = 3;
+  const int repetitions = 1024;
+
+  int cnt, repeat;
+
+  for (repeat = 0; repeat < repetitions; repeat++)
+    for (cnt = 0; cnt < max_elems; cnt++) 
+      {
+        struct llx_list list;
+        struct element **elems;
+        struct llx **elemp;
+        int *values;
+        int *old_values = xnmalloc (max_elems, sizeof *values);
+        int *new_values = xnmalloc (max_elems, sizeof *values);
+
+        unsigned permutation_cnt;
+        int left = cnt;
+        int value = 0;
+      
+        allocate_elements (cnt, &list, &elems, &elemp, &values);
+
+        value = 0;
+        while (left > 0)
+          {
+            int max = left < max_dup ? left : max_dup;
+            int n = rand () % max + 1;
+            while (n-- > 0)
+              {
+                int idx = cnt - left--;
+                values[idx] = elems[idx]->x = value;
+              }
+            value++;
+          }
+
+        permutation_cnt = 1;
+        extract_values (&list, old_values, cnt);
+        while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                     compare_elements, &aux_data)) 
+          {
+            extract_values (&list, new_values, cnt);
+            check (lexicographical_compare_3way (new_values, cnt,
+                                                 old_values, cnt,
+                                                 sizeof *new_values,
+                                                 compare_ints, NULL) > 0);
+            memcpy (old_values, new_values, cnt * sizeof *old_values);
+            permutation_cnt++;
+          }
+        check (permutation_cnt == expected_perms (values, cnt));
+        check_list_contents (&list, values, cnt);
+
+        permutation_cnt = 1;
+        llx_reverse (llx_head (&list), llx_null (&list));
+        extract_values (&list, old_values, cnt);
+        while (llx_prev_permutation (llx_head (&list), llx_null (&list),
+                                     compare_elements, &aux_data)) 
+          {
+            extract_values (&list, new_values, cnt);
+            check (lexicographical_compare_3way (new_values, cnt,
+                                                 old_values, cnt,
+                                                 sizeof *new_values,
+                                                 compare_ints, NULL) < 0);
+            permutation_cnt++;
+          }
+        llx_reverse (llx_head (&list), llx_null (&list));
+        check (permutation_cnt == expected_perms (values, cnt));
+        check_list_contents (&list, values, cnt);
+
+        free_elements (cnt, &list, elems, elemp, values);
+        free (old_values);
+        free (new_values);
+      }
+}
+
+/* Tests llx_merge when no equal values are to be merged. */
+static void
+test_merge_no_dups (void) 
+{
+  const int max_elems = 8;
+  const int max_fillxer = 3;
+
+  int merge_cnt, pattern, pfx, gap, sfx, order;
+  
+  for (merge_cnt = 0; merge_cnt < max_elems; merge_cnt++)
+    for (pattern = 0; pattern <= (1 << merge_cnt); pattern++)
+      for (pfx = 0; pfx < max_fillxer; pfx++)
+        for (gap = 0; gap < max_fillxer; gap++)
+          for (sfx = 0; sfx < max_fillxer; sfx++)
+            for (order = 0; order < 2; order++)
+              {
+                struct llx_list list;
+                struct element **elems;
+                struct llx **elemp;
+                int *values;
+
+                int list_cnt = pfx + merge_cnt + gap + sfx;
+                int a0, a1, b0, b1;
+                int i, j;
+
+                allocate_elements (list_cnt, &list,
+                                   &elems, &elemp, &values);
+
+                j = 0;
+                for (i = 0; i < pfx; i++)
+                  elems[j++]->x = 100 + i;
+                a0 = j;
+                for (i = 0; i < merge_cnt; i++)
+                  if (pattern & (1u << i))
+                    elems[j++]->x = i;
+                a1 = j;
+                for (i = 0; i < gap; i++)
+                  elems[j++]->x = 200 + i;
+                b0 = j;
+                for (i = 0; i < merge_cnt; i++)
+                  if (!(pattern & (1u << i)))
+                    elems[j++]->x = i;
+                b1 = j;
+                for (i = 0; i < sfx; i++)
+                  elems[j++]->x = 300 + i;
+                check (list_cnt == j);
+
+                j = 0;
+                for (i = 0; i < pfx; i++)
+                  values[j++] = 100 + i;
+                if (order == 0)
+                  for (i = 0; i < merge_cnt; i++)
+                    values[j++] = i;
+                for (i = 0; i < gap; i++)
+                  values[j++] = 200 + i;
+                if (order == 1)
+                  for (i = 0; i < merge_cnt; i++)
+                    values[j++] = i;
+                for (i = 0; i < sfx; i++)
+                  values[j++] = 300 + i;
+                check (list_cnt == j);
+
+                if (order == 0)
+                  llx_merge (elemp[a0], elemp[a1], elemp[b0], elemp[b1],
+                             compare_elements, &aux_data);
+                else
+                  llx_merge (elemp[b0], elemp[b1], elemp[a0], elemp[a1],
+                             compare_elements, &aux_data);
+
+                check_list_contents (&list, values, list_cnt);
+
+                free_elements (list_cnt, &list, elems, elemp, values);
+              }
+}
+
+/* Tests llx_merge when equal values are to be merged. */
+static void
+test_merge_with_dups (void) 
+{
+  const int max_elems = 8;
+
+  int cnt, merge_pat, inc_pat, order;
+  
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (merge_pat = 0; merge_pat <= (1 << cnt); merge_pat++)
+      for (inc_pat = 0; inc_pat <= (1 << cnt); inc_pat++)
+        for (order = 0; order < 2; order++)
+          {
+            struct llx_list list;
+            struct element **elems;
+            struct llx **elemp;
+            int *values;
+
+            int mid;
+            int i, j, k;
+
+            allocate_elements (cnt, &list, &elems, &elemp, &values);
+
+            j = 0;
+            for (i = k = 0; i < cnt; i++) 
+              {
+                if (merge_pat & (1u << i)) 
+                  elems[j++]->x = k;
+                if (inc_pat & (1u << i))
+                  k++;
+              }
+            mid = j;
+            for (i = k = 0; i < cnt; i++)
+              {
+                if (!(merge_pat & (1u << i))) 
+                  elems[j++]->x = k;
+                if (inc_pat & (1u << i))
+                  k++;
+              }
+            check (cnt == j);
+
+            if (order == 0) 
+              {
+                for (i = 0; i < cnt; i++)
+                  elems[i]->y = i; 
+              }
+            else 
+              {
+                for (i = 0; i < mid; i++)
+                  elems[i]->y = 100 + i;
+                for (i = mid; i < cnt; i++)
+                  elems[i]->y = i;
+              }
+
+            j = 0;
+            for (i = k = 0; i < cnt; i++) 
+              {
+                values[j++] = k;
+                if (inc_pat & (1u << i))
+                  k++; 
+              }
+            check (cnt == j);
+
+            if (order == 0)
+              llx_merge (elemp[0], elemp[mid], elemp[mid], elemp[cnt],
+                         compare_elements, &aux_data);
+            else
+              llx_merge (elemp[mid], elemp[cnt], elemp[0], elemp[mid],
+                         compare_elements, &aux_data);
+
+            check_list_contents (&list, values, cnt);
+            check (llx_is_sorted (llx_head (&list), llx_null (&list),
+                                  compare_elements_x_y, &aux_data));
+
+            free_elements (cnt, &list, elems, elemp, values);
+          }
+}
+
+/* Tests llx_sort on all permutations up to a maximum number of
+   elements. */
+static void
+test_sort_exhaustive (void) 
+{
+  const int max_elems = 8;
+  int cnt;
+
+  for (cnt = 0; cnt <= max_elems; cnt++) 
+    {
+      struct llx_list list;
+      struct element **elems;
+      int *values;
+
+      struct element **perm_elems;
+      int *perm_values;
+
+      size_t perm_cnt;
+
+      allocate_ascending (cnt, &list, &elems, NULL, &values);
+      allocate_elements (cnt, NULL, &perm_elems, NULL, &perm_values);
+
+      perm_cnt = 1;
+      while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                   compare_elements, &aux_data)) 
+        {
+          struct llx_list perm_list;
+          int j;
+
+          extract_values (&list, perm_values, cnt);
+          llx_init (&perm_list);
+          for (j = 0; j < cnt; j++)
+            { 
+              perm_elems[j]->x = perm_values[j];
+              llx_push_tail (&perm_list, perm_elems[j], &llx_malloc_mgr);
+            }
+          llx_sort (llx_head (&perm_list), llx_null (&perm_list),
+                    compare_elements, &aux_data);
+          check_list_contents (&perm_list, values, cnt);
+          check (llx_is_sorted (llx_head (&perm_list), llx_null (&perm_list),
+                                compare_elements, &aux_data));
+          llx_destroy (&perm_list, NULL, NULL, &llx_malloc_mgr);
+          perm_cnt++; 
+        }
+      check (perm_cnt == factorial (cnt));
+
+      free_elements (cnt, &list, elems, NULL, values);
+      free_elements (cnt, NULL, perm_elems, NULL, perm_values);
+    }
+}
+
+/* Tests that llx_sort is stable in the presence of equal
+   values. */
+static void
+test_sort_stable (void) 
+{
+  const int max_elems = 6;
+  int cnt, inc_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (inc_pat = 0; inc_pat <= 1 << cnt; inc_pat++)
+      {
+        struct llx_list list;
+        struct element **elems;
+        int *values;
+
+        struct element **perm_elems;
+        int *perm_values;
+
+        size_t perm_cnt;
+        int i, j;
+
+        allocate_elements (cnt, &list, &elems, NULL, &values);
+        allocate_elements (cnt, NULL, &perm_elems, NULL, &perm_values);
+
+        j = 0;
+        for (i = 0; i < cnt; i++) 
+          {
+            elems[i]->x = values[i] = j;
+            if (inc_pat & (1 << i))
+              j++;
+            elems[i]->y = i;
+          }
+
+        perm_cnt = 1;
+        while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                     compare_elements_y, &aux_data)) 
+          {
+            struct llx_list perm_list;
+
+            extract_values (&list, perm_values, cnt);
+            llx_init (&perm_list);
+            for (i = 0; i < cnt; i++)
+              { 
+                perm_elems[i]->x = perm_values[i];
+                perm_elems[i]->y = i;
+                llx_push_tail (&perm_list, perm_elems[i], &llx_malloc_mgr);
+              }
+            llx_sort (llx_head (&perm_list), llx_null (&perm_list),
+                      compare_elements, &aux_data);
+            check_list_contents (&perm_list, values, cnt);
+            check (llx_is_sorted (llx_head (&perm_list), llx_null (&perm_list),
+                                  compare_elements_x_y, &aux_data));
+            llx_destroy (&perm_list, NULL, NULL, &llx_malloc_mgr);
+            perm_cnt++; 
+          }
+        check (perm_cnt == factorial (cnt));
+
+        free_elements (cnt, &list, elems, NULL, values);
+        free_elements (cnt, NULL, perm_elems, NULL, perm_values);
+      }
+}
+
+/* Tests that llx_sort does not disturb elements outside the
+   range sorted. */
+static void
+test_sort_subset (void)
+{
+  const int max_elems = 8;
+
+  int cnt, r0, r1, repeat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (repeat = 0; repeat < 100; repeat++)
+      for (r0 = 0; r0 <= cnt; r0++) 
+        for (r1 = r0; r1 <= cnt; r1++)
+          {
+            struct llx_list list;
+            struct element **elems;
+            struct llx **elemp;
+            int *values;
+
+            allocate_random (cnt, &list, &elems, &elemp, &values);
+
+            qsort (&values[r0], r1 - r0, sizeof *values, compare_ints_noaux);
+            llx_sort (elemp[r0], elemp[r1], compare_elements, &aux_data);
+            check_list_contents (&list, values, cnt);
+
+            free_elements (cnt, &list, elems, elemp, values);
+          }
+}
+
+/* Tests that llx_sort works with large lists. */
+static void
+test_sort_big (void)
+{
+  const int max_elems = 1024;
+
+  int cnt;
+
+  for (cnt = 0; cnt < max_elems; cnt++)
+    {
+      struct llx_list list;
+      struct element **elems;
+      int *values;
+
+      allocate_random (cnt, &list, &elems, NULL, &values);
+
+      qsort (values, cnt, sizeof *values, compare_ints_noaux);
+      llx_sort (llx_head (&list), llx_null (&list), compare_elements, &aux_data);
+      check_list_contents (&list, values, cnt);
+
+      free_elements (cnt, &list, elems, NULL, values);
+    }
+}
+
+/* Tests llx_unique. */
+static void
+test_unique (void) 
+{
+  const int max_elems = 10;
+
+  int *ascending = xnmalloc (max_elems, sizeof *ascending);
+
+  int cnt, inc_pat, i, j, unique_values;
+
+  for (i = 0; i < max_elems; i++)
+    ascending[i] = i;
+
+  for (cnt = 0; cnt < max_elems; cnt++)
+    for (inc_pat = 0; inc_pat < (1 << cnt); inc_pat++)
+      {
+        struct llx_list list, dups;
+        struct element **elems;
+        int *values;
+
+        allocate_elements (cnt, &list, &elems, NULL, &values);
+
+        j = unique_values = 0;
+        for (i = 0; i < cnt; i++) 
+          {
+            unique_values = j + 1;
+            elems[i]->x = values[i] = j;
+            if (inc_pat & (1 << i))
+              j++;
+          }
+        check_list_contents (&list, values, cnt);
+
+        llx_init (&dups);
+        check (llx_unique (llx_head (&list), llx_null (&list),
+                           llx_null (&dups),
+                           compare_elements, &aux_data,
+                           &llx_malloc_mgr)
+               == (size_t) unique_values);
+        check_list_contents (&list, ascending, unique_values);
+
+        llx_splice (llx_null (&list), llx_head (&dups), llx_null (&dups));
+        llx_sort (llx_head (&list), llx_null (&list), compare_elements, &aux_data);
+        check_list_contents (&list, values, cnt);
+
+        llx_destroy (&dups, NULL, NULL, &llx_malloc_mgr);
+        free_elements (cnt, &list, elems, NULL, values);
+      }
+
+  free (ascending);
+}
+
+/* Tests llx_sort_unique. */
+static void
+test_sort_unique (void) 
+{
+  const int max_elems = 7;
+  int cnt, inc_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (inc_pat = 0; inc_pat <= 1 << cnt; inc_pat++)
+      {
+        struct llx_list list;
+        struct element **elems;
+        int *values;
+
+        struct element **perm_elems;
+        int *perm_values;
+
+        int unique_cnt;
+        int *unique_values;
+
+        size_t perm_cnt;
+        int i, j;
+
+        allocate_elements (cnt, &list, &elems, NULL, &values);
+        allocate_elements (cnt, NULL, &perm_elems, NULL, &perm_values);
+
+        j = unique_cnt = 0;
+        for (i = 0; i < cnt; i++) 
+          {
+            elems[i]->x = values[i] = j;
+            unique_cnt = j + 1;
+            if (inc_pat & (1 << i))
+              j++;
+          }
+
+        unique_values = xnmalloc (unique_cnt, sizeof *unique_values);
+        for (i = 0; i < unique_cnt; i++)
+          unique_values[i] = i;
+
+        perm_cnt = 1;
+        while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                     compare_elements, &aux_data)) 
+          {
+            struct llx_list perm_list;
+
+            extract_values (&list, perm_values, cnt);
+            llx_init (&perm_list);
+            for (i = 0; i < cnt; i++)
+              { 
+                perm_elems[i]->x = perm_values[i];
+                perm_elems[i]->y = i;
+                llx_push_tail (&perm_list, perm_elems[i], &llx_malloc_mgr);
+              }
+            llx_sort_unique (llx_head (&perm_list), llx_null (&perm_list), NULL,
+                             compare_elements, &aux_data,
+                             &llx_malloc_mgr);
+            check_list_contents (&perm_list, unique_values, unique_cnt);
+            check (llx_is_sorted (llx_head (&perm_list), llx_null (&perm_list),
+                                  compare_elements_x_y, &aux_data));
+            llx_destroy (&perm_list, NULL, NULL, &llx_malloc_mgr);
+            perm_cnt++;
+          }
+        check (perm_cnt == expected_perms (values, cnt));
+        
+        free_elements (cnt, &list, elems, NULL, values);
+        free_elements (cnt, NULL, perm_elems, NULL, perm_values);
+        free (unique_values);
+      }
+}
+
+/* Tests llx_insert_ordered. */
+static void
+test_insert_ordered (void) 
+{
+  const int max_elems = 6;
+  int cnt, inc_pat;
+
+  for (cnt = 0; cnt <= max_elems; cnt++)
+    for (inc_pat = 0; inc_pat <= 1 << cnt; inc_pat++)
+      {
+        struct llx_list list;
+        struct element **elems;
+        int *values;
+
+        struct element **perm_elems;
+        int *perm_values;
+
+        size_t perm_cnt;
+        int i, j;
+
+        allocate_elements (cnt, &list, &elems, NULL, &values);
+        allocate_elements (cnt, NULL, &perm_elems, NULL, &perm_values);
+
+        j = 0;
+        for (i = 0; i < cnt; i++) 
+          {
+            elems[i]->x = values[i] = j;
+            if (inc_pat & (1 << i))
+              j++;
+            elems[i]->y = i;
+          }
+
+        perm_cnt = 1;
+        while (llx_next_permutation (llx_head (&list), llx_null (&list),
+                                     compare_elements_y, &aux_data)) 
+          {
+            struct llx_list perm_list;
+
+            extract_values (&list, perm_values, cnt);
+            llx_init (&perm_list);
+            for (i = 0; i < cnt; i++)
+              { 
+                perm_elems[i]->x = perm_values[i];
+                perm_elems[i]->y = i;
+                llx_insert_ordered (llx_head (&perm_list),
+                                    llx_null (&perm_list),
+                                    perm_elems[i],
+                                    compare_elements, &aux_data,
+                                    &llx_malloc_mgr);
+              }
+            check (llx_is_sorted (llx_head (&perm_list), llx_null (&perm_list),
+                                  compare_elements_x_y, &aux_data));
+            llx_destroy (&perm_list, NULL, NULL, &llx_malloc_mgr);
+            perm_cnt++; 
+          }
+        check (perm_cnt == factorial (cnt));
+
+        free_elements (cnt, &list, elems, NULL, values);
+        free_elements (cnt, NULL, perm_elems, NULL, perm_values);
+      }
+}
+
+/* Tests llx_partition. */
+static void
+test_partition (void)
+{
+  const int max_elems = 10;
+  
+  int cnt;
+  unsigned pbase;
+  int r0, r1;
+
+  for (cnt = 0; cnt < max_elems; cnt++)
+    for (r0 = 0; r0 <= cnt; r0++)
+      for (r1 = r0; r1 <= cnt; r1++)
+        for (pbase = 0; pbase <= (1u << (r1 - r0)); pbase++)
+          {
+            struct llx_list list;
+            struct element **elems;
+            struct llx **elemp;
+            int *values;
+
+            unsigned pattern = pbase << r0;
+            int i, j;
+            int first_false;
+            struct llx *part_llx;
+         
+            allocate_ascending (cnt, &list, &elems, &elemp, &values);
+
+            /* Check that llx_find_partition works okay in every
+               case.  We use it after partitioning, too, but that
+               only tests cases where it returns non-null. */
+            for (i = r0; i < r1; i++)
+              if (!(pattern & (1u << i)))
+                break;
+            j = i;
+            for (; i < r1; i++)
+              if (pattern & (1u << i)) 
+                break;
+            part_llx = llx_find_partition (elemp[r0], elemp[r1],
+                                           pattern_pred,
+                                           &pattern);
+            if (i == r1) 
+              check (part_llx == elemp[j]);
+            else
+              check (part_llx == NULL);
+
+            /* Figure out expected results. */
+            j = 0;
+            first_false = -1;
+            for (i = 0; i < r0; i++)
+              values[j++] = i;
+            for (i = r0; i < r1; i++)
+              if (pattern & (1u << i))
+                values[j++] = i;
+            for (i = r0; i < r1; i++)
+              if (!(pattern & (1u << i)))
+                {
+                  if (first_false == -1)
+                    first_false = i;
+                  values[j++] = i; 
+                }
+            if (first_false == -1)
+              first_false = r1;
+            for (i = r1; i < cnt; i++)
+              values[j++] = i;
+            check (j == cnt);
+
+            /* Partition and check for expected results. */
+            check (llx_partition (elemp[r0], elemp[r1],
+                                  pattern_pred, &pattern)
+                   == elemp[first_false]);
+            check (llx_find_partition (elemp[r0], elemp[r1],
+                                       pattern_pred, &pattern)
+                   == elemp[first_false]);
+            check_list_contents (&list, values, cnt);
+            check ((int) llx_count (&list) == cnt);
+
+            free_elements (cnt, &list, elems, elemp, values);
+          }
+}
+
+/* Tests that allocation failure is gracefully handled. */
+static void
+test_allocation_failure (void) 
+{
+  struct llx_list list;
+
+  llx_init (&list);
+  check (llx_push_head (&list, NULL, &llx_null_mgr) == NULL); 
+  check (llx_push_tail (&list, NULL, &llx_null_mgr) == NULL); 
+  check (llx_insert (llx_null (&list), NULL, &llx_null_mgr) == NULL); 
+  check_list_contents (&list, NULL, 0);
+}
+\f
+/* Main program. */
+
+/* Runs TEST_FUNCTION and prints a message about NAME. */
+static void
+run_test (void (*test_function) (void), const char *name) 
+{
+  printf ("Running %s test...  ", name);
+  fflush (stdout);
+  test_function ();
+  printf ("done.\n");
+}
+
+int
+main (void) 
+{
+  run_test (test_push_pop, "push/pop");
+  run_test (test_insert_remove, "insert/remove");
+  run_test (test_swap, "swap");
+  run_test (test_swap_range, "swap_range");
+  run_test (test_remove_range, "remove_range");
+  run_test (test_remove_equal, "remove_equal");
+  run_test (test_remove_if, "remove_if");
+  run_test (test_find_equal, "find_equal");
+  run_test (test_find_if, "find_if");
+  run_test (test_find_adjacent_equal, "find_adjacent_equal");
+  run_test (test_count_range, "count_range");
+  run_test (test_count_equal, "count_equal");
+  run_test (test_count_if, "count_if");
+  run_test (test_min_max, "min/max");
+  run_test (test_lexicographical_compare_3way, "lexicographical_compare_3way");
+  run_test (test_apply, "apply");
+  run_test (test_destroy, "destroy");
+  run_test (test_reverse, "reverse");
+  run_test (test_permutations_no_dups, "permutations (no dups)");
+  run_test (test_permutations_with_dups, "permutations (with dups)");
+  run_test (test_merge_no_dups, "merge (no dups)");
+  run_test (test_merge_with_dups, "merge (with dups)");
+  run_test (test_sort_exhaustive, "sort (exhaustive)");
+  run_test (test_sort_stable, "sort (stability)");
+  run_test (test_sort_subset, "sort (subset)");
+  run_test (test_sort_big, "sort (big)");
+  run_test (test_unique, "unique");
+  run_test (test_sort_unique, "sort_unique");
+  run_test (test_insert_ordered, "insert_ordered");
+  run_test (test_partition, "partition");
+  run_test (test_allocation_failure, "allocation failure");
+
+  return 0;
+}
+
+/*
+  Local Variables:
+  compile-command: "gcc -Wall -Wstrict-prototypes -Wmissing-prototypes ll.c llx.c llx-test.c -o llx-test -g"
+  End:
+ */