LIST: Improve wording of error messages.
[pspp.git] / tests / libpspp / string-map-test.c
blob68fde9c1a31d13a50b3b250b628a94d5c8f82859
1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* This is a test program for the string_map_* routines defined in
18 string-map.c. This test program aims to be as comprehensive as possible.
19 "gcov -a -b" should report almost complete coverage of lines, blocks and
20 branches in string-map.c, except that one branch caused by hash collision is
21 not exercised because our hash function has so few collisions. "valgrind
22 --leak-check=yes --show-reachable=yes" should give a clean report. */
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include <libpspp/string-map.h>
30 #include <assert.h>
31 #include <limits.h>
32 #include <stdbool.h>
33 #include <stddef.h>
34 #include <stdint.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
39 #include <libpspp/hash-functions.h>
40 #include <libpspp/compiler.h>
41 #include <libpspp/string-set.h>
43 /* Exit with a failure code.
44 (Place a breakpoint on this function while debugging.) */
45 static void
46 check_die (void)
48 exit (EXIT_FAILURE);
51 /* If OK is not true, prints a message about failure on the
52 current source file and the given LINE and terminates. */
53 static void
54 check_func (bool ok, int line)
56 if (!ok)
58 fprintf (stderr, "%s:%d: check failed\n", __FILE__, line);
59 check_die ();
63 /* Verifies that EXPR evaluates to true.
64 If not, prints a message citing the calling line number and
65 terminates. */
66 #define check(EXPR) check_func ((EXPR), __LINE__)
68 /* Prints a message about memory exhaustion and exits with a
69 failure code. */
70 static void
71 xalloc_die (void)
73 printf ("virtual memory exhausted\n");
74 exit (EXIT_FAILURE);
77 static void *xmalloc (size_t n) MALLOC_LIKE;
78 static void *xnmalloc (size_t n, size_t m) MALLOC_LIKE;
79 static void *xmemdup (const void *p, size_t n) MALLOC_LIKE;
81 /* Allocates and returns N bytes of memory. */
82 static void *
83 xmalloc (size_t n)
85 if (n != 0)
87 void *p = malloc (n);
88 if (p == NULL)
89 xalloc_die ();
91 return p;
93 else
94 return NULL;
97 static void *
98 xmemdup (const void *p, size_t n)
100 void *q = xmalloc (n);
101 memcpy (q, p, n);
102 return q;
105 /* Clone STRING. */
106 static char *
107 xstrdup (const char *string)
109 return xmemdup (string, strlen (string) + 1);
112 /* Allocates and returns N * M bytes of memory. */
113 static void *
114 xnmalloc (size_t n, size_t m)
116 if ((size_t) -1 / m <= n)
117 xalloc_die ();
118 return xmalloc (n * m);
121 /* Support routines. */
123 enum {
124 IDX_BITS = 10,
125 MAX_IDX = 1 << IDX_BITS,
126 KEY_MASK = (MAX_IDX - 1),
127 KEY_SHIFT = 0,
128 VALUE_MASK = (MAX_IDX - 1) << IDX_BITS,
129 VALUE_SHIFT = IDX_BITS
132 static char *string_table[MAX_IDX];
134 static const char *
135 get_string (int idx)
137 char **s;
139 assert (idx >= 0 && idx < MAX_IDX);
140 s = &string_table[idx];
141 if (*s == NULL)
143 *s = xmalloc (16);
144 sprintf (*s, "%d", idx);
146 return *s;
149 static void
150 free_strings (void)
152 int i;
154 for (i = 0; i < MAX_IDX; i++)
155 free (string_table[i]);
158 static const char *
159 make_key (int value)
161 return get_string ((value & KEY_MASK) >> KEY_SHIFT);
164 static const char *
165 make_value (int value)
167 return get_string ((value & VALUE_MASK) >> VALUE_SHIFT);
170 static int
171 random_value (unsigned int seed, int basis)
173 return hash_int (seed, basis) & VALUE_MASK;
176 /* Swaps *A and *B. */
177 static void
178 swap (int *a, int *b)
180 int t = *a;
181 *a = *b;
182 *b = t;
185 /* Reverses the order of the CNT integers starting at VALUES. */
186 static void
187 reverse (int *values, size_t cnt)
189 size_t i = 0;
190 size_t j = cnt;
192 while (j > i)
193 swap (&values[i++], &values[--j]);
196 /* Arranges the CNT elements in VALUES into the lexicographically next greater
197 permutation. Returns true if successful. If VALUES is already the
198 lexicographically greatest permutation of its elements (i.e. ordered from
199 greatest to smallest), arranges them into the lexicographically least
200 permutation (i.e. ordered from smallest to largest) and returns false.
202 Comparisons among elements of VALUES consider only the bits in KEY_MASK. */
203 static bool
204 next_permutation (int *values, size_t cnt)
206 if (cnt > 0)
208 size_t i = cnt - 1;
209 while (i != 0)
211 i--;
212 if ((values[i] & KEY_MASK) < (values[i + 1] & KEY_MASK))
214 size_t j;
215 for (j = cnt - 1;
216 (values[i] & KEY_MASK) >= (values[j] & KEY_MASK);
217 j--)
218 continue;
219 swap (values + i, values + j);
220 reverse (values + (i + 1), cnt - (i + 1));
221 return true;
225 reverse (values, cnt);
228 return false;
231 /* Returns N!. */
232 static unsigned int
233 factorial (unsigned int n)
235 unsigned int value = 1;
236 while (n > 1)
237 value *= n--;
238 return value;
241 /* Randomly shuffles the CNT elements in ARRAY, each of which is
242 SIZE bytes in size. */
243 static void
244 random_shuffle (void *array_, size_t cnt, size_t size)
246 char *array = array_;
247 char *tmp = xmalloc (size);
248 size_t i;
250 for (i = 0; i < cnt; i++)
252 size_t j = rand () % (cnt - i) + i;
253 if (i != j)
255 memcpy (tmp, array + j * size, size);
256 memcpy (array + j * size, array + i * size, size);
257 memcpy (array + i * size, tmp, size);
261 free (tmp);
264 /* Checks that MAP contains the CNT strings in DATA, that its structure is
265 correct, and that certain operations on MAP produce the expected results. */
266 static void
267 check_string_map (struct string_map *map, const int data[], size_t cnt)
269 size_t i;
271 check (string_map_is_empty (map) == (cnt == 0));
272 check (string_map_count (map) == cnt);
274 for (i = 0; i < cnt; i++)
276 struct string_map_node *node;
277 const char *key = make_key (data[i]);
278 const char *value = make_value (data[i]);
279 const char *found_value;
281 check (string_map_contains (map, key));
283 node = string_map_find_node (map, key);
284 check (node != NULL);
285 check (!strcmp (key, string_map_node_get_key (node)));
286 check (!strcmp (value, string_map_node_get_value (node)));
288 check (node == string_map_insert (map, key, "abc"));
289 check (!strcmp (value, string_map_node_get_value (node)));
291 check (node == string_map_insert_nocopy (map, xstrdup (key),
292 xstrdup ("def")));
293 check (!strcmp (value, string_map_node_get_value (node)));
295 found_value = string_map_find (map, key);
296 check (found_value != NULL);
297 check (!strcmp (found_value, value));
300 check (!string_map_contains (map, "xxx"));
301 check (string_map_find (map, "z") == NULL);
302 check (string_map_find_node (map, "") == NULL);
303 check (!string_map_delete (map, "xyz"));
305 if (cnt == 0)
306 check (string_map_first (map) == NULL);
307 else
309 const struct string_map_node *node;
310 int *data_copy;
311 int left;
313 data_copy = xmemdup (data, cnt * sizeof *data);
314 left = cnt;
315 for (node = string_map_first (map), i = 0; i < cnt;
316 node = string_map_next (map, node), i++)
318 const char *key = string_map_node_get_key (node);
319 const char *value = string_map_node_get_value (node);
320 size_t j;
322 for (j = 0; j < left; j++)
323 if (!strcmp (key, make_key (data_copy[j]))
324 || !strcmp (value, make_value (data_copy[j])))
326 data_copy[j] = data_copy[--left];
327 goto next;
329 check_die ();
331 next: ;
333 check (node == NULL);
334 free (data_copy);
338 /* Inserts the CNT strings from 0 to CNT - 1 (inclusive) into a map in the
339 order specified by INSERTIONS, then deletes them in the order specified by
340 DELETIONS, checking the map's contents for correctness after each
341 operation. */
342 static void
343 test_insert_delete (const int insertions[],
344 const int deletions[],
345 size_t cnt)
347 struct string_map map;
348 size_t i;
350 string_map_init (&map);
351 check_string_map (&map, NULL, 0);
352 for (i = 0; i < cnt; i++)
354 check (string_map_insert (&map, make_key (insertions[i]),
355 make_value (insertions[i])));
356 check_string_map (&map, insertions, i + 1);
358 for (i = 0; i < cnt; i++)
360 check (string_map_delete (&map, make_key (deletions[i])));
361 check_string_map (&map, deletions + i + 1, cnt - i - 1);
363 string_map_destroy (&map);
366 /* Inserts strings into a map in each possible order, then removes them in each
367 possible order, up to a specified maximum size. */
368 static void
369 test_insert_any_remove_any (void)
371 const int basis = 0;
372 const int max_elems = 5;
373 int cnt;
375 for (cnt = 0; cnt <= max_elems; cnt++)
377 int *insertions, *deletions;
378 unsigned int ins_perm_cnt;
379 int i;
381 insertions = xnmalloc (cnt, sizeof *insertions);
382 deletions = xnmalloc (cnt, sizeof *deletions);
383 for (i = 0; i < cnt; i++)
384 insertions[i] = i | random_value (i, basis);
386 for (ins_perm_cnt = 0;
387 ins_perm_cnt == 0 || next_permutation (insertions, cnt);
388 ins_perm_cnt++)
390 unsigned int del_perm_cnt;
391 int i;
393 for (i = 0; i < cnt; i++)
394 deletions[i] = i | random_value (i, basis);
396 for (del_perm_cnt = 0;
397 del_perm_cnt == 0 || next_permutation (deletions, cnt);
398 del_perm_cnt++)
399 test_insert_delete (insertions, deletions, cnt);
401 check (del_perm_cnt == factorial (cnt));
403 check (ins_perm_cnt == factorial (cnt));
405 free (insertions);
406 free (deletions);
410 /* Inserts strings into a map in each possible order, then removes them in the
411 same order, up to a specified maximum size. */
412 static void
413 test_insert_any_remove_same (void)
415 const int max_elems = 7;
416 int cnt;
418 for (cnt = 0; cnt <= max_elems; cnt++)
420 int *values;
421 unsigned int permutation_cnt;
422 int i;
424 values = xnmalloc (cnt, sizeof *values);
425 for (i = 0; i < cnt; i++)
426 values[i] = i | random_value (i, 1);
428 for (permutation_cnt = 0;
429 permutation_cnt == 0 || next_permutation (values, cnt);
430 permutation_cnt++)
431 test_insert_delete (values, values, cnt);
432 check (permutation_cnt == factorial (cnt));
434 free (values);
438 /* Inserts strings into a map in each possible order, then
439 removes them in reverse order, up to a specified maximum
440 size. */
441 static void
442 test_insert_any_remove_reverse (void)
444 const int max_elems = 7;
445 int cnt;
447 for (cnt = 0; cnt <= max_elems; cnt++)
449 int *insertions, *deletions;
450 unsigned int permutation_cnt;
451 int i;
453 insertions = xnmalloc (cnt, sizeof *insertions);
454 deletions = xnmalloc (cnt, sizeof *deletions);
455 for (i = 0; i < cnt; i++)
456 insertions[i] = i | random_value (i, 2);
458 for (permutation_cnt = 0;
459 permutation_cnt == 0 || next_permutation (insertions, cnt);
460 permutation_cnt++)
462 memcpy (deletions, insertions, sizeof *insertions * cnt);
463 reverse (deletions, cnt);
465 test_insert_delete (insertions, deletions, cnt);
467 check (permutation_cnt == factorial (cnt));
469 free (insertions);
470 free (deletions);
474 /* Inserts and removes strings in a map, in random order. */
475 static void
476 test_random_sequence (void)
478 const int basis = 3;
479 const int max_elems = 64;
480 const int max_trials = 8;
481 int cnt;
483 for (cnt = 0; cnt <= max_elems; cnt += 2)
485 int *insertions, *deletions;
486 int trial;
487 int i;
489 insertions = xnmalloc (cnt, sizeof *insertions);
490 deletions = xnmalloc (cnt, sizeof *deletions);
491 for (i = 0; i < cnt; i++)
492 insertions[i] = i | random_value (i, basis);
493 for (i = 0; i < cnt; i++)
494 deletions[i] = i | random_value (i, basis);
496 for (trial = 0; trial < max_trials; trial++)
498 random_shuffle (insertions, cnt, sizeof *insertions);
499 random_shuffle (deletions, cnt, sizeof *deletions);
501 test_insert_delete (insertions, deletions, cnt);
504 free (insertions);
505 free (deletions);
509 /* Inserts strings into a map in ascending order, then delete in ascending
510 order. */
511 static void
512 test_insert_ordered (void)
514 const int max_elems = 64;
515 int *values;
516 struct string_map map;
517 int i;
519 string_map_init (&map);
520 values = xnmalloc (max_elems, sizeof *values);
521 for (i = 0; i < max_elems; i++)
523 values[i] = i | random_value (i, 4);
524 string_map_insert_nocopy (&map, xstrdup (make_key (values[i])),
525 xstrdup (make_value (values[i])));
526 check_string_map (&map, values, i + 1);
528 for (i = 0; i < max_elems; i++)
530 string_map_delete (&map, make_key (i));
531 check_string_map (&map, values + i + 1, max_elems - i - 1);
533 string_map_destroy (&map);
534 free (values);
537 /* Inserts and replaces strings in a map, in random order. */
538 static void
539 test_replace (void)
541 const int basis = 15;
542 enum { MAX_ELEMS = 16 };
543 const int max_trials = 8;
544 int cnt;
546 for (cnt = 0; cnt <= MAX_ELEMS; cnt++)
548 int insertions[MAX_ELEMS];
549 int trial;
550 int i;
552 for (i = 0; i < cnt; i++)
553 insertions[i] = (i / 2) | random_value (i, basis);
555 for (trial = 0; trial < max_trials; trial++)
557 struct string_map map;
558 int data[MAX_ELEMS];
559 int n_data;
561 /* Insert with replacement in random order. */
562 n_data = 0;
563 string_map_init (&map);
564 random_shuffle (insertions, cnt, sizeof *insertions);
565 for (i = 0; i < cnt; i++)
567 const char *key = make_key (insertions[i]);
568 const char *value = make_value (insertions[i]);
569 int j;
571 for (j = 0; j < n_data; j++)
572 if ((data[j] & KEY_MASK) == (insertions[i] & KEY_MASK))
574 data[j] = insertions[i];
575 goto found;
577 data[n_data++] = insertions[i];
578 found:
580 if (i % 2)
581 string_map_replace (&map, key, value);
582 else
583 string_map_replace_nocopy (&map,
584 xstrdup (key), xstrdup (value));
585 check_string_map (&map, data, n_data);
588 /* Delete in original order. */
589 for (i = 0; i < cnt; i++)
591 const char *expected_value;
592 char *value;
593 int j;
595 expected_value = NULL;
596 for (j = 0; j < n_data; j++)
597 if ((data[j] & KEY_MASK) == (insertions[i] & KEY_MASK))
599 expected_value = make_value (data[j]);
600 data[j] = data[--n_data];
601 break;
604 value = string_map_find_and_delete (&map,
605 make_key (insertions[i]));
606 check ((value != NULL) == (expected_value != NULL));
607 check (value == NULL || !strcmp (value, expected_value));
608 free (value);
610 assert (string_map_is_empty (&map));
612 string_map_destroy (&map);
617 static void
618 make_patterned_map (struct string_map *map, unsigned int pattern, int basis,
619 int insertions[], int *np)
621 int n;
622 int i;
624 string_map_init (map);
626 n = 0;
627 for (i = 0; pattern != 0; i++)
628 if (pattern & (1u << i))
630 pattern &= pattern - 1;
631 insertions[n] = i | random_value (i, basis);
632 check (string_map_insert (map, make_key (insertions[n]),
633 make_value (insertions[n])));
634 n++;
636 check_string_map (map, insertions, n);
638 *np = n;
641 static void
642 for_each_map (void (*cb)(struct string_map *, int data[], int n),
643 int basis)
645 enum { MAX_ELEMS = 5 };
646 unsigned int pattern;
648 for (pattern = 0; pattern < (1u << MAX_ELEMS); pattern++)
650 int data[MAX_ELEMS];
651 struct string_map map;
652 int n;
654 make_patterned_map (&map, pattern, basis, data, &n);
655 (*cb) (&map, data, n);
656 string_map_destroy (&map);
660 static void
661 for_each_pair_of_maps (
662 void (*cb)(struct string_map *a, int a_data[], int n_a,
663 struct string_map *b, int b_data[], int n_b),
664 int a_basis, int b_basis)
666 enum { MAX_ELEMS = 5 };
667 unsigned int a_pattern, b_pattern;
669 for (a_pattern = 0; a_pattern < (1u << MAX_ELEMS); a_pattern++)
670 for (b_pattern = 0; b_pattern < (1u << MAX_ELEMS); b_pattern++)
672 int a_data[MAX_ELEMS], b_data[MAX_ELEMS];
673 struct string_map a_map, b_map;
674 int n_a, n_b;
676 make_patterned_map (&a_map, a_pattern, a_basis, a_data, &n_a);
677 make_patterned_map (&b_map, b_pattern, b_basis, b_data, &n_b);
678 (*cb) (&a_map, a_data, n_a, &b_map, b_data, n_b);
679 string_map_destroy (&a_map);
680 string_map_destroy (&b_map);
684 static void
685 clear_cb (struct string_map *map, int data[] UNUSED, int n UNUSED)
687 string_map_clear (map);
688 check_string_map (map, NULL, 0);
691 static void
692 test_clear (void)
694 for_each_map (clear_cb, 5);
697 static void
698 clone_cb (struct string_map *map, int data[], int n)
700 struct string_map clone;
702 string_map_clone (&clone, map);
703 check_string_map (&clone, data, n);
704 string_map_destroy (&clone);
707 static void
708 test_clone (void)
710 for_each_map (clone_cb, 6);
713 static void
714 node_swap_value_cb (struct string_map *map, int data[], int n)
716 int i;
718 for (i = 0; i < n; i++)
720 const char *value = make_value (data[i]);
721 struct string_map_node *node;
722 char *old_value;
724 node = string_map_find_node (map, make_key (data[i]));
725 check (node != NULL);
726 check (!strcmp (string_map_node_get_value (node), value));
727 data[i] = (data[i] & KEY_MASK) | random_value (i, 15);
728 old_value = string_map_node_swap_value (node, make_value (data[i]));
729 check (old_value != NULL);
730 check (!strcmp (value, old_value));
731 free (old_value);
735 static void
736 test_node_swap_value (void)
738 for_each_map (node_swap_value_cb, 14);
741 static void
742 swap_cb (struct string_map *a, int a_data[], int n_a,
743 struct string_map *b, int b_data[], int n_b)
745 string_map_swap (a, b);
746 check_string_map (a, b_data, n_b);
747 check_string_map (b, a_data, n_a);
750 static void
751 test_swap (void)
753 for_each_pair_of_maps (swap_cb, 7, 8);
756 static void
757 insert_map_cb (struct string_map *a, int a_data[], int n_a,
758 struct string_map *b, int b_data[], int n_b)
760 int i, j;
762 string_map_insert_map (a, b);
764 for (i = 0; i < n_b; i++)
766 for (j = 0; j < n_a; j++)
767 if ((b_data[i] & KEY_MASK) == (a_data[j] & KEY_MASK))
768 goto found;
769 a_data[n_a++] = b_data[i];
770 found:;
772 check_string_map (a, a_data, n_a);
773 check_string_map (b, b_data, n_b);
776 static void
777 test_insert_map (void)
779 for_each_pair_of_maps (insert_map_cb, 91, 10);
782 static void
783 replace_map_cb (struct string_map *a, int a_data[], int n_a,
784 struct string_map *b, int b_data[], int n_b)
786 int i, j;
788 string_map_replace_map (a, b);
790 for (i = 0; i < n_b; i++)
792 for (j = 0; j < n_a; j++)
793 if ((b_data[i] & KEY_MASK) == (a_data[j] & KEY_MASK))
795 a_data[j] = (a_data[j] & KEY_MASK) | (b_data[i] & VALUE_MASK);
796 goto found;
798 a_data[n_a++] = b_data[i];
799 found:;
801 check_string_map (a, a_data, n_a);
802 check_string_map (b, b_data, n_b);
805 static void
806 test_replace_map (void)
808 for_each_pair_of_maps (replace_map_cb, 11, 12);
811 static void
812 check_set (struct string_set *set, const int *data, int n_data,
813 int mask, int shift)
815 int *unique;
816 int n_unique;
817 int i;
819 n_unique = 0;
820 unique = xmalloc (n_data * sizeof *unique);
821 for (i = 0; i < n_data; i++)
823 int idx = (data[i] & mask) >> shift;
824 int j;
826 for (j = 0; j < n_unique; j++)
827 if (unique[j] == idx)
828 goto found;
829 unique[n_unique++] = idx;
830 found:;
833 check (string_set_count (set) == n_unique);
834 for (i = 0; i < n_unique; i++)
835 check (string_set_contains (set, get_string (unique[i])));
836 string_set_destroy (set);
837 free (unique);
840 static void
841 get_keys_and_values_cb (struct string_map *map, int data[], int n)
843 struct string_set keys, values;
845 string_set_init (&keys);
846 string_set_init (&values);
847 string_map_get_keys (map, &keys);
848 string_map_get_values (map, &values);
849 check_set (&keys, data, n, KEY_MASK, KEY_SHIFT);
850 check_set (&values, data, n, VALUE_MASK, VALUE_SHIFT);
853 static void
854 test_get_keys_and_values (void)
856 for_each_map (get_keys_and_values_cb, 13);
859 static void
860 test_destroy_null (void)
862 string_map_destroy (NULL);
865 /* Main program. */
867 struct test
869 const char *name;
870 const char *description;
871 void (*function) (void);
874 static const struct test tests[] =
877 "insert-any-remove-any",
878 "insert any order, delete any order",
879 test_insert_any_remove_any
882 "insert-any-remove-same",
883 "insert any order, delete same order",
884 test_insert_any_remove_same
887 "insert-any-remove-reverse",
888 "insert any order, delete reverse order",
889 test_insert_any_remove_reverse
892 "random-sequence",
893 "insert and delete in random sequence",
894 test_random_sequence
897 "replace",
898 "insert and replace in random sequence",
899 test_replace
902 "insert-ordered",
903 "insert in ascending order",
904 test_insert_ordered
907 "clear",
908 "clear",
909 test_clear
912 "clone",
913 "clone",
914 test_clone
917 "swap",
918 "swap",
919 test_swap
922 "node-swap-value",
923 "node_swap_value",
924 test_node_swap_value
927 "insert-map",
928 "insert_map",
929 test_insert_map
932 "replace-map",
933 "replace_map",
934 test_replace_map
937 "get-keys-and-values",
938 "get keys and values",
939 test_get_keys_and_values
942 "destroy-null",
943 "destroying null table",
944 test_destroy_null
948 enum { N_TESTS = sizeof tests / sizeof *tests };
951 main (int argc, char *argv[])
953 int i;
955 if (argc != 2)
957 fprintf (stderr, "exactly one argument required; use --help for help\n");
958 return EXIT_FAILURE;
960 else if (!strcmp (argv[1], "--help"))
962 printf ("%s: test string map library\n"
963 "usage: %s TEST-NAME\n"
964 "where TEST-NAME is one of the following:\n",
965 argv[0], argv[0]);
966 for (i = 0; i < N_TESTS; i++)
967 printf (" %s\n %s\n", tests[i].name, tests[i].description);
968 return 0;
970 else
972 for (i = 0; i < N_TESTS; i++)
973 if (!strcmp (argv[1], tests[i].name))
975 tests[i].function ();
976 free_strings ();
977 return 0;
980 fprintf (stderr, "unknown test %s; use --help for help\n", argv[1]);
981 return EXIT_FAILURE;