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