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