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 hmapx_* routines defined in
18 hmapx.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 hmapx.c (when compiled with -DNDEBUG).
21 "valgrind --leak-check=yes --show-reachable=yes" should give a
28 #include <libpspp/hmapx.h>
39 #include <libpspp/compiler.h>
41 /* If OK is not true, prints a message about failure on the
42 current source file and the given LINE and terminates. */
44 check_func (bool ok, int line)
48 fprintf (stderr, "%s:%d: check failed\n", __FILE__, line);
53 /* Verifies that EXPR evaluates to true.
54 If not, prints a message citing the calling line number and
56 #define check(EXPR) check_func ((EXPR), __LINE__)
58 /* Prints a message about memory exhaustion and exits with a
63 printf ("virtual memory exhausted\n");
67 /* Allocates and returns N bytes of memory. */
84 xmemdup (const void *p, size_t n)
86 void *q = xmalloc (n);
91 /* Allocates and returns N * M bytes of memory. */
93 xnmalloc (size_t n, size_t m)
95 if ((size_t) -1 / m <= n)
97 return xmalloc (n * m);
100 /* Node type and support routines. */
102 /* Test data element. */
105 int data; /* Primary value. */
108 /* Compares A and B and returns a strcmp-type return value. */
110 compare_ints (const void *a_, const void *b_)
115 return *a < *b ? -1 : *a > *b;
118 /* Swaps *A and *B. */
120 swap (int *a, int *b)
127 /* Reverses the order of the CNT integers starting at VALUES. */
129 reverse (int *values, size_t cnt)
135 swap (&values[i++], &values[--j]);
138 /* Arranges the CNT elements in VALUES into the lexicographically
139 next greater permutation. Returns true if successful.
140 If VALUES is already the lexicographically greatest
141 permutation of its elements (i.e. ordered from greatest to
142 smallest), arranges them into the lexicographically least
143 permutation (i.e. ordered from smallest to largest) and
146 next_permutation (int *values, size_t cnt)
154 if (values[i] < values[i + 1])
157 for (j = cnt - 1; values[i] >= values[j]; j--)
159 swap (values + i, values + j);
160 reverse (values + (i + 1), cnt - (i + 1));
165 reverse (values, cnt);
173 factorial (unsigned int n)
175 unsigned int value = 1;
181 /* Randomly shuffles the CNT elements in ARRAY, each of which is
182 SIZE bytes in size. */
184 random_shuffle (void *array_, size_t cnt, size_t size)
186 char *array = array_;
187 char *tmp = xmalloc (size);
190 for (i = 0; i < cnt; i++)
192 size_t j = rand () % (cnt - i) + i;
195 memcpy (tmp, array + j * size, size);
196 memcpy (array + j * size, array + i * size, size);
197 memcpy (array + i * size, tmp, size);
204 typedef size_t hash_function (int data);
207 identity_hash (int data)
213 constant_hash (int data UNUSED)
218 static inline uint32_t
219 md4_round (uint32_t a, uint32_t b, uint32_t c, uint32_t d,
220 uint32_t data, uint32_t n)
222 uint32_t x = a + (d ^ (b & (c ^ d))) + data;
223 return (x << n) | (x >> (32 - n));
227 random_hash (int data)
233 a = md4_round (a, b, c, d, 0, 3);
234 d = md4_round (d, a, b, c, 1, 7);
235 c = md4_round (c, d, a, b, 2, 11);
236 b = md4_round (b, c, d, a, 3, 19);
237 return a ^ b ^ c ^ d;
240 static struct hmapx_node *
241 find_element (struct hmapx *hmapx, int data, hash_function *hash)
243 struct hmapx_node *node;
245 HMAPX_FOR_EACH_WITH_HASH (e, node, hash (data), hmapx)
251 /* Checks that HMAPX contains the CNT ints in DATA, that its
252 structure is correct, and that certain operations on HMAPX
253 produce the expected results. */
255 check_hmapx (struct hmapx *hmapx, const int data[], size_t cnt,
261 check (hmapx_is_empty (hmapx) == (cnt == 0));
262 check (hmapx_count (hmapx) == cnt);
263 check (cnt <= hmapx_capacity (hmapx));
265 order = xmemdup (data, cnt * sizeof *data);
266 qsort (order, cnt, sizeof *order, compare_ints);
268 for (i = 0; i < cnt; i = j)
270 struct hmapx_node *node;
274 for (j = i + 1; j < cnt; j++)
275 if (order[i] != order[j])
279 HMAPX_FOR_EACH_WITH_HASH (e, node, hash (order[i]), hmapx)
280 if (e->data == order[i])
283 check (count == j - i);
286 check (find_element (hmapx, -1, hash) == NULL);
289 check (hmapx_first (hmapx) == NULL);
292 struct hmapx_node *p;
296 for (p = hmapx_first (hmapx), i = 0; i < cnt;
297 p = hmapx_next (hmapx, p), i++)
299 struct element *e = hmapx_node_data (p);
302 check (hmapx_node_hash (p) == hash (e->data));
303 for (j = 0; j < left; j++)
304 if (order[j] == e->data)
306 order[j] = order[--left];
319 /* Inserts the CNT values from 0 to CNT - 1 (inclusive) into an
320 HMAPX in the order specified by INSERTIONS, then deletes them in
321 the order specified by DELETIONS, checking the HMAPX's contents
322 for correctness after each operation. Uses HASH as the hash
325 test_insert_delete (const int insertions[],
326 const int deletions[],
331 struct element *elements;
332 struct hmapx_node **nodes;
336 elements = xnmalloc (cnt, sizeof *elements);
337 nodes = xnmalloc (cnt, sizeof *nodes);
338 for (i = 0; i < cnt; i++)
339 elements[i].data = i;
342 hmapx_reserve (&hmapx, reserve);
343 check_hmapx (&hmapx, NULL, 0, hash);
344 for (i = 0; i < cnt; i++)
346 struct hmapx_node *(*insert) (struct hmapx *, void *, size_t hash);
349 /* Insert the node. Use hmapx_insert_fast if we have not
350 yet exceeded the reserve. */
351 insert = i < reserve ? hmapx_insert_fast : hmapx_insert;
352 nodes[insertions[i]] = insert (&hmapx, &elements[insertions[i]],
353 hash (insertions[i]));
354 check_hmapx (&hmapx, insertions, i + 1, hash);
356 /* A series of insertions should not produce a shrinkable hmapx. */
359 capacity = hmapx_capacity (&hmapx);
360 hmapx_shrink (&hmapx);
361 check (capacity == hmapx_capacity (&hmapx));
364 for (i = 0; i < cnt; i++)
366 hmapx_delete (&hmapx, nodes[deletions[i]]);
367 check_hmapx (&hmapx, deletions + i + 1, cnt - i - 1, hash);
369 hmapx_destroy (&hmapx);
375 /* Inserts values into an HMAPX in each possible order, then
376 removes them in each possible order, up to a specified maximum
377 size, using hash function HASH. */
379 test_insert_any_remove_any (hash_function *hash)
381 const int max_elems = 5;
384 for (cnt = 0; cnt <= max_elems; cnt++)
386 int *insertions, *deletions;
387 unsigned int ins_perm_cnt;
390 insertions = xnmalloc (cnt, sizeof *insertions);
391 deletions = xnmalloc (cnt, sizeof *deletions);
392 for (i = 0; i < cnt; i++)
395 for (ins_perm_cnt = 0;
396 ins_perm_cnt == 0 || next_permutation (insertions, cnt);
399 unsigned int del_perm_cnt;
402 for (i = 0; i < cnt; i++)
405 for (del_perm_cnt = 0;
406 del_perm_cnt == 0 || next_permutation (deletions, cnt);
408 test_insert_delete (insertions, deletions, cnt, hash, 1);
410 check (del_perm_cnt == factorial (cnt));
412 check (ins_perm_cnt == factorial (cnt));
420 test_insert_any_remove_any_random_hash (void)
422 test_insert_any_remove_any (random_hash);
426 test_insert_any_remove_any_identity_hash (void)
428 test_insert_any_remove_any (identity_hash);
432 test_insert_any_remove_any_constant_hash (void)
434 test_insert_any_remove_any (constant_hash);
437 /* Inserts values into an HMAPX in each possible order, then
438 removes them in the same order, up to a specified maximum
439 size, using hash function HASH. */
441 test_insert_any_remove_same (hash_function *hash)
443 const int max_elems = 7;
446 for (cnt = 0; cnt <= max_elems; cnt++)
449 unsigned int permutation_cnt;
452 values = xnmalloc (cnt, sizeof *values);
453 for (i = 0; i < cnt; i++)
456 for (permutation_cnt = 0;
457 permutation_cnt == 0 || next_permutation (values, cnt);
459 test_insert_delete (values, values, cnt, hash, cnt / 2);
460 check (permutation_cnt == factorial (cnt));
467 test_insert_any_remove_same_random_hash (void)
469 test_insert_any_remove_same (random_hash);
473 test_insert_any_remove_same_identity_hash (void)
475 test_insert_any_remove_same (identity_hash);
479 test_insert_any_remove_same_constant_hash (void)
481 test_insert_any_remove_same (constant_hash);
484 /* Inserts values into an HMAPX in each possible order, then
485 removes them in reverse order, up to a specified maximum
486 size, using hash function HASH. */
488 test_insert_any_remove_reverse (hash_function *hash)
490 const int max_elems = 7;
493 for (cnt = 0; cnt <= max_elems; cnt++)
495 int *insertions, *deletions;
496 unsigned int permutation_cnt;
499 insertions = xnmalloc (cnt, sizeof *insertions);
500 deletions = xnmalloc (cnt, sizeof *deletions);
501 for (i = 0; i < cnt; i++)
504 for (permutation_cnt = 0;
505 permutation_cnt == 0 || next_permutation (insertions, cnt);
508 memcpy (deletions, insertions, sizeof *insertions * cnt);
509 reverse (deletions, cnt);
511 test_insert_delete (insertions, deletions, cnt, hash, cnt);
513 check (permutation_cnt == factorial (cnt));
521 test_insert_any_remove_reverse_random_hash (void)
523 test_insert_any_remove_reverse (random_hash);
527 test_insert_any_remove_reverse_identity_hash (void)
529 test_insert_any_remove_reverse (identity_hash);
533 test_insert_any_remove_reverse_constant_hash (void)
535 test_insert_any_remove_reverse (constant_hash);
538 /* Inserts and removes up to MAX_ELEMS values in an hmapx, in
539 random order, using hash function HASH. */
541 test_random_sequence (int max_elems, hash_function *hash)
543 const int max_trials = 8;
546 for (cnt = 0; cnt <= max_elems; cnt += 2)
548 int *insertions, *deletions;
552 insertions = xnmalloc (cnt, sizeof *insertions);
553 deletions = xnmalloc (cnt, sizeof *deletions);
554 for (i = 0; i < cnt; i++)
556 for (i = 0; i < cnt; i++)
559 for (trial = 0; trial < max_trials; trial++)
561 random_shuffle (insertions, cnt, sizeof *insertions);
562 random_shuffle (deletions, cnt, sizeof *deletions);
564 test_insert_delete (insertions, deletions, cnt, hash, 0);
573 test_random_sequence_random_hash (void)
575 test_random_sequence (64, random_hash);
579 test_random_sequence_identity_hash (void)
581 test_random_sequence (64, identity_hash);
585 test_random_sequence_constant_hash (void)
587 test_random_sequence (32, constant_hash);
590 /* Inserts MAX_ELEMS elements into an HMAPX in ascending order,
591 then delete in ascending order and shrink the hmapx at each
592 step, using hash function HASH. */
594 test_insert_ordered (int max_elems, hash_function *hash)
596 struct element *elements;
597 struct hmapx_node **nodes;
603 elements = xnmalloc (max_elems, sizeof *elements);
604 nodes = xnmalloc (max_elems, sizeof *nodes);
605 values = xnmalloc (max_elems, sizeof *values);
606 for (i = 0; i < max_elems; i++)
608 values[i] = elements[i].data = i;
609 nodes[i] = hmapx_insert (&hmapx, &elements[i], hash (elements[i].data));
610 check_hmapx (&hmapx, values, i + 1, hash);
612 if (hash == identity_hash)
614 /* Check that every every hash bucket has (almost) the
615 same number of nodes in it. */
620 for (j = 0; j <= hmapx.hmap.mask; j++)
623 struct hmap_node *node;
625 for (node = hmapx.hmap.buckets[j]; node != NULL;
633 check (max - min <= 1);
636 for (i = 0; i < max_elems; i++)
638 hmapx_delete (&hmapx, nodes[i]);
639 hmapx_shrink (&hmapx);
640 check_hmapx (&hmapx, values + i + 1, max_elems - i - 1, hash);
642 hmapx_destroy (&hmapx);
649 test_insert_ordered_random_hash (void)
651 test_insert_ordered (1024, random_hash);
655 test_insert_ordered_identity_hash (void)
657 test_insert_ordered (1024, identity_hash);
661 test_insert_ordered_constant_hash (void)
663 test_insert_ordered (128, constant_hash);
666 /* Inserts up to MAX_ELEMS elements into an HMAPX, then moves the
667 nodes around in memory, using hash function HASH. */
669 test_moved (int max_elems, hash_function *hash)
671 struct element *e[2];
674 struct hmapx_node **nodes;
679 e[0] = xnmalloc (max_elems, sizeof *e[0]);
680 e[1] = xnmalloc (max_elems, sizeof *e[1]);
681 values = xnmalloc (max_elems, sizeof *values);
682 nodes = xnmalloc (max_elems, sizeof *nodes);
684 for (i = 0; i < max_elems; i++)
686 values[i] = e[cur][i].data = i;
687 nodes[i] = hmapx_insert (&hmapx, &e[cur][i], hash (e[cur][i].data));
688 check_hmapx (&hmapx, values, i + 1, hash);
690 for (j = 0; j <= i; j++)
692 e[!cur][j] = e[cur][j];
693 hmapx_move (nodes[j], &e[cur][j]);
694 check_hmapx (&hmapx, values, i + 1, hash);
698 hmapx_destroy (&hmapx);
706 test_moved_random_hash (void)
708 test_moved (128, random_hash);
712 test_moved_identity_hash (void)
714 test_moved (128, identity_hash);
718 test_moved_constant_hash (void)
720 test_moved (32, constant_hash);
723 /* Inserts values into an HMAPX, then changes their values, using
724 hash function HASH. */
726 test_changed (hash_function *hash)
728 const int max_elems = 6;
731 for (cnt = 0; cnt <= max_elems; cnt++)
733 int *values, *changed_values;
734 struct hmapx_node **nodes;
735 struct element *elements;
736 unsigned int permutation_cnt;
739 values = xnmalloc (cnt, sizeof *values);
740 changed_values = xnmalloc (cnt, sizeof *changed_values);
741 elements = xnmalloc (cnt, sizeof *elements);
742 nodes = xnmalloc (cnt, sizeof *nodes);
743 for (i = 0; i < cnt; i++)
746 for (permutation_cnt = 0;
747 permutation_cnt == 0 || next_permutation (values, cnt);
750 for (i = 0; i < cnt; i++)
753 for (j = 0; j <= cnt; j++)
759 /* Add to HMAPX in order. */
760 for (k = 0; k < cnt; k++)
763 elements[n].data = n;
764 nodes[n] = hmapx_insert (&hmapx, &elements[n],
765 hash (elements[n].data));
767 check_hmapx (&hmapx, values, cnt, hash);
769 /* Change value i to j. */
770 elements[i].data = j;
771 hmapx_changed (&hmapx, nodes[i],
772 hash (elements[i].data));
773 for (k = 0; k < cnt; k++)
774 changed_values[k] = k;
775 changed_values[i] = j;
776 check_hmapx (&hmapx, changed_values, cnt, hash);
778 hmapx_destroy (&hmapx);
782 check (permutation_cnt == factorial (cnt));
785 free (changed_values);
792 test_changed_random_hash (void)
794 test_changed (random_hash);
798 test_changed_identity_hash (void)
800 test_changed (identity_hash);
804 test_changed_constant_hash (void)
806 test_changed (constant_hash);
809 /* Inserts values into an HMAPX, then changes and moves their
810 values, using hash function HASH. */
812 test_change (hash_function *hash)
814 const int max_elems = 6;
817 for (cnt = 0; cnt <= max_elems; cnt++)
819 int *values, *changed_values;
820 struct hmapx_node **nodes;
821 struct element *elements;
822 struct element replacement;
823 unsigned int permutation_cnt;
826 values = xnmalloc (cnt, sizeof *values);
827 changed_values = xnmalloc (cnt, sizeof *changed_values);
828 elements = xnmalloc (cnt, sizeof *elements);
829 nodes = xnmalloc (cnt, sizeof *nodes);
830 for (i = 0; i < cnt; i++)
833 for (permutation_cnt = 0;
834 permutation_cnt == 0 || next_permutation (values, cnt);
837 for (i = 0; i < cnt; i++)
840 for (j = 0; j <= cnt; j++)
846 /* Add to HMAPX in order. */
847 for (k = 0; k < cnt; k++)
850 elements[n].data = n;
851 nodes[n] = hmapx_insert (&hmapx, &elements[n],
852 hash (elements[n].data));
854 check_hmapx (&hmapx, values, cnt, hash);
856 /* Change value i to j. */
857 replacement.data = j;
858 hmapx_change (&hmapx, nodes[i], &replacement, hash (j));
859 for (k = 0; k < cnt; k++)
860 changed_values[k] = k;
861 changed_values[i] = j;
862 check_hmapx (&hmapx, changed_values, cnt, hash);
864 hmapx_destroy (&hmapx);
868 check (permutation_cnt == factorial (cnt));
871 free (changed_values);
878 test_change_random_hash (void)
880 test_change (random_hash);
884 test_change_identity_hash (void)
886 test_change (identity_hash);
890 test_change_constant_hash (void)
892 test_change (constant_hash);
896 test_swap (int max_elems, hash_function *hash)
898 struct element *elements;
901 struct hmapx *working, *empty;
908 elements = xnmalloc (max_elems, sizeof *elements);
909 values = xnmalloc (max_elems, sizeof *values);
910 for (i = 0; i < max_elems; i++)
913 values[i] = elements[i].data = i;
914 hmapx_insert (working, &elements[i], hash (elements[i].data));
915 check_hmapx (working, values, i + 1, hash);
916 check_hmapx (empty, NULL, 0, hash);
929 test_swap_random_hash (void)
931 test_swap (128, random_hash);
934 /* Inserts elements into an HMAPX in ascending order, then clears the hash
935 table using hmapx_clear(). */
939 const int max_elems = 128;
940 struct element *elements;
941 struct hmapx_node **nodes;
946 elements = xnmalloc (max_elems, sizeof *elements);
947 nodes = xnmalloc (max_elems, sizeof *nodes);
948 values = xnmalloc (max_elems, sizeof *values);
951 for (cnt = 0; cnt <= max_elems; cnt++)
955 for (i = 0; i < cnt; i++)
957 values[i] = elements[i].data = i;
958 nodes[i] = hmapx_insert (&hmapx, &elements[i],
959 random_hash (elements[i].data));
960 check_hmapx (&hmapx, values, i + 1, random_hash);
962 hmapx_clear (&hmapx);
963 check_hmapx (&hmapx, NULL, 0, random_hash);
965 hmapx_destroy (&hmapx);
973 test_destroy_null (void)
975 hmapx_destroy (NULL);
978 /* Test shrinking an empty hash table. */
980 test_shrink_empty (void)
985 hmapx_reserve (&hmapx, 123);
986 hmapx_shrink (&hmapx);
987 hmapx_destroy (&hmapx);
995 const char *description;
996 void (*function) (void);
999 static const struct test tests[] =
1002 "insert-any-remove-any-random-hash",
1003 "insert any order, delete any order (random hash)",
1004 test_insert_any_remove_any_random_hash
1007 "insert-any-remove-any-identity-hash",
1008 "insert any order, delete any order (identity hash)",
1009 test_insert_any_remove_any_identity_hash
1012 "insert-any-remove-any-constant-hash",
1013 "insert any order, delete any order (constant hash)",
1014 test_insert_any_remove_any_constant_hash
1017 "insert-any-remove-same-random-hash",
1018 "insert any order, delete same order (random hash)",
1019 test_insert_any_remove_same_random_hash
1022 "insert-any-remove-same-identity-hash",
1023 "insert any order, delete same order (identity hash)",
1024 test_insert_any_remove_same_identity_hash
1027 "insert-any-remove-same-constant-hash",
1028 "insert any order, delete same order (constant hash)",
1029 test_insert_any_remove_same_constant_hash
1032 "insert-any-remove-reverse-random-hash",
1033 "insert any order, delete reverse order (random hash)",
1034 test_insert_any_remove_reverse_random_hash
1037 "insert-any-remove-reverse-identity-hash",
1038 "insert any order, delete reverse order (identity hash)",
1039 test_insert_any_remove_reverse_identity_hash
1042 "insert-any-remove-reverse-constant-hash",
1043 "insert any order, delete reverse order (constant hash)",
1044 test_insert_any_remove_reverse_constant_hash
1047 "random-sequence-random-hash",
1048 "insert and delete in random sequence (random hash)",
1049 test_random_sequence_random_hash
1052 "random-sequence-identity-hash",
1053 "insert and delete in random sequence (identity hash)",
1054 test_random_sequence_identity_hash
1057 "random-sequence-constant-hash",
1058 "insert and delete in random sequence (constant hash)",
1059 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
1077 "moved-random-hash",
1078 "move elements around in memory (random hash)",
1079 test_moved_random_hash
1082 "moved-identity-hash",
1083 "move elements around in memory (identity hash)",
1084 test_moved_identity_hash
1087 "moved-constant-hash",
1088 "move elements around in memory (constant hash)",
1089 test_moved_constant_hash
1092 "changed-random-hash",
1093 "change key data in nodes (random hash)",
1094 test_changed_random_hash
1097 "changed-identity-hash",
1098 "change key data in nodes (identity hash)",
1099 test_changed_identity_hash
1102 "changed-constant-hash",
1103 "change key data in nodes (constant hash)",
1104 test_changed_constant_hash
1107 "change-random-hash",
1108 "change and move key data in nodes (random hash)",
1109 test_change_random_hash
1112 "change-identity-hash",
1113 "change and move key data in nodes (identity hash)",
1114 test_change_identity_hash
1117 "change-constant-hash",
1118 "change and move key data in nodes (constant hash)",
1119 test_change_constant_hash
1123 "test swapping tables",
1124 test_swap_random_hash
1128 "test clearing hash table",
1133 "test destroying null table",
1138 "test shrinking an empty table",
1143 enum { N_TESTS = sizeof tests / sizeof *tests };
1146 main (int argc, char *argv[])
1152 fprintf (stderr, "exactly one argument required; use --help for help\n");
1153 return EXIT_FAILURE;
1155 else if (!strcmp (argv[1], "--help"))
1157 printf ("%s: test hash map of pointers\n"
1158 "usage: %s TEST-NAME\n"
1159 "where TEST-NAME is one of the following:\n",
1161 for (i = 0; i < N_TESTS; i++)
1162 printf (" %s\n %s\n", tests[i].name, tests[i].description);
1167 for (i = 0; i < N_TESTS; i++)
1168 if (!strcmp (argv[1], tests[i].name))
1170 tests[i].function ();
1174 fprintf (stderr, "unknown test %s; use --help for help\n", argv[1]);
1175 return EXIT_FAILURE;