chiark / gitweb /
debugging for thing that crashed
[innduct.git] / tests / overview / tradindexed-t.c
1 /* $Id: tradindexed-t.c 6388 2003-07-12 19:07:56Z rra $ */
2 /* tradindexed test suite. */
3
4 #include "config.h"
5 #include "clibrary.h"
6 #include <errno.h>
7 #include <sys/stat.h>
8
9 #include "inn/innconf.h"
10 #include "inn/hashtab.h"
11 #include "inn/messages.h"
12 #include "inn/vector.h"
13 #include "libinn.h"
14 #include "libtest.h"
15 #include "ov.h"
16 #include "storage.h"
17
18 #include "../storage/tradindexed/tradindexed.h"
19
20 /* Used as the artificial token for all articles inserted into overview. */
21 static const TOKEN faketoken = { 1, 1, "" };
22
23 struct group {
24     char *group;
25     unsigned long count;
26     unsigned long low;
27     unsigned long high;
28 };
29
30 static const void *
31 group_key(const void *entry)
32 {
33     const struct group *group = (const struct group *) entry;
34     return group->group;
35 }
36
37 static bool
38 group_eq(const void *key, const void *entry)
39 {
40     const char *first = key;
41     const char *second;
42
43     second = ((const struct group *) entry)->group;
44     return !strcmp(first, second);
45 }
46
47 static void
48 group_del(void *entry)
49 {
50     struct group *group = (struct group *) entry;
51
52     free(group->group);
53     free(group);
54 }
55
56 /* Build a stripped-down innconf struct that contains only those settings that
57    the tradindexed overview method cares about. */
58 static void
59 fake_innconf(void)
60 {
61     innconf = xmalloc(sizeof(*innconf));
62     innconf->pathoverview = xstrdup("tdx-tmp");
63     innconf->overcachesize = 20;
64     innconf->groupbaseexpiry = true;
65     innconf->tradindexedmmap = true;
66 }
67
68 /* Initialize the overview database. */
69 static bool
70 overview_init(void)
71 {
72     fake_innconf();
73     if (access("data/basic", F_OK) < 0)
74         if (access("overview/data/basic", F_OK) == 0)
75             if (chdir("overview") != 0)
76                 sysdie("Cannot cd to overview");
77     if (mkdir("tdx-tmp", 0755) != 0 && errno != EEXIST)
78         sysdie("Cannot mkdir tdx-tmp");
79     return tradindexed_open(OV_READ | OV_WRITE);
80 }
81
82 /* Check to be sure that the line wasn't too long, and then parse the
83    beginning of the line from one of our data files, setting the article
84    number (via the passed pointer) and returning a pointer to the beginning of
85    the real overview data.  This function nul-terminates the group name and
86    leaves it at the beginning of the buffer.  (Ugly interface, but it's just a
87    test suite.) */
88 static char *
89 overview_data_parse(char *data, unsigned long *artnum)
90 {
91     char *start;
92
93     if (data[strlen(data) - 1] != '\n')
94         die("Line too long in input data");
95
96     start = strchr(data, ':');
97     if (start == NULL)
98         die("No colon found in input data");
99     *start = '\0';
100     start++;
101     *artnum = strtoul(start, NULL, 10);
102     if (artnum == 0)
103         die("Cannot parse article number in input data");
104     return start;
105 }
106
107 /* Load an empty overview database from a file, in the process populating a
108    hash table with each group, the high water mark, and the count of messages
109    that should be in the group.  Returns the hash table on success and dies on
110    failure.  Takes the name of the data file to load. */
111 static struct hash *
112 overview_load(const char *data)
113 {
114     struct hash *groups;
115     struct group *group;
116     FILE *overview;
117     char buffer[4096];
118     char flag[] = "y";
119     char *start;
120     unsigned long artnum;
121
122     /* Run through the overview data.  Each time we see a group, we update our
123        stored information about that group, which we'll use for verification
124        later.  We store that in a local hash table. */
125     groups = hash_create(32, hash_string, group_key, group_eq, group_del);
126     if (groups == NULL)
127         die("Cannot create a hash table");
128     overview = fopen(data, "r");
129     if (overview == NULL)
130         sysdie("Cannot open %s for reading", data);
131     while (fgets(buffer, sizeof(buffer), overview) != NULL) {
132         start = overview_data_parse(buffer, &artnum);
133
134         /* See if we've already seen this group.  If not, create it in the
135            overview and the hash table; otherwise, update our local hash table
136            entry. */
137         group = hash_lookup(groups, buffer);
138         if (group == NULL) {
139             group = xmalloc(sizeof(struct group));
140             group->group = xstrdup(buffer);
141             group->count = 1;
142             group->low = artnum;
143             group->high = artnum;
144             if (!hash_insert(groups, group->group, group))
145                 die("Cannot insert %s into hash table", group->group);
146             if (!tradindexed_groupadd(group->group, 0, 0, flag))
147                 die("Cannot insert group %s into overview", group->group);
148         } else {
149             group->count++;
150             group->low = (artnum < group->low) ? artnum : group->low;
151             group->high = (artnum > group->high) ? artnum : group->high;
152         }
153
154         /* Do the actual insert of the data.  Note that we set the arrival
155            time and expires time in a deterministic fashion so that we can
156            check later if that data is being stored properly. */
157         if (!tradindexed_add(group->group, artnum, faketoken, start,
158                              strlen(start), artnum * 10,
159                              (artnum % 5 == 0) ? artnum * 100 : artnum))
160             die("Cannot insert %s:%lu into overview", group->group, artnum);
161     }
162     fclose(overview);
163     return groups;
164 }
165
166 /* Verify that all of the group data looks correct; this is low mark, high
167    mark, and article count.  Returns true if all the data is right, false
168    otherwise.  This function is meant to be called as a hash traversal
169    function, which means that it will be called for each element in our local
170    hash table of groups with the group struct as the first argument and a
171    pointer to a status as the second argument. */
172 static void
173 overview_verify_groups(void *data, void *cookie)
174 {
175     struct group *group = (struct group *) data;
176     bool *status = (bool *) cookie;
177     int low, high, count, flag;
178
179     if (!tradindexed_groupstats(group->group, &low, &high, &count, &flag)) {
180         warn("Unable to get data for %s", group->group);
181         *status = false;
182         return;
183     }
184     if ((unsigned long) low != group->low) {
185         warn("Low article wrong for %s: %lu != %lu", group->group,
186              (unsigned long) low, group->low);
187         *status = false;
188     }
189     if ((unsigned long) high != group->high) {
190         warn("High article wrong for %s: %lu != %lu", group->group,
191              (unsigned long) high, group->high);
192         *status = false;
193     }
194     if ((unsigned long) count != group->count) {
195         warn("Article count wrong for %s: %lu != %lu", group->group,
196              (unsigned long) count, group->count);
197         *status = false;
198     }
199     if (flag != 'y') {
200         warn("Flag wrong for %s: %c != y", group->group, (char) flag);
201         *status = false;
202     }
203 }
204
205 /* Verify the components of the overview data for a particular entry. */
206 static bool
207 check_data(const char *group, unsigned long artnum, const char *expected,
208            const char *seen, int length, TOKEN token)
209 {
210     bool status = true;
211
212     if (strlen(expected) != (size_t) length) {
213         warn("Length wrong for %s:%lu: %d != %lu", group, artnum, length,
214              (unsigned long) strlen(expected));
215         status = false;
216     }
217     if (memcmp(&token, &faketoken, sizeof(token)) != 0) {
218         warn("Token wrong for %s:%lu", group, artnum);
219         status = false;
220     }
221     if (memcmp(expected, seen, length) != 0) {
222         warn("Data mismatch for %s:%lu", group, artnum);
223         warn("====\n%s\n====\n%s\n====", expected, seen);
224         status = false;
225     }
226     return status;
227 }
228
229 /* Read through the data again, looking up each article as we go and verifying
230    that the data stored in overview is the same as the data we put there.  Do
231    this two ways each time, once via getartinfo and once via opensearch.
232    Return true if everything checks out, false otherwise.  Takes the path to
233    the data file. */
234 static bool
235 overview_verify_data(const char *data)
236 {
237     FILE *overdata;
238     char buffer[4096];
239     char *start;
240     unsigned long artnum, overnum;
241     char *overview;
242     int length;
243     TOKEN token;
244     bool status = true;
245     void *search;
246     time_t arrived;
247
248     overdata = fopen(data, "r");
249     if (overdata == NULL)
250         sysdie("Cannot open %s for reading", data);
251     while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
252         start = overview_data_parse(buffer, &artnum);
253
254         /* Now check that the overview data is correct for that group. */
255         if (!tradindexed_getartinfo(buffer, artnum, &token)) {
256             warn("No overview data found for %s:%lu", buffer, artnum);
257             status = false;
258             continue;
259         }
260         if (memcmp(&token, &faketoken, sizeof(token)) != 0) {
261             warn("Token wrong for %s:%lu", buffer, artnum);
262             status = false;
263         }
264
265         /* Do the same thing, except use search. */
266         search = tradindexed_opensearch(buffer, artnum, artnum);
267         if (search == NULL) {
268             warn("Unable to open search for %s:%lu", buffer, artnum);
269             status = false;
270             continue;
271         }
272         if (!tradindexed_search(search, &overnum, &overview, &length, &token,
273                                 &arrived)) {
274             warn("No overview data found for %s:%lu", buffer, artnum);
275             status = false;
276             continue;
277         }
278         if (overnum != artnum) {
279             warn("Incorrect article number in search for %s:%lu: %lu != %lu",
280                  buffer, artnum, overnum, artnum);
281             status = false;
282         }
283         if (!check_data(buffer, artnum, start, overview, length, token))
284             status = false;
285         if ((unsigned long) arrived != artnum * 10) {
286             warn("Arrival time wrong for %s:%lu: %lu != %lu", buffer, artnum,
287                  (unsigned long) arrived, artnum * 10);
288             status = false;
289         }
290         if (tradindexed_search(search, &overnum, &overview, &length, &token,
291                                &arrived)) {
292             warn("Unexpected article found for %s:%lu", buffer, artnum);
293             status = false;
294         }
295         tradindexed_closesearch(search);
296     }
297     fclose(overdata);
298     return status;
299 }
300
301 /* Try an overview search and verify that all of the data is returned in the
302    right order.  The first group mentioned in the provided data file will be
303    the group the search is done in, and the search will cover all articles
304    from the second article to the second-to-the-last article in the group.
305    Returns true if everything checks out, false otherwise. */
306 static bool
307 overview_verify_search(const char *data)
308 {
309     unsigned long artnum, overnum, i;
310     unsigned long start = 0;
311     unsigned long end = 0;
312     unsigned long last = 0;
313     struct vector *expected;
314     char *line, *group;
315     FILE *overview;
316     char buffer[4096];
317     int length;
318     TOKEN token;
319     void *search;
320     time_t arrived;
321     bool status = true;
322
323     overview = fopen(data, "r");
324     if (overview == NULL)
325         sysdie("Cannot open %s for reading", data);
326     expected = vector_new();
327     if (fgets(buffer, sizeof(buffer), overview) == NULL)
328         die("Unexpected end of file in %s", data);
329     overview_data_parse(buffer, &artnum);
330     group = xstrdup(buffer);
331     while (fgets(buffer, sizeof(buffer), overview) != NULL) {
332         line = overview_data_parse(buffer, &artnum);
333         if (strcmp(group, buffer) != 0)
334             continue;
335         vector_add(expected, line);
336         if (start == 0)
337             start = artnum;
338         end = last;
339         last = artnum;
340     }
341     search = tradindexed_opensearch(group, start, end);
342     if (search == NULL) {
343         warn("Unable to open search for %s:%lu", buffer, start);
344         free(group);
345         vector_free(expected);
346         return false;
347     }
348     i = 0;
349     while (tradindexed_search(search, &overnum, &line, &length, &token,
350                               &arrived)) {
351         if (!check_data(group, overnum, expected->strings[i], line, length,
352                         token))
353             status = false;
354         if ((unsigned long) arrived != overnum * 10) {
355             warn("Arrival time wrong for %s:%lu: %lu != %lu", group, overnum,
356                  (unsigned long) arrived, overnum * 10);
357             status = false;
358         }
359         i++;
360     }
361     tradindexed_closesearch(search);
362     if (overnum != end) {
363         warn("End of search in %s wrong: %lu != %lu", group, overnum, end);
364         status = false;
365     }
366     if (i != expected->count - 1) {
367         warn("Didn't see all expected entries in %s", group);
368         status = false;
369     }
370     free(group);
371     vector_free(expected);
372     return status;
373 }
374
375 /* Try an overview search and verify that all of the data is returned in the
376    right order.  The search will cover everything from article 1 to the
377    highest numbered article plus one.  There were some problems with a search
378    low-water mark lower than the base of the group.  Returns true if
379    everything checks out, false otherwise. */
380 static bool
381 overview_verify_full_search(const char *data)
382 {
383     unsigned long artnum, overnum, i;
384     unsigned long end = 0;
385     struct vector *expected;
386     char *line;
387     char *group = NULL;
388     FILE *overview;
389     char buffer[4096];
390     int length;
391     TOKEN token;
392     void *search;
393     time_t arrived;
394     bool status = true;
395
396     overview = fopen(data, "r");
397     if (overview == NULL)
398         sysdie("Cannot open %s for reading", data);
399     expected = vector_new();
400     while (fgets(buffer, sizeof(buffer), overview) != NULL) {
401         line = overview_data_parse(buffer, &artnum);
402         if (group == NULL)
403             group = xstrdup(buffer);
404         vector_add(expected, line);
405         end = artnum;
406     }
407     search = tradindexed_opensearch(group, 1, end + 1);
408     if (search == NULL) {
409         warn("Unable to open full search for %s", group);
410         free(group);
411         vector_free(expected);
412         return false;
413     }
414     i = 0;
415     while (tradindexed_search(search, &overnum, &line, &length, &token,
416                               &arrived)) {
417         if (!check_data(group, overnum, expected->strings[i], line, length,
418                         token))
419             status = false;
420         if ((unsigned long) arrived != overnum * 10) {
421             warn("Arrival time wrong for %s:%lu: %lu != %lu", group, overnum,
422                  (unsigned long) arrived, overnum * 10);
423             status = false;
424         }
425         i++;
426     }
427     tradindexed_closesearch(search);
428     if (overnum != end) {
429         warn("End of search in %s wrong: %lu != %lu", group, overnum, end);
430         status = false;
431     }
432     if (i != expected->count) {
433         warn("Didn't see all expected entries in %s", group);
434         status = false;
435     }
436     free(group);
437     vector_free(expected);
438     return status;
439 }
440
441 int
442 main(void)
443 {
444     struct hash *groups;
445     bool status;
446
447     puts("21");
448
449     if (!overview_init())
450         die("Opening the overview database failed, cannot continue");
451     ok(1, true);
452
453     groups = overview_load("data/basic");
454     ok(2, true);
455     status = true;
456     hash_traverse(groups, overview_verify_groups, &status);
457     ok(3, status);
458     ok(4, overview_verify_data("data/basic"));
459     ok(5, overview_verify_search("data/basic"));
460     hash_free(groups);
461     tradindexed_close();
462     system("/bin/rm -r tdx-tmp");
463     ok(6, true);
464
465     if (!overview_init())
466         die("Opening the overview database failed, cannot continue");
467     ok(7, true);
468
469     groups = overview_load("data/reversed");
470     ok(8, true);
471     status = true;
472     hash_traverse(groups, overview_verify_groups, &status);
473     ok(9, status);
474     ok(10, overview_verify_data("data/basic"));
475     ok(11, overview_verify_search("data/basic"));
476     hash_free(groups);
477     tradindexed_close();
478     system("/bin/rm -r tdx-tmp");
479     ok(12, true);
480
481     if (!overview_init())
482         die("Opening the overview database failed, cannot continue");
483     ok(13, true);
484
485     groups = overview_load("data/high-numbered");
486     ok(14, true);
487     ok(15, overview_verify_data("data/high-numbered"));
488     ok(16, overview_verify_full_search("data/high-numbered"));
489     hash_free(groups);
490     tradindexed_close();
491     system("/bin/rm -r tdx-tmp");
492     ok(17, true);
493
494     if (!overview_init())
495         die("Opening the overview database failed, cannot continue");
496     ok(18, true);
497
498     groups = overview_load("data/bogus");
499     ok(19, true);
500     ok(20, overview_verify_data("data/bogus"));
501     hash_free(groups);
502     tradindexed_close();
503     system("/bin/rm -r tdx-tmp");
504     ok(21, true);
505
506     return 0;
507 }