/* 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
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)
## 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
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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:
-*/
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-/* 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 */
+++ /dev/null
-/* 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, ¶ms.max_rows, ¶ms.max_cols))
- return CMD_FAILURE;
- }
- else if (lex_match_id (lexer, "BACKING"))
- {
- if (!parse_coordinates (lexer,
- ¶ms.backing_rows, ¶ms.backing_cols))
- return CMD_FAILURE;
- }
- else
- break;
- lex_match (lexer, '/');
- }
-
- ok = check_model (lexer, datasheet_test, ¶ms);
- 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;
-}
-
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 \
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 \
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 \
+++ /dev/null
-#!/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;
--- /dev/null
+/* 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, ¶ms);
+ 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, ¶ms);
+ 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;
+}
--- /dev/null
+#!/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