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>
99 #include <libpspp/compiler.h>
101 /* Exit with a failure code.
102 (Place a breakpoint on this function while debugging.) */
109 /* If OK is not true, prints a message about failure on the
110 current source file and the given LINE and terminates. */
112 check_func (bool ok, int line)
116 fprintf (stderr, "%s:%d: check failed\n", __FILE__, line);
121 /* Verifies that EXPR evaluates to true.
122 If not, prints a message citing the calling line number and
124 #define check(EXPR) check_func ((EXPR), __LINE__)
126 /* Prints a message about memory exhaustion and exits with a
131 printf ("virtual memory exhausted\n");
135 static void *xmalloc (size_t n) MALLOC_LIKE;
136 static void *xnmalloc (size_t n, size_t m) MALLOC_LIKE;
137 static void *xmemdup (const void *p, size_t n) MALLOC_LIKE;
139 /* Allocates and returns N bytes of memory. */
145 void *p = malloc (n);
156 xmemdup (const void *p, size_t n)
158 void *q = xmalloc (n);
163 /* Allocates and returns N * M bytes of memory. */
165 xnmalloc (size_t n, size_t m)
167 if ((size_t) -1 / m <= n)
169 return xmalloc (n * m);
172 /* Node type and support routines. */
174 /* Test data element. */
177 struct hmap_node node; /* Embedded hash table element. */
178 int data; /* Primary value. */
181 /* Returns the `struct element' that NODE is embedded within. */
182 static struct element *
183 hmap_node_to_element (const struct hmap_node *node)
185 return HMAP_DATA (node, struct element, node);
188 /* Compares A and B and returns a strcmp-type return value. */
190 compare_ints (const void *a_, const void *b_)
195 return *a < *b ? -1 : *a > *b;
198 /* Swaps *A and *B. */
200 swap (int *a, int *b)
207 /* Reverses the order of the N integers starting at VALUES. */
209 reverse (int *values, size_t n)
215 swap (&values[i++], &values[--j]);
218 /* Arranges the N elements in VALUES into the lexicographically
219 next greater permutation. Returns true if successful.
220 If VALUES is already the lexicographically greatest
221 permutation of its elements (i.e. ordered from greatest to
222 smallest), arranges them into the lexicographically least
223 permutation (i.e. ordered from smallest to largest) and
226 next_permutation (int *values, size_t n)
234 if (values[i] < values[i + 1])
237 for (j = n - 1; values[i] >= values[j]; j--)
239 swap (values + i, values + j);
240 reverse (values + (i + 1), n - (i + 1));
253 factorial (unsigned int n)
255 unsigned int value = 1;
261 /* Randomly shuffles the N elements in ARRAY, each of which is
262 SIZE bytes in size. */
264 random_shuffle (void *array_, size_t n, size_t size)
266 char *array = array_;
267 char *tmp = xmalloc (size);
270 for (i = 0; i < n; i++)
272 size_t j = rand () % (n - i) + i;
275 memcpy (tmp, array + j * size, size);
276 memcpy (array + j * size, array + i * size, size);
277 memcpy (array + i * size, tmp, size);
284 typedef size_t hash_function (int data);
287 identity_hash (int data)
293 constant_hash (int data UNUSED)
298 static inline uint32_t
299 md4_round (uint32_t a, uint32_t b, uint32_t c, uint32_t d,
300 uint32_t data, uint32_t n)
302 uint32_t x = a + (d ^ (b & (c ^ d))) + data;
303 return (x << n) | (x >> (32 - n));
307 random_hash (int data)
313 a = md4_round (a, b, c, d, 0, 3);
314 d = md4_round (d, a, b, c, 1, 7);
315 c = md4_round (c, d, a, b, 2, 11);
316 b = md4_round (b, c, d, a, 3, 19);
317 return a ^ b ^ c ^ d;
320 static struct hmap_node *
321 find_element (struct hmap *hmap, int data, hash_function *hash)
324 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (data), hmap)
330 /* Checks that HMAP contains the N ints in DATA, that its
331 structure is correct, and that certain operations on HMAP
332 produce the expected results. */
334 check_hmap (struct hmap *hmap, const int data[], size_t n,
340 check (hmap_is_empty (hmap) == (n == 0));
341 check (hmap_count (hmap) == n);
342 check (n <= hmap_capacity (hmap));
344 order = xmemdup (data, n * sizeof *data);
345 qsort (order, n, sizeof *order, compare_ints);
347 for (i = 0; i < n; i = j)
352 for (j = i + 1; j < n; j++)
353 if (order[i] != order[j])
357 HMAP_FOR_EACH_WITH_HASH (e, struct element, node, hash (order[i]), hmap)
358 if (e->data == order[i])
361 check (count == j - i);
364 check (find_element (hmap, -1, hash) == NULL);
367 check (hmap_first (hmap) == NULL);
374 for (p = hmap_first (hmap), i = 0; i < n; p = hmap_next (hmap, p), i++)
376 struct element *e = hmap_node_to_element (p);
378 check (hmap_node_hash (&e->node) == hash (e->data));
379 for (j = 0; j < left; j++)
380 if (order[j] == e->data)
382 order[j] = order[--left];
395 /* Inserts the N values from 0 to N - 1 (inclusive) into an
396 HMAP in the order specified by INSERTIONS, then deletes them in
397 the order specified by DELETIONS, checking the HMAP's contents
398 for correctness after each operation. Uses HASH as the hash
401 test_insert_delete (const int insertions[],
402 const int deletions[],
406 struct element *elements;
410 elements = xnmalloc (n, sizeof *elements);
411 for (i = 0; i < n; i++)
412 elements[i].data = i;
415 hmap_reserve (&hmap, 1);
416 check_hmap (&hmap, NULL, 0, hash);
417 for (i = 0; i < n; i++)
420 hmap_insert (&hmap, &elements[insertions[i]].node, hash (insertions[i]));
421 check_hmap (&hmap, insertions, i + 1, hash);
423 /* A series of insertions should not produce a shrinkable hmap. */
424 capacity = hmap_capacity (&hmap);
426 check (capacity == hmap_capacity (&hmap));
428 for (i = 0; i < n; i++)
430 hmap_delete (&hmap, &elements[deletions[i]].node);
431 check_hmap (&hmap, deletions + i + 1, n - i - 1, hash);
433 hmap_destroy (&hmap);
438 /* Inserts values into an HMAP in each possible order, then
439 removes them in each possible order, up to a specified maximum
440 size, using hash function HASH. */
442 test_insert_any_remove_any (hash_function *hash)
444 const int max_elems = 5;
447 for (n = 0; n <= max_elems; n++)
449 int *insertions, *deletions;
450 unsigned int ins_n_perms;
453 insertions = xnmalloc (n, sizeof *insertions);
454 deletions = xnmalloc (n, sizeof *deletions);
455 for (i = 0; i < n; i++)
458 for (ins_n_perms = 0;
459 ins_n_perms == 0 || next_permutation (insertions, n);
462 unsigned int del_n_perms;
465 for (i = 0; i < n; i++)
468 for (del_n_perms = 0;
469 del_n_perms == 0 || next_permutation (deletions, n);
471 test_insert_delete (insertions, deletions, n, hash);
473 check (del_n_perms == factorial (n));
475 check (ins_n_perms == factorial (n));
483 test_insert_any_remove_any_random_hash (void)
485 test_insert_any_remove_any (random_hash);
489 test_insert_any_remove_any_identity_hash (void)
491 test_insert_any_remove_any (identity_hash);
495 test_insert_any_remove_any_constant_hash (void)
497 test_insert_any_remove_any (constant_hash);
500 /* Inserts values into an HMAP in each possible order, then
501 removes them in the same order, up to a specified maximum
502 size, using hash function HASH. */
504 test_insert_any_remove_same (hash_function *hash)
506 const int max_elems = 7;
509 for (n = 0; n <= max_elems; n++)
512 unsigned int n_permutations;
515 values = xnmalloc (n, sizeof *values);
516 for (i = 0; i < n; i++)
519 for (n_permutations = 0;
520 n_permutations == 0 || next_permutation (values, n);
522 test_insert_delete (values, values, n, hash);
523 check (n_permutations == factorial (n));
530 test_insert_any_remove_same_random_hash (void)
532 test_insert_any_remove_same (random_hash);
536 test_insert_any_remove_same_identity_hash (void)
538 test_insert_any_remove_same (identity_hash);
542 test_insert_any_remove_same_constant_hash (void)
544 test_insert_any_remove_same (constant_hash);
547 /* Inserts values into an HMAP in each possible order, then
548 removes them in reverse order, up to a specified maximum
549 size, using hash function HASH. */
551 test_insert_any_remove_reverse (hash_function *hash)
553 const int max_elems = 7;
556 for (n = 0; n <= max_elems; n++)
558 int *insertions, *deletions;
559 unsigned int n_permutations;
562 insertions = xnmalloc (n, sizeof *insertions);
563 deletions = xnmalloc (n, sizeof *deletions);
564 for (i = 0; i < n; i++)
567 for (n_permutations = 0;
568 n_permutations == 0 || next_permutation (insertions, n);
571 memcpy (deletions, insertions, sizeof *insertions * n);
572 reverse (deletions, n);
574 test_insert_delete (insertions, deletions, n, hash);
576 check (n_permutations == factorial (n));
584 test_insert_any_remove_reverse_random_hash (void)
586 test_insert_any_remove_reverse (random_hash);
590 test_insert_any_remove_reverse_identity_hash (void)
592 test_insert_any_remove_reverse (identity_hash);
596 test_insert_any_remove_reverse_constant_hash (void)
598 test_insert_any_remove_reverse (constant_hash);
601 /* Inserts and removes up to MAX_ELEMS values in an hmap, in
602 random order, using hash function HASH. */
604 test_random_sequence (int max_elems, hash_function *hash)
606 const int max_trials = 8;
609 for (n = 0; n <= max_elems; n += 2)
611 int *insertions, *deletions;
615 insertions = xnmalloc (n, sizeof *insertions);
616 deletions = xnmalloc (n, sizeof *deletions);
617 for (i = 0; i < n; i++)
619 for (i = 0; i < n; i++)
622 for (trial = 0; trial < max_trials; trial++)
624 random_shuffle (insertions, n, sizeof *insertions);
625 random_shuffle (deletions, n, sizeof *deletions);
627 test_insert_delete (insertions, deletions, n, hash);
636 test_random_sequence_random_hash (void)
638 test_random_sequence (64, random_hash);
642 test_random_sequence_identity_hash (void)
644 test_random_sequence (64, identity_hash);
648 test_random_sequence_constant_hash (void)
650 test_random_sequence (32, constant_hash);
653 /* Inserts MAX_ELEMS elements into an HMAP in ascending order,
654 then delete in ascending order and shrink the hmap at each
655 step, using hash function HASH. */
657 test_insert_ordered (int max_elems, hash_function *hash)
659 struct element *elements;
664 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
665 /* This tells the Autotest framework that the test was skipped. */
670 elements = xnmalloc (max_elems, sizeof *elements);
671 values = xnmalloc (max_elems, sizeof *values);
672 for (i = 0; i < max_elems; i++)
674 values[i] = elements[i].data = i;
675 hmap_insert (&hmap, &elements[i].node, hash (elements[i].data));
676 check_hmap (&hmap, values, i + 1, hash);
678 if (hash == identity_hash)
680 /* Check that every every hash bucket has (almost) the
681 same number of nodes in it. */
686 for (j = 0; j <= hmap.mask; j++)
689 struct hmap_node *node;
691 for (node = hmap.buckets[j]; node != NULL; node = node->next)
698 check (max - min <= 1);
701 for (i = 0; i < max_elems; i++)
703 hmap_delete (&hmap, &elements[i].node);
705 check_hmap (&hmap, values + i + 1, max_elems - i - 1, hash);
707 hmap_destroy (&hmap);
713 test_insert_ordered_random_hash (void)
715 test_insert_ordered (1024, random_hash);
719 test_insert_ordered_identity_hash (void)
721 test_insert_ordered (1024, identity_hash);
725 test_insert_ordered_constant_hash (void)
727 test_insert_ordered (128, constant_hash);
730 /* Inserts up to MAX_ELEMS elements into an HMAP, then moves the
731 nodes around in memory, using hash function HASH. */
733 test_moved (int max_elems, hash_function *hash)
735 struct element *e[2];
741 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
742 /* This tells the Autotest framework that the test was skipped. */
747 e[0] = xnmalloc (max_elems, sizeof *e[0]);
748 e[1] = xnmalloc (max_elems, sizeof *e[1]);
749 values = xnmalloc (max_elems, sizeof *values);
751 for (i = 0; i < max_elems; i++)
753 values[i] = e[cur][i].data = i;
754 hmap_insert (&hmap, &e[cur][i].node, hash (e[cur][i].data));
755 check_hmap (&hmap, values, i + 1, hash);
757 for (j = 0; j <= i; j++)
759 e[!cur][j] = e[cur][j];
760 hmap_moved (&hmap, &e[!cur][j].node, &e[cur][j].node);
761 check_hmap (&hmap, values, i + 1, hash);
765 hmap_destroy (&hmap);
772 test_moved_random_hash (void)
774 test_moved (128, random_hash);
778 test_moved_identity_hash (void)
780 test_moved (128, identity_hash);
784 test_moved_constant_hash (void)
786 test_moved (32, constant_hash);
789 /* Inserts values into an HMAP, then changes their values, using
790 hash function HASH. */
792 test_changed (hash_function *hash)
794 const int max_elems = 6;
797 for (n = 0; n <= max_elems; n++)
799 int *values, *changed_values;
800 struct element *elements;
801 unsigned int n_permutations;
804 values = xnmalloc (n, sizeof *values);
805 changed_values = xnmalloc (n, sizeof *changed_values);
806 elements = xnmalloc (n, sizeof *elements);
807 for (i = 0; i < n; i++)
810 for (n_permutations = 0;
811 n_permutations == 0 || next_permutation (values, n);
814 for (i = 0; i < n; i++)
817 for (j = 0; j <= n; j++)
823 /* Add to HMAP in order. */
824 for (k = 0; k < n; k++)
827 elements[n].data = n;
828 hmap_insert (&hmap, &elements[n].node,
829 hash (elements[n].data));
831 check_hmap (&hmap, values, n, hash);
833 /* Change value i to j. */
834 elements[i].data = j;
835 hmap_changed (&hmap, &elements[i].node,
836 hash (elements[i].data));
837 for (k = 0; k < n; k++)
838 changed_values[k] = k;
839 changed_values[i] = j;
840 check_hmap (&hmap, changed_values, n, hash);
842 hmap_destroy (&hmap);
846 check (n_permutations == factorial (n));
849 free (changed_values);
855 test_changed_random_hash (void)
857 test_changed (random_hash);
861 test_changed_identity_hash (void)
863 test_changed (identity_hash);
867 test_changed_constant_hash (void)
869 test_changed (constant_hash);
873 test_swap (int max_elems, hash_function *hash)
875 struct element *elements;
878 struct hmap *working, *empty;
881 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
882 /* This tells the Autotest framework that the test was skipped. */
890 elements = xnmalloc (max_elems, sizeof *elements);
891 values = xnmalloc (max_elems, sizeof *values);
892 for (i = 0; i < max_elems; i++)
895 values[i] = elements[i].data = i;
896 hmap_insert (working, &elements[i].node, hash (elements[i].data));
897 check_hmap (working, values, i + 1, hash);
898 check_hmap (empty, NULL, 0, hash);
911 test_swap_random_hash (void)
913 test_swap (128, random_hash);
916 /* Inserts elements into an hmap in ascending order, then clears the hash table
917 using hmap_clear(). */
921 const int max_elems = 128;
922 struct element *elements;
927 #if __GNUC__ == 4 && __GNUC_MINOR__ == 3
928 /* This tells the Autotest framework that the test was skipped. */
932 elements = xnmalloc (max_elems, sizeof *elements);
933 values = xnmalloc (max_elems, sizeof *values);
935 for (n = 0; n <= max_elems; n++)
940 for (i = 0; i < n; i++)
942 values[i] = elements[i].data = i;
943 hmap_insert (&hmap, &elements[i].node,
944 random_hash (elements[i].data));
945 check_hmap (&hmap, values, i + 1, random_hash);
948 check_hmap (&hmap, NULL, 0, random_hash);
949 hmap_destroy (&hmap);
957 test_destroy_null (void)
962 /* Test shrinking an empty hash table. */
964 test_shrink_empty (void)
969 hmap_reserve (&hmap, 123);
971 hmap_destroy (&hmap);
979 const char *description;
980 void (*function) (void);
983 static const struct test tests[] =
986 "insert-any-remove-any-random-hash",
987 "insert any order, delete any order (random hash)",
988 test_insert_any_remove_any_random_hash
991 "insert-any-remove-any-identity-hash",
992 "insert any order, delete any order (identity hash)",
993 test_insert_any_remove_any_identity_hash
996 "insert-any-remove-any-constant-hash",
997 "insert any order, delete any order (constant hash)",
998 test_insert_any_remove_any_constant_hash
1002 "insert-any-remove-same-random-hash",
1003 "insert any order, delete same order (random hash)",
1004 test_insert_any_remove_same_random_hash
1007 "insert-any-remove-same-identity-hash",
1008 "insert any order, delete same order (identity hash)",
1009 test_insert_any_remove_same_identity_hash
1012 "insert-any-remove-same-constant-hash",
1013 "insert any order, delete same order (constant hash)",
1014 test_insert_any_remove_same_constant_hash
1018 "insert-any-remove-reverse-random-hash",
1019 "insert any order, delete reverse order (random hash)",
1020 test_insert_any_remove_reverse_random_hash
1023 "insert-any-remove-reverse-identity-hash",
1024 "insert any order, delete reverse order (identity hash)",
1025 test_insert_any_remove_reverse_identity_hash
1028 "insert-any-remove-reverse-constant-hash",
1029 "insert any order, delete reverse order (constant hash)",
1030 test_insert_any_remove_reverse_constant_hash
1034 "random-sequence-random-hash",
1035 "insert and delete in random sequence (random hash)",
1036 test_random_sequence_random_hash
1039 "random-sequence-identity-hash",
1040 "insert and delete in random sequence (identity hash)",
1041 test_random_sequence_identity_hash
1044 "random-sequence-constant-hash",
1045 "insert and delete in random sequence (constant hash)",
1046 test_random_sequence_constant_hash
1050 "insert-ordered-random-hash",
1051 "insert in ascending order (random hash)",
1052 test_insert_ordered_random_hash
1055 "insert-ordered-identity-hash",
1056 "insert in ascending order (identity hash)",
1057 test_insert_ordered_identity_hash
1060 "insert-ordered-constant-hash",
1061 "insert in ascending order (constant hash)",
1062 test_insert_ordered_constant_hash
1066 "moved-random-hash",
1067 "move elements around in memory (random hash)",
1068 test_moved_random_hash
1071 "moved-identity-hash",
1072 "move elements around in memory (identity hash)",
1073 test_moved_identity_hash
1076 "moved-constant-hash",
1077 "move elements around in memory (constant hash)",
1078 test_moved_constant_hash
1082 "changed-random-hash",
1083 "change key data in nodes (random hash)",
1084 test_changed_random_hash
1087 "changed-identity-hash",
1088 "change key data in nodes (identity hash)",
1089 test_changed_identity_hash
1092 "changed-constant-hash",
1093 "change key data in nodes (constant hash)",
1094 test_changed_constant_hash
1099 "test swapping tables",
1100 test_swap_random_hash
1104 "test clearing hash table",
1109 "test destroying null table",
1114 "test shrinking an empty table",
1119 enum { N_TESTS = sizeof tests / sizeof *tests };
1122 main (int argc, char *argv[])
1128 fprintf (stderr, "exactly one argument required; use --help for help\n");
1129 return EXIT_FAILURE;
1131 else if (!strcmp (argv[1], "--help"))
1133 printf ("%s: test hash map\n"
1134 "usage: %s TEST-NAME\n"
1135 "where TEST-NAME is one of the following:\n",
1137 for (i = 0; i < N_TESTS; i++)
1138 printf (" %s\n %s\n", tests[i].name, tests[i].description);
1143 for (i = 0; i < N_TESTS; i++)
1144 if (!strcmp (argv[1], tests[i].name))
1146 tests[i].function ();
1150 fprintf (stderr, "unknown test %s; use --help for help\n", argv[1]);
1151 return EXIT_FAILURE;