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 for (i = 0; i < 32; i++)
295 if (data & (1u << i))
297 size_t high_bit = (size_t) 1 << (sizeof (size_t) * CHAR_BIT - 1);
298 hash |= high_bit >> i;
305 constant_hash (int data UNUSED)
310 static inline uint32_t
311 md4_round (uint32_t a, uint32_t b, uint32_t c, uint32_t d,
312 uint32_t data, uint32_t n)
314 uint32_t x = a + (d ^ (b & (c ^ d))) + data;
315 return (x << n) | (x >> (32 - n));
319 random_hash (int data)
325 a = md4_round (a, b, c, d, 0, 3);
326 d = md4_round (d, a, b, c, 1, 7);
327 c = md4_round (c, d, a, b, 2, 11);
328 b = md4_round (b, c, d, a, 3, 19);
329 return a ^ b ^ c ^ d;
332 static struct hmap_node *
333 find_element (struct hmap *hmap, int data, hash_function *hash)
336 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (data), hmap)
342 /* Checks that HMAP contains the CNT ints in DATA, that its
343 structure is correct, and that certain operations on HMAP
344 produce the expected results. */
346 check_hmap (struct hmap *hmap, const int data[], size_t cnt,
352 check (hmap_is_empty (hmap) == (cnt == 0));
353 check (hmap_count (hmap) == cnt);
354 check (cnt <= hmap_capacity (hmap));
356 order = xmemdup (data, cnt * sizeof *data);
357 qsort (order, cnt, sizeof *order, compare_ints);
359 for (i = 0; i < cnt; i = j)
364 for (j = i + 1; j < cnt; j++)
365 if (order[i] != order[j])
369 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (order[i]), hmap)
370 if (e->data == order[i])
373 check (count == j - i);
376 check (find_element (hmap, -1, hash) == NULL);
379 check (hmap_first (hmap) == NULL);
386 for (p = hmap_first (hmap), i = 0; i < cnt; p = hmap_next (hmap, p), i++)
388 struct element *e = hmap_node_to_element (p);
390 check (hmap_node_hash (&e->node) == hash (e->data));
391 for (j = 0; j < left; j++)
392 if (order[j] == e->data)
394 order[j] = order[--left];
407 /* Inserts the CNT values from 0 to CNT - 1 (inclusive) into an
408 HMAP in the order specified by INSERTIONS, then deletes them in
409 the order specified by DELETIONS, checking the HMAP's contents
410 for correctness after each operation. Uses HASH as the hash
413 test_insert_delete (const int insertions[],
414 const int deletions[],
418 struct element *elements;
422 elements = xnmalloc (cnt, sizeof *elements);
423 for (i = 0; i < cnt; i++)
424 elements[i].data = i;
427 hmap_reserve (&hmap, 1);
428 check_hmap (&hmap, NULL, 0, hash);
429 for (i = 0; i < cnt; i++)
432 hmap_insert (&hmap, &elements[insertions[i]].node, hash (insertions[i]));
433 check_hmap (&hmap, insertions, i + 1, hash);
435 /* A series of insertions should not produce a shrinkable hmap. */
436 capacity = hmap_capacity (&hmap);
438 check (capacity == hmap_capacity (&hmap));
440 for (i = 0; i < cnt; i++)
442 hmap_delete (&hmap, &elements[deletions[i]].node);
443 check_hmap (&hmap, deletions + i + 1, cnt - i - 1, hash);
445 hmap_destroy (&hmap);
450 /* Inserts values into an HMAP in each possible order, then
451 removes them in each possible order, up to a specified maximum
452 size, using hash function HASH. */
454 test_insert_any_remove_any (hash_function *hash)
456 const int max_elems = 5;
459 for (cnt = 0; cnt <= max_elems; cnt++)
461 int *insertions, *deletions;
462 unsigned int ins_perm_cnt;
465 insertions = xnmalloc (cnt, sizeof *insertions);
466 deletions = xnmalloc (cnt, sizeof *deletions);
467 for (i = 0; i < cnt; i++)
470 for (ins_perm_cnt = 0;
471 ins_perm_cnt == 0 || next_permutation (insertions, cnt);
474 unsigned int del_perm_cnt;
477 for (i = 0; i < cnt; i++)
480 for (del_perm_cnt = 0;
481 del_perm_cnt == 0 || next_permutation (deletions, cnt);
483 test_insert_delete (insertions, deletions, cnt, hash);
485 check (del_perm_cnt == factorial (cnt));
487 check (ins_perm_cnt == factorial (cnt));
495 test_insert_any_remove_any_random_hash (void)
497 test_insert_any_remove_any (random_hash);
501 test_insert_any_remove_any_identity_hash (void)
503 test_insert_any_remove_any (identity_hash);
507 test_insert_any_remove_any_constant_hash (void)
509 test_insert_any_remove_any (constant_hash);
512 /* Inserts values into an HMAP in each possible order, then
513 removes them in the same order, up to a specified maximum
514 size, using hash function HASH. */
516 test_insert_any_remove_same (hash_function *hash)
518 const int max_elems = 7;
521 for (cnt = 0; cnt <= max_elems; cnt++)
524 unsigned int permutation_cnt;
527 values = xnmalloc (cnt, sizeof *values);
528 for (i = 0; i < cnt; i++)
531 for (permutation_cnt = 0;
532 permutation_cnt == 0 || next_permutation (values, cnt);
534 test_insert_delete (values, values, cnt, hash);
535 check (permutation_cnt == factorial (cnt));
542 test_insert_any_remove_same_random_hash (void)
544 test_insert_any_remove_same (random_hash);
548 test_insert_any_remove_same_identity_hash (void)
550 test_insert_any_remove_same (identity_hash);
554 test_insert_any_remove_same_constant_hash (void)
556 test_insert_any_remove_same (constant_hash);
559 /* Inserts values into an HMAP in each possible order, then
560 removes them in reverse order, up to a specified maximum
561 size, using hash function HASH. */
563 test_insert_any_remove_reverse (hash_function *hash)
565 const int max_elems = 7;
568 for (cnt = 0; cnt <= max_elems; cnt++)
570 int *insertions, *deletions;
571 unsigned int permutation_cnt;
574 insertions = xnmalloc (cnt, sizeof *insertions);
575 deletions = xnmalloc (cnt, sizeof *deletions);
576 for (i = 0; i < cnt; i++)
579 for (permutation_cnt = 0;
580 permutation_cnt == 0 || next_permutation (insertions, cnt);
583 memcpy (deletions, insertions, sizeof *insertions * cnt);
584 reverse (deletions, cnt);
586 test_insert_delete (insertions, deletions, cnt, hash);
588 check (permutation_cnt == factorial (cnt));
596 test_insert_any_remove_reverse_random_hash (void)
598 test_insert_any_remove_reverse (random_hash);
602 test_insert_any_remove_reverse_identity_hash (void)
604 test_insert_any_remove_reverse (identity_hash);
608 test_insert_any_remove_reverse_constant_hash (void)
610 test_insert_any_remove_reverse (constant_hash);
613 /* Inserts and removes up to MAX_ELEMS values in an hmap, in
614 random order, using hash function HASH. */
616 test_random_sequence (int max_elems, hash_function *hash)
618 const int max_trials = 8;
621 for (cnt = 0; cnt <= max_elems; cnt += 2)
623 int *insertions, *deletions;
627 insertions = xnmalloc (cnt, sizeof *insertions);
628 deletions = xnmalloc (cnt, sizeof *deletions);
629 for (i = 0; i < cnt; i++)
631 for (i = 0; i < cnt; i++)
634 for (trial = 0; trial < max_trials; trial++)
636 random_shuffle (insertions, cnt, sizeof *insertions);
637 random_shuffle (deletions, cnt, sizeof *deletions);
639 test_insert_delete (insertions, deletions, cnt, hash);
648 test_random_sequence_random_hash (void)
650 test_random_sequence (64, random_hash);
654 test_random_sequence_identity_hash (void)
656 test_random_sequence (64, identity_hash);
660 test_random_sequence_constant_hash (void)
662 test_random_sequence (32, constant_hash);
665 /* Inserts MAX_ELEMS elements into an HMAP in ascending order,
666 then delete in ascending order and shrink the hmap at each
667 step, using hash function HASH. */
669 test_insert_ordered (int max_elems, hash_function *hash)
671 struct element *elements;
676 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
677 /* This tells the Autotest framework that the test was skipped. */
682 elements = xnmalloc (max_elems, sizeof *elements);
683 values = xnmalloc (max_elems, sizeof *values);
684 for (i = 0; i < max_elems; i++)
686 values[i] = elements[i].data = i;
687 hmap_insert (&hmap, &elements[i].node, hash (elements[i].data));
688 check_hmap (&hmap, values, i + 1, hash);
690 if (hash == identity_hash)
692 /* Check that every every hash bucket has (almost) the
693 same number of nodes in it. */
698 for (j = 0; j < hmap_n_buckets (&hmap); j++)
701 struct hmap_node *node;
703 for (node = hmap.buckets[j]; node != NULL; node = node->next)
710 check (max - min <= 1);
713 for (i = 0; i < max_elems; i++)
715 hmap_delete (&hmap, &elements[i].node);
717 check_hmap (&hmap, values + i + 1, max_elems - i - 1, hash);
719 hmap_destroy (&hmap);
725 test_insert_ordered_random_hash (void)
727 test_insert_ordered (1024, random_hash);
731 test_insert_ordered_identity_hash (void)
733 test_insert_ordered (1024, identity_hash);
737 test_insert_ordered_constant_hash (void)
739 test_insert_ordered (128, constant_hash);
742 /* Inserts up to MAX_ELEMS elements into an HMAP, then moves the
743 nodes around in memory, using hash function HASH. */
745 test_moved (int max_elems, hash_function *hash)
747 struct element *e[2];
753 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
754 /* This tells the Autotest framework that the test was skipped. */
759 e[0] = xnmalloc (max_elems, sizeof *e[0]);
760 e[1] = xnmalloc (max_elems, sizeof *e[1]);
761 values = xnmalloc (max_elems, sizeof *values);
763 for (i = 0; i < max_elems; i++)
765 values[i] = e[cur][i].data = i;
766 hmap_insert (&hmap, &e[cur][i].node, hash (e[cur][i].data));
767 check_hmap (&hmap, values, i + 1, hash);
769 for (j = 0; j <= i; j++)
771 e[!cur][j] = e[cur][j];
772 hmap_moved (&hmap, &e[!cur][j].node, &e[cur][j].node);
773 check_hmap (&hmap, values, i + 1, hash);
777 hmap_destroy (&hmap);
784 test_moved_random_hash (void)
786 test_moved (128, random_hash);
790 test_moved_identity_hash (void)
792 test_moved (128, identity_hash);
796 test_moved_constant_hash (void)
798 test_moved (32, constant_hash);
801 /* Inserts values into an HMAP, then changes their values, using
802 hash function HASH. */
804 test_changed (hash_function *hash)
806 const int max_elems = 6;
809 for (cnt = 0; cnt <= max_elems; cnt++)
811 int *values, *changed_values;
812 struct element *elements;
813 unsigned int permutation_cnt;
816 values = xnmalloc (cnt, sizeof *values);
817 changed_values = xnmalloc (cnt, sizeof *changed_values);
818 elements = xnmalloc (cnt, sizeof *elements);
819 for (i = 0; i < cnt; i++)
822 for (permutation_cnt = 0;
823 permutation_cnt == 0 || next_permutation (values, cnt);
826 for (i = 0; i < cnt; i++)
829 for (j = 0; j <= cnt; j++)
835 /* Add to HMAP in order. */
836 for (k = 0; k < cnt; k++)
839 elements[n].data = n;
840 hmap_insert (&hmap, &elements[n].node,
841 hash (elements[n].data));
843 check_hmap (&hmap, values, cnt, hash);
845 /* Change value i to j. */
846 elements[i].data = j;
847 hmap_changed (&hmap, &elements[i].node,
848 hash (elements[i].data));
849 for (k = 0; k < cnt; k++)
850 changed_values[k] = k;
851 changed_values[i] = j;
852 check_hmap (&hmap, changed_values, cnt, hash);
854 hmap_destroy (&hmap);
858 check (permutation_cnt == factorial (cnt));
861 free (changed_values);
867 test_changed_random_hash (void)
869 test_changed (random_hash);
873 test_changed_identity_hash (void)
875 test_changed (identity_hash);
879 test_changed_constant_hash (void)
881 test_changed (constant_hash);
885 test_swap (int max_elems, hash_function *hash)
887 struct element *elements;
890 struct hmap *working, *empty;
893 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
894 /* This tells the Autotest framework that the test was skipped. */
902 elements = xnmalloc (max_elems, sizeof *elements);
903 values = xnmalloc (max_elems, sizeof *values);
904 for (i = 0; i < max_elems; i++)
907 values[i] = elements[i].data = i;
908 hmap_insert (working, &elements[i].node, hash (elements[i].data));
909 check_hmap (working, values, i + 1, hash);
910 check_hmap (empty, NULL, 0, hash);
923 test_swap_random_hash (void)
925 test_swap (128, random_hash);
928 /* Inserts elements into an hmap in ascending order, then clears the hash table
929 using hmap_clear(). */
933 const int max_elems = 128;
934 struct element *elements;
939 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
940 /* This tells the Autotest framework that the test was skipped. */
944 elements = xnmalloc (max_elems, sizeof *elements);
945 values = xnmalloc (max_elems, sizeof *values);
947 for (cnt = 0; cnt <= max_elems; cnt++)
952 for (i = 0; i < cnt; i++)
954 values[i] = elements[i].data = i;
955 hmap_insert (&hmap, &elements[i].node,
956 random_hash (elements[i].data));
957 check_hmap (&hmap, values, i + 1, random_hash);
960 check_hmap (&hmap, NULL, 0, random_hash);
961 hmap_destroy (&hmap);
969 test_destroy_null (void)
974 /* Test shrinking an empty hash table. */
976 test_shrink_empty (void)
981 hmap_reserve (&hmap, 123);
983 hmap_destroy (&hmap);
991 const char *description;
992 void (*function) (void);
995 static const struct test tests[] =
998 "insert-any-remove-any-random-hash",
999 "insert any order, delete any order (random hash)",
1000 test_insert_any_remove_any_random_hash
1003 "insert-any-remove-any-identity-hash",
1004 "insert any order, delete any order (identity hash)",
1005 test_insert_any_remove_any_identity_hash
1008 "insert-any-remove-any-constant-hash",
1009 "insert any order, delete any order (constant hash)",
1010 test_insert_any_remove_any_constant_hash
1014 "insert-any-remove-same-random-hash",
1015 "insert any order, delete same order (random hash)",
1016 test_insert_any_remove_same_random_hash
1019 "insert-any-remove-same-identity-hash",
1020 "insert any order, delete same order (identity hash)",
1021 test_insert_any_remove_same_identity_hash
1024 "insert-any-remove-same-constant-hash",
1025 "insert any order, delete same order (constant hash)",
1026 test_insert_any_remove_same_constant_hash
1030 "insert-any-remove-reverse-random-hash",
1031 "insert any order, delete reverse order (random hash)",
1032 test_insert_any_remove_reverse_random_hash
1035 "insert-any-remove-reverse-identity-hash",
1036 "insert any order, delete reverse order (identity hash)",
1037 test_insert_any_remove_reverse_identity_hash
1040 "insert-any-remove-reverse-constant-hash",
1041 "insert any order, delete reverse order (constant hash)",
1042 test_insert_any_remove_reverse_constant_hash
1046 "random-sequence-random-hash",
1047 "insert and delete in random sequence (random hash)",
1048 test_random_sequence_random_hash
1051 "random-sequence-identity-hash",
1052 "insert and delete in random sequence (identity hash)",
1053 test_random_sequence_identity_hash
1056 "random-sequence-constant-hash",
1057 "insert and delete in random sequence (constant hash)",
1058 test_random_sequence_constant_hash
1062 "insert-ordered-random-hash",
1063 "insert in ascending order (random hash)",
1064 test_insert_ordered_random_hash
1067 "insert-ordered-identity-hash",
1068 "insert in ascending order (identity hash)",
1069 test_insert_ordered_identity_hash
1072 "insert-ordered-constant-hash",
1073 "insert in ascending order (constant hash)",
1074 test_insert_ordered_constant_hash
1078 "moved-random-hash",
1079 "move elements around in memory (random hash)",
1080 test_moved_random_hash
1083 "moved-identity-hash",
1084 "move elements around in memory (identity hash)",
1085 test_moved_identity_hash
1088 "moved-constant-hash",
1089 "move elements around in memory (constant hash)",
1090 test_moved_constant_hash
1094 "changed-random-hash",
1095 "change key data in nodes (random hash)",
1096 test_changed_random_hash
1099 "changed-identity-hash",
1100 "change key data in nodes (identity hash)",
1101 test_changed_identity_hash
1104 "changed-constant-hash",
1105 "change key data in nodes (constant hash)",
1106 test_changed_constant_hash
1111 "test swapping tables",
1112 test_swap_random_hash
1116 "test clearing hash table",
1121 "test destroying null table",
1126 "test shrinking an empty table",
1131 enum { N_TESTS = sizeof tests / sizeof *tests };
1134 main (int argc, char *argv[])
1140 fprintf (stderr, "exactly one argument required; use --help for help\n");
1141 return EXIT_FAILURE;
1143 else if (!strcmp (argv[1], "--help"))
1145 printf ("%s: test hash map\n"
1146 "usage: %s TEST-NAME\n"
1147 "where TEST-NAME is one of the following:\n",
1149 for (i = 0; i < N_TESTS; i++)
1150 printf (" %s\n %s\n", tests[i].name, tests[i].description);
1155 for (i = 0; i < N_TESTS; i++)
1156 if (!strcmp (argv[1], tests[i].name))
1158 tests[i].function ();
1162 fprintf (stderr, "unknown test %s; use --help for help\n", argv[1]);
1163 return EXIT_FAILURE;