Move datasheet test out of PSPP into a separate binary.
authorBen Pfaff <blp@gnu.org>
Wed, 27 May 2009 05:02:48 +0000 (22:02 -0700)
committerBen Pfaff <blp@gnu.org>
Sun, 7 Jun 2009 04:11:16 +0000 (21:11 -0700)
When it's not difficult to do so, it is better to put tests in separate
binaries instead of in the PSPP binaries, so that the binaries are not
burdened with code that is not of real interest to users and to make the
main PSPP binaries build faster.

src/language/command.def
src/language/tests/automake.mk
src/language/tests/check-model.h [deleted file]
src/language/tests/check-model.q [deleted file]
src/language/tests/datasheet-check.c [deleted file]
src/language/tests/datasheet-check.h [deleted file]
src/language/tests/datasheet-test.c [deleted file]
tests/automake.mk
tests/command/datasheet.sh [deleted file]
tests/data/datasheet-test.c [new file with mode: 0644]
tests/data/datasheet-test.sh [new file with mode: 0755]

index f87747d892e8d9566da798936f243ad0fdd46ec1..4c8df335722aa192eb2e38297c6449f5679b19f4 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2006 Free Software Foundation, Inc.
+   Copyright (C) 2006, 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
@@ -132,7 +132,6 @@ DEF_CMD (S_INPUT_PROGRAM, 0, "END INPUT PROGRAM", cmd_end_input_program)
 DEF_CMD (S_INPUT_PROGRAM, 0, "REREAD", cmd_reread)
 
 /* Commands for testing PSPP. */
-DEF_CMD (S_ANY, F_TESTING, "DEBUG DATASHEET", cmd_debug_datasheet)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG EVALUATE", cmd_debug_evaluate)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG FORMAT GUESSER", cmd_debug_format_guesser)
 DEF_CMD (S_ANY, F_TESTING, "DEBUG MOMENTS", cmd_debug_moments)
index 6268422e812b0461b6e57e7722e03d4a7c846d7e..5f025cd5ef7ff4775c09ccba9f752fa46c932249 100644 (file)
@@ -1,21 +1,10 @@
 ## Process this file with automake to produce Makefile.in  -*- makefile -*-
 
-language_tests_built_sources = \
-       src/language/tests/check-model.c
-
 language_tests_sources = \
-       src/language/tests/check-model.h \
-       src/language/tests/datasheet-test.c \
-       src/language/tests/datasheet-check.c \
-       src/language/tests/datasheet-check.h \
        src/language/tests/format-guesser-test.c \
        src/language/tests/float-format.c \
        src/language/tests/moments-test.c \
        src/language/tests/paper-size.c \
        src/language/tests/pool-test.c 
 
-all_q_sources += $(language_tests_built_sources:.c=.q)
-EXTRA_DIST += $(language_tests_built_sources:.c=.q)
-CLEANFILES += $(language_tests_built_sources)
-
 EXTRA_DIST += src/language/tests/OChangeLog
diff --git a/src/language/tests/check-model.h b/src/language/tests/check-model.h
deleted file mode 100644 (file)
index 2c93355..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   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 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/>. */
-
-/* PSPP syntax interface to model checker.
-
-   A model checker is a software testing tool.  PSPP includes a
-   generic model checker in libpspp/model-checker.[ch].  This
-   module layers a PSPP syntax interface on top of the model
-   checker's options. */
-
-#ifndef LANGUAGE_TESTS_CHECK_MODEL
-#define LANGUAGE_TESTS_CHECK_MODEL 1
-
-#include <stdbool.h>
-
-struct lexer;
-struct mc_options;
-struct mc_results;
-
-bool check_model (struct lexer *lexer,
-                  struct mc_results *(*checker) (struct mc_options *, void *),
-                  void *aux);
-
-#endif /* check-model.h */
diff --git a/src/language/tests/check-model.q b/src/language/tests/check-model.q
deleted file mode 100644 (file)
index f9ab6f2..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-/* 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/>. */
-
-#include <config.h>
-#include <limits.h>
-
-#include <language/tests/check-model.h>
-
-#include <errno.h>
-
-#include <language/lexer/lexer.h>
-#include <libpspp/model-checker.h>
-
-#include "error.h"
-#include "fwriteerror.h"
-
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-#define N_(msgid) msgid
-
-/* (headers) */
-
-/* (specification)
-   "CHECK MODEL" (chm_):
-    search=strategy:broad/deep/random,
-           :mxd(n:max_depth),
-           :hash(n:hash_bits);
-    path=integer list;
-    queue=:limit(n:queue_limit,"%s>0"),
-          drop:newest/oldest/random;
-    seed=integer;
-    stop=:states(n:max_unique_states,"%s>0"),
-         :errors(n:max_errors),
-         :timeout(d:time_limit,"%s>0");
-    progress=progress:none/dots/fancy/verbose;
-    output=:verbosity(n:verbosity),
-           :errverbosity(n:err_verbosity),
-           :file(s:output_file).
-*/
-/* (declarations) */
-/* (functions) */
-
-static struct mc_options *parse_options (struct lexer *);
-
-/* Parses a syntax description of model checker options from
-   LEXER and passes them, along with AUX, to the CHECKER
-   function, which must wrap a call to mc_run and return the
-   mc_results that it returned.  This function then prints a
-   description of the mc_results to the output file.  Returns
-   true if the model checker run found no errors, false
-   otherwise. */
-bool
-check_model (struct lexer *lexer,
-             struct mc_results *(*checker) (struct mc_options *, void *aux),
-             void *aux)
-{
-  struct mc_options *options;
-  struct mc_results *results;
-  FILE *output_file;
-  bool ok;
-
-  options = parse_options (lexer);
-  if (options == NULL)
-    return false;
-  output_file = mc_options_get_output_file (options);
-
-  results = checker (options, aux);
-
-  mc_results_print (results, output_file);
-
-  if (output_file != stdout && output_file != stderr)
-    {
-      if (fwriteerror (output_file) < 0)
-        {
-          /* We've already discarded the name of the output file.
-             Oh well. */
-          error (0, errno, "error closing output file");
-        }
-    }
-
-  ok = mc_results_get_error_count (results) == 0;
-  mc_results_destroy (results);
-
-  return ok;
-}
-
-/* Parses options from LEXER and returns a corresponding
-   mc_options, or a null pointer if parsing fails. */
-static struct mc_options *
-parse_options (struct lexer *lexer)
-{
-  struct cmd_check_model cmd;
-  struct mc_options *options;
-
-  if (!parse_check_model (lexer, NULL, &cmd, NULL))
-    return NULL;
-
-  options = mc_options_create ();
-  if (cmd.strategy != -1)
-    mc_options_set_strategy (options,
-                             cmd.strategy == CHM_BROAD ? MC_BROAD
-                             : cmd.strategy == CHM_DEEP ? MC_DEEP
-                             : cmd.strategy == CHM_RANDOM ? MC_RANDOM
-                             : -1);
-  if (cmd.sbc_path > 0)
-    {
-      if (cmd.sbc_search > 0)
-        msg (SW, _("PATH and SEARCH subcommands are mutually exclusive.  "
-                   "Ignoring PATH."));
-      else
-        {
-          struct subc_list_int *list = &cmd.il_path[0];
-          int count = subc_list_int_count (list);
-          if (count > 0)
-            {
-              struct mc_path path;
-              int i;
-
-              mc_path_init (&path);
-              for (i = 0; i < count; i++)
-                mc_path_push (&path, subc_list_int_at (list, i));
-              mc_options_set_follow_path (options, &path);
-              mc_path_destroy (&path);
-            }
-          else
-            msg (SW, _("At least one value must be specified on PATH."));
-        }
-    }
-  if (cmd.max_depth != LONG_MIN)
-    mc_options_set_max_depth (options, cmd.max_depth);
-  if (cmd.hash_bits != LONG_MIN)
-    {
-      int hash_bits;
-      mc_options_set_hash_bits (options, cmd.hash_bits);
-      hash_bits = mc_options_get_hash_bits (options);
-      if (hash_bits != cmd.hash_bits)
-        msg (SW, _("Hash bits adjusted to %d."), hash_bits);
-    }
-  if (cmd.queue_limit != LONG_MIN)
-    mc_options_set_queue_limit (options, cmd.queue_limit);
-  if (cmd.drop != -1)
-    {
-      enum mc_queue_limit_strategy drop
-        = (cmd.drop == CHM_NEWEST ? MC_DROP_NEWEST
-           : cmd.drop == CHM_OLDEST ? MC_DROP_OLDEST
-           : cmd.drop == CHM_RANDOM ? MC_DROP_RANDOM
-           : -1);
-      mc_options_set_queue_limit_strategy (options, drop);
-    }
-  if (cmd.sbc_search > 0)
-    mc_options_set_seed (options, cmd.n_seed[0]);
-  if (cmd.max_unique_states != LONG_MIN)
-    mc_options_set_max_unique_states (options, cmd.max_unique_states);
-  if (cmd.max_errors != LONG_MIN)
-    mc_options_set_max_errors (options, cmd.max_errors);
-  if (cmd.time_limit != SYSMIS)
-    mc_options_set_time_limit (options, cmd.time_limit);
-  if (cmd.verbosity != LONG_MIN)
-    mc_options_set_verbosity (options, cmd.verbosity);
-  if (cmd.err_verbosity != LONG_MIN)
-    mc_options_set_failure_verbosity (options, cmd.err_verbosity);
-  if (cmd.progress != -1)
-    {
-      if (cmd.progress == CHM_NONE)
-        mc_options_set_progress_usec (options, 0);
-      else if (cmd.progress == CHM_DOTS)
-        mc_options_set_progress_func (options, mc_progress_dots);
-      else if (cmd.progress == CHM_FANCY)
-        mc_options_set_progress_func (options, mc_progress_fancy);
-      else if (cmd.progress == CHM_VERBOSE)
-        mc_options_set_progress_func (options, mc_progress_verbose);
-    }
-  if (cmd.output_file != NULL)
-    {
-      FILE *output_file = fopen (cmd.output_file, "w");
-      if (output_file == NULL)
-        {
-          error (0, errno, _("error opening \"%s\" for writing"),
-                 cmd.output_file);
-          free_check_model (&cmd);
-          mc_options_destroy (options);
-          return NULL;
-        }
-      mc_options_set_output_file (options, output_file);
-    }
-
-
-  free_check_model (&cmd);
-
-  return options;
-}
-
-/*
-  Local Variables:
-  mode: c
-  End:
-*/
diff --git a/src/language/tests/datasheet-check.c b/src/language/tests/datasheet-check.c
deleted file mode 100644 (file)
index 27fb27f..0000000
+++ /dev/null
@@ -1,692 +0,0 @@
-/* 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/>. */
-
-#include <config.h>
-
-#include <data/datasheet.h>
-#include "datasheet-check.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <data/casereader-provider.h>
-#include <data/casereader.h>
-#include <data/casewriter.h>
-#include <data/lazy-casereader.h>
-#include <libpspp/array.h>
-#include <libpspp/assertion.h>
-#include <libpspp/hash-functions.h>
-#include <libpspp/model-checker.h>
-#include <libpspp/range-map.h>
-#include <libpspp/range-set.h>
-#include <libpspp/str.h>
-#include <libpspp/taint.h>
-#include <libpspp/tower.h>
-
-#include "minmax.h"
-#include "xalloc.h"
-
-
-/* lazy_casereader callback function to instantiate a casereader
-   from the datasheet. */
-static struct casereader *
-lazy_callback (void *ds_)
-{
-  struct datasheet *ds = ds_;
-  return datasheet_make_reader (ds);
-}
-
-
-/* Maximum size of datasheet supported for model checking
-   purposes. */
-#define MAX_ROWS 5
-#define MAX_COLS 5
-
-
-static bool
-check_caseproto (struct mc *mc, const struct caseproto *benchmark,
-                 const struct caseproto *test, const char *test_name)
-{
-  size_t n_columns = caseproto_get_n_widths (benchmark);
-  size_t col;
-  bool ok;
-
-  if (n_columns != caseproto_get_n_widths (test))
-    {
-      mc_error (mc, "%s column count (%zu) does not match expected (%zu)",
-                test_name, caseproto_get_n_widths (test), n_columns);
-      return false;
-    }
-
-  ok = true;
-  for (col = 0; col < n_columns; col++)
-    {
-      int benchmark_width = caseproto_get_width (benchmark, col);
-      int test_width = caseproto_get_width (test, col);
-      if (benchmark_width != test_width)
-        {
-          mc_error (mc, "%s column %zu width (%d) differs from expected (%d)",
-                    test_name, col, test_width, benchmark_width);
-          ok = false;
-        }
-    }
-  return ok;
-}
-
-/* Checks that READER contains the N_ROWS rows and N_COLUMNS
-   columns of data in ARRAY, reporting any errors via MC. */
-static void
-check_datasheet_casereader (struct mc *mc, struct casereader *reader,
-                            union value array[MAX_ROWS][MAX_COLS],
-                            size_t n_rows, const struct caseproto *proto)
-{
-  size_t n_columns = caseproto_get_n_widths (proto);
-
-  if (!check_caseproto (mc, proto, casereader_get_proto (reader),
-                        "casereader"))
-    return;
-  else if (casereader_get_case_cnt (reader) != n_rows)
-    {
-      if (casereader_get_case_cnt (reader) == CASENUMBER_MAX
-          && casereader_count_cases (reader) == n_rows)
-        mc_error (mc, "datasheet casereader has unknown case count");
-      else
-        mc_error (mc, "casereader row count (%lu) does not match "
-                  "expected (%zu)",
-                  (unsigned long int) casereader_get_case_cnt (reader),
-                  n_rows);
-    }
-  else
-    {
-      struct ccase *c;
-      size_t row;
-
-      for (row = 0; row < n_rows; row++)
-        {
-          size_t col;
-
-          c = casereader_read (reader);
-          if (c == NULL)
-            {
-              mc_error (mc, "casereader_read failed reading row %zu of %zu "
-                        "(%zu columns)", row, n_rows, n_columns);
-              return;
-            }
-
-          for (col = 0; col < n_columns; col++)
-            {
-              int width = caseproto_get_width (proto, col);
-              if (!value_equal (case_data_idx (c, col), &array[row][col],
-                                width))
-                {
-                  if (width == 0)
-                    mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
-                              "%g != %g",
-                              row, col, n_rows, n_columns,
-                              case_num_idx (c, col), array[row][col].f);
-                  else
-                    mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
-                              "'%.*s' != '%.*s'",
-                              row, col, n_rows, n_columns,
-                              width, case_str_idx (c, col),
-                              width, value_str (&array[row][col], width));
-                }
-            }
-
-         case_unref (c);
-        }
-
-      c = casereader_read (reader);
-      if (c != NULL)
-        mc_error (mc, "casereader has extra cases (expected %zu)", n_rows);
-    }
-}
-
-/* Checks that datasheet DS contains has N_ROWS rows, N_COLUMNS
-   columns, and the same contents as ARRAY, reporting any
-   mismatches via mc_error.  Then, adds DS to MC as a new state. */
-static void
-check_datasheet (struct mc *mc, struct datasheet *ds,
-                 union value array[MAX_ROWS][MAX_COLS],
-                 size_t n_rows, const struct caseproto *proto)
-{
-  size_t n_columns = caseproto_get_n_widths (proto);
-  struct datasheet *ds2;
-  struct casereader *reader;
-  unsigned long int serial = 0;
-
-  assert (n_rows < MAX_ROWS);
-  assert (n_columns < MAX_COLS);
-
-  /* If it is a duplicate hash, discard the state before checking
-     its consistency, to save time. */
-  if (mc_discard_dup_state (mc, hash_datasheet (ds)))
-    {
-      datasheet_destroy (ds);
-      return;
-    }
-
-  /* Check contents of datasheet via datasheet functions. */
-  if (!check_caseproto (mc, proto, datasheet_get_proto (ds), "datasheet"))
-    {
-      /* check_caseproto emitted errors already. */
-    }
-  else if (n_rows != datasheet_get_n_rows (ds))
-    mc_error (mc, "row count (%lu) does not match expected (%zu)",
-              (unsigned long int) datasheet_get_n_rows (ds), n_rows);
-  else
-    {
-      size_t row, col;
-      bool difference = false;
-
-      for (row = 0; row < n_rows; row++)
-        for (col = 0; col < n_columns; col++)
-          {
-            int width = caseproto_get_width (proto, col);
-            union value *av = &array[row][col];
-            union value v;
-
-            value_init (&v, width);
-            if (!datasheet_get_value (ds, row, col, &v))
-              NOT_REACHED ();
-            if (!value_equal (&v, av, width))
-              {
-                if (width == 0)
-                  mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
-                            "%g != %g", row, col, n_rows, n_columns,
-                            v.f, av->f);
-                else
-                  mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
-                            "'%.*s' != '%.*s'",
-                            row, col, n_rows, n_columns,
-                            width, value_str (&v, width),
-                            width, value_str (av, width));
-                difference = true;
-              }
-            value_destroy (&v, width);
-          }
-
-      if (difference)
-        {
-          struct string s;
-
-          mc_error (mc, "expected:");
-          ds_init_empty (&s);
-          for (row = 0; row < n_rows; row++)
-            {
-              ds_clear (&s);
-              ds_put_format (&s, "row %zu:", row);
-              for (col = 0; col < n_columns; col++)
-                {
-                  const union value *v = &array[row][col];
-                  int width = caseproto_get_width (proto, col);
-                  if (width == 0)
-                    ds_put_format (&s, " %g", v->f);
-                  else
-                    ds_put_format (&s, " '%.*s'", width, value_str (v, width));
-                }
-              mc_error (mc, "%s", ds_cstr (&s));
-            }
-
-          mc_error (mc, "actual:");
-          ds_init_empty (&s);
-          for (row = 0; row < n_rows; row++)
-            {
-              ds_clear (&s);
-              ds_put_format (&s, "row %zu:", row);
-              for (col = 0; col < n_columns; col++)
-                {
-                  union value v;
-                  value_init (&v, 0);
-                  if (!datasheet_get_value (ds, row, col, &v))
-                    NOT_REACHED ();
-                  ds_put_format (&s, " %g", v.f);
-                }
-              mc_error (mc, "%s", ds_cstr (&s));
-            }
-
-          ds_destroy (&s);
-        }
-    }
-
-  /* Check that datasheet contents are correct when read through
-     casereader. */
-  ds2 = clone_datasheet (ds);
-  reader = datasheet_make_reader (ds2);
-  check_datasheet_casereader (mc, reader, array, n_rows, proto);
-  casereader_destroy (reader);
-
-  /* Check that datasheet contents are correct when read through
-     casereader with lazy_casereader wrapped around it.  This is
-     valuable because otherwise there is no non-GUI code that
-     uses the lazy_casereader. */
-  ds2 = clone_datasheet (ds);
-  reader = lazy_casereader_create (datasheet_get_proto (ds2), n_rows,
-                                   lazy_callback, ds2, &serial);
-  check_datasheet_casereader (mc, reader, array, n_rows, proto);
-  if (lazy_casereader_destroy (reader, serial))
-    {
-      /* Lazy casereader was never instantiated.  This will
-         only happen if there are no rows (because in that case
-         casereader_read never gets called). */
-      datasheet_destroy (ds2);
-      if (n_rows != 0)
-        mc_error (mc, "lazy casereader not instantiated, but should "
-                  "have been (size %zu,%zu)", n_rows, n_columns);
-    }
-  else
-    {
-      /* Lazy casereader was instantiated.  This is the common
-         case, in which some casereader operation
-         (casereader_read in this case) was performed on the
-         lazy casereader. */
-      casereader_destroy (reader);
-      if (n_rows == 0)
-        mc_error (mc, "lazy casereader instantiated, but should not "
-                  "have been (size %zu,%zu)", n_rows, n_columns);
-    }
-
-  mc_add_state (mc, ds);
-}
-
-/* Extracts the contents of DS into DATA. */
-static void
-extract_data (const struct datasheet *ds, union value data[MAX_ROWS][MAX_COLS])
-{
-  const struct caseproto *proto = datasheet_get_proto (ds);
-  size_t n_columns = datasheet_get_n_columns (ds);
-  size_t n_rows = datasheet_get_n_rows (ds);
-  size_t row, col;
-
-  assert (n_rows < MAX_ROWS);
-  assert (n_columns < MAX_COLS);
-  for (row = 0; row < n_rows; row++)
-    for (col = 0; col < n_columns; col++)
-      {
-        int width = caseproto_get_width (proto, col);
-        union value *v = &data[row][col];
-        value_init (v, width);
-        if (!datasheet_get_value (ds, row, col, v))
-          NOT_REACHED ();
-      }
-}
-
-/* Copies the contents of ODATA into DATA.  Each of the N_ROWS
-   rows of ODATA and DATA must have prototype PROTO. */
-static void
-clone_data (size_t n_rows, const struct caseproto *proto,
-            union value odata[MAX_ROWS][MAX_COLS],
-            union value data[MAX_ROWS][MAX_COLS])
-{
-  size_t n_columns = caseproto_get_n_widths (proto);
-  size_t row, col;
-
-  assert (n_rows < MAX_ROWS);
-  assert (n_columns < MAX_COLS);
-  for (row = 0; row < n_rows; row++)
-    for (col = 0; col < n_columns; col++)
-      {
-        int width = caseproto_get_width (proto, col);
-        const union value *ov = &odata[row][col];
-        union value *v = &data[row][col];
-        value_init (v, width);
-        value_copy (v, ov, width);
-      }
-}
-
-static void
-release_data (size_t n_rows, const struct caseproto *proto,
-              union value data[MAX_ROWS][MAX_COLS])
-{
-  size_t n_columns = caseproto_get_n_widths (proto);
-  size_t row, col;
-
-  assert (n_rows < MAX_ROWS);
-  assert (n_columns < MAX_COLS);
-  for (col = 0; col < n_columns; col++)
-    {
-      int width = caseproto_get_width (proto, col);
-      if (value_needs_init (width))
-        for (row = 0; row < n_rows; row++)
-          value_destroy (&data[row][col], width);
-    }
-}
-
-/* Clones the structure and contents of ODS into *DS,
-   and the contents of ODATA into DATA. */
-static void
-clone_model (const struct datasheet *ods,
-             union value odata[MAX_ROWS][MAX_COLS],
-             struct datasheet **ds,
-             union value data[MAX_ROWS][MAX_COLS])
-{
-  *ds = clone_datasheet (ods);
-  clone_data (datasheet_get_n_rows (ods), datasheet_get_proto (ods),
-              odata, data);
-}
-
-/* "init" function for struct mc_class. */
-static void
-datasheet_mc_init (struct mc *mc)
-{
-  struct datasheet_test_params *params = mc_get_aux (mc);
-  struct datasheet *ds;
-
-  if (params->backing_rows == 0 && params->backing_cols == 0)
-    {
-      /* Create unbacked datasheet. */
-      ds = datasheet_create (NULL);
-      mc_name_operation (mc, "empty datasheet");
-      check_datasheet (mc, ds, NULL, 0, caseproto_create ());
-    }
-  else
-    {
-      /* Create datasheet with backing. */
-      struct casewriter *writer;
-      struct casereader *reader;
-      union value data[MAX_ROWS][MAX_COLS];
-      struct caseproto *proto;
-      int row, col;
-
-      assert (params->backing_rows > 0 && params->backing_rows <= MAX_ROWS);
-      assert (params->backing_cols > 0 && params->backing_cols <= MAX_COLS);
-
-      /* XXX support different backing column widths */
-      proto = caseproto_create ();
-      for (col = 0; col < params->backing_cols; col++)
-        proto = caseproto_add_width (proto, 0);
-
-      writer = mem_writer_create (proto);
-      for (row = 0; row < params->backing_rows; row++)
-        {
-          struct ccase *c;
-
-          c = case_create (proto);
-          for (col = 0; col < params->backing_cols; col++)
-            {
-              double value = params->next_value++;
-              data[row][col].f = value;
-              case_data_rw_idx (c, col)->f = value;
-            }
-          casewriter_write (writer, c);
-        }
-      caseproto_unref (proto);
-
-      reader = casewriter_make_reader (writer);
-      assert (reader != NULL);
-
-      ds = datasheet_create (reader);
-      mc_name_operation (mc, "datasheet with (%d,%d) backing",
-                         params->backing_rows, params->backing_cols);
-      check_datasheet (mc, ds, data,
-                       params->backing_rows, proto);
-    }
-}
-
-static void
-value_from_param (union value *value, int width, int idx)
-{
-  if (width == 0)
-    value->f = idx;
-  else
-    {
-      unsigned int hash = hash_int (idx, 0);
-      char *string = value_str_rw (value, width);
-      int offset;
-
-      assert (width < 32);
-      for (offset = 0; offset < width; offset++)
-        string[offset] = "ABCDEFGHIJ"[(hash >> offset) % 10];
-    }
-}
-
-/* "mutate" function for struct mc_class. */
-static void
-datasheet_mc_mutate (struct mc *mc, const void *ods_)
-{
-  struct datasheet_test_params *params = mc_get_aux (mc);
-
-  static const int widths[] = {0, 1, 11};
-  const size_t n_widths = sizeof widths / sizeof *widths;
-
-  const struct datasheet *ods = ods_;
-  union value odata[MAX_ROWS][MAX_COLS];
-  union value data[MAX_ROWS][MAX_COLS];
-  const struct caseproto *oproto = datasheet_get_proto (ods);
-  size_t n_columns = datasheet_get_n_columns (ods);
-  size_t n_rows = datasheet_get_n_rows (ods);
-  size_t pos, new_pos, cnt, width_idx;
-
-  extract_data (ods, odata);
-
-  /* Insert a column in each possible position. */
-  if (n_columns < params->max_cols)
-    for (pos = 0; pos <= n_columns; pos++)
-      for (width_idx = 0; width_idx < n_widths; width_idx++)
-        if (mc_include_state (mc))
-          {
-            int width = widths[width_idx];
-            struct caseproto *proto;
-            struct datasheet *ds;
-            union value new;
-            size_t i;
-
-            mc_name_operation (mc, "insert column at %zu "
-                               "(from %zu to %zu columns)",
-                               pos, n_columns, n_columns + 1);
-            clone_model (ods, odata, &ds, data);
-
-            value_init (&new, width);
-            value_from_param (&new, width, params->next_value++);
-            if (!datasheet_insert_column (ds, &new, width, pos))
-              mc_error (mc, "datasheet_insert_column failed");
-            proto = caseproto_insert_width (caseproto_ref (oproto),
-                                            pos, width);
-
-            for (i = 0; i < n_rows; i++)
-              {
-                insert_element (&data[i][0], n_columns, sizeof data[i][0],
-                                pos);
-                value_init (&data[i][pos], width);
-                value_copy (&data[i][pos], &new, width);
-              }
-            value_destroy (&new, width);
-
-            check_datasheet (mc, ds, data, n_rows, proto);
-            release_data (n_rows, proto, data);
-            caseproto_unref (proto);
-          }
-
-  /* Delete all possible numbers of columns from all possible
-     positions. */
-  for (pos = 0; pos < n_columns; pos++)
-    for (cnt = 0; cnt < n_columns - pos; cnt++)
-      if (mc_include_state (mc))
-        {
-          struct caseproto *proto;
-          struct datasheet *ds;
-          size_t i, j;
-
-          mc_name_operation (mc, "delete %zu columns at %zu "
-                             "(from %zu to %zu columns)",
-                             cnt, pos, n_columns, n_columns - cnt);
-          clone_model (ods, odata, &ds, data);
-
-          datasheet_delete_columns (ds, pos, cnt);
-          proto = caseproto_remove_widths (caseproto_ref (oproto), pos, cnt);
-
-          for (i = 0; i < n_rows; i++)
-            {
-              for (j = pos; j < pos + cnt; j++)
-                value_destroy (&data[i][j], caseproto_get_width (oproto, j));
-              remove_range (&data[i], n_columns, sizeof *data[i], pos, cnt);
-            }
-
-          check_datasheet (mc, ds, data, n_rows, proto);
-          release_data (n_rows, proto, data);
-          caseproto_unref (proto);
-        }
-
-  /* Move all possible numbers of columns from all possible
-     existing positions to all possible new positions. */
-  for (pos = 0; pos < n_columns; pos++)
-    for (cnt = 0; cnt < n_columns - pos; cnt++)
-      for (new_pos = 0; new_pos < n_columns - cnt; new_pos++)
-        if (mc_include_state (mc))
-          {
-            struct caseproto *proto;
-            struct datasheet *ds;
-            size_t i;
-
-            clone_model (ods, odata, &ds, data);
-            mc_name_operation (mc, "move %zu columns (of %zu) from %zu to %zu",
-                               cnt, n_columns, pos, new_pos);
-
-            datasheet_move_columns (ds, pos, new_pos, cnt);
-
-            for (i = 0; i < n_rows; i++)
-              move_range (&data[i], n_columns, sizeof data[i][0],
-                          pos, new_pos, cnt);
-            proto = caseproto_move_widths (caseproto_ref (oproto),
-                                           pos, new_pos, cnt);
-
-            check_datasheet (mc, ds, data, n_rows, proto);
-            release_data (n_rows, proto, data);
-            caseproto_unref (proto);
-          }
-
-  /* Insert all possible numbers of rows in all possible
-     positions. */
-  for (pos = 0; pos <= n_rows; pos++)
-    for (cnt = 0; cnt <= params->max_rows - n_rows; cnt++)
-      if (mc_include_state (mc))
-        {
-          struct datasheet *ds;
-          struct ccase *c[MAX_ROWS];
-          size_t i, j;
-
-          clone_model (ods, odata, &ds, data);
-          mc_name_operation (mc, "insert %zu rows at %zu "
-                             "(from %zu to %zu rows)",
-                             cnt, pos, n_rows, n_rows + cnt);
-
-          for (i = 0; i < cnt; i++)
-            {
-              c[i] = case_create (oproto);
-              for (j = 0; j < n_columns; j++)
-                value_from_param (case_data_rw_idx (c[i], j),
-                                  caseproto_get_width (oproto, j),
-                                  params->next_value++);
-            }
-
-          insert_range (data, n_rows, sizeof data[pos], pos, cnt);
-          for (i = 0; i < cnt; i++)
-            for (j = 0; j < n_columns; j++)
-              {
-                int width = caseproto_get_width (oproto, j);
-                value_init (&data[i + pos][j], width);
-                value_copy (&data[i + pos][j], case_data_idx (c[i], j), width);
-              }
-
-          if (!datasheet_insert_rows (ds, pos, c, cnt))
-            mc_error (mc, "datasheet_insert_rows failed");
-
-          check_datasheet (mc, ds, data, n_rows + cnt, oproto);
-          release_data (n_rows + cnt, oproto, data);
-        }
-
-  /* Delete all possible numbers of rows from all possible
-     positions. */
-  for (pos = 0; pos < n_rows; pos++)
-    for (cnt = 0; cnt < n_rows - pos; cnt++)
-      if (mc_include_state (mc))
-        {
-          struct datasheet *ds;
-
-          clone_model (ods, odata, &ds, data);
-          mc_name_operation (mc, "delete %zu rows at %zu "
-                             "(from %zu to %zu rows)",
-                             cnt, pos, n_rows, n_rows - cnt);
-
-          datasheet_delete_rows (ds, pos, cnt);
-
-          release_data (cnt, oproto, &data[pos]);
-          remove_range (&data[0], n_rows, sizeof data[0], pos, cnt);
-
-          check_datasheet (mc, ds, data, n_rows - cnt, oproto);
-          release_data (n_rows - cnt, oproto, data);
-        }
-
-  /* Move all possible numbers of rows from all possible existing
-     positions to all possible new positions. */
-  for (pos = 0; pos < n_rows; pos++)
-    for (cnt = 0; cnt < n_rows - pos; cnt++)
-      for (new_pos = 0; new_pos < n_rows - cnt; new_pos++)
-        if (mc_include_state (mc))
-          {
-            struct datasheet *ds;
-
-            clone_model (ods, odata, &ds, data);
-            mc_name_operation (mc, "move %zu rows (of %zu) from %zu to %zu",
-                               cnt, n_rows, pos, new_pos);
-
-            datasheet_move_rows (ds, pos, new_pos, cnt);
-
-            move_range (&data[0], n_rows, sizeof data[0],
-                        pos, new_pos, cnt);
-
-            check_datasheet (mc, ds, data, n_rows, oproto);
-            release_data (n_rows, oproto, data);
-          }
-
-  release_data (n_rows, oproto, odata);
-}
-
-/* "destroy" function for struct mc_class. */
-static void
-datasheet_mc_destroy (const struct mc *mc UNUSED, void *ds_)
-{
-  struct datasheet *ds = ds_;
-  datasheet_destroy (ds);
-}
-
-/* Executes the model checker on the datasheet test driver with
-   the given OPTIONS and passing in the given PARAMS, which must
-   point to a modifiable "struct datasheet_test_params".  If any
-   value in PARAMS is out of range, it will be adjusted into the
-   valid range before running the test.
-
-   Returns the results of the model checking run. */
-struct mc_results *
-datasheet_test (struct mc_options *options UNUSED, void *params_ UNUSED)
-{
-  struct datasheet_test_params *params = params_;
-  static const struct mc_class datasheet_mc_class =
-    {
-      datasheet_mc_init,
-      datasheet_mc_mutate,
-      datasheet_mc_destroy,
-    };
-
-  params->next_value = 1;
-  params->max_rows = MIN (params->max_rows, MAX_ROWS);
-  params->max_cols = MIN (params->max_cols, MAX_COLS);
-  params->backing_rows = MIN (params->backing_rows, params->max_rows);
-  params->backing_cols = MIN (params->backing_cols, params->max_cols);
-
-  mc_options_set_aux (options, params);
-  return mc_run (&datasheet_mc_class, options);
-}
diff --git a/src/language/tests/datasheet-check.h b/src/language/tests/datasheet-check.h
deleted file mode 100644 (file)
index d5a1c09..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/* 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/>. */
-
-#ifndef DATA_DATASHEET_TEST_H
-#define DATA_DATASHEET_TEST_H 1
-
-#if 0
-#include <data/case.h>
-#include <data/value.h>
-
-struct casereader;
-
-/* A datasheet is a 2-d array of data that may be stored in
-   memory or on disk.  It efficiently supports data storage and
-   retrieval, as well as adding, removing, and rearranging both
-   rows and columns.  */
-
-struct datasheet *datasheet_create (struct casereader *);
-void datasheet_destroy (struct datasheet *);
-struct datasheet *datasheet_rename (struct datasheet *);
-
-bool datasheet_error (const struct datasheet *);
-void datasheet_force_error (struct datasheet *);
-const struct taint *datasheet_get_taint (const struct datasheet *);
-
-struct casereader *datasheet_make_reader (struct datasheet *);
-
-/* Columns. */
-size_t datasheet_get_column_cnt (const struct datasheet *);
-bool datasheet_insert_columns (struct datasheet *,
-                               const union value[], size_t cnt,
-                               size_t before);
-void datasheet_delete_columns (struct datasheet *, size_t start, size_t cnt);
-void datasheet_move_columns (struct datasheet *,
-                             size_t old_start, size_t new_start,
-                             size_t cnt);
-
-/* Rows. */
-casenumber datasheet_get_row_cnt (const struct datasheet *);
-bool datasheet_insert_rows (struct datasheet *,
-                            casenumber before, struct ccase *rows[],
-                            casenumber cnt);
-void datasheet_delete_rows (struct datasheet *,
-                            casenumber first, casenumber cnt);
-void datasheet_move_rows (struct datasheet *,
-                          size_t old_start, size_t new_start,
-                          size_t cnt);
-
-/* Data. */
-struct ccase *datasheet_get_row (const struct datasheet *, casenumber);
-bool datasheet_put_row (struct datasheet *, casenumber, struct ccase *);
-bool datasheet_get_value (const struct datasheet *, casenumber, size_t column,
-                          union value *, int width);
-bool datasheet_put_value (struct datasheet *, casenumber, size_t column,
-                          const union value *, int width);
-
-#endif
-
-/* Testing. */
-struct mc_options;
-
-struct datasheet_test_params
-  {
-    /* Parameters. */
-    int max_rows;
-    int max_cols;
-    int backing_rows;
-    int backing_cols;
-
-    /* State. */
-    int next_value;
-  };
-
-struct mc_results *datasheet_test (struct mc_options *options, void *params);
-
-#endif /* data/datasheet.h */
diff --git a/src/language/tests/datasheet-test.c b/src/language/tests/datasheet-test.c
deleted file mode 100644 (file)
index dfe8b6b..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   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 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/>. */
-
-#include <config.h>
-
-#include "datasheet-check.h"
-
-#include <language/command.h>
-#include <language/lexer/lexer.h>
-#include <language/tests/check-model.h>
-#include <libpspp/array.h>
-#include <libpspp/assertion.h>
-
-#include "error.h"
-#include "xalloc.h"
-
-static bool parse_coordinates (struct lexer *, int *rows, int *cols);
-
-/* Parses and executes the DEBUG DATASHEET command, which runs
-   the model checker on the datasheet data structure.  The
-   command may include a specification of the form
-   MAX=(ROWS,COLS) to specify the maximum size of the data sheet
-   during the model checker run (default: 4x4) or
-   BACKING=(ROWS,COLS) to specify the size of the casereader
-   backing the datasheet (default: no backing).  These may be
-   optionally followed by any of the common model checker option
-   specifications (see check-model.q). */
-int
-cmd_debug_datasheet (struct lexer *lexer, struct dataset *dataset UNUSED)
-{
-  struct datasheet_test_params params;
-  bool ok;
-
-  params.max_rows = 4;
-  params.max_cols = 4;
-  params.backing_rows = 0;
-  params.backing_cols = 0;
-
-
-  for (;;)
-    {
-      if (lex_match_id (lexer, "MAX"))
-        {
-          if (!parse_coordinates (lexer, &params.max_rows, &params.max_cols))
-            return CMD_FAILURE;
-        }
-      else if (lex_match_id (lexer, "BACKING"))
-        {
-          if (!parse_coordinates (lexer,
-                                  &params.backing_rows, &params.backing_cols))
-            return CMD_FAILURE;
-        }
-      else
-        break;
-      lex_match (lexer, '/');
-    }
-
-  ok = check_model (lexer, datasheet_test, &params);
-  printf ("Datasheet test max(%d,%d) backing(%d,%d) %s.\n",
-          params.max_rows, params.max_cols,
-          params.backing_rows, params.backing_cols,
-          ok ? "successful" : "failed");
-  return ok ? lex_end_of_command (lexer) : CMD_FAILURE;
-}
-
-/* Parses a pair of coordinates with the syntax =(ROWS,COLS),
-   where all of the delimiters are optional, into *ROWS and
-   *COLS.  Returns true if successful, false on parse failure. */
-static bool
-parse_coordinates (struct lexer *lexer, int *rows, int *cols)
-{
-  lex_match (lexer, '=');
-  lex_match (lexer, '(');
-
-  if (!lex_force_int (lexer))
-    return false;
-  *rows = lex_integer (lexer);
-  lex_get (lexer);
-
-  lex_match (lexer, ',');
-
-  if (!lex_force_int (lexer))
-    return false;
-  *cols = lex_integer (lexer);
-  lex_get (lexer);
-
-  lex_match (lexer, ')');
-  return true;
-}
-
index 0dbfcc6098cbfc44ae98b6543bcf1d80ead19970..7dda26df8d53ebd60623bcf35aa6e49b4d487b6e 100644 (file)
@@ -16,7 +16,6 @@ dist_TESTS = \
        tests/command/beg-data.sh \
        tests/command/bignum.sh \
        tests/command/count.sh \
-       tests/command/datasheet.sh \
        tests/command/data-list.sh \
        tests/command/do-if.sh \
        tests/command/do-repeat.sh \
@@ -151,6 +150,7 @@ dist_TESTS = \
        tests/bugs/temp-freq.sh \
        tests/bugs/print-crash.sh \
        tests/bugs/keep-all.sh \
+       tests/data/datasheet-test.sh \
        tests/libpspp/sparse-xarray-test.sh \
        tests/output/paper-size.sh \
        tests/xforms/recode.sh \
@@ -194,9 +194,14 @@ TESTS = $(dist_TESTS) $(nodist_TESTS)
 
 check_PROGRAMS += \
        $(nodist_TESTS) \
+       tests/data/datasheet-test \
        tests/formats/inexactify \
        tests/libpspp/sparse-xarray-test
 
+tests_data_datasheet_test_SOURCES = \
+       tests/data/datasheet-test.c
+tests_data_datasheet_test_LDADD = src/libpspp-core.la @LIBINTL@ 
+
 tests_libpspp_ll_test_SOURCES = \
        src/libpspp/ll.c \
        src/libpspp/ll.h \
diff --git a/tests/command/datasheet.sh b/tests/command/datasheet.sh
deleted file mode 100755 (executable)
index 2ad30d7..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/bin/sh
-
-# This program tests datasheet support.
-
-TEMPDIR=/tmp/pspp-tst-$$
-TESTFILE=$TEMPDIR/`basename $0`.sps
-
-# ensure that top_builddir  are absolute
-if [ -z "$top_builddir" ] ; then top_builddir=. ; fi
-if [ -z "$top_srcdir" ] ; then top_srcdir=. ; fi
-top_builddir=`cd $top_builddir; pwd`
-PSPP=$top_builddir/src/ui/terminal/pspp
-
-# ensure that top_srcdir is absolute
-top_srcdir=`cd $top_srcdir; pwd`
-
-STAT_CONFIG_PATH=$top_srcdir/config
-export STAT_CONFIG_PATH
-
-LANG=C
-export LANG
-
-cleanup()
-{
-     if [ x"$PSPP_TEST_NO_CLEANUP" != x ] ; then 
-       echo "NOT cleaning $TEMPDIR" 
-       return ; 
-     fi
-     cd /
-     rm -rf $TEMPDIR
-}
-
-
-fail()
-{
-    echo $activity
-    echo FAILED
-    cleanup;
-    exit 1;
-}
-
-
-no_result()
-{
-    echo $activity
-    echo NO RESULT;
-    cleanup;
-    exit 2;
-}
-
-pass()
-{
-    cleanup;
-    exit 0;
-}
-
-mkdir -p $TEMPDIR
-
-cd $TEMPDIR
-
-activity="Create File 1"
-cat > $TESTFILE <<EOF
-debug datasheet max=3,3 backing=0,0/progress=none/output=file("/dev/null").
-debug datasheet max=3,3 backing=3,3/progress=none/output=file("/dev/null").
-debug datasheet max=3,3 backing=3,1/progress=none/output=file("/dev/null").
-debug datasheet max=3,3 backing=1,3/progress=none/output=file("/dev/null").
-EOF
-if [ $? -ne 0 ] ; then no_result ; fi
-
-
-activity="Run pspp 1"
-$SUPERVISOR $PSPP --testing-mode $TESTFILE > datasheet.out
-if [ $? -ne 0 ] ; then no_result ; fi
-
-activity="compare results"
-diff -b  $TEMPDIR/datasheet.out - <<EOF
-Datasheet test max(3,3) backing(0,0) successful.
-Datasheet test max(3,3) backing(3,3) successful.
-Datasheet test max(3,3) backing(3,1) successful.
-Datasheet test max(3,3) backing(1,3) successful.
-EOF
-if [ $? -ne 0 ] ; then fail ; fi
-
-pass;
diff --git a/tests/data/datasheet-test.c b/tests/data/datasheet-test.c
new file mode 100644 (file)
index 0000000..2b88c9b
--- /dev/null
@@ -0,0 +1,809 @@
+/* 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/>. */
+
+#include <config.h>
+
+#include <data/datasheet.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <data/casereader-provider.h>
+#include <data/casereader.h>
+#include <data/casewriter.h>
+#include <data/lazy-casereader.h>
+#include <libpspp/argv-parser.h>
+#include <libpspp/array.h>
+#include <libpspp/assertion.h>
+#include <libpspp/hash-functions.h>
+#include <libpspp/model-checker.h>
+#include <libpspp/range-map.h>
+#include <libpspp/range-set.h>
+#include <libpspp/str.h>
+#include <libpspp/taint.h>
+#include <libpspp/tower.h>
+
+#include "minmax.h"
+#include "progname.h"
+#include "xalloc.h"
+
+/* lazy_casereader callback function to instantiate a casereader
+   from the datasheet. */
+static struct casereader *
+lazy_callback (void *ds_)
+{
+  struct datasheet *ds = ds_;
+  return datasheet_make_reader (ds);
+}
+
+
+/* Maximum size of datasheet supported for model checking
+   purposes. */
+#define MAX_ROWS 5
+#define MAX_COLS 5
+
+/* Test params. */
+struct datasheet_test_params
+  {
+    /* Parameters. */
+    int max_rows;               /* Maximum number of rows. */
+    int max_cols;               /* Maximum number of columns. */
+    int backing_rows;           /* Number of rows of backing store. */
+    int backing_cols;           /* Number of columns of backing store. */
+
+    /* State. */
+    int next_value;
+  };
+
+static bool
+check_caseproto (struct mc *mc, const struct caseproto *benchmark,
+                 const struct caseproto *test, const char *test_name)
+{
+  size_t n_columns = caseproto_get_n_widths (benchmark);
+  size_t col;
+  bool ok;
+
+  if (n_columns != caseproto_get_n_widths (test))
+    {
+      mc_error (mc, "%s column count (%zu) does not match expected (%zu)",
+                test_name, caseproto_get_n_widths (test), n_columns);
+      return false;
+    }
+
+  ok = true;
+  for (col = 0; col < n_columns; col++)
+    {
+      int benchmark_width = caseproto_get_width (benchmark, col);
+      int test_width = caseproto_get_width (test, col);
+      if (benchmark_width != test_width)
+        {
+          mc_error (mc, "%s column %zu width (%d) differs from expected (%d)",
+                    test_name, col, test_width, benchmark_width);
+          ok = false;
+        }
+    }
+  return ok;
+}
+
+/* Checks that READER contains the N_ROWS rows and N_COLUMNS
+   columns of data in ARRAY, reporting any errors via MC. */
+static void
+check_datasheet_casereader (struct mc *mc, struct casereader *reader,
+                            union value array[MAX_ROWS][MAX_COLS],
+                            size_t n_rows, const struct caseproto *proto)
+{
+  size_t n_columns = caseproto_get_n_widths (proto);
+
+  if (!check_caseproto (mc, proto, casereader_get_proto (reader),
+                        "casereader"))
+    return;
+  else if (casereader_get_case_cnt (reader) != n_rows)
+    {
+      if (casereader_get_case_cnt (reader) == CASENUMBER_MAX
+          && casereader_count_cases (reader) == n_rows)
+        mc_error (mc, "datasheet casereader has unknown case count");
+      else
+        mc_error (mc, "casereader row count (%lu) does not match "
+                  "expected (%zu)",
+                  (unsigned long int) casereader_get_case_cnt (reader),
+                  n_rows);
+    }
+  else
+    {
+      struct ccase *c;
+      size_t row;
+
+      for (row = 0; row < n_rows; row++)
+        {
+          size_t col;
+
+          c = casereader_read (reader);
+          if (c == NULL)
+            {
+              mc_error (mc, "casereader_read failed reading row %zu of %zu "
+                        "(%zu columns)", row, n_rows, n_columns);
+              return;
+            }
+
+          for (col = 0; col < n_columns; col++)
+            {
+              int width = caseproto_get_width (proto, col);
+              if (!value_equal (case_data_idx (c, col), &array[row][col],
+                                width))
+                {
+                  if (width == 0)
+                    mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
+                              "%g != %g",
+                              row, col, n_rows, n_columns,
+                              case_num_idx (c, col), array[row][col].f);
+                  else
+                    mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
+                              "'%.*s' != '%.*s'",
+                              row, col, n_rows, n_columns,
+                              width, case_str_idx (c, col),
+                              width, value_str (&array[row][col], width));
+                }
+            }
+
+         case_unref (c);
+        }
+
+      c = casereader_read (reader);
+      if (c != NULL)
+        mc_error (mc, "casereader has extra cases (expected %zu)", n_rows);
+    }
+}
+
+/* Checks that datasheet DS contains has N_ROWS rows, N_COLUMNS
+   columns, and the same contents as ARRAY, reporting any
+   mismatches via mc_error.  Then, adds DS to MC as a new state. */
+static void
+check_datasheet (struct mc *mc, struct datasheet *ds,
+                 union value array[MAX_ROWS][MAX_COLS],
+                 size_t n_rows, const struct caseproto *proto)
+{
+  size_t n_columns = caseproto_get_n_widths (proto);
+  struct datasheet *ds2;
+  struct casereader *reader;
+  unsigned long int serial = 0;
+
+  assert (n_rows < MAX_ROWS);
+  assert (n_columns < MAX_COLS);
+
+  /* If it is a duplicate hash, discard the state before checking
+     its consistency, to save time. */
+  if (mc_discard_dup_state (mc, hash_datasheet (ds)))
+    {
+      datasheet_destroy (ds);
+      return;
+    }
+
+  /* Check contents of datasheet via datasheet functions. */
+  if (!check_caseproto (mc, proto, datasheet_get_proto (ds), "datasheet"))
+    {
+      /* check_caseproto emitted errors already. */
+    }
+  else if (n_rows != datasheet_get_n_rows (ds))
+    mc_error (mc, "row count (%lu) does not match expected (%zu)",
+              (unsigned long int) datasheet_get_n_rows (ds), n_rows);
+  else
+    {
+      size_t row, col;
+      bool difference = false;
+
+      for (row = 0; row < n_rows; row++)
+        for (col = 0; col < n_columns; col++)
+          {
+            int width = caseproto_get_width (proto, col);
+            union value *av = &array[row][col];
+            union value v;
+
+            value_init (&v, width);
+            if (!datasheet_get_value (ds, row, col, &v))
+              NOT_REACHED ();
+            if (!value_equal (&v, av, width))
+              {
+                if (width == 0)
+                  mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
+                            "%g != %g", row, col, n_rows, n_columns,
+                            v.f, av->f);
+                else
+                  mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
+                            "'%.*s' != '%.*s'",
+                            row, col, n_rows, n_columns,
+                            width, value_str (&v, width),
+                            width, value_str (av, width));
+                difference = true;
+              }
+            value_destroy (&v, width);
+          }
+
+      if (difference)
+        {
+          struct string s;
+
+          mc_error (mc, "expected:");
+          ds_init_empty (&s);
+          for (row = 0; row < n_rows; row++)
+            {
+              ds_clear (&s);
+              ds_put_format (&s, "row %zu:", row);
+              for (col = 0; col < n_columns; col++)
+                {
+                  const union value *v = &array[row][col];
+                  int width = caseproto_get_width (proto, col);
+                  if (width == 0)
+                    ds_put_format (&s, " %g", v->f);
+                  else
+                    ds_put_format (&s, " '%.*s'", width, value_str (v, width));
+                }
+              mc_error (mc, "%s", ds_cstr (&s));
+            }
+
+          mc_error (mc, "actual:");
+          ds_init_empty (&s);
+          for (row = 0; row < n_rows; row++)
+            {
+              ds_clear (&s);
+              ds_put_format (&s, "row %zu:", row);
+              for (col = 0; col < n_columns; col++)
+                {
+                  union value v;
+                  value_init (&v, 0);
+                  if (!datasheet_get_value (ds, row, col, &v))
+                    NOT_REACHED ();
+                  ds_put_format (&s, " %g", v.f);
+                }
+              mc_error (mc, "%s", ds_cstr (&s));
+            }
+
+          ds_destroy (&s);
+        }
+    }
+
+  /* Check that datasheet contents are correct when read through
+     casereader. */
+  ds2 = clone_datasheet (ds);
+  reader = datasheet_make_reader (ds2);
+  check_datasheet_casereader (mc, reader, array, n_rows, proto);
+  casereader_destroy (reader);
+
+  /* Check that datasheet contents are correct when read through
+     casereader with lazy_casereader wrapped around it.  This is
+     valuable because otherwise there is no non-GUI code that
+     uses the lazy_casereader. */
+  ds2 = clone_datasheet (ds);
+  reader = lazy_casereader_create (datasheet_get_proto (ds2), n_rows,
+                                   lazy_callback, ds2, &serial);
+  check_datasheet_casereader (mc, reader, array, n_rows, proto);
+  if (lazy_casereader_destroy (reader, serial))
+    {
+      /* Lazy casereader was never instantiated.  This will
+         only happen if there are no rows (because in that case
+         casereader_read never gets called). */
+      datasheet_destroy (ds2);
+      if (n_rows != 0)
+        mc_error (mc, "lazy casereader not instantiated, but should "
+                  "have been (size %zu,%zu)", n_rows, n_columns);
+    }
+  else
+    {
+      /* Lazy casereader was instantiated.  This is the common
+         case, in which some casereader operation
+         (casereader_read in this case) was performed on the
+         lazy casereader. */
+      casereader_destroy (reader);
+      if (n_rows == 0)
+        mc_error (mc, "lazy casereader instantiated, but should not "
+                  "have been (size %zu,%zu)", n_rows, n_columns);
+    }
+
+  mc_add_state (mc, ds);
+}
+
+/* Extracts the contents of DS into DATA. */
+static void
+extract_data (const struct datasheet *ds, union value data[MAX_ROWS][MAX_COLS])
+{
+  const struct caseproto *proto = datasheet_get_proto (ds);
+  size_t n_columns = datasheet_get_n_columns (ds);
+  size_t n_rows = datasheet_get_n_rows (ds);
+  size_t row, col;
+
+  assert (n_rows < MAX_ROWS);
+  assert (n_columns < MAX_COLS);
+  for (row = 0; row < n_rows; row++)
+    for (col = 0; col < n_columns; col++)
+      {
+        int width = caseproto_get_width (proto, col);
+        union value *v = &data[row][col];
+        value_init (v, width);
+        if (!datasheet_get_value (ds, row, col, v))
+          NOT_REACHED ();
+      }
+}
+
+/* Copies the contents of ODATA into DATA.  Each of the N_ROWS
+   rows of ODATA and DATA must have prototype PROTO. */
+static void
+clone_data (size_t n_rows, const struct caseproto *proto,
+            union value odata[MAX_ROWS][MAX_COLS],
+            union value data[MAX_ROWS][MAX_COLS])
+{
+  size_t n_columns = caseproto_get_n_widths (proto);
+  size_t row, col;
+
+  assert (n_rows < MAX_ROWS);
+  assert (n_columns < MAX_COLS);
+  for (row = 0; row < n_rows; row++)
+    for (col = 0; col < n_columns; col++)
+      {
+        int width = caseproto_get_width (proto, col);
+        const union value *ov = &odata[row][col];
+        union value *v = &data[row][col];
+        value_init (v, width);
+        value_copy (v, ov, width);
+      }
+}
+
+static void
+release_data (size_t n_rows, const struct caseproto *proto,
+              union value data[MAX_ROWS][MAX_COLS])
+{
+  size_t n_columns = caseproto_get_n_widths (proto);
+  size_t row, col;
+
+  assert (n_rows < MAX_ROWS);
+  assert (n_columns < MAX_COLS);
+  for (col = 0; col < n_columns; col++)
+    {
+      int width = caseproto_get_width (proto, col);
+      if (value_needs_init (width))
+        for (row = 0; row < n_rows; row++)
+          value_destroy (&data[row][col], width);
+    }
+}
+
+/* Clones the structure and contents of ODS into *DS,
+   and the contents of ODATA into DATA. */
+static void
+clone_model (const struct datasheet *ods,
+             union value odata[MAX_ROWS][MAX_COLS],
+             struct datasheet **ds,
+             union value data[MAX_ROWS][MAX_COLS])
+{
+  *ds = clone_datasheet (ods);
+  clone_data (datasheet_get_n_rows (ods), datasheet_get_proto (ods),
+              odata, data);
+}
+
+/* "init" function for struct mc_class. */
+static void
+datasheet_mc_init (struct mc *mc)
+{
+  struct datasheet_test_params *params = mc_get_aux (mc);
+  struct datasheet *ds;
+
+  if (params->backing_rows == 0 && params->backing_cols == 0)
+    {
+      /* Create unbacked datasheet. */
+      ds = datasheet_create (NULL);
+      mc_name_operation (mc, "empty datasheet");
+      check_datasheet (mc, ds, NULL, 0, caseproto_create ());
+    }
+  else
+    {
+      /* Create datasheet with backing. */
+      struct casewriter *writer;
+      struct casereader *reader;
+      union value data[MAX_ROWS][MAX_COLS];
+      struct caseproto *proto;
+      int row, col;
+
+      assert (params->backing_rows > 0 && params->backing_rows <= MAX_ROWS);
+      assert (params->backing_cols > 0 && params->backing_cols <= MAX_COLS);
+
+      /* XXX support different backing column widths */
+      proto = caseproto_create ();
+      for (col = 0; col < params->backing_cols; col++)
+        proto = caseproto_add_width (proto, 0);
+
+      writer = mem_writer_create (proto);
+      for (row = 0; row < params->backing_rows; row++)
+        {
+          struct ccase *c;
+
+          c = case_create (proto);
+          for (col = 0; col < params->backing_cols; col++)
+            {
+              double value = params->next_value++;
+              data[row][col].f = value;
+              case_data_rw_idx (c, col)->f = value;
+            }
+          casewriter_write (writer, c);
+        }
+      caseproto_unref (proto);
+
+      reader = casewriter_make_reader (writer);
+      assert (reader != NULL);
+
+      ds = datasheet_create (reader);
+      mc_name_operation (mc, "datasheet with (%d,%d) backing",
+                         params->backing_rows, params->backing_cols);
+      check_datasheet (mc, ds, data,
+                       params->backing_rows, proto);
+    }
+}
+
+static void
+value_from_param (union value *value, int width, int idx)
+{
+  if (width == 0)
+    value->f = idx;
+  else
+    {
+      unsigned int hash = hash_int (idx, 0);
+      char *string = value_str_rw (value, width);
+      int offset;
+
+      assert (width < 32);
+      for (offset = 0; offset < width; offset++)
+        string[offset] = "ABCDEFGHIJ"[(hash >> offset) % 10];
+    }
+}
+
+/* "mutate" function for struct mc_class. */
+static void
+datasheet_mc_mutate (struct mc *mc, const void *ods_)
+{
+  struct datasheet_test_params *params = mc_get_aux (mc);
+
+  static const int widths[] = {0, 1, 11};
+  const size_t n_widths = sizeof widths / sizeof *widths;
+
+  const struct datasheet *ods = ods_;
+  union value odata[MAX_ROWS][MAX_COLS];
+  union value data[MAX_ROWS][MAX_COLS];
+  const struct caseproto *oproto = datasheet_get_proto (ods);
+  size_t n_columns = datasheet_get_n_columns (ods);
+  size_t n_rows = datasheet_get_n_rows (ods);
+  size_t pos, new_pos, cnt, width_idx;
+
+  extract_data (ods, odata);
+
+  /* Insert a column in each possible position. */
+  if (n_columns < params->max_cols)
+    for (pos = 0; pos <= n_columns; pos++)
+      for (width_idx = 0; width_idx < n_widths; width_idx++)
+        if (mc_include_state (mc))
+          {
+            int width = widths[width_idx];
+            struct caseproto *proto;
+            struct datasheet *ds;
+            union value new;
+            size_t i;
+
+            mc_name_operation (mc, "insert column at %zu "
+                               "(from %zu to %zu columns)",
+                               pos, n_columns, n_columns + 1);
+            clone_model (ods, odata, &ds, data);
+
+            value_init (&new, width);
+            value_from_param (&new, width, params->next_value++);
+            if (!datasheet_insert_column (ds, &new, width, pos))
+              mc_error (mc, "datasheet_insert_column failed");
+            proto = caseproto_insert_width (caseproto_ref (oproto),
+                                            pos, width);
+
+            for (i = 0; i < n_rows; i++)
+              {
+                insert_element (&data[i][0], n_columns, sizeof data[i][0],
+                                pos);
+                value_init (&data[i][pos], width);
+                value_copy (&data[i][pos], &new, width);
+              }
+            value_destroy (&new, width);
+
+            check_datasheet (mc, ds, data, n_rows, proto);
+            release_data (n_rows, proto, data);
+            caseproto_unref (proto);
+          }
+
+  /* Delete all possible numbers of columns from all possible
+     positions. */
+  for (pos = 0; pos < n_columns; pos++)
+    for (cnt = 0; cnt < n_columns - pos; cnt++)
+      if (mc_include_state (mc))
+        {
+          struct caseproto *proto;
+          struct datasheet *ds;
+          size_t i, j;
+
+          mc_name_operation (mc, "delete %zu columns at %zu "
+                             "(from %zu to %zu columns)",
+                             cnt, pos, n_columns, n_columns - cnt);
+          clone_model (ods, odata, &ds, data);
+
+          datasheet_delete_columns (ds, pos, cnt);
+          proto = caseproto_remove_widths (caseproto_ref (oproto), pos, cnt);
+
+          for (i = 0; i < n_rows; i++)
+            {
+              for (j = pos; j < pos + cnt; j++)
+                value_destroy (&data[i][j], caseproto_get_width (oproto, j));
+              remove_range (&data[i], n_columns, sizeof *data[i], pos, cnt);
+            }
+
+          check_datasheet (mc, ds, data, n_rows, proto);
+          release_data (n_rows, proto, data);
+          caseproto_unref (proto);
+        }
+
+  /* Move all possible numbers of columns from all possible
+     existing positions to all possible new positions. */
+  for (pos = 0; pos < n_columns; pos++)
+    for (cnt = 0; cnt < n_columns - pos; cnt++)
+      for (new_pos = 0; new_pos < n_columns - cnt; new_pos++)
+        if (mc_include_state (mc))
+          {
+            struct caseproto *proto;
+            struct datasheet *ds;
+            size_t i;
+
+            clone_model (ods, odata, &ds, data);
+            mc_name_operation (mc, "move %zu columns (of %zu) from %zu to %zu",
+                               cnt, n_columns, pos, new_pos);
+
+            datasheet_move_columns (ds, pos, new_pos, cnt);
+
+            for (i = 0; i < n_rows; i++)
+              move_range (&data[i], n_columns, sizeof data[i][0],
+                          pos, new_pos, cnt);
+            proto = caseproto_move_widths (caseproto_ref (oproto),
+                                           pos, new_pos, cnt);
+
+            check_datasheet (mc, ds, data, n_rows, proto);
+            release_data (n_rows, proto, data);
+            caseproto_unref (proto);
+          }
+
+  /* Insert all possible numbers of rows in all possible
+     positions. */
+  for (pos = 0; pos <= n_rows; pos++)
+    for (cnt = 0; cnt <= params->max_rows - n_rows; cnt++)
+      if (mc_include_state (mc))
+        {
+          struct datasheet *ds;
+          struct ccase *c[MAX_ROWS];
+          size_t i, j;
+
+          clone_model (ods, odata, &ds, data);
+          mc_name_operation (mc, "insert %zu rows at %zu "
+                             "(from %zu to %zu rows)",
+                             cnt, pos, n_rows, n_rows + cnt);
+
+          for (i = 0; i < cnt; i++)
+            {
+              c[i] = case_create (oproto);
+              for (j = 0; j < n_columns; j++)
+                value_from_param (case_data_rw_idx (c[i], j),
+                                  caseproto_get_width (oproto, j),
+                                  params->next_value++);
+            }
+
+          insert_range (data, n_rows, sizeof data[pos], pos, cnt);
+          for (i = 0; i < cnt; i++)
+            for (j = 0; j < n_columns; j++)
+              {
+                int width = caseproto_get_width (oproto, j);
+                value_init (&data[i + pos][j], width);
+                value_copy (&data[i + pos][j], case_data_idx (c[i], j), width);
+              }
+
+          if (!datasheet_insert_rows (ds, pos, c, cnt))
+            mc_error (mc, "datasheet_insert_rows failed");
+
+          check_datasheet (mc, ds, data, n_rows + cnt, oproto);
+          release_data (n_rows + cnt, oproto, data);
+        }
+
+  /* Delete all possible numbers of rows from all possible
+     positions. */
+  for (pos = 0; pos < n_rows; pos++)
+    for (cnt = 0; cnt < n_rows - pos; cnt++)
+      if (mc_include_state (mc))
+        {
+          struct datasheet *ds;
+
+          clone_model (ods, odata, &ds, data);
+          mc_name_operation (mc, "delete %zu rows at %zu "
+                             "(from %zu to %zu rows)",
+                             cnt, pos, n_rows, n_rows - cnt);
+
+          datasheet_delete_rows (ds, pos, cnt);
+
+          release_data (cnt, oproto, &data[pos]);
+          remove_range (&data[0], n_rows, sizeof data[0], pos, cnt);
+
+          check_datasheet (mc, ds, data, n_rows - cnt, oproto);
+          release_data (n_rows - cnt, oproto, data);
+        }
+
+  /* Move all possible numbers of rows from all possible existing
+     positions to all possible new positions. */
+  for (pos = 0; pos < n_rows; pos++)
+    for (cnt = 0; cnt < n_rows - pos; cnt++)
+      for (new_pos = 0; new_pos < n_rows - cnt; new_pos++)
+        if (mc_include_state (mc))
+          {
+            struct datasheet *ds;
+
+            clone_model (ods, odata, &ds, data);
+            mc_name_operation (mc, "move %zu rows (of %zu) from %zu to %zu",
+                               cnt, n_rows, pos, new_pos);
+
+            datasheet_move_rows (ds, pos, new_pos, cnt);
+
+            move_range (&data[0], n_rows, sizeof data[0],
+                        pos, new_pos, cnt);
+
+            check_datasheet (mc, ds, data, n_rows, oproto);
+            release_data (n_rows, oproto, data);
+          }
+
+  release_data (n_rows, oproto, odata);
+}
+
+/* "destroy" function for struct mc_class. */
+static void
+datasheet_mc_destroy (const struct mc *mc UNUSED, void *ds_)
+{
+  struct datasheet *ds = ds_;
+  datasheet_destroy (ds);
+}
+\f
+enum
+  {
+    OPT_MAX_ROWS,
+    OPT_MAX_COLUMNS,
+    OPT_BACKING_ROWS,
+    OPT_BACKING_COLUMNS,
+    OPT_HELP,
+    N_DATASHEET_OPTIONS
+  };
+
+static struct argv_option datasheet_argv_options[N_DATASHEET_OPTIONS] =
+  {
+    {"max-rows", 0, required_argument, OPT_MAX_ROWS},
+    {"max-columns", 0, required_argument, OPT_MAX_COLUMNS},
+    {"backing-rows", 0, required_argument, OPT_BACKING_ROWS},
+    {"backing-columns", 0, required_argument, OPT_BACKING_COLUMNS},
+    {"help", 'h', no_argument, OPT_HELP},
+  };
+
+static void usage (void);
+
+static void
+datasheet_option_callback (int id, void *params_)
+{
+  struct datasheet_test_params *params = params_;
+  switch (id)
+    {
+    case OPT_MAX_ROWS:
+      params->max_rows = atoi (optarg);
+      break;
+
+    case OPT_MAX_COLUMNS:
+      params->max_cols = atoi (optarg);
+      break;
+
+    case OPT_BACKING_ROWS:
+      params->backing_rows = atoi (optarg);
+      break;
+
+    case OPT_BACKING_COLUMNS:
+      params->backing_cols = atoi (optarg);
+      break;
+
+    case OPT_HELP:
+      usage ();
+      break;
+
+    default:
+      NOT_REACHED ();
+    }
+}
+
+static void
+usage (void)
+{
+  printf ("%s, for testing the datasheet implementation.\n"
+          "Usage: %s [OPTION]...\n"
+          "\nTest state space parameters (min...max, default):\n"
+          "  --max-rows=N         Maximum number of rows (0...5, 3)\n"
+          "  --max-rows=N         Maximum number of columns (0...5, 3)\n"
+          "  --backing-rows=N     Rows of backing store (0...max_rows, 0)\n"
+          "  --backing-columns=N  Columns of backing store (0...max_cols, 0)\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);
+}
+
+int
+main (int argc, char *argv[])
+{
+  static const struct mc_class datasheet_mc_class =
+    {
+      datasheet_mc_init,
+      datasheet_mc_mutate,
+      datasheet_mc_destroy,
+    };
+
+  struct datasheet_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.max_rows = 3;
+  params.max_cols = 3;
+  params.backing_rows = 0;
+  params.backing_cols = 0;
+  params.next_value = 1;
+
+  /* Parse comand line. */
+  parser = argv_parser_create ();
+  options = mc_options_create ();
+  mc_options_register_argv_parser (options, parser);
+  argv_parser_add_options (parser, datasheet_argv_options, N_DATASHEET_OPTIONS,
+                           datasheet_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.max_rows = MIN (params.max_rows, MAX_ROWS);
+  params.max_cols = MIN (params.max_cols, MAX_COLS);
+  params.backing_rows = MIN (params.backing_rows, params.max_rows);
+  params.backing_cols = MIN (params.backing_cols, params.max_cols);
+  mc_options_set_aux (options, &params);
+  results = mc_run (&datasheet_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: "
+              "--max-rows=%d --max-columns=%d "
+              "--backing-rows=%d --backing-columns=%d\n\n",
+              params.max_rows, params.max_cols,
+              params.backing_rows, params.backing_cols);
+      mc_results_print (results, stdout);
+    }
+  mc_results_destroy (results);
+
+  return success ? 0 : EXIT_FAILURE;
+}
diff --git a/tests/data/datasheet-test.sh b/tests/data/datasheet-test.sh
new file mode 100755 (executable)
index 0000000..42bb8ba
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# This program tests the datasheet implementation.
+
+set -e
+
+: ${top_builddir:=.}
+RUN_TEST="${top_builddir}/tests/data/datasheet-test --verbosity=0"
+
+$RUN_TEST --max-rows=3 --max-columns=3 --backing-rows=0 --backing-columns=0
+$RUN_TEST --max-rows=3 --max-columns=3 --backing-rows=3 --backing-columns=3
+$RUN_TEST --max-rows=3 --max-columns=3 --backing-rows=3 --backing-columns=1
+$RUN_TEST --max-rows=3 --max-columns=3 --backing-rows=1 --backing-columns=3