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 hmap_* routines defined in
18 hmap.c. This test program aims to be as comprehensive as
19 possible. "gcov -a -b" should report 100% coverage of lines,
20 blocks and branches in hmap.c (when compiled with -DNDEBUG).
21 "valgrind --leak-check=yes --show-reachable=yes" should give a
24 /* GCC 4.3 miscompiles some of the tests below, so we do not run
25 these tests on GCC 4.3. This is a bug in GCC 4.3 triggered by
26 the test program, not a bug in the library under test. GCC
27 4.2 or earlier and GCC 4.4 or later do not have this bug.
29 Here is a minimal test program that demonstrates the same or a
30 similar bug in GCC 4.3:
54 check_list (struct list *list)
56 int i __attribute__((unused));
58 for (e = list->head; e != NULL; e = e->next)
59 if (e->data1 != e->data2)
67 struct node *elements = xmalloc (MAX_ELEMS * sizeof *elements);
68 int *values = xmalloc (MAX_ELEMS * sizeof *values);
73 for (i = 0; i < MAX_ELEMS; i++)
75 values[i] = elements[i].data2 = i;
76 elements[i].data1 = elements[i].data2;
77 elements[i].next = list.head;
78 list.head = &elements[i];
89 #include <libpspp/hmap.h>
100 #include <libpspp/compiler.h>
102 /* Exit with a failure code.
103 (Place a breakpoint on this function while debugging.) */
110 /* If OK is not true, prints a message about failure on the
111 current source file and the given LINE and terminates. */
113 check_func (bool ok, int line)
117 fprintf (stderr, "%s:%d: check failed\n", __FILE__, line);
122 /* Verifies that EXPR evaluates to true.
123 If not, prints a message citing the calling line number and
125 #define check(EXPR) check_func ((EXPR), __LINE__)
127 /* Prints a message about memory exhaustion and exits with a
132 printf ("virtual memory exhausted\n");
136 static void *xmalloc (size_t n) MALLOC_LIKE;
137 static void *xnmalloc (size_t n, size_t m) MALLOC_LIKE;
138 static void *xmemdup (const void *p, size_t n) MALLOC_LIKE;
140 /* Allocates and returns N bytes of memory. */
146 void *p = malloc (n);
157 xmemdup (const void *p, size_t n)
159 void *q = xmalloc (n);
164 /* Allocates and returns N * M bytes of memory. */
166 xnmalloc (size_t n, size_t m)
168 if ((size_t) -1 / m <= n)
170 return xmalloc (n * m);
173 /* Node type and support routines. */
175 /* Test data element. */
178 struct hmap_node node; /* Embedded hash table element. */
179 int data; /* Primary value. */
182 /* Returns the `struct element' that NODE is embedded within. */
183 static struct element *
184 hmap_node_to_element (const struct hmap_node *node)
186 return HMAP_DATA (node, struct element, node);
189 /* Compares A and B and returns a strcmp-type return value. */
191 compare_ints (const void *a_, const void *b_)
196 return *a < *b ? -1 : *a > *b;
199 /* Swaps *A and *B. */
201 swap (int *a, int *b)
208 /* Reverses the order of the CNT integers starting at VALUES. */
210 reverse (int *values, size_t cnt)
216 swap (&values[i++], &values[--j]);
219 /* Arranges the CNT elements in VALUES into the lexicographically
220 next greater permutation. Returns true if successful.
221 If VALUES is already the lexicographically greatest
222 permutation of its elements (i.e. ordered from greatest to
223 smallest), arranges them into the lexicographically least
224 permutation (i.e. ordered from smallest to largest) and
227 next_permutation (int *values, size_t cnt)
235 if (values[i] < values[i + 1])
238 for (j = cnt - 1; values[i] >= values[j]; j--)
240 swap (values + i, values + j);
241 reverse (values + (i + 1), cnt - (i + 1));
246 reverse (values, cnt);
254 factorial (unsigned int n)
256 unsigned int value = 1;
262 /* Randomly shuffles the CNT elements in ARRAY, each of which is
263 SIZE bytes in size. */
265 random_shuffle (void *array_, size_t cnt, size_t size)
267 char *array = array_;
268 char *tmp = xmalloc (size);
271 for (i = 0; i < cnt; i++)
273 size_t j = rand () % (cnt - i) + i;
276 memcpy (tmp, array + j * size, size);
277 memcpy (array + j * size, array + i * size, size);
278 memcpy (array + i * size, tmp, size);
285 typedef size_t hash_function (int data);
288 identity_hash (int data)
294 constant_hash (int data UNUSED)
299 static inline uint32_t
300 md4_round (uint32_t a, uint32_t b, uint32_t c, uint32_t d,
301 uint32_t data, uint32_t n)
303 uint32_t x = a + (d ^ (b & (c ^ d))) + data;
304 return (x << n) | (x >> (32 - n));
308 random_hash (int data)
314 a = md4_round (a, b, c, d, 0, 3);
315 d = md4_round (d, a, b, c, 1, 7);
316 c = md4_round (c, d, a, b, 2, 11);
317 b = md4_round (b, c, d, a, 3, 19);
318 return a ^ b ^ c ^ d;
321 static struct hmap_node *
322 find_element (struct hmap *hmap, int data, hash_function *hash)
325 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (data), hmap)
331 /* Checks that HMAP contains the CNT ints in DATA, that its
332 structure is correct, and that certain operations on HMAP
333 produce the expected results. */
335 check_hmap (struct hmap *hmap, const int data[], size_t cnt,
341 check (hmap_is_empty (hmap) == (cnt == 0));
342 check (hmap_count (hmap) == cnt);
343 check (cnt <= hmap_capacity (hmap));
345 order = xmemdup (data, cnt * sizeof *data);
346 qsort (order, cnt, sizeof *order, compare_ints);
348 for (i = 0; i < cnt; i = j)
353 for (j = i + 1; j < cnt; j++)
354 if (order[i] != order[j])
358 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (order[i]), hmap)
359 if (e->data == order[i])
362 check (count == j - i);
365 check (find_element (hmap, -1, hash) == NULL);
368 check (hmap_first (hmap) == NULL);
375 for (p = hmap_first (hmap), i = 0; i < cnt; p = hmap_next (hmap, p), i++)
377 struct element *e = hmap_node_to_element (p);
379 check (hmap_node_hash (&e->node) == hash (e->data));
380 for (j = 0; j < left; j++)
381 if (order[j] == e->data)
383 order[j] = order[--left];
396 /* Inserts the CNT values from 0 to CNT - 1 (inclusive) into an
397 HMAP in the order specified by INSERTIONS, then deletes them in
398 the order specified by DELETIONS, checking the HMAP's contents
399 for correctness after each operation. Uses HASH as the hash
402 test_insert_delete (const int insertions[],
403 const int deletions[],
407 struct element *elements;
411 elements = xnmalloc (cnt, sizeof *elements);
412 for (i = 0; i < cnt; i++)
413 elements[i].data = i;
416 hmap_reserve (&hmap, 1);
417 check_hmap (&hmap, NULL, 0, hash);
418 for (i = 0; i < cnt; i++)
421 hmap_insert (&hmap, &elements[insertions[i]].node, hash (insertions[i]));
422 check_hmap (&hmap, insertions, i + 1, hash);
424 /* A series of insertions should not produce a shrinkable hmap. */
425 capacity = hmap_capacity (&hmap);
427 check (capacity == hmap_capacity (&hmap));
429 for (i = 0; i < cnt; i++)
431 hmap_delete (&hmap, &elements[deletions[i]].node);
432 check_hmap (&hmap, deletions + i + 1, cnt - i - 1, hash);
434 hmap_destroy (&hmap);
439 /* Inserts values into an HMAP in each possible order, then
440 removes them in each possible order, up to a specified maximum
441 size, using hash function HASH. */
443 test_insert_any_remove_any (hash_function *hash)
445 const int max_elems = 5;
448 for (cnt = 0; cnt <= max_elems; cnt++)
450 int *insertions, *deletions;
451 unsigned int ins_perm_cnt;
454 insertions = xnmalloc (cnt, sizeof *insertions);
455 deletions = xnmalloc (cnt, sizeof *deletions);
456 for (i = 0; i < cnt; i++)
459 for (ins_perm_cnt = 0;
460 ins_perm_cnt == 0 || next_permutation (insertions, cnt);
463 unsigned int del_perm_cnt;
466 for (i = 0; i < cnt; i++)
469 for (del_perm_cnt = 0;
470 del_perm_cnt == 0 || next_permutation (deletions, cnt);
472 test_insert_delete (insertions, deletions, cnt, hash);
474 check (del_perm_cnt == factorial (cnt));
476 check (ins_perm_cnt == factorial (cnt));
484 test_insert_any_remove_any_random_hash (void)
486 test_insert_any_remove_any (random_hash);
490 test_insert_any_remove_any_identity_hash (void)
492 test_insert_any_remove_any (identity_hash);
496 test_insert_any_remove_any_constant_hash (void)
498 test_insert_any_remove_any (constant_hash);
501 /* Inserts values into an HMAP in each possible order, then
502 removes them in the same order, up to a specified maximum
503 size, using hash function HASH. */
505 test_insert_any_remove_same (hash_function *hash)
507 const int max_elems = 7;
510 for (cnt = 0; cnt <= max_elems; cnt++)
513 unsigned int permutation_cnt;
516 values = xnmalloc (cnt, sizeof *values);
517 for (i = 0; i < cnt; i++)
520 for (permutation_cnt = 0;
521 permutation_cnt == 0 || next_permutation (values, cnt);
523 test_insert_delete (values, values, cnt, hash);
524 check (permutation_cnt == factorial (cnt));
531 test_insert_any_remove_same_random_hash (void)
533 test_insert_any_remove_same (random_hash);
537 test_insert_any_remove_same_identity_hash (void)
539 test_insert_any_remove_same (identity_hash);
543 test_insert_any_remove_same_constant_hash (void)
545 test_insert_any_remove_same (constant_hash);
548 /* Inserts values into an HMAP in each possible order, then
549 removes them in reverse order, up to a specified maximum
550 size, using hash function HASH. */
552 test_insert_any_remove_reverse (hash_function *hash)
554 const int max_elems = 7;
557 for (cnt = 0; cnt <= max_elems; cnt++)
559 int *insertions, *deletions;
560 unsigned int permutation_cnt;
563 insertions = xnmalloc (cnt, sizeof *insertions);
564 deletions = xnmalloc (cnt, sizeof *deletions);
565 for (i = 0; i < cnt; i++)
568 for (permutation_cnt = 0;
569 permutation_cnt == 0 || next_permutation (insertions, cnt);
572 memcpy (deletions, insertions, sizeof *insertions * cnt);
573 reverse (deletions, cnt);
575 test_insert_delete (insertions, deletions, cnt, hash);
577 check (permutation_cnt == factorial (cnt));
585 test_insert_any_remove_reverse_random_hash (void)
587 test_insert_any_remove_reverse (random_hash);
591 test_insert_any_remove_reverse_identity_hash (void)
593 test_insert_any_remove_reverse (identity_hash);
597 test_insert_any_remove_reverse_constant_hash (void)
599 test_insert_any_remove_reverse (constant_hash);
602 /* Inserts and removes up to MAX_ELEMS values in an hmap, in
603 random order, using hash function HASH. */
605 test_random_sequence (int max_elems, hash_function *hash)
607 const int max_trials = 8;
610 for (cnt = 0; cnt <= max_elems; cnt += 2)
612 int *insertions, *deletions;
616 insertions = xnmalloc (cnt, sizeof *insertions);
617 deletions = xnmalloc (cnt, sizeof *deletions);
618 for (i = 0; i < cnt; i++)
620 for (i = 0; i < cnt; i++)
623 for (trial = 0; trial < max_trials; trial++)
625 random_shuffle (insertions, cnt, sizeof *insertions);
626 random_shuffle (deletions, cnt, sizeof *deletions);
628 test_insert_delete (insertions, deletions, cnt, hash);
637 test_random_sequence_random_hash (void)
639 test_random_sequence (64, random_hash);
643 test_random_sequence_identity_hash (void)
645 test_random_sequence (64, identity_hash);
649 test_random_sequence_constant_hash (void)
651 test_random_sequence (32, constant_hash);
654 /* Inserts MAX_ELEMS elements into an HMAP in ascending order,
655 then delete in ascending order and shrink the hmap at each
656 step, using hash function HASH. */
658 test_insert_ordered (int max_elems, hash_function *hash)
660 struct element *elements;
665 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
666 /* This tells the Autotest framework that the test was skipped. */
671 elements = xnmalloc (max_elems, sizeof *elements);
672 values = xnmalloc (max_elems, sizeof *values);
673 for (i = 0; i < max_elems; i++)
675 values[i] = elements[i].data = i;
676 hmap_insert (&hmap, &elements[i].node, hash (elements[i].data));
677 check_hmap (&hmap, values, i + 1, hash);
679 if (hash == identity_hash)
681 /* Check that every every hash bucket has (almost) the
682 same number of nodes in it. */
687 for (j = 0; j <= hmap.mask; j++)
690 struct hmap_node *node;
692 for (node = hmap.buckets[j]; node != NULL; node = node->next)
699 check (max - min <= 1);
702 for (i = 0; i < max_elems; i++)
704 hmap_delete (&hmap, &elements[i].node);
706 check_hmap (&hmap, values + i + 1, max_elems - i - 1, hash);
708 hmap_destroy (&hmap);
714 test_insert_ordered_random_hash (void)
716 test_insert_ordered (1024, random_hash);
720 test_insert_ordered_identity_hash (void)
722 test_insert_ordered (1024, identity_hash);
726 test_insert_ordered_constant_hash (void)
728 test_insert_ordered (128, constant_hash);
731 /* Inserts up to MAX_ELEMS elements into an HMAP, then moves the
732 nodes around in memory, using hash function HASH. */
734 test_moved (int max_elems, hash_function *hash)
736 struct element *e[2];
742 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
743 /* This tells the Autotest framework that the test was skipped. */
748 e[0] = xnmalloc (max_elems, sizeof *e[0]);
749 e[1] = xnmalloc (max_elems, sizeof *e[1]);
750 values = xnmalloc (max_elems, sizeof *values);
752 for (i = 0; i < max_elems; i++)
754 values[i] = e[cur][i].data = i;
755 hmap_insert (&hmap, &e[cur][i].node, hash (e[cur][i].data));
756 check_hmap (&hmap, values, i + 1, hash);
758 for (j = 0; j <= i; j++)
760 e[!cur][j] = e[cur][j];
761 hmap_moved (&hmap, &e[!cur][j].node, &e[cur][j].node);
762 check_hmap (&hmap, values, i + 1, hash);
766 hmap_destroy (&hmap);
773 test_moved_random_hash (void)
775 test_moved (128, random_hash);
779 test_moved_identity_hash (void)
781 test_moved (128, identity_hash);
785 test_moved_constant_hash (void)
787 test_moved (32, constant_hash);
790 /* Inserts values into an HMAP, then changes their values, using
791 hash function HASH. */
793 test_changed (hash_function *hash)
795 const int max_elems = 6;
798 for (cnt = 0; cnt <= max_elems; cnt++)
800 int *values, *changed_values;
801 struct element *elements;
802 unsigned int permutation_cnt;
805 values = xnmalloc (cnt, sizeof *values);
806 changed_values = xnmalloc (cnt, sizeof *changed_values);
807 elements = xnmalloc (cnt, sizeof *elements);
808 for (i = 0; i < cnt; i++)
811 for (permutation_cnt = 0;
812 permutation_cnt == 0 || next_permutation (values, cnt);
815 for (i = 0; i < cnt; i++)
818 for (j = 0; j <= cnt; j++)
824 /* Add to HMAP in order. */
825 for (k = 0; k < cnt; k++)
828 elements[n].data = n;
829 hmap_insert (&hmap, &elements[n].node,
830 hash (elements[n].data));
832 check_hmap (&hmap, values, cnt, hash);
834 /* Change value i to j. */
835 elements[i].data = j;
836 hmap_changed (&hmap, &elements[i].node,
837 hash (elements[i].data));
838 for (k = 0; k < cnt; k++)
839 changed_values[k] = k;
840 changed_values[i] = j;
841 check_hmap (&hmap, changed_values, cnt, hash);
843 hmap_destroy (&hmap);
847 check (permutation_cnt == factorial (cnt));
850 free (changed_values);
856 test_changed_random_hash (void)
858 test_changed (random_hash);
862 test_changed_identity_hash (void)
864 test_changed (identity_hash);
868 test_changed_constant_hash (void)
870 test_changed (constant_hash);
874 test_swap (int max_elems, hash_function *hash)
876 struct element *elements;
879 struct hmap *working, *empty;
882 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
883 /* This tells the Autotest framework that the test was skipped. */
891 elements = xnmalloc (max_elems, sizeof *elements);
892 values = xnmalloc (max_elems, sizeof *values);
893 for (i = 0; i < max_elems; i++)
896 values[i] = elements[i].data = i;
897 hmap_insert (working, &elements[i].node, hash (elements[i].data));
898 check_hmap (working, values, i + 1, hash);
899 check_hmap (empty, NULL, 0, hash);
912 test_swap_random_hash (void)
914 test_swap (128, random_hash);
917 /* Inserts elements into an hmap in ascending order, then clears the hash table
918 using hmap_clear(). */
922 const int max_elems = 128;
923 struct element *elements;
928 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
929 /* This tells the Autotest framework that the test was skipped. */
933 elements = xnmalloc (max_elems, sizeof *elements);
934 values = xnmalloc (max_elems, sizeof *values);
936 for (cnt = 0; cnt <= max_elems; cnt++)
941 for (i = 0; i < cnt; i++)
943 values[i] = elements[i].data = i;
944 hmap_insert (&hmap, &elements[i].node,
945 random_hash (elements[i].data));
946 check_hmap (&hmap, values, i + 1, random_hash);
949 check_hmap (&hmap, NULL, 0, random_hash);
950 hmap_destroy (&hmap);
958 test_destroy_null (void)
963 /* Test shrinking an empty hash table. */
965 test_shrink_empty (void)
970 hmap_reserve (&hmap, 123);
972 hmap_destroy (&hmap);
980 const char *description;
981 void (*function) (void);
984 static const struct test tests[] =
987 "insert-any-remove-any-random-hash",
988 "insert any order, delete any order (random hash)",
989 test_insert_any_remove_any_random_hash
992 "insert-any-remove-any-identity-hash",
993 "insert any order, delete any order (identity hash)",
994 test_insert_any_remove_any_identity_hash
997 "insert-any-remove-any-constant-hash",
998 "insert any order, delete any order (constant hash)",
999 test_insert_any_remove_any_constant_hash
1003 "insert-any-remove-same-random-hash",
1004 "insert any order, delete same order (random hash)",
1005 test_insert_any_remove_same_random_hash
1008 "insert-any-remove-same-identity-hash",
1009 "insert any order, delete same order (identity hash)",
1010 test_insert_any_remove_same_identity_hash
1013 "insert-any-remove-same-constant-hash",
1014 "insert any order, delete same order (constant hash)",
1015 test_insert_any_remove_same_constant_hash
1019 "insert-any-remove-reverse-random-hash",
1020 "insert any order, delete reverse order (random hash)",
1021 test_insert_any_remove_reverse_random_hash
1024 "insert-any-remove-reverse-identity-hash",
1025 "insert any order, delete reverse order (identity hash)",
1026 test_insert_any_remove_reverse_identity_hash
1029 "insert-any-remove-reverse-constant-hash",
1030 "insert any order, delete reverse order (constant hash)",
1031 test_insert_any_remove_reverse_constant_hash
1035 "random-sequence-random-hash",
1036 "insert and delete in random sequence (random hash)",
1037 test_random_sequence_random_hash
1040 "random-sequence-identity-hash",
1041 "insert and delete in random sequence (identity hash)",
1042 test_random_sequence_identity_hash
1045 "random-sequence-constant-hash",
1046 "insert and delete in random sequence (constant hash)",
1047 test_random_sequence_constant_hash
1051 "insert-ordered-random-hash",
1052 "insert in ascending order (random hash)",
1053 test_insert_ordered_random_hash
1056 "insert-ordered-identity-hash",
1057 "insert in ascending order (identity hash)",
1058 test_insert_ordered_identity_hash
1061 "insert-ordered-constant-hash",
1062 "insert in ascending order (constant hash)",
1063 test_insert_ordered_constant_hash
1067 "moved-random-hash",
1068 "move elements around in memory (random hash)",
1069 test_moved_random_hash
1072 "moved-identity-hash",
1073 "move elements around in memory (identity hash)",
1074 test_moved_identity_hash
1077 "moved-constant-hash",
1078 "move elements around in memory (constant hash)",
1079 test_moved_constant_hash
1083 "changed-random-hash",
1084 "change key data in nodes (random hash)",
1085 test_changed_random_hash
1088 "changed-identity-hash",
1089 "change key data in nodes (identity hash)",
1090 test_changed_identity_hash
1093 "changed-constant-hash",
1094 "change key data in nodes (constant hash)",
1095 test_changed_constant_hash
1100 "test swapping tables",
1101 test_swap_random_hash
1105 "test clearing hash table",
1110 "test destroying null table",
1115 "test shrinking an empty table",
1120 enum { N_TESTS = sizeof tests / sizeof *tests };
1123 main (int argc, char *argv[])
1129 fprintf (stderr, "exactly one argument required; use --help for help\n");
1130 return EXIT_FAILURE;
1132 else if (!strcmp (argv[1], "--help"))
1134 printf ("%s: test hash map\n"
1135 "usage: %s TEST-NAME\n"
1136 "where TEST-NAME is one of the following:\n",
1138 for (i = 0; i < N_TESTS; i++)
1139 printf (" %s\n %s\n", tests[i].name, tests[i].description);
1144 for (i = 0; i < N_TESTS; i++)
1145 if (!strcmp (argv[1], tests[i].name))
1147 tests[i].function ();
1151 fprintf (stderr, "unknown test %s; use --help for help\n", argv[1]);
1152 return EXIT_FAILURE;