a9f4bf2fb27f43b525fb764c07fb14c706d111d6
[pspp-builds.git] / tests / data / datasheet-test.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2007, 2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <data/datasheet.h>
20
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <data/casereader-provider.h>
26 #include <data/casereader.h>
27 #include <data/casewriter.h>
28 #include <data/lazy-casereader.h>
29 #include <libpspp/argv-parser.h>
30 #include <libpspp/array.h>
31 #include <libpspp/assertion.h>
32 #include <libpspp/hash-functions.h>
33 #include <libpspp/model-checker.h>
34 #include <libpspp/range-map.h>
35 #include <libpspp/range-set.h>
36 #include <libpspp/str.h>
37 #include <libpspp/taint.h>
38 #include <libpspp/tower.h>
39
40 #include "error.h"
41 #include "minmax.h"
42 #include "progname.h"
43 #include "xalloc.h"
44
45 /* lazy_casereader callback function to instantiate a casereader
46    from the datasheet. */
47 static struct casereader *
48 lazy_callback (void *ds_)
49 {
50   struct datasheet *ds = ds_;
51   return datasheet_make_reader (ds);
52 }
53
54
55 /* Maximum size of datasheet supported for model checking
56    purposes. */
57 #define MAX_ROWS 5
58 #define MAX_COLS 5
59 #define MAX_WIDTHS 5
60
61 /* Test params. */
62 struct datasheet_test_params
63   {
64     /* Parameters. */
65     int max_rows;               /* Maximum number of rows. */
66     int max_cols;               /* Maximum number of columns. */
67     int backing_rows;           /* Number of rows of backing store. */
68     int backing_widths[MAX_COLS]; /* Widths of columns of backing store. */
69     int n_backing_cols;           /* Number of columns of backing store. */
70     int widths[MAX_WIDTHS];     /* Allowed column widths. */
71     int n_widths;
72
73     /* State. */
74     unsigned int next_value;
75   };
76
77 static bool
78 check_caseproto (struct mc *mc, const struct caseproto *benchmark,
79                  const struct caseproto *test, const char *test_name)
80 {
81   size_t n_columns = caseproto_get_n_widths (benchmark);
82   size_t col;
83   bool ok;
84
85   if (n_columns != caseproto_get_n_widths (test))
86     {
87       mc_error (mc, "%s column count (%zu) does not match expected (%zu)",
88                 test_name, caseproto_get_n_widths (test), n_columns);
89       return false;
90     }
91
92   ok = true;
93   for (col = 0; col < n_columns; col++)
94     {
95       int benchmark_width = caseproto_get_width (benchmark, col);
96       int test_width = caseproto_get_width (test, col);
97       if (benchmark_width != test_width)
98         {
99           mc_error (mc, "%s column %zu width (%d) differs from expected (%d)",
100                     test_name, col, test_width, benchmark_width);
101           ok = false;
102         }
103     }
104   return ok;
105 }
106
107 /* Checks that READER contains the N_ROWS rows and N_COLUMNS
108    columns of data in ARRAY, reporting any errors via MC. */
109 static void
110 check_datasheet_casereader (struct mc *mc, struct casereader *reader,
111                             union value array[MAX_ROWS][MAX_COLS],
112                             size_t n_rows, const struct caseproto *proto)
113 {
114   size_t n_columns = caseproto_get_n_widths (proto);
115
116   if (!check_caseproto (mc, proto, casereader_get_proto (reader),
117                         "casereader"))
118     return;
119   else if (casereader_get_case_cnt (reader) != n_rows)
120     {
121       if (casereader_get_case_cnt (reader) == CASENUMBER_MAX
122           && casereader_count_cases (reader) == n_rows)
123         mc_error (mc, "datasheet casereader has unknown case count");
124       else
125         mc_error (mc, "casereader row count (%lu) does not match "
126                   "expected (%zu)",
127                   (unsigned long int) casereader_get_case_cnt (reader),
128                   n_rows);
129     }
130   else
131     {
132       struct ccase *c;
133       size_t row;
134
135       for (row = 0; row < n_rows; row++)
136         {
137           size_t col;
138
139           c = casereader_read (reader);
140           if (c == NULL)
141             {
142               mc_error (mc, "casereader_read failed reading row %zu of %zu "
143                         "(%zu columns)", row, n_rows, n_columns);
144               return;
145             }
146
147           for (col = 0; col < n_columns; col++)
148             {
149               int width = caseproto_get_width (proto, col);
150               if (!value_equal (case_data_idx (c, col), &array[row][col],
151                                 width))
152                 {
153                   if (width == 0)
154                     mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
155                               "%g != %g",
156                               row, col, n_rows, n_columns,
157                               case_num_idx (c, col), array[row][col].f);
158                   else
159                     mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
160                               "'%.*s' != '%.*s'",
161                               row, col, n_rows, n_columns,
162                               width, case_str_idx (c, col),
163                               width, value_str (&array[row][col], width));
164                 }
165             }
166
167           case_unref (c);
168         }
169
170       c = casereader_read (reader);
171       if (c != NULL)
172         mc_error (mc, "casereader has extra cases (expected %zu)", n_rows);
173     }
174 }
175
176 /* Checks that datasheet DS contains has N_ROWS rows, N_COLUMNS
177    columns, and the same contents as ARRAY, reporting any
178    mismatches via mc_error.  Then, adds DS to MC as a new state. */
179 static void
180 check_datasheet (struct mc *mc, struct datasheet *ds,
181                  union value array[MAX_ROWS][MAX_COLS],
182                  size_t n_rows, const struct caseproto *proto)
183 {
184   size_t n_columns = caseproto_get_n_widths (proto);
185   struct datasheet *ds2;
186   struct casereader *reader;
187   unsigned long int serial = 0;
188
189   assert (n_rows < MAX_ROWS);
190   assert (n_columns < MAX_COLS);
191
192   /* Check contents of datasheet via datasheet functions. */
193   if (!check_caseproto (mc, proto, datasheet_get_proto (ds), "datasheet"))
194     {
195       /* check_caseproto emitted errors already. */
196     }
197   else if (n_rows != datasheet_get_n_rows (ds))
198     mc_error (mc, "row count (%lu) does not match expected (%zu)",
199               (unsigned long int) datasheet_get_n_rows (ds), n_rows);
200   else
201     {
202       size_t row, col;
203       bool difference = false;
204
205       for (row = 0; row < n_rows; row++)
206         for (col = 0; col < n_columns; col++)
207           {
208             int width = caseproto_get_width (proto, col);
209             union value *av = &array[row][col];
210             union value v;
211
212             value_init (&v, width);
213             if (!datasheet_get_value (ds, row, col, &v))
214               NOT_REACHED ();
215             if (!value_equal (&v, av, width))
216               {
217                 if (width == 0)
218                   mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
219                             "%g != %g", row, col, n_rows, n_columns,
220                             v.f, av->f);
221                 else
222                   mc_error (mc, "element %zu,%zu (of %zu,%zu) differs: "
223                             "'%.*s' != '%.*s'",
224                             row, col, n_rows, n_columns,
225                             width, value_str (&v, width),
226                             width, value_str (av, width));
227                 difference = true;
228               }
229             value_destroy (&v, width);
230           }
231
232       if (difference)
233         {
234           struct string s;
235
236           mc_error (mc, "expected:");
237           ds_init_empty (&s);
238           for (row = 0; row < n_rows; row++)
239             {
240               ds_clear (&s);
241               ds_put_format (&s, "row %zu:", row);
242               for (col = 0; col < n_columns; col++)
243                 {
244                   const union value *v = &array[row][col];
245                   int width = caseproto_get_width (proto, col);
246                   if (width == 0)
247                     ds_put_format (&s, " %g", v->f);
248                   else
249                     ds_put_format (&s, " '%.*s'", width, value_str (v, width));
250                 }
251               mc_error (mc, "%s", ds_cstr (&s));
252             }
253
254           mc_error (mc, "actual:");
255           ds_init_empty (&s);
256           for (row = 0; row < n_rows; row++)
257             {
258               ds_clear (&s);
259               ds_put_format (&s, "row %zu:", row);
260               for (col = 0; col < n_columns; col++)
261                 {
262                   int width = caseproto_get_width (proto, col);
263                   union value v;
264                   value_init (&v, width);
265                   if (!datasheet_get_value (ds, row, col, &v))
266                     NOT_REACHED ();
267                   if (width == 0)
268                     ds_put_format (&s, " %g", v.f);
269                   else
270                     ds_put_format (&s, " '%.*s'",
271                                    width, value_str (&v, width));
272                 }
273               mc_error (mc, "%s", ds_cstr (&s));
274             }
275
276           ds_destroy (&s);
277         }
278     }
279
280   /* Check that datasheet contents are correct when read through
281      casereader. */
282   ds2 = clone_datasheet (ds);
283   reader = datasheet_make_reader (ds2);
284   check_datasheet_casereader (mc, reader, array, n_rows, proto);
285   casereader_destroy (reader);
286
287   /* Check that datasheet contents are correct when read through
288      casereader with lazy_casereader wrapped around it.  This is
289      valuable because otherwise there is no non-GUI code that
290      uses the lazy_casereader. */
291   ds2 = clone_datasheet (ds);
292   reader = lazy_casereader_create (datasheet_get_proto (ds2), n_rows,
293                                    lazy_callback, ds2, &serial);
294   check_datasheet_casereader (mc, reader, array, n_rows, proto);
295   if (lazy_casereader_destroy (reader, serial))
296     {
297       /* Lazy casereader was never instantiated.  This will
298          only happen if there are no rows (because in that case
299          casereader_read never gets called). */
300       datasheet_destroy (ds2);
301       if (n_rows != 0)
302         mc_error (mc, "lazy casereader not instantiated, but should "
303                   "have been (size %zu,%zu)", n_rows, n_columns);
304     }
305   else
306     {
307       /* Lazy casereader was instantiated.  This is the common
308          case, in which some casereader operation
309          (casereader_read in this case) was performed on the
310          lazy casereader. */
311       casereader_destroy (reader);
312       if (n_rows == 0)
313         mc_error (mc, "lazy casereader instantiated, but should not "
314                   "have been (size %zu,%zu)", n_rows, n_columns);
315     }
316
317   if (mc_discard_dup_state (mc, hash_datasheet (ds)))
318     datasheet_destroy (ds);
319   else
320     mc_add_state (mc, ds);
321 }
322
323 /* Extracts the contents of DS into DATA. */
324 static void
325 extract_data (const struct datasheet *ds, union value data[MAX_ROWS][MAX_COLS])
326 {
327   const struct caseproto *proto = datasheet_get_proto (ds);
328   size_t n_columns = datasheet_get_n_columns (ds);
329   size_t n_rows = datasheet_get_n_rows (ds);
330   size_t row, col;
331
332   assert (n_rows < MAX_ROWS);
333   assert (n_columns < MAX_COLS);
334   for (row = 0; row < n_rows; row++)
335     for (col = 0; col < n_columns; col++)
336       {
337         int width = caseproto_get_width (proto, col);
338         union value *v = &data[row][col];
339         value_init (v, width);
340         if (!datasheet_get_value (ds, row, col, v))
341           NOT_REACHED ();
342       }
343 }
344
345 /* Copies the contents of ODATA into DATA.  Each of the N_ROWS
346    rows of ODATA and DATA must have prototype PROTO. */
347 static void
348 clone_data (size_t n_rows, const struct caseproto *proto,
349             union value odata[MAX_ROWS][MAX_COLS],
350             union value data[MAX_ROWS][MAX_COLS])
351 {
352   size_t n_columns = caseproto_get_n_widths (proto);
353   size_t row, col;
354
355   assert (n_rows < MAX_ROWS);
356   assert (n_columns < MAX_COLS);
357   for (row = 0; row < n_rows; row++)
358     for (col = 0; col < n_columns; col++)
359       {
360         int width = caseproto_get_width (proto, col);
361         const union value *ov = &odata[row][col];
362         union value *v = &data[row][col];
363         value_init (v, width);
364         value_copy (v, ov, width);
365       }
366 }
367
368 static void
369 release_data (size_t n_rows, const struct caseproto *proto,
370               union value data[MAX_ROWS][MAX_COLS])
371 {
372   size_t n_columns = caseproto_get_n_widths (proto);
373   size_t row, col;
374
375   assert (n_rows < MAX_ROWS);
376   assert (n_columns < MAX_COLS);
377   for (col = 0; col < n_columns; col++)
378     {
379       int width = caseproto_get_width (proto, col);
380       if (value_needs_init (width))
381         for (row = 0; row < n_rows; row++)
382           value_destroy (&data[row][col], width);
383     }
384 }
385
386 /* Clones the structure and contents of ODS into *DS,
387    and the contents of ODATA into DATA. */
388 static void
389 clone_model (const struct datasheet *ods,
390              union value odata[MAX_ROWS][MAX_COLS],
391              struct datasheet **ds,
392              union value data[MAX_ROWS][MAX_COLS])
393 {
394   *ds = clone_datasheet (ods);
395   clone_data (datasheet_get_n_rows (ods), datasheet_get_proto (ods),
396               odata, data);
397 }
398
399 static void
400 value_from_param (union value *value, int width, unsigned int idx)
401 {
402   if (width == 0)
403     value->f = idx & 0xffff;
404   else
405     {
406       unsigned int hash = hash_int (idx, 0);
407       char *string = value_str_rw (value, width);
408       int offset;
409
410       assert (width < 32);
411       for (offset = 0; offset < width; offset++)
412         string[offset] = "ABCDEFGHIJ"[(hash >> offset) % 10];
413     }
414 }
415
416 /* "init" function for struct mc_class. */
417 static void
418 datasheet_mc_init (struct mc *mc)
419 {
420   struct datasheet_test_params *params = mc_get_aux (mc);
421   struct datasheet *ds;
422
423   if (params->backing_rows == 0 && params->n_backing_cols == 0)
424     {
425       /* Create unbacked datasheet. */
426       ds = datasheet_create (NULL);
427       mc_name_operation (mc, "empty datasheet");
428       check_datasheet (mc, ds, NULL, 0, caseproto_create ());
429     }
430   else
431     {
432       /* Create datasheet with backing. */
433       struct casewriter *writer;
434       struct casereader *reader;
435       union value data[MAX_ROWS][MAX_COLS];
436       struct caseproto *proto;
437       int row, col;
438
439       assert (params->backing_rows > 0 && params->backing_rows <= MAX_ROWS);
440       assert (params->n_backing_cols > 0
441               && params->n_backing_cols <= MAX_COLS);
442
443       proto = caseproto_create ();
444       for (col = 0; col < params->n_backing_cols; col++)
445         proto = caseproto_add_width (proto, params->backing_widths[col]);
446
447       writer = mem_writer_create (proto);
448       for (row = 0; row < params->backing_rows; row++)
449         {
450           struct ccase *c;
451
452           c = case_create (proto);
453           for (col = 0; col < params->n_backing_cols; col++)
454             {
455               int width = params->backing_widths[col];
456               union value *value = &data[row][col];
457               value_init (value, width);
458               value_from_param (value, width, params->next_value++);
459               value_copy (case_data_rw_idx (c, col), value, width);
460             }
461           casewriter_write (writer, c);
462         }
463
464       reader = casewriter_make_reader (writer);
465       assert (reader != NULL);
466
467       ds = datasheet_create (reader);
468       mc_name_operation (mc, "datasheet with (%d,%d) backing",
469                          params->backing_rows, params->n_backing_cols);
470       check_datasheet (mc, ds, data,
471                        params->backing_rows, proto);
472       release_data (params->backing_rows, proto, data);
473       caseproto_unref (proto);
474     }
475 }
476
477 struct resize_cb_aux
478   {
479     int old_width;
480     int new_width;
481   };
482
483 static void
484 resize_cb (const union value *old_value, union value *new_value, void *aux_)
485 {
486   struct resize_cb_aux *aux = aux_;
487
488   value_from_param (new_value, aux->new_width,
489                     value_hash (old_value, aux->old_width, 0));
490 }
491
492 /* "mutate" function for struct mc_class. */
493 static void
494 datasheet_mc_mutate (struct mc *mc, const void *ods_)
495 {
496   struct datasheet_test_params *params = mc_get_aux (mc);
497
498   const struct datasheet *ods = ods_;
499   union value odata[MAX_ROWS][MAX_COLS];
500   union value data[MAX_ROWS][MAX_COLS];
501   const struct caseproto *oproto = datasheet_get_proto (ods);
502   size_t n_columns = datasheet_get_n_columns (ods);
503   size_t n_rows = datasheet_get_n_rows (ods);
504   size_t pos, new_pos, cnt, width_idx;
505
506   extract_data (ods, odata);
507
508   /* Insert a column in each possible position. */
509   if (n_columns < params->max_cols)
510     for (pos = 0; pos <= n_columns; pos++)
511       for (width_idx = 0; width_idx < params->n_widths; width_idx++)
512         if (mc_include_state (mc))
513           {
514             int width = params->widths[width_idx];
515             struct caseproto *proto;
516             struct datasheet *ds;
517             union value new;
518             size_t i;
519
520             mc_name_operation (mc, "insert column at %zu "
521                                "(from %zu to %zu columns)",
522                                pos, n_columns, n_columns + 1);
523             clone_model (ods, odata, &ds, data);
524
525             value_init (&new, width);
526             value_from_param (&new, width, params->next_value++);
527             if (!datasheet_insert_column (ds, &new, width, pos))
528               mc_error (mc, "datasheet_insert_column failed");
529             proto = caseproto_insert_width (caseproto_ref (oproto),
530                                             pos, width);
531
532             for (i = 0; i < n_rows; i++)
533               {
534                 insert_element (&data[i][0], n_columns, sizeof data[i][0],
535                                 pos);
536                 value_init (&data[i][pos], width);
537                 value_copy (&data[i][pos], &new, width);
538               }
539             value_destroy (&new, width);
540
541             check_datasheet (mc, ds, data, n_rows, proto);
542             release_data (n_rows, proto, data);
543             caseproto_unref (proto);
544           }
545
546   /* Resize each column to each possible new size. */
547   for (pos = 0; pos < n_columns; pos++)
548     for (width_idx = 0; width_idx < params->n_widths; width_idx++)
549       {
550         int owidth = caseproto_get_width (oproto, pos);
551         int width = params->widths[width_idx];
552         if (mc_include_state (mc))
553           {
554             struct resize_cb_aux aux;
555             struct caseproto *proto;
556             struct datasheet *ds;
557             size_t i;
558
559             mc_name_operation (mc, "resize column %zu (of %zu) "
560                                "from width %d to %d",
561                                pos, n_columns, owidth, width);
562             clone_model (ods, odata, &ds, data);
563
564             aux.old_width = owidth;
565             aux.new_width = width;
566             if (!datasheet_resize_column (ds, pos, width, resize_cb, &aux))
567               NOT_REACHED ();
568             proto = caseproto_set_width (caseproto_ref (oproto), pos, width);
569
570             for (i = 0; i < n_rows; i++)
571               {
572                 union value *old_value = &data[i][pos];
573                 union value new_value;
574                 value_init (&new_value, width);
575                 resize_cb (old_value, &new_value, &aux);
576                 value_swap (old_value, &new_value);
577                 value_destroy (&new_value, owidth);
578               }
579
580             check_datasheet (mc, ds, data, n_rows, proto);
581             release_data (n_rows, proto, data);
582             caseproto_unref (proto);
583           }
584       }
585
586   /* Delete all possible numbers of columns from all possible
587      positions. */
588   for (pos = 0; pos < n_columns; pos++)
589     for (cnt = 1; cnt < n_columns - pos; cnt++)
590       if (mc_include_state (mc))
591         {
592           struct caseproto *proto;
593           struct datasheet *ds;
594           size_t i, j;
595
596           mc_name_operation (mc, "delete %zu columns at %zu "
597                              "(from %zu to %zu columns)",
598                              cnt, pos, n_columns, n_columns - cnt);
599           clone_model (ods, odata, &ds, data);
600
601           datasheet_delete_columns (ds, pos, cnt);
602           proto = caseproto_remove_widths (caseproto_ref (oproto), pos, cnt);
603
604           for (i = 0; i < n_rows; i++)
605             {
606               for (j = pos; j < pos + cnt; j++)
607                 value_destroy (&data[i][j], caseproto_get_width (oproto, j));
608               remove_range (&data[i], n_columns, sizeof *data[i], pos, cnt);
609             }
610
611           check_datasheet (mc, ds, data, n_rows, proto);
612           release_data (n_rows, proto, data);
613           caseproto_unref (proto);
614         }
615
616   /* Move all possible numbers of columns from all possible
617      existing positions to all possible new positions. */
618   for (pos = 0; pos < n_columns; pos++)
619     for (cnt = 1; cnt < n_columns - pos; cnt++)
620       for (new_pos = 0; new_pos < n_columns - cnt; new_pos++)
621         if (mc_include_state (mc))
622           {
623             struct caseproto *proto;
624             struct datasheet *ds;
625             size_t i;
626
627             clone_model (ods, odata, &ds, data);
628             mc_name_operation (mc, "move %zu columns (of %zu) from %zu to %zu",
629                                cnt, n_columns, pos, new_pos);
630
631             datasheet_move_columns (ds, pos, new_pos, cnt);
632
633             for (i = 0; i < n_rows; i++)
634               move_range (&data[i], n_columns, sizeof data[i][0],
635                           pos, new_pos, cnt);
636             proto = caseproto_move_widths (caseproto_ref (oproto),
637                                            pos, new_pos, cnt);
638
639             check_datasheet (mc, ds, data, n_rows, proto);
640             release_data (n_rows, proto, data);
641             caseproto_unref (proto);
642           }
643
644   /* Insert all possible numbers of rows in all possible
645      positions. */
646   for (pos = 0; pos <= n_rows; pos++)
647     for (cnt = 1; cnt <= params->max_rows - n_rows; cnt++)
648       if (mc_include_state (mc))
649         {
650           struct datasheet *ds;
651           struct ccase *c[MAX_ROWS];
652           size_t i, j;
653
654           clone_model (ods, odata, &ds, data);
655           mc_name_operation (mc, "insert %zu rows at %zu "
656                              "(from %zu to %zu rows)",
657                              cnt, pos, n_rows, n_rows + cnt);
658
659           for (i = 0; i < cnt; i++)
660             {
661               c[i] = case_create (oproto);
662               for (j = 0; j < n_columns; j++)
663                 value_from_param (case_data_rw_idx (c[i], j),
664                                   caseproto_get_width (oproto, j),
665                                   params->next_value++);
666             }
667
668           insert_range (data, n_rows, sizeof data[pos], pos, cnt);
669           for (i = 0; i < cnt; i++)
670             for (j = 0; j < n_columns; j++)
671               {
672                 int width = caseproto_get_width (oproto, j);
673                 value_init (&data[i + pos][j], width);
674                 value_copy (&data[i + pos][j], case_data_idx (c[i], j), width);
675               }
676
677           if (!datasheet_insert_rows (ds, pos, c, cnt))
678             mc_error (mc, "datasheet_insert_rows failed");
679
680           check_datasheet (mc, ds, data, n_rows + cnt, oproto);
681           release_data (n_rows + cnt, oproto, data);
682         }
683
684   /* Delete all possible numbers of rows from all possible
685      positions. */
686   for (pos = 0; pos < n_rows; pos++)
687     for (cnt = 1; cnt < n_rows - pos; cnt++)
688       if (mc_include_state (mc))
689         {
690           struct datasheet *ds;
691
692           clone_model (ods, odata, &ds, data);
693           mc_name_operation (mc, "delete %zu rows at %zu "
694                              "(from %zu to %zu rows)",
695                              cnt, pos, n_rows, n_rows - cnt);
696
697           datasheet_delete_rows (ds, pos, cnt);
698
699           release_data (cnt, oproto, &data[pos]);
700           remove_range (&data[0], n_rows, sizeof data[0], pos, cnt);
701
702           check_datasheet (mc, ds, data, n_rows - cnt, oproto);
703           release_data (n_rows - cnt, oproto, data);
704         }
705
706   /* Move all possible numbers of rows from all possible existing
707      positions to all possible new positions. */
708   for (pos = 0; pos < n_rows; pos++)
709     for (cnt = 1; cnt < n_rows - pos; cnt++)
710       for (new_pos = 0; new_pos < n_rows - cnt; new_pos++)
711         if (mc_include_state (mc))
712           {
713             struct datasheet *ds;
714
715             clone_model (ods, odata, &ds, data);
716             mc_name_operation (mc, "move %zu rows (of %zu) from %zu to %zu",
717                                cnt, n_rows, pos, new_pos);
718
719             datasheet_move_rows (ds, pos, new_pos, cnt);
720
721             move_range (&data[0], n_rows, sizeof data[0],
722                         pos, new_pos, cnt);
723
724             check_datasheet (mc, ds, data, n_rows, oproto);
725             release_data (n_rows, oproto, data);
726           }
727
728   release_data (n_rows, oproto, odata);
729 }
730
731 /* "destroy" function for struct mc_class. */
732 static void
733 datasheet_mc_destroy (const struct mc *mc UNUSED, void *ds_)
734 {
735   struct datasheet *ds = ds_;
736   datasheet_destroy (ds);
737 }
738 \f
739 enum
740   {
741     OPT_MAX_ROWS,
742     OPT_MAX_COLUMNS,
743     OPT_BACKING_ROWS,
744     OPT_BACKING_WIDTHS,
745     OPT_WIDTHS,
746     OPT_HELP,
747     N_DATASHEET_OPTIONS
748   };
749
750 static struct argv_option datasheet_argv_options[N_DATASHEET_OPTIONS] =
751   {
752     {"max-rows", 0, required_argument, OPT_MAX_ROWS},
753     {"max-columns", 0, required_argument, OPT_MAX_COLUMNS},
754     {"backing-rows", 0, required_argument, OPT_BACKING_ROWS},
755     {"backing-widths", 0, required_argument, OPT_BACKING_WIDTHS},
756     {"widths", 0, required_argument, OPT_WIDTHS},
757     {"help", 'h', no_argument, OPT_HELP},
758   };
759
760 static void usage (void);
761
762 static void
763 datasheet_option_callback (int id, void *params_)
764 {
765   struct datasheet_test_params *params = params_;
766   switch (id)
767     {
768     case OPT_MAX_ROWS:
769       params->max_rows = atoi (optarg);
770       break;
771
772     case OPT_MAX_COLUMNS:
773       params->max_cols = atoi (optarg);
774       break;
775
776     case OPT_BACKING_ROWS:
777       params->backing_rows = atoi (optarg);
778       break;
779
780     case OPT_BACKING_WIDTHS:
781       {
782         char *w;
783
784         params->n_backing_cols = 0;
785         for (w = strtok (optarg, ", "); w != NULL; w = strtok (NULL, ", "))
786           {
787             int value = atoi (w);
788
789             if (params->n_backing_cols >= MAX_COLS)
790               error (1, 0, "Too many widths on --backing-widths "
791                      "(only %d are allowed)", MAX_COLS);
792             if (!isdigit (w[0]) || value < 0 || value > 31)
793               error (1, 0, "--backing-widths argument must be a list of 1 to "
794                      "%d integers between 0 and 31 in increasing order",
795                      MAX_COLS);
796             params->backing_widths[params->n_backing_cols++] = value;
797           }
798       }
799       break;
800
801     case OPT_WIDTHS:
802       {
803         int last = -1;
804         char *w;
805
806         params->n_widths = 0;
807         for (w = strtok (optarg, ", "); w != NULL; w = strtok (NULL, ", "))
808           {
809             int value = atoi (w);
810
811             if (params->n_widths >= MAX_WIDTHS)
812               error (1, 0, "Too many widths on --widths (only %d are allowed)",
813                      MAX_WIDTHS);
814             if (!isdigit (w[0]) || value < 0 || value > 31)
815               error (1, 0, "--widths argument must be a list of 1 to %d "
816                      "integers between 0 and 31 in increasing order",
817                      MAX_WIDTHS);
818
819             /* This is an artificial requirement merely to ensure
820                that there are no duplicates.  Duplicates aren't a
821                real problem but they would waste time. */
822             if (value <= last)
823               error (1, 0, "--widths arguments must be in increasing order");
824
825             params->widths[params->n_widths++] = value;
826           }
827         if (params->n_widths == 0)
828           error (1, 0, "at least one value must be specified on --widths");
829       }
830       break;
831
832     case OPT_HELP:
833       usage ();
834       break;
835
836     default:
837       NOT_REACHED ();
838     }
839 }
840
841 static void
842 usage (void)
843 {
844   printf ("%s, for testing the datasheet implementation.\n"
845           "Usage: %s [OPTION]...\n"
846           "\nTest state space parameters (min...max, default):\n"
847           "  --max-rows=N         Maximum number of rows (0...5, 3)\n"
848           "  --max-rows=N         Maximum number of columns (0...5, 3)\n"
849           "  --backing-rows=N     Rows of backing store (0...max_rows, 0)\n"
850           "  --backing-widths=W[,W]...  Backing store widths to test (0=num)\n"
851           "  --widths=W[,W]...    Column widths to test, where 0=numeric,\n"
852           "                       other values are string widths (0,1,11)\n",
853           program_name, program_name);
854   mc_options_usage ();
855   fputs ("\nOther options:\n"
856          "  --help               Display this help message\n"
857          "\nReport bugs to <bug-gnu-pspp@gnu.org>\n",
858          stdout);
859   exit (0);
860 }
861
862 int
863 main (int argc, char *argv[])
864 {
865   static const struct mc_class datasheet_mc_class =
866     {
867       datasheet_mc_init,
868       datasheet_mc_mutate,
869       datasheet_mc_destroy,
870     };
871
872   struct datasheet_test_params params;
873   struct mc_options *options;
874   struct mc_results *results;
875   struct argv_parser *parser;
876   int verbosity;
877   bool success;
878
879   set_program_name (argv[0]);
880
881   /* Default parameters. */
882   params.max_rows = 3;
883   params.max_cols = 3;
884   params.backing_rows = 0;
885   params.n_backing_cols = 0;
886   params.widths[0] = 0;
887   params.widths[1] = 1;
888   params.widths[2] = 11;
889   params.n_widths = 3;
890   params.next_value = 1;
891
892   /* Parse comand line. */
893   parser = argv_parser_create ();
894   options = mc_options_create ();
895   mc_options_register_argv_parser (options, parser);
896   argv_parser_add_options (parser, datasheet_argv_options, N_DATASHEET_OPTIONS,
897                            datasheet_option_callback, &params);
898   if (!argv_parser_run (parser, argc, argv))
899     exit (EXIT_FAILURE);
900   argv_parser_destroy (parser);
901   verbosity = mc_options_get_verbosity (options);
902
903   /* Force parameters into allowed ranges. */
904   params.max_rows = MIN (params.max_rows, MAX_ROWS);
905   params.max_cols = MIN (params.max_cols, MAX_COLS);
906   params.backing_rows = MIN (params.backing_rows, params.max_rows);
907   params.n_backing_cols = MIN (params.n_backing_cols, params.max_cols);
908   mc_options_set_aux (options, &params);
909   results = mc_run (&datasheet_mc_class, options);
910
911   /* Output results. */
912   success = (mc_results_get_stop_reason (results) != MC_MAX_ERROR_COUNT
913              && mc_results_get_stop_reason (results) != MC_INTERRUPTED);
914   if (verbosity > 0 || !success)
915     {
916       int i;
917
918       printf ("Parameters: --max-rows=%d --max-columns=%d --backing-rows=%d ",
919               params.max_rows, params.max_cols, params.backing_rows);
920
921       printf ("--backing-widths=");
922       for (i = 0; i < params.n_backing_cols; i++)
923         {
924           if (i > 0)
925             printf (",");
926           printf ("%d", params.backing_widths[i]);
927         }
928       printf (" ");
929
930       printf ("--widths=");
931       for (i = 0; i < params.n_widths; i++)
932         {
933           if (i > 0)
934             printf (",");
935           printf ("%d", params.widths[i]);
936         }
937       printf ("\n\n");
938       mc_results_print (results, stdout);
939     }
940   mc_results_destroy (results);
941
942   return success ? 0 : EXIT_FAILURE;
943 }