Use spaces for indentation, not tabs.
[gnulib.git] / tests / test-hash.c
1 /*
2  * Copyright (C) 2009 Free Software Foundation
3  * Written by Jim Meyering
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 #include "hash.h"
21 #include "hash-pjw.h"
22 #include "inttostr.h"
23 #include "xalloc.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdbool.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #define STREQ(a, b) (strcmp (a, b) == 0)
32 #define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array))
33
34 #define ASSERT(expr) \
35   do                                                                         \
36     {                                                                        \
37       if (!(expr))                                                           \
38         {                                                                    \
39           fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
40           fflush (stderr);                                                   \
41           abort ();                                                          \
42         }                                                                    \
43     }                                                                        \
44   while (0)
45
46 static bool
47 hash_compare_strings (void const *x, void const *y)
48 {
49   ASSERT (x != y);
50   return STREQ (x, y) ? true : false;
51 }
52
53 static void
54 hash_freer (void *x)
55 {
56   free (x);
57 }
58
59 static void
60 insert_new (Hash_table *ht, const void *ent)
61 {
62   void *e = hash_insert (ht, ent);
63   ASSERT (e == ent);
64 }
65
66 static bool
67 walk (void *ent, void *data)
68 {
69   char *str = ent;
70   unsigned int *map = data;
71   switch (*str)
72     {
73     case 'a': *map |= 1; return true;
74     case 'b': *map |= 2; return true;
75     case 'c': *map |= 4; return true;
76     }
77   *map |= 8;
78   return false;
79 }
80
81 static int
82 get_seed (char const *str, unsigned int *seed)
83 {
84   size_t len = strlen (str);
85   if (len == 0 || strspn (str, "0123456789") != len || 10 < len)
86     return 1;
87
88   *seed = atoi (str);
89   return 0;
90 }
91
92 int
93 main (int argc, char **argv)
94 {
95   unsigned int i;
96   unsigned int k;
97   unsigned int table_size[] = {1, 2, 3, 4, 5, 23, 53};
98   Hash_table *ht;
99   Hash_tuning tuning;
100
101   hash_reset_tuning (&tuning);
102   tuning.shrink_threshold = 0.3;
103   tuning.shrink_factor = 0.707;
104   tuning.growth_threshold = 1.5;
105   tuning.growth_factor = 2.0;
106   tuning.is_n_buckets = true;
107
108   if (1 < argc)
109     {
110       unsigned int seed;
111       if (get_seed (argv[1], &seed) != 0)
112         {
113           fprintf (stderr, "invalid seed: %s\n", argv[1]);
114           exit (EXIT_FAILURE);
115         }
116
117       srand (seed);
118     }
119
120   for (i = 0; i < ARRAY_CARDINALITY (table_size); i++)
121     {
122       size_t sz = table_size[i];
123       ht = hash_initialize (sz, NULL, hash_pjw, hash_compare_strings, NULL);
124       ASSERT (ht);
125       insert_new (ht, "a");
126       {
127         char *str1 = xstrdup ("a");
128         char *str2 = hash_insert (ht, str1);
129         ASSERT (str1 != str2);
130         ASSERT (STREQ (str1, str2));
131         free (str1);
132       }
133       insert_new (ht, "b");
134       insert_new (ht, "c");
135       i = 0;
136       ASSERT (hash_do_for_each (ht, walk, &i) == 3);
137       ASSERT (i == 7);
138       {
139         void *buf[5] = { NULL };
140         ASSERT (hash_get_entries (ht, NULL, 0) == 0);
141         ASSERT (hash_get_entries (ht, buf, 5) == 3);
142         ASSERT (STREQ (buf[0], "a") || STREQ (buf[0], "b") || STREQ (buf[0], "c"));
143       }
144       ASSERT (hash_delete (ht, "a"));
145       ASSERT (hash_delete (ht, "a") == NULL);
146       ASSERT (hash_delete (ht, "b"));
147       ASSERT (hash_delete (ht, "c"));
148
149       ASSERT (hash_rehash (ht, 47));
150       ASSERT (hash_rehash (ht, 467));
151
152       /* Free an empty table. */
153       hash_clear (ht);
154       hash_free (ht);
155
156       ht = hash_initialize (sz, NULL, hash_pjw, hash_compare_strings, NULL);
157       ASSERT (ht);
158
159       insert_new (ht, "z");
160       insert_new (ht, "y");
161       insert_new (ht, "x");
162       insert_new (ht, "w");
163       insert_new (ht, "v");
164       insert_new (ht, "u");
165
166       hash_clear (ht);
167       ASSERT (hash_get_n_entries (ht) == 0);
168       hash_free (ht);
169
170       /* Test pointer hashing.  */
171       ht = hash_initialize (sz, NULL, NULL, NULL, NULL);
172       ASSERT (ht);
173       {
174         char *str = xstrdup ("a");
175         insert_new (ht, "a");
176         insert_new (ht, str);
177         ASSERT (hash_lookup (ht, str) == str);
178         free (str);
179       }
180       hash_free (ht);
181     }
182
183   hash_reset_tuning (&tuning);
184   tuning.shrink_threshold = 0.3;
185   tuning.shrink_factor = 0.707;
186   tuning.growth_threshold = 1.5;
187   tuning.growth_factor = 2.0;
188   tuning.is_n_buckets = true;
189   /* Invalid tuning.  */
190   ht = hash_initialize (4651, &tuning, hash_pjw, hash_compare_strings,
191                         hash_freer);
192   ASSERT (!ht);
193
194   /* Alternate tuning.  */
195   tuning.growth_threshold = 0.89;
196
197   /* Run with default tuning, then with custom tuning settings.  */
198   for (k = 0; k < 2; k++)
199     {
200       Hash_tuning const *tune = (k == 0 ? NULL : &tuning);
201       /* Now, each entry is malloc'd.  */
202       ht = hash_initialize (4651, tune, hash_pjw,
203                             hash_compare_strings, hash_freer);
204       ASSERT (ht);
205       for (i = 0; i < 10000; i++)
206         {
207           unsigned int op = rand () % 10;
208           switch (op)
209             {
210             case 0:
211             case 1:
212             case 2:
213             case 3:
214             case 4:
215             case 5:
216               {
217                 char buf[50];
218                 char const *p = uinttostr (i, buf);
219                 insert_new (ht, xstrdup (p));
220               }
221               break;
222
223             case 6:
224               {
225                 size_t n = hash_get_n_entries (ht);
226                 ASSERT (hash_rehash (ht, n + rand () % 20));
227               }
228               break;
229
230             case 7:
231               {
232                 size_t n = hash_get_n_entries (ht);
233                 size_t delta = rand () % 20;
234                 if (delta < n)
235                   ASSERT (hash_rehash (ht, n - delta));
236               }
237               break;
238
239             case 8:
240             case 9:
241               {
242                 /* Delete a random entry.  */
243                 size_t n = hash_get_n_entries (ht);
244                 if (n)
245                   {
246                     size_t kk = rand () % n;
247                     void const *p;
248                     void *v;
249                     for (p = hash_get_first (ht); kk;
250                          --kk, p = hash_get_next (ht, p))
251                       {
252                         /* empty */
253                       }
254                     ASSERT (p);
255                     v = hash_delete (ht, p);
256                     ASSERT (v);
257                     free (v);
258                   }
259                 break;
260               }
261             }
262           ASSERT (hash_table_ok (ht));
263         }
264
265       hash_free (ht);
266     }
267
268   return 0;
269 }