New data structure sparse_xarray.
[pspp-builds.git] / tests / libpspp / sparse-xarray-test.c
diff --git a/tests/libpspp/sparse-xarray-test.c b/tests/libpspp/sparse-xarray-test.c
new file mode 100644 (file)
index 0000000..798b842
--- /dev/null
@@ -0,0 +1,644 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2007, 2009 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+/* This is a test program for the sparse array routines defined
+   in sparse-xarray.c.  This test program aims to be as
+   comprehensive as possible.  "gcov -b" should report 100%
+   coverage of lines and branches in sparse-xarray.c when
+   compiled with -DNDEBUG.  "valgrind --leak-check=yes
+   --show-reachable=yes" should give a clean report. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <getopt.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libpspp/argv-parser.h>
+#include <libpspp/assertion.h>
+#include <libpspp/hash-functions.h>
+#include <libpspp/model-checker.h>
+#include <libpspp/sparse-xarray.h>
+#include <libpspp/str.h>
+
+#include "error.h"
+#include "minmax.h"
+#include "progname.h"
+#include "xalloc.h"
+\f
+/* Maximum size of sparse_xarray supported for model checking
+   purposes. */
+#define MAX_ROWS 5
+#define MAX_COLS 5
+
+/* Test parameters. */
+struct test_params
+  {
+    /* Controlling the test state space. */
+    int n_columns;              /* Number of columns in each row. */
+    int max_rows;               /* Maximum number of rows. */
+    int max_memory_rows;        /* Max rows before writing to disk. */
+    unsigned char n_values;     /* Number of unique cell values. */
+    int n_xarrays;              /* Number of sparse_xarrays in state. */
+
+    /* Types of operations to perform. */
+    bool write_cells;           /* Write to individual cells. */
+    bool write_rows;            /* Write whole rows. */
+    bool write_columns;         /* Write whole columns. */
+    bool copy_within_xarray;    /* Copy column ranges in a single xarray. */
+  };
+
+struct test_state
+  {
+    struct sparse_xarray *xarrays[2];
+  };
+
+static void
+test_state_destroy (const struct test_params *params, struct test_state *ts)
+{
+  int i;
+
+  for (i = 0; i < params->n_xarrays; i++)
+    sparse_xarray_destroy (ts->xarrays[i]);
+}
+
+static struct test_state *
+test_state_clone (const struct test_params *params,
+                  const struct test_state *ots)
+{
+  struct test_state *ts;
+  int i;
+
+  ts = xmalloc (sizeof *ts);
+  for (i = 0; i < params->n_xarrays; i++)
+    {
+      ts->xarrays[i] = sparse_xarray_clone (ots->xarrays[i]);
+      if (ts->xarrays[i] == NULL)
+        NOT_REACHED ();
+    }
+  return ts;
+}
+
+struct xarray_model
+  {
+    uint8_t data[MAX_ROWS][MAX_COLS];
+    bool contains_row[MAX_ROWS];
+  };
+
+struct test_model
+  {
+    struct xarray_model models[2];
+  };
+
+/* Extracts the contents of TS into TM. */
+static void
+test_model_extract (const struct test_params *params,
+                    const struct test_state *ts, struct test_model *tm)
+{
+  int i;
+
+  for (i = 0; i < params->n_xarrays; i++)
+    {
+      const struct sparse_xarray *sx = ts->xarrays[i];
+      struct xarray_model *model = &tm->models[i];
+      size_t n_columns = sparse_xarray_get_n_columns (sx);
+      size_t n_rows = sparse_xarray_get_n_rows (sx);
+      size_t row;
+
+      assert (n_rows < MAX_ROWS);
+      assert (n_columns < MAX_COLS);
+      for (row = 0; row < params->max_rows; row++)
+        {
+          model->contains_row[row] = sparse_xarray_contains_row (sx, row);
+          if (!sparse_xarray_read (sx, row, 0, n_columns, model->data[row]))
+            NOT_REACHED ();
+        }
+    }
+}
+
+/* Checks that test state TS matches the test model TM and
+   reports any mismatches via mc_error.  Then, adds SX to MC as a
+   new state. */
+static void
+check_state (struct mc *mc, struct test_state *ts, const struct test_model *tm)
+{
+  const struct test_params *params = mc_get_aux (mc);
+  int n_columns = params->n_columns;
+  unsigned int hash;
+  int i;
+
+  for (i = 0; i < params->n_xarrays; i++)
+    {
+      const struct xarray_model *model = &tm->models[i];
+      const struct sparse_xarray *sx = ts->xarrays[i];
+      bool difference;
+      int row, col;
+      int n_rows;
+
+      assert (n_columns < MAX_COLS);
+
+      /* Check row count. */
+      n_rows = 0;
+      for (row = 0; row < params->max_rows; row++)
+        if (model->contains_row[row])
+          n_rows = row + 1;
+      if (n_rows != sparse_xarray_get_n_rows (sx))
+        mc_error (mc, "xarray %d: row count (%zu) does not match expected "
+                  "(%d)", i, sparse_xarray_get_n_rows (sx), n_rows);
+
+      /* Check row containment. */
+      for (row = 0; row < params->max_rows; row++)
+        {
+          bool contains = sparse_xarray_contains_row (sx, row);
+          if (contains && !model->contains_row[row])
+            mc_error (mc, "xarray %d: row %d is contained by sparse_xarray "
+                      "but should not be", i, row);
+          else if (!contains && model->contains_row[row])
+            mc_error (mc, "xarray %d: row %d is not contained by "
+                      "sparse_xarray but should be", i, row);
+        }
+
+      /* Check contents. */
+      difference = false;
+      for (row = 0; row < params->max_rows; row++)
+        {
+          unsigned char data[MAX_COLS];
+
+          if (!sparse_xarray_read (sx, row, 0, n_columns, data))
+            NOT_REACHED ();
+          for (col = 0; col < params->n_columns; col++)
+            if (data[col] != model->data[row][col])
+              {
+                mc_error (mc, "xarray %d: element %zu,%zu (of %zu,%zu) "
+                          "differs: %d should be %d",
+                          i, row, col, n_rows, n_columns, data[col],
+                          model->data[row][col]);
+                difference = true;
+              }
+        }
+
+      if (difference)
+        {
+          struct string ds;
+
+          mc_error (mc, "xarray %d: expected:", i);
+          ds_init_empty (&ds);
+          for (row = 0; row < params->max_rows; row++)
+            {
+              ds_clear (&ds);
+              for (col = 0; col < n_columns; col++)
+                ds_put_format (&ds, " %d", model->data[row][col]);
+              mc_error (mc, "xarray %d: row %zu:%s", i, row, ds_cstr (&ds));
+            }
+
+          mc_error (mc, "xarray %d: actual:", i);
+          ds_init_empty (&ds);
+          for (row = 0; row < params->max_rows; row++)
+            {
+              unsigned char data[MAX_COLS];
+
+              if (!sparse_xarray_read (sx, row, 0, n_columns, data))
+                NOT_REACHED ();
+
+              ds_clear (&ds);
+              for (col = 0; col < n_columns; col++)
+                ds_put_format (&ds, " %d", data[col]);
+              mc_error (mc, "xarray %d: row %zu:%s", i, row, ds_cstr (&ds));
+            }
+
+          ds_destroy (&ds);
+        }
+    }
+
+  hash = 0;
+  for (i = 0; i < params->n_xarrays; i++)
+    hash = sparse_xarray_model_checker_hash (ts->xarrays[i], hash);
+  if (mc_discard_dup_state (mc, hash))
+    test_state_destroy (params, ts);
+  else
+    mc_add_state (mc, ts);
+}
+
+static bool
+next_data (unsigned char *data, int n, int n_values)
+{
+  int i;
+  for (i = n - 1; i >= 0; i--)
+    {
+      data[i]++;
+      if (data[i] < n_values)
+        return true;
+      data[i] = 0;
+    }
+  return false;
+}
+
+struct copy_columns_params
+  {
+    int n;                      /* Number of columns to copy. */
+    int src;                    /* Offset of first source column. */
+    int dst;                    /* Offset of first destination column. */
+  };
+
+static bool
+copy_columns (const void *src_, void *dst_, void *copy_)
+{
+  const struct copy_columns_params *copy = copy_;
+  const uint8_t *src = src_;
+  uint8_t *dst = dst_;
+
+  memmove (dst + copy->dst, src + copy->src, copy->n);
+  return true;
+}
+
+/* "init" function for struct mc_class. */
+static void
+sparse_xarray_mc_init (struct mc *mc)
+{
+  struct test_params *params = mc_get_aux (mc);
+  struct test_state *ts;
+  struct test_model tm;
+  int i;
+
+  mc_name_operation (mc, "empty sparse_xarray with n_columns=%zu, "
+                     "max_memory_rows=%zu",
+                     params->n_columns, params->max_memory_rows);
+  ts = xmalloc (sizeof *ts);
+  for (i = 0; i < params->n_xarrays; i++)
+    ts->xarrays[i] = sparse_xarray_create (params->n_columns,
+                                           params->max_memory_rows);
+  memset (&tm, 0, sizeof tm);
+  check_state (mc, ts, &tm);
+}
+
+/* "mutate" function for struct mc_class. */
+static void
+sparse_xarray_mc_mutate (struct mc *mc, const void *ots_)
+{
+  struct test_params *params = mc_get_aux (mc);
+  size_t n_columns = params->n_columns;
+  const struct test_state *ots = ots_;
+  struct test_model otm;
+  int i;
+
+  test_model_extract (params, ots, &otm);
+  for (i = 0; i < params->n_xarrays; i++)
+    {
+      unsigned char value;
+      int row, col, n, src, dst;
+
+      /* Write all possible values to each possible single cell. */
+      if (params->write_cells)
+        for (row = 0; row < params->max_rows; row++)
+          for (col = 0; col < n_columns; col++)
+            for (value = 0; value < params->n_values; value++)
+              if (mc_include_state (mc))
+                {
+                  struct test_state *ts = test_state_clone (params, ots);
+                  struct sparse_xarray *sx = ts->xarrays[i];
+                  struct test_model tm = otm;
+                  struct xarray_model *model = &tm.models[i];
+
+                  mc_name_operation (mc, "xarray %d: set (%d,%d) to %d",
+                                     i, row, col, value);
+                  if (!sparse_xarray_write (sx, row, col, 1, &value))
+                    NOT_REACHED ();
+                  model->data[row][col] = value;
+                  model->contains_row[row] = true;
+                  check_state (mc, ts, &tm);
+                }
+
+      /* Write all possible row contents to each row. */
+      if (params->write_rows)
+        for (row = 0; row < params->max_rows; row++)
+          {
+            struct test_model tm = otm;
+            struct xarray_model *model = &tm.models[i];
+
+            memset (model->data[row], 0, n_columns);
+            model->contains_row[row] = true;
+            do
+              {
+                if (mc_include_state (mc))
+                  {
+                    struct test_state *ts = test_state_clone (params, ots);
+                    struct sparse_xarray *sx = ts->xarrays[i];
+                    char row_string[MAX_COLS + 1];
+
+                    mc_name_operation (mc, "xarray %d: set row %d to %s",
+                                       i, row, row_string);
+                    for (col = 0; col < n_columns; col++)
+                      {
+                        value = model->data[row][col];
+                        row_string[col] = value < 10 ? '0' + value : '*';
+                      }
+                    row_string[n_columns] = '\0';
+                    if (!sparse_xarray_write (sx, row, 0, n_columns,
+                                              model->data[row]))
+                      NOT_REACHED ();
+                    check_state (mc, ts, &tm);
+                  }
+              }
+            while (next_data (model->data[row], n_columns, params->n_values));
+          }
+
+      /* Write all possible values to each possible column. */
+      if (params->write_columns)
+        for (col = 0; col < n_columns; col++)
+          for (value = 0; value < params->n_values; value++)
+            if (mc_include_state (mc))
+              {
+                struct test_state *ts = test_state_clone (params, ots);
+                struct sparse_xarray *sx = ts->xarrays[i];
+                struct test_model tm = otm;
+                struct xarray_model *model = &tm.models[i];
+
+                mc_name_operation (mc, "xarray %d: write value %d to "
+                                   "column %d", i, value, col);
+                if (!sparse_xarray_write_columns (sx, col, 1, &value))
+                  NOT_REACHED ();
+                for (row = 0; row < params->max_rows; row++)
+                  model->data[row][col] = value;
+                check_state (mc, ts, &tm);
+              }
+
+      /* Copy all possible column ranges within a single sparse_xarray. */
+      if (params->copy_within_xarray)
+        for (n = 1; n <= n_columns; n++)
+          for (src = 0; src <= n_columns - n; src++)
+            for (dst = 0; dst <= n_columns - n; dst++)
+              if (mc_include_state (mc))
+                {
+                  struct copy_columns_params copy_aux;
+                  struct test_state *ts = test_state_clone (params, ots);
+                  struct sparse_xarray *sx = ts->xarrays[i];
+                  struct test_model tm = otm;
+                  struct xarray_model *model = &tm.models[i];
+
+                  mc_name_operation (mc, "xarray %d: copy %d columns from "
+                                     "offset %d to offset %d", i, n, src, dst);
+
+                  copy_aux.n = n;
+                  copy_aux.src = src;
+                  copy_aux.dst = dst;
+                  if (!sparse_xarray_copy (sx, sx, copy_columns, &copy_aux))
+                    NOT_REACHED ();
+
+                  for (row = 0; row < params->max_rows; row++)
+                    memmove (&model->data[row][dst],
+                             &model->data[row][src], n);
+
+                  check_state (mc, ts, &tm);
+                }
+    }
+
+  if (params->n_xarrays == 2)
+    {
+      int row, n, src, dst;
+
+      /* Copy all possible column ranges from xarrays[0] to xarrays[1]. */
+      for (n = 1; n <= n_columns; n++)
+        for (src = 0; src <= n_columns - n; src++)
+          for (dst = 0; dst <= n_columns - n; dst++)
+            if (mc_include_state (mc))
+              {
+                struct copy_columns_params copy_aux;
+                struct test_state *ts = test_state_clone (params, ots);
+                struct test_model tm = otm;
+
+                mc_name_operation (mc, "copy %d columns from offset %d in "
+                                   "xarray 0 to offset %d in xarray 1",
+                                   n, src, dst);
+
+                copy_aux.n = n;
+                copy_aux.src = src;
+                copy_aux.dst = dst;
+                if (!sparse_xarray_copy (ts->xarrays[0], ts->xarrays[1],
+                                         copy_columns, &copy_aux))
+                  NOT_REACHED ();
+
+                for (row = 0; row < params->max_rows; row++)
+                  {
+                    if (tm.models[0].contains_row[row])
+                      tm.models[1].contains_row[row] = true;
+                    memmove (&tm.models[1].data[row][dst],
+                             &tm.models[0].data[row][src], n);
+                  }
+
+                check_state (mc, ts, &tm);
+              }
+    }
+}
+
+/* "destroy" function for struct mc_class. */
+static void
+sparse_xarray_mc_destroy (const struct mc *mc UNUSED, void *ts_)
+{
+  struct test_params *params = mc_get_aux (mc);
+  struct test_state *ts = ts_;
+
+  test_state_destroy (params, ts);
+}
+
+static void
+usage (void)
+{
+  printf ("%s, for testing the sparse_xarray implementation.\n"
+          "Usage: %s [OPTION]...\n"
+          "\nTest state space parameters (min...max, default):\n"
+          "  --columns=N          Number of columns per row (0...5, 3)\n"
+          "  --max-rows=N         Maximum number of rows (0...5, 3)\n"
+          "  --max-memory-rows=N  Max rows before paging to disk (0...5, 3)\n"
+          "  --values=N           Number of unique cell values (1...254, 3)\n"
+          "  --xarrays=N          Number of xarrays at a time (1...2, 1)\n"
+          "\nTest operation parameters:\n"
+          "  --no-write-cells     Do not write individual cells\n"
+          "  --no-write-rows      Do not write whole rows\n"
+          "  --no-write-columns   Do not write whole columns\n"
+          "  --no-copy-columns    Do not copy column ranges in an xarray\n",
+          program_name, program_name);
+  mc_options_usage ();
+  fputs ("\nOther options:\n"
+         "  --help               Display this help message\n"
+         "\nReport bugs to <bug-gnu-pspp@gnu.org>\n",
+         stdout);
+  exit (0);
+}
+\f
+enum
+  {
+    OPT_COLUMNS,
+    OPT_MAX_ROWS,
+    OPT_MAX_MEMORY_ROWS,
+    OPT_VALUES,
+    OPT_XARRAYS,
+    OPT_NO_WRITE_CELLS,
+    OPT_NO_WRITE_ROWS,
+    OPT_NO_WRITE_COLUMNS,
+    OPT_NO_COPY_COLUMNS,
+    OPT_HELP
+  };
+
+static struct argv_option sparse_xarray_argv_options[] =
+  {
+    {"columns", 0, required_argument, OPT_COLUMNS},
+    {"max-rows", 0, required_argument, OPT_MAX_ROWS},
+    {"max-memory-rows", 0, required_argument, OPT_MAX_MEMORY_ROWS},
+    {"values", 0, required_argument, OPT_VALUES},
+    {"xarrays", 0, required_argument, OPT_XARRAYS},
+    {"no-write-cells", 0, no_argument, OPT_NO_WRITE_CELLS},
+    {"no-write-rows", 0, no_argument, OPT_NO_WRITE_ROWS},
+    {"no-write-columns", 0, no_argument, OPT_NO_WRITE_COLUMNS},
+    {"no-copy-columns", 0, no_argument, OPT_NO_COPY_COLUMNS},
+    {"help", 'h', no_argument, OPT_HELP},
+  };
+enum { N_SPARSE_XARRAY_ARGV_OPTIONS = (sizeof sparse_xarray_argv_options
+                                       / sizeof *sparse_xarray_argv_options) };
+
+static void
+sparse_xarray_option_callback (int id, void *params_)
+{
+  struct test_params *params = params_;
+  switch (id)
+    {
+    case OPT_COLUMNS:
+      params->n_columns = atoi (optarg);
+      break;
+
+    case OPT_MAX_ROWS:
+      params->max_rows = atoi (optarg);
+      break;
+
+    case OPT_MAX_MEMORY_ROWS:
+      params->max_memory_rows = atoi (optarg);
+      break;
+
+    case OPT_VALUES:
+      params->n_values = atoi (optarg);
+      break;
+
+    case OPT_XARRAYS:
+      params->n_xarrays = atoi (optarg);
+      break;
+
+    case OPT_NO_WRITE_CELLS:
+      params->write_cells = false;
+      break;
+
+    case OPT_NO_WRITE_ROWS:
+      params->write_rows = false;
+      break;
+
+    case OPT_NO_WRITE_COLUMNS:
+      params->write_columns = false;
+      break;
+
+    case OPT_NO_COPY_COLUMNS:
+      params->copy_within_xarray = false;
+      break;
+
+    case OPT_HELP:
+      usage ();
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+}
+
+int
+main (int argc, char *argv[])
+{
+  static const struct mc_class sparse_xarray_mc_class =
+    {
+      sparse_xarray_mc_init,
+      sparse_xarray_mc_mutate,
+      sparse_xarray_mc_destroy,
+    };
+
+  struct test_params params;
+  struct mc_options *options;
+  struct mc_results *results;
+  struct argv_parser *parser;
+  int verbosity;
+  bool success;
+
+  set_program_name (argv[0]);
+
+  /* Default parameters. */
+  params.n_columns = 3;
+  params.max_rows = 3;
+  params.max_memory_rows = 3;
+  params.n_values = 3;
+  params.n_xarrays = 1;
+  params.write_cells = true;
+  params.write_rows = true;
+  params.write_columns = true;
+  params.copy_within_xarray = true;
+
+  /* Parse command line. */
+  parser = argv_parser_create ();
+  options = mc_options_create ();
+  mc_options_register_argv_parser (options, parser);
+  argv_parser_add_options (parser, sparse_xarray_argv_options,
+                           N_SPARSE_XARRAY_ARGV_OPTIONS,
+                           sparse_xarray_option_callback, &params);
+  if (!argv_parser_run (parser, argc, argv))
+    exit (EXIT_FAILURE);
+  argv_parser_destroy (parser);
+  verbosity = mc_options_get_verbosity (options);
+
+  /* Force parameters into allowed ranges. */
+  params.n_columns = MAX (0, MIN (params.n_columns, MAX_COLS));
+  params.max_rows = MAX (0, MIN (params.max_rows, MAX_ROWS));
+  params.max_memory_rows = MAX (0, MIN (params.max_memory_rows,
+                                        params.max_rows));
+  params.n_values = MIN (254, MAX (1, params.n_values));
+  params.n_xarrays = MAX (1, MIN (2, params.n_xarrays));
+  mc_options_set_aux (options, &params);
+  results = mc_run (&sparse_xarray_mc_class, options);
+
+  /* Output results. */
+  success = (mc_results_get_stop_reason (results) != MC_MAX_ERROR_COUNT
+             && mc_results_get_stop_reason (results) != MC_INTERRUPTED);
+  if (verbosity > 0 || !success)
+    {
+      printf ("Parameters: "
+              "--columns=%d --max-rows=%d --max-memory-rows=%d --values=%d "
+              "--xarrays=%d",
+              params.n_columns, params.max_rows, params.max_memory_rows,
+              params.n_values, params.n_xarrays);
+      if (!params.write_cells)
+        printf (" --no-write-cells");
+      if (!params.write_rows)
+        printf (" --no-write-rows");
+      if (!params.write_columns)
+        printf (" --no-write-columns");
+      if (!params.copy_within_xarray)
+        printf (" --no-copy-columns");
+      printf ("\n\n");
+      mc_results_print (results, stdout);
+    }
+  mc_results_destroy (results);
+
+  return success ? 0 : EXIT_FAILURE;
+}