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