1 /* $Id: api-t.c 10308 2018-12-02 14:33:59Z iulius $ */
2 /* Test suite for overview API. */
3
4 #include "config.h"
5 #include "clibrary.h"
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <sys/stat.h>
9
10 #include "inn/innconf.h"
11 #include "inn/hashtab.h"
12 #include "inn/messages.h"
13 #include "inn/overview.h"
14 #include "inn/vector.h"
15 #include "inn/libinn.h"
16 #include "tap/basic.h"
17 #include "inn/storage.h"
18
19 /* Used as the artificial token for all articles inserted into overview. */
20 static const TOKEN faketoken = { 1, 1, "" };
21
22 struct group {
23 char *group;
24 unsigned long count;
25 unsigned long low;
26 unsigned long high;
27 };
28
29 /* Used for walking the hash table and verifying all the group data. */
30 struct verify {
31 struct overview *overview;
32 bool status;
33 };
34
35 static const void *
group_key(const void * entry)36 group_key(const void *entry)
37 {
38 const struct group *group = (const struct group *) entry;
39 return group->group;
40 }
41
42 static bool
group_eq(const void * key,const void * entry)43 group_eq(const void *key, const void *entry)
44 {
45 const char *first = key;
46 const char *second;
47
48 second = ((const struct group *) entry)->group;
49 return !strcmp(first, second);
50 }
51
52 static void
group_del(void * entry)53 group_del(void *entry)
54 {
55 struct group *group = (struct group *) entry;
56
57 free(group->group);
58 free(group);
59 }
60
61 /* Build a stripped-down innconf struct that contains only those settings that
62 the overview backend cares about. (May still be missing additional bits
63 for ovdb.)
64 innconf->icdsynccount defines OVBUFF_SYNC_COUNT. */
65 static void
fake_innconf(void)66 fake_innconf(void)
67 {
68 if (innconf != NULL) {
69 free(innconf->ovmethod);
70 free(innconf->pathdb);
71 free(innconf->pathetc);
72 free(innconf->pathoverview);
73 free(innconf->pathrun);
74 free(innconf);
75 }
76 innconf = xmalloc(sizeof(*innconf));
77 memset(innconf, 0, sizeof(*innconf));
78 innconf->enableoverview = true;
79 innconf->groupbaseexpiry = true;
80 innconf->icdsynccount = 10;
81 innconf->keepmmappedthreshold = 1024;
82 innconf->nfsreader = false;
83 innconf->overcachesize = 20;
84 innconf->ovgrouppat = NULL;
85 innconf->pathdb = xstrdup("ov-tmp");
86 innconf->pathetc = xstrdup("etc");
87 innconf->pathoverview = xstrdup("ov-tmp");
88 innconf->pathrun = xstrdup("ov-tmp");
89 }
90
91 /* Initialize an empty buffindexed buffer. */
92 static void
overview_init_buffindexed(void)93 overview_init_buffindexed(void)
94 {
95 int fd, i;
96 char zero[1024];
97
98 fd = open("ov-tmp/buffer", O_CREAT | O_TRUNC | O_WRONLY, 0666);
99 if (fd < 0)
100 sysdie("Cannot create ov-tmp/buffer");
101 memset(zero, 0, sizeof(zero));
102 for (i = 0; i < 1024; i++)
103 if (write(fd, zero, sizeof(zero)) < (ssize_t) sizeof(zero))
104 sysdie("Cannot write to ov-tmp/buffer");
105 close(fd);
106 }
107
108 /* Initialize the overview database. */
109 static struct overview *
overview_init(void)110 overview_init(void)
111 {
112 if (system("/bin/rm -rf ov-tmp") < 0)
113 sysdie("Cannot rm ov-tmp");
114 if (mkdir("ov-tmp", 0755))
115 sysdie("Cannot mkdir ov-tmp");
116 if (strcmp(innconf->ovmethod, "buffindexed") == 0)
117 overview_init_buffindexed();
118 return overview_open(OV_READ | OV_WRITE);
119 }
120
121 /* Check to be sure that the line wasn't too long, and then parse the
122 beginning of the line from one of our data files, setting the article
123 number (via the passed pointer) and returning a pointer to the beginning of
124 the real overview data. This function nul-terminates the group name and
125 leaves it at the beginning of the buffer. (Ugly interface, but it's just a
126 test suite.) */
127 static char *
overview_data_parse(char * data,unsigned long * artnum)128 overview_data_parse(char *data, unsigned long *artnum)
129 {
130 char *start;
131 size_t length;
132
133 length = strlen(data);
134 if (data[length - 1] != '\n')
135 die("Line too long in input data");
136 data[length - 1] = '\0';
137
138 start = strchr(data, ':');
139 if (start == NULL)
140 die("No colon found in input data");
141 *start = '\0';
142 start++;
143 *artnum = strtoul(start, NULL, 10);
144 if (*artnum == 0)
145 die("Cannot parse article number in input data");
146 return start;
147 }
148
149 /* Load an empty overview database from a file, in the process populating a
150 hash table with each group, the high water mark, and the count of messages
151 that should be in the group. Returns the hash table on success and dies on
152 failure. Takes the name of the data file to load and the overview
153 struct. */
154 static struct hash *
overview_load(const char * data,struct overview * overview)155 overview_load(const char *data, struct overview *overview)
156 {
157 struct hash *groups;
158 struct group *group;
159 FILE *overdata;
160 char buffer[4096];
161 char *start;
162 unsigned long artnum;
163 struct overview_group stats = { 0, 0, 0, NF_FLAG_OK };
164 struct overview_data article;
165
166 /* Run through the overview data. Each time we see a group, we update our
167 stored information about that group, which we'll use for verification
168 later. We store that in a local hash table. */
169 groups = hash_create(32, hash_string, group_key, group_eq, group_del);
170 if (groups == NULL)
171 die("Cannot create a hash table");
172 overdata = fopen(data, "r");
173 if (overdata == NULL)
174 sysdie("Cannot open %s for reading", data);
175 while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
176 start = overview_data_parse(buffer, &artnum);
177
178 /* The overview API adds the article number, so strip that out before
179 storing the data (overview_data_parse leaves it in because we want
180 it in for data validation). */
181 start = strchr(start, '\t');
182 if (start == NULL)
183 die("No tab found after number in input data");
184 *start = '\0';
185 start++;
186
187 /* See if we've already seen this group. If not, create it in the
188 overview and the hash table; otherwise, update our local hash table
189 entry. */
190 group = hash_lookup(groups, buffer);
191 if (group == NULL) {
192 group = xmalloc(sizeof(struct group));
193 group->group = xstrdup(buffer);
194 group->count = 1;
195 group->low = artnum;
196 group->high = artnum;
197 if (!hash_insert(groups, group->group, group))
198 die("Cannot insert %s into hash table", group->group);
199 if (!overview_group_add(overview, group->group, &stats))
200 die("Cannot insert group %s into overview", group->group);
201 } else {
202 group->count++;
203 group->low = (artnum < group->low) ? artnum : group->low;
204 group->high = (artnum > group->high) ? artnum : group->high;
205 }
206
207 /* Do the actual insert of the data. Note that we set the arrival
208 time and expires time in a deterministic fashion so that we can
209 check later if that data is being stored properly. */
210 article.number = artnum;
211 article.overview = start;
212 article.overlen = strlen(start);
213 article.token = faketoken;
214 article.arrived = artnum * 10;
215 article.expires = (artnum % 5 == 0) ? artnum * 100 : artnum;
216 if (!overview_add(overview, group->group, &article))
217 die("Cannot insert %s:%lu into overview", group->group, artnum);
218 }
219 fclose(overdata);
220 return groups;
221 }
222
223 /* Verify that all of the group data looks correct; this is low mark, high
224 mark, and article count. Returns true if all the data is right, false
225 otherwise. This function is meant to be called as a hash traversal
226 function, which means that it will be called for each element in our local
227 hash table of groups with the group struct as the first argument and a
228 pointer to a struct verify as the second argument. */
229 static void
overview_verify_groups(void * data,void * cookie)230 overview_verify_groups(void *data, void *cookie)
231 {
232 struct group *group = (struct group *) data;
233 struct verify *verify = cookie;
234 struct overview_group stats;
235
236 if (!overview_group(verify->overview, group->group, &stats)) {
237 warn("Unable to get data for %s", group->group);
238 verify->status = false;
239 return;
240 }
241 if (stats.low != group->low) {
242 warn("Low article wrong for %s: %lu != %lu", group->group,
243 stats.low, group->low);
244 verify->status = false;
245 }
246 if (stats.high != group->high) {
247 warn("High article wrong for %s: %lu != %lu", group->group,
248 stats.high, group->high);
249 verify->status = false;
250 }
251 if (stats.count != group->count) {
252 warn("Article count wrong for %s: %lu != %lu", group->group,
253 stats.count, group->count);
254 verify->status = false;
255 }
256 if (stats.flag != NF_FLAG_OK) {
257 warn("Flag wrong for %s: %c != %c", group->group, stats.flag,
258 NF_FLAG_OK);
259 verify->status = false;
260 }
261 }
262
263 /* Verify the components of the overview data for a particular entry. */
264 static bool
check_data(const char * group,ARTNUM artnum,const char * expected,struct overview_data * data)265 check_data(const char *group, ARTNUM artnum, const char *expected,
266 struct overview_data *data)
267 {
268 bool status = true;
269 time_t expires UNUSED; // See below why it is still unused.
270 char *saw;
271
272 if (artnum != data->number) {
273 warn("Incorrect article number in search for %s:%lu: %lu != %lu",
274 group, artnum, data->number, artnum);
275 status = false;
276 }
277 if (strlen(expected) != data->overlen - 2) {
278 warn("Length wrong for %s:%lu: %lu != %lu", group, artnum,
279 (unsigned long) data->overlen, (unsigned long) strlen(expected));
280 status = false;
281 }
282 if (memcmp(&data->token, &faketoken, sizeof(faketoken)) != 0) {
283 warn("Token wrong for %s:%lu", group, artnum);
284 status = false;
285 }
286 if (memcmp(expected, data->overview, data->overlen - 2) != 0) {
287 warn("Data mismatch for %s:%lu", group, artnum);
288 saw = xstrndup(data->overview, data->overlen);
289 warn("====\n%s====\n%s====", expected, saw);
290 free(saw);
291 status = false;
292 }
293 if (memcmp("\r\n", data->overview + data->overlen - 2, 2) != 0) {
294 warn("Missing CRLF after data for %s:%lu", group, artnum);
295 status = false;
296 }
297 if (data->arrived != (time_t) artnum * 10) {
298 warn("Arrival time wrong for %s:%lu: %lu != %lu", group, artnum,
299 (unsigned long) data->arrived, artnum * 10);
300 status = false;
301 }
302
303 /* expires is always 0 for right now because the underlying API doesn't
304 return it; this will change when the new API has been pushed all the
305 way down to the overview implementations. */
306 expires = (artnum % 5 == 0) ? artnum * 100 : artnum;
307 if (data->expires != 0) {
308 warn("Expires time wrong for %s:%lu: %lu != %lu", group, artnum,
309 (unsigned long) data->expires, 0UL);
310 status = false;
311 }
312 return status;
313 }
314
315 /* Read through the data again, looking up each article as we go and verifying
316 that the data stored in overview is the same as the data we put there. Do
317 this two ways each time, once via overview_token and once via
318 overview_search. Return true if everything checks out, false otherwise.
319 Takes the path to the data file and the overview struct. */
320 static bool
overview_verify_data(const char * data,struct overview * overview)321 overview_verify_data(const char *data, struct overview *overview)
322 {
323 FILE *overdata;
324 char buffer[4096];
325 char *start;
326 unsigned long artnum;
327 TOKEN token;
328 void *search;
329 struct overview_data article;
330 bool status = true;
331
332 overdata = fopen(data, "r");
333 if (overdata == NULL)
334 sysdie("Cannot open %s for reading", data);
335 while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
336 start = overview_data_parse(buffer, &artnum);
337
338 /* Now check that the overview data is correct for that group. */
339 if (!overview_token(overview, buffer, artnum, &token)) {
340 warn("No overview data found for %s:%lu", buffer, artnum);
341 status = false;
342 continue;
343 }
344 if (memcmp(&token, &faketoken, sizeof(token)) != 0) {
345 warn("Token wrong for %s:%lu", buffer, artnum);
346 status = false;
347 }
348
349 /* Do the same thing, except use search. */
350 search = overview_search_open(overview, buffer, artnum, artnum);
351 if (search == NULL) {
352 warn("Unable to open search for %s:%lu", buffer, artnum);
353 status = false;
354 continue;
355 }
356 if (!overview_search(overview, search, &article)) {
357 warn("No overview data found for %s:%lu", buffer, artnum);
358 status = false;
359 continue;
360 }
361 if (!check_data(buffer, artnum, start, &article))
362 status = false;
363 if (overview_search(overview, search, &article)) {
364 warn("Unexpected article found for %s:%lu", buffer, artnum);
365 status = false;
366 }
367 overview_search_close(overview, search);
368 }
369 fclose(overdata);
370 return status;
371 }
372
373 /* Try an overview search and verify that all of the data is returned in the
374 right order. The first group mentioned in the provided data file will be
375 the group the search is done in, and the search will cover all articles
376 from the second article to the second-to-the-last article in the group.
377 Returns true if everything checks out, false otherwise. */
378 static bool
overview_verify_search(const char * data,struct overview * overview)379 overview_verify_search(const char *data, struct overview *overview)
380 {
381 unsigned long artnum, i;
382 unsigned long start = 0;
383 unsigned long end = 0;
384 unsigned long last = 0;
385 struct vector *expected;
386 char *group, *line;
387 FILE *overdata;
388 char buffer[4096];
389 void *search;
390 struct overview_data article;
391 bool status = true;
392
393 overdata = fopen(data, "r");
394 if (overdata == NULL)
395 sysdie("Cannot open %s for reading", data);
396 expected = vector_new();
397 if (fgets(buffer, sizeof(buffer), overdata) == NULL) {
398 fclose(overdata);
399 die("Unexpected end of file in %s", data);
400 }
401 overview_data_parse(buffer, &artnum);
402 group = xstrdup(buffer);
403 while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
404 line = overview_data_parse(buffer, &artnum);
405 if (strcmp(group, buffer) != 0)
406 continue;
407 vector_add(expected, line);
408 if (start == 0)
409 start = artnum;
410 end = last;
411 last = artnum;
412 }
413 search = overview_search_open(overview, group, start, end);
414 if (search == NULL) {
415 warn("Unable to open search for %s:%lu", buffer, start);
416 free(group);
417 vector_free(expected);
418 fclose(overdata);
419 return false;
420 }
421 i = 0;
422 while (overview_search(overview, search, &article)) {
423 if (!check_data(group, article.number, expected->strings[i], &article))
424 status = false;
425 i++;
426 }
427 overview_search_close(overview, search);
428 if (article.number != end) {
429 warn("End of search in %s wrong: %lu != %lu", group, article.number,
430 end);
431 status = false;
432 }
433 if (i != expected->count - 1) {
434 warn("Didn't see all expected entries in %s", group);
435 status = false;
436 }
437 free(group);
438 vector_free(expected);
439 fclose(overdata);
440 return status;
441 }
442
443 /* Try an overview search and verify that all of the data is returned in the
444 right order. The search will cover everything from article 1 to the
445 highest numbered article plus one. There were some problems with a search
446 low water mark lower than the base of the group. Returns true if
447 everything checks out, false otherwise. */
448 static bool
overview_verify_full_search(const char * data,struct overview * overview)449 overview_verify_full_search(const char *data, struct overview *overview)
450 {
451 unsigned long artnum, i;
452 unsigned long end = 0;
453 struct vector *expected;
454 char *line;
455 char *group = NULL;
456 FILE *overdata;
457 char buffer[4096];
458 void *search;
459 struct overview_data article;
460 bool status = true;
461
462 overdata = fopen(data, "r");
463 if (overdata == NULL)
464 sysdie("Cannot open %s for reading", data);
465 expected = vector_new();
466 while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
467 line = overview_data_parse(buffer, &artnum);
468 if (group == NULL)
469 group = xstrdup(buffer);
470 vector_add(expected, line);
471 end = artnum;
472 }
473 search = overview_search_open(overview, group, 1, end + 1);
474 if (search == NULL) {
475 warn("Unable to open full search for %s", group);
476 free(group);
477 vector_free(expected);
478 fclose(overdata);
479 return false;
480 }
481 i = 0;
482 while (overview_search(overview, search, &article)) {
483 if (!check_data(group, article.number, expected->strings[i], &article))
484 status = false;
485 i++;
486 }
487 overview_search_close(overview, search);
488 if (article.number != end) {
489 warn("End of search in %s wrong: %lu != %lu", group, article.number,
490 end);
491 status = false;
492 }
493 if (i != expected->count) {
494 warn("Didn't see all expected entries in %s", group);
495 status = false;
496 }
497 free(group);
498 vector_free(expected);
499 fclose(overdata);
500 return status;
501 }
502
503 /* Run the tests on a particular overview setup. Expects to be called
504 multiple times with different inn.conf configurations to test the various
505 iterations of overview support. Takes the current test number and returns
506 the next test number. */
507 static int
overview_tests(int n)508 overview_tests(int n)
509 {
510 struct hash *groups;
511 struct overview *overview;
512 struct verify verify;
513 void *search;
514 struct overview_data data;
515 TOKEN token;
516
517 overview = overview_init();
518 if (overview == NULL)
519 die("Opening the overview database failed, cannot continue");
520 ok(n++, true);
521
522 groups = overview_load("overview/basic", overview);
523 ok(n++, true);
524 verify.status = true;
525 verify.overview = overview;
526 hash_traverse(groups, overview_verify_groups, &verify);
527 ok(n++, verify.status);
528 ok(n++, overview_verify_data("overview/basic", overview));
529 ok(n++, overview_verify_search("overview/basic", overview));
530 hash_free(groups);
531 overview_close(overview);
532 ok(n++, true);
533
534 overview = overview_init();
535 if (overview == NULL)
536 die("Opening the overview database failed, cannot continue");
537 ok(n++, true);
538
539 groups = overview_load("overview/reversed", overview);
540 ok(n++, true);
541 verify.status = true;
542 verify.overview = overview;
543 hash_traverse(groups, overview_verify_groups, &verify);
544 ok(n++, verify.status);
545 ok(n++, overview_verify_data("overview/basic", overview));
546 ok(n++, overview_verify_search("overview/basic", overview));
547 hash_free(groups);
548 overview_close(overview);
549 ok(n++, true);
550
551 overview = overview_init();
552 if (overview == NULL)
553 die("Opening the overview database failed, cannot continue");
554 ok(n++, true);
555
556 groups = overview_load("overview/high-numbered", overview);
557 ok(n++, true);
558 ok(n++, overview_verify_data("overview/high-numbered", overview));
559 ok(n++, overview_verify_full_search("overview/high-numbered", overview));
560 if (strcmp(innconf->ovmethod, "buffindexed") == 0) {
561 skip_block(n, 6, "buffindexed doesn't support cancel");
562 n += 6;
563 } else {
564 ok(n++, overview_cancel(overview, "example.test", 7498));
565 ok(n++, !overview_token(overview, "example.test", 7498, &token));
566 search = overview_search_open(overview, "example.test", 7498, 7499);
567 ok(n++, search != NULL);
568 ok(n++, overview_search(overview, search, &data));
569 ok_int(n++, 7499, data.number);
570 ok(n++, !overview_search(overview, search, &data));
571 overview_search_close(overview, search);
572 }
573 hash_free(groups);
574 overview_close(overview);
575 ok(n++, true);
576
577 overview = overview_init();
578 if (overview == NULL)
579 die("Opening the overview database failed, cannot continue");
580 ok(n++, true);
581
582 groups = overview_load("overview/bogus", overview);
583 ok(n++, true);
584 ok(n++, overview_verify_data("overview/bogus", overview));
585 hash_free(groups);
586 overview_close(overview);
587 if (system("/bin/rm -rf ov-tmp") < 0)
588 sysdie("Cannot rm ov-tmp");
589 ok(n++, true);
590 return n;
591 }
592
593 /* Run the tests on a particular overview setup. This is a copy of
594 overview_tests that closes and reopens the overview without mmap to test
595 tradindexedmmap. Takes the current test and returns the next test
596 number. */
597 static int
overview_mmap_tests(int n)598 overview_mmap_tests(int n)
599 {
600 struct hash *groups;
601 struct overview *overview;
602 struct verify verify;
603
604 overview = overview_init();
605 if (overview == NULL)
606 die("Opening the overview database failed, cannot continue");
607 ok(n++, true);
608
609 groups = overview_load("overview/basic", overview);
610 ok(n++, true);
611 overview_close(overview);
612 innconf->tradindexedmmap = false;
613 overview = overview_open(OV_READ);
614 if (overview == NULL)
615 die("Opening the overview database failed, cannot continue");
616 verify.status = true;
617 verify.overview = overview;
618 hash_traverse(groups, overview_verify_groups, &verify);
619 ok(n++, verify.status);
620 ok(n++, overview_verify_data("overview/basic", overview));
621 ok(n++, overview_verify_search("overview/basic", overview));
622 hash_free(groups);
623 overview_close(overview);
624 ok(n++, true);
625
626 innconf->tradindexedmmap = true;
627 overview = overview_init();
628 if (overview == NULL)
629 die("Opening the overview database failed, cannot continue");
630 ok(n++, true);
631
632 groups = overview_load("overview/reversed", overview);
633 ok(n++, true);
634 overview_close(overview);
635 innconf->tradindexedmmap = false;
636 overview = overview_open(OV_READ);
637 if (overview == NULL)
638 die("Opening the overview database failed, cannot continue");
639 verify.status = true;
640 verify.overview = overview;
641 hash_traverse(groups, overview_verify_groups, &verify);
642 ok(n++, verify.status);
643 ok(n++, overview_verify_data("overview/basic", overview));
644 ok(n++, overview_verify_search("overview/basic", overview));
645 hash_free(groups);
646 overview_close(overview);
647 ok(n++, true);
648
649 innconf->tradindexedmmap = true;
650 overview = overview_init();
651 if (overview == NULL)
652 die("Opening the overview database failed, cannot continue");
653 ok(n++, true);
654
655 groups = overview_load("overview/high-numbered", overview);
656 ok(n++, true);
657 overview_close(overview);
658 innconf->tradindexedmmap = false;
659 overview = overview_open(OV_READ);
660 if (overview == NULL)
661 die("Opening the overview database failed, cannot continue");
662 ok(n++, overview_verify_data("overview/high-numbered", overview));
663 ok(n++, overview_verify_full_search("overview/high-numbered", overview));
664 hash_free(groups);
665 overview_close(overview);
666 ok(n++, true);
667
668 innconf->tradindexedmmap = true;
669 overview = overview_init();
670 if (overview == NULL)
671 die("Opening the overview database failed, cannot continue");
672 ok(n++, true);
673
674 groups = overview_load("overview/bogus", overview);
675 ok(n++, true);
676 overview_close(overview);
677 innconf->tradindexedmmap = false;
678 overview = overview_open(OV_READ);
679 if (overview == NULL)
680 die("Opening the overview database failed, cannot continue");
681 ok(n++, overview_verify_data("overview/bogus", overview));
682 hash_free(groups);
683 overview_close(overview);
684 if (system("/bin/rm -rf ov-tmp") < 0)
685 sysdie("Cannot rm ov-tmp");
686 ok(n++, true);
687 return n;
688 }
689
690 int
main(void)691 main(void)
692 {
693 int n = 1;
694
695 if (access("../data/overview/basic", F_OK) == 0) {
696 if (chdir("../data") < 0) {
697 sysbail("cannot chdir to ../data");
698 }
699 } else if (access("data/overview/basic", F_OK) == 0) {
700 if (chdir("data") < 0) {
701 sysbail("cannot chdir to data");
702 }
703 } else if (access("tests/data/overview/basic", F_OK) == 0) {
704 if (chdir("tests/data") < 0) {
705 sysbail("cannot chdir to tests/data");
706 }
707 }
708
709 /* Cancels can't be tested with mmap, so there are only 21 tests there. */
710 test_init(27 * 2 + 21);
711
712 fake_innconf();
713 innconf->ovmethod = xstrdup("tradindexed");
714 innconf->tradindexedmmap = true;
715 diag("tradindexed with mmap");
716 n = overview_tests(1);
717
718 diag("tradindexed without mmap");
719 n = overview_mmap_tests(n);
720
721 free(innconf->ovmethod);
722 innconf->ovmethod = xstrdup("buffindexed");
723 diag("buffindexed");
724 overview_tests(n);
725
726 return 0;
727 }
728