Apply patches #5828, #5837, #5841, #5843.
[pspp-builds.git] / tests / libpspp / range-map-test.c
diff --git a/tests/libpspp/range-map-test.c b/tests/libpspp/range-map-test.c
new file mode 100644 (file)
index 0000000..2cb0ce6
--- /dev/null
@@ -0,0 +1,515 @@
+/* PSPP - computes sample statistics.
+   Copyright (C) 2007 Free Software Foundation, Inc.
+
+   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 routines defined in
+   range-map.c.  This test program aims to be as comprehensive as
+   possible.  With -DNDEBUG, "gcov -b" should report 100%
+   coverage of lines and branches in range-map.c routines.
+   (Without -DNDEBUG, branches caused by failed assertions will
+   not be taken.)  "valgrind --leak-check=yes
+   --show-reachable=yes" should give a clean report, both with
+   and without -DNDEBUG. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libpspp/range-map.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libpspp/compiler.h>
+
+#include "xalloc.h"
+\f
+/* Currently running test. */
+static const char *test_name;
+
+/* 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 in %s test at %s, line %d\n",
+              test_name, __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__)
+\f
+/* Swaps *A and *B. */
+static void
+swap (int *a, int *b) 
+{
+  int t = *a;
+  *a = *b;
+  *b = t;
+}
+
+/* Reverses the order of the CNT integers starting at VALUES. */
+static void
+reverse (int *values, size_t cnt)
+{
+  size_t i = 0;
+  size_t j = cnt;
+
+  while (j > i)
+    swap (&values[i++], &values[--j]);
+}
+
+/* Arranges the CNT blocks in VALUES into the lexicographically
+   next greater permutation.  Returns true if successful.
+   If VALUES is already the lexicographically greatest
+   permutation of its blocks (i.e. ordered from greatest to
+   smallest), arranges them into the lexicographically least
+   permutation (i.e. ordered from smallest to largest) and
+   returns false. */
+static bool
+next_permutation (int *values, size_t cnt)
+{
+  if (cnt > 0)
+    {
+      size_t i = cnt - 1;
+      while (i != 0) 
+        {
+          i--;
+          if (values[i] < values[i + 1])
+            {
+              size_t j;
+              for (j = cnt - 1; values[i] >= values[j]; j--)
+                continue;
+              swap (values + i, values + j);
+              reverse (values + (i + 1), cnt - (i + 1));
+              return true;
+            } 
+        }
+      
+      reverse (values, cnt);
+    }
+  
+  return false;
+}
+
+/* Returns N!. */
+static unsigned int
+factorial (unsigned int n) 
+{
+  unsigned int value = 1;
+  /* Disallow N values that overflow on 32-bit machines. */
+  assert (n <= 12);
+  for (; n > 1; )
+    value *= n--;
+  return value;
+}
+
+/* Tests whether PARTS is a K-part integer composition of N.
+   Returns true if so, false otherwise. */
+static bool UNUSED
+is_k_composition (int n, int k, const int parts[]) 
+{
+  int sum;
+  int i;
+
+  sum = 0;
+  for (i = 0; i < k; i++)
+    {
+      if (parts[i] < 1 || parts[i] > n)
+        return false;
+      sum += parts[i];
+    }
+  return sum == n;
+}
+
+/* Advances the K-part integer composition of N stored in PARTS
+   to the next lexicographically greater one.
+   Returns true if successful, false if the composition was
+   already the greatest K-part composition of N (in which case
+   PARTS is unaltered). */
+static bool
+next_k_composition (int n UNUSED, int k, int parts[]) 
+{
+  int x, i;
+
+  assert (is_k_composition (n, k, parts));
+  if (k == 1)
+    return false;
+
+  for (i = k - 1; i > 0; i--)
+    if (parts[i] > 1)
+      break;
+  if (i == 0)
+    return false;
+
+  x = parts[i] - 1;
+  parts[i] = 1;
+  parts[i - 1]++;
+  parts[k - 1] = x;
+
+  assert (is_k_composition (n, k, parts));
+  return true;
+}
+
+/* Sets the K integers in PARTS to the lexicographically first
+   K-part composition of N. */
+static void
+first_k_composition (int n, int k, int parts[]) 
+{
+  int i;
+
+  assert (n >= k);
+
+  for (i = 0; i < k; i++)
+    parts[i] = 1;
+  parts[k - 1] += n - k;
+}
+
+/* Advances *K and PARTS to the next integer composition of N.
+   Compositions are ordered from shortest to longest and in
+   lexicographical order within a given length.
+   Before the first call, initialize *K to 0.
+   After each successful call, *K contains the length of the
+   current composition and the *K blocks in PARTS contain its
+   parts.
+   Returns true if successful, false if the set of compositions
+   has been exhausted. */
+static bool
+next_composition (int n, int *k, int parts[]) 
+{
+  if (*k >= 1 && next_k_composition (n, *k, parts))
+    return true;
+  else if (*k < n)
+    {
+      first_k_composition (n, ++*k, parts);
+      return true;
+    }
+  else
+    return false;
+}
+\f
+/* Test data element. */
+struct element
+  {
+    struct range_map_node node; /* Embedded tower block. */
+    int x;                      /* Primary value. */
+  };
+
+static struct element *
+range_map_node_to_element (struct range_map_node *node) 
+{
+  return range_map_data (node, struct element, node);
+}
+
+/* Element we expect to find. */
+struct expected_element 
+  {
+    int x;                      /* Primary value. */
+    unsigned long int start;    /* Start of region. */
+    unsigned long int end;      /* End of region. */
+  };
+
+/* Compares expected_element A and B and returns a strcmp()-type
+   result. */
+static int
+compare_expected_element (const void *a_, const void *b_) 
+{
+  const struct expected_element *a = (const struct expected_element *) a_;
+  const struct expected_element *b = (const struct expected_element *) b_;
+  return a->start < b->start ? -1 : a->start > b->start;
+}
+
+/* Checks that RM contains the ELEM_CNT elements described by
+   ELEMENTS[]. */
+static void
+check_range_map (struct range_map *rm,
+                 struct expected_element elements[], size_t elem_cnt) 
+{
+  struct expected_element *sorted;
+  struct range_map_node *node;
+  size_t i;
+
+  sorted = xnmalloc (elem_cnt, sizeof *sorted);
+  memcpy (sorted, elements, elem_cnt * sizeof *elements);
+  qsort (sorted, elem_cnt, sizeof *sorted, compare_expected_element);
+  
+  check (range_map_is_empty (rm) == (elem_cnt == 0));
+
+  for (i = 0; i < elem_cnt; i++) 
+    {
+      struct expected_element *e = &sorted[i];
+      unsigned long int position;
+
+      /* Check that range_map_lookup finds all the positions
+         within the element. */
+      for (position = e->start; position < e->end; position++) 
+        {
+          struct range_map_node *found = range_map_lookup (rm, position);
+          check (found != NULL);
+          check (range_map_node_to_element (found)->x == e->x);
+          check (range_map_node_get_start (found) == e->start);
+          check (range_map_node_get_end (found) == e->end);
+          check (range_map_node_get_width (found) == e->end - e->start);
+        }
+
+      /* If there shouldn't be any elements in the positions just
+         before or after the element, verify that
+         range_map_lookup doesn't find any there. */
+      if (e->start > 0 && (i == 0 || e[-1].end < e->start))
+        check (range_map_lookup (rm, e->start - 1) == NULL);
+      if (i == elem_cnt - 1 || e->end < e[1].start)
+        check (range_map_lookup (rm, e->end) == NULL);
+    }
+
+  for (node = (rand () % 2 ? range_map_first (rm) : range_map_next (rm, NULL)),
+         i = 0;
+       node != NULL;
+       node = range_map_next (rm, node), i++) 
+    {
+      struct expected_element *e = &sorted[i];
+      check (range_map_node_to_element (node)->x == e->x);
+    }
+  check (i == elem_cnt);
+
+  free (sorted);
+}
+\f
+/* Tests inserting all possible sets of ranges into a range map
+   in all possible orders, up to a specified maximum overall
+   range. */
+static void
+test_insert (void) 
+{
+  const int max_range = 7;
+  int cnt;
+
+  for (cnt = 1; cnt <= max_range; cnt++) 
+    {
+      unsigned int composition_cnt;
+      struct expected_element *expected;
+      int *widths;
+      int elem_cnt;
+      int *order;
+      struct element *elements;
+      
+      expected = xnmalloc (cnt, sizeof *expected);
+      widths = xnmalloc (cnt, sizeof *widths);
+      order = xnmalloc (cnt, sizeof *order);
+      elements = xnmalloc (cnt, sizeof *elements);
+
+      elem_cnt = 0;
+      composition_cnt = 0;
+      while (next_composition (cnt, &elem_cnt, widths)) 
+        {
+          int i, j;
+          unsigned int permutation_cnt;
+
+          for (i = 0; i < elem_cnt; i++) 
+            order[i] = i;
+
+          permutation_cnt = 0;
+          while (permutation_cnt == 0 || next_permutation (order, elem_cnt)) 
+            {
+              struct range_map rm;
+
+              /* Inserts the elem_cnt elements with the given
+                 widths[] into T in the order given by order[]. */
+              range_map_init (&rm);
+              for (i = 0; i < elem_cnt; i++) 
+                {
+                  unsigned long int start, end;
+                  int idx;
+
+                  idx = order[i];
+                  elements[idx].x = idx;
+
+                  /* Find start and end of element. */
+                  start = 0;
+                  for (j = 0; j < idx; j++)
+                    start += widths[j];
+                  end = start + widths[j];
+
+                  /* Insert. */
+                  range_map_insert (&rm, start, end - start,
+                                    &elements[idx].node);
+
+                  /* Check map contents. */
+                  expected[i].x = idx;
+                  expected[i].start = start;
+                  expected[i].end = end;
+                  check_range_map (&rm, expected, i + 1);
+                }
+              permutation_cnt++;
+            }
+          check (permutation_cnt == factorial (elem_cnt));
+          
+          composition_cnt++;
+        }
+      check (composition_cnt == 1 << (cnt - 1));
+
+      free (expected);
+      free (widths);
+      free (order);
+      free (elements);
+    }
+}
+
+/* Tests deleting ranges from a range map in all possible orders,
+   up to a specified maximum overall range. */
+static void
+test_delete (int gap) 
+{
+  const int max_range = 7;
+  int cnt;
+
+  for (cnt = 1; cnt <= max_range; cnt++) 
+    {
+      unsigned int composition_cnt;
+      struct expected_element *expected;
+      int *widths;
+      int elem_cnt;
+      int *order;
+      struct element *elements;
+      
+      expected = xnmalloc (cnt, sizeof *expected);
+      widths = xnmalloc (cnt, sizeof *widths);
+      order = xnmalloc (cnt, sizeof *order);
+      elements = xnmalloc (cnt, sizeof *elements);
+
+      elem_cnt = 0;
+      composition_cnt = 0;
+      while (next_composition (cnt, &elem_cnt, widths)) 
+        {
+          int i, j;
+          unsigned int permutation_cnt;
+
+          for (i = 0; i < elem_cnt; i++) 
+            order[i] = i;
+
+          permutation_cnt = 0;
+          while (permutation_cnt == 0 || next_permutation (order, elem_cnt)) 
+            {
+              struct range_map rm;
+              unsigned long int start;
+
+              /* Insert all the elements. */
+              range_map_init (&rm);
+              start = 0;
+              for (i = 0; i < elem_cnt; i++) 
+                {
+                  int width = widths[i] > gap ? widths[i] - gap : widths[i];
+                  unsigned long int end = start + width;
+
+                  elements[i].x = i;
+                  range_map_insert (&rm, start, end - start,
+                                    &elements[i].node);
+
+                  for (j = 0; ; j++) 
+                    {
+                      assert (j < elem_cnt);
+                      if (order[j] == i) 
+                        {
+                          expected[j].x = i;
+                          expected[j].start = start;
+                          expected[j].end = end;
+                          break;
+                        }
+                    }
+                  
+                  start += widths[i];
+                }
+              check_range_map (&rm, expected, elem_cnt);
+
+              /* Delete the elements in the specified order. */
+              for (i = 0; i < elem_cnt; i++) 
+                {
+                  range_map_delete (&rm, &elements[order[i]].node);
+                  check_range_map (&rm, expected + i + 1, elem_cnt - i - 1);
+                }
+
+              permutation_cnt++;
+            }
+          check (permutation_cnt == factorial (elem_cnt));
+          
+          composition_cnt++;
+        }
+      check (composition_cnt == 1 << (cnt - 1));
+
+      free (expected);
+      free (widths);
+      free (order);
+      free (elements);
+    }
+}
+
+/* Tests deleting ranges from a range map filled with contiguous
+   ranges in all possible orders, up to a specified maximum
+   overall range. */
+static void
+test_delete_contiguous (void) 
+{
+  test_delete (0);
+}
+
+/* Tests deleting ranges from a range map filled with ranges
+   sometimes separated by gaps in all possible orders, up to a
+   specified maximum overall range. */
+static void
+test_delete_gaps (void) 
+{
+  test_delete (1);
+}
+\f
+/* Main program. */
+
+/* Runs TEST_FUNCTION and prints a message about NAME. */
+static void
+run_test (void (*test_function) (void), const char *name) 
+{
+  test_name = name;
+  putchar ('.');
+  fflush (stdout);
+  test_function ();
+}
+
+int
+main (void) 
+{
+  run_test (test_insert, "insert");
+  run_test (test_delete_contiguous, "delete from contiguous ranges");
+  run_test (test_delete_gaps, "delete from ranges separated by gaps");
+  putchar ('\n');
+
+  return 0;
+}