]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/ccanlint.c
5abcb9e8920ddfd36bc7401031db928bbbfa5fa0
[ccan] / tools / ccanlint / ccanlint.c
1 /*
2  * ccanlint: assorted checks and advice for a ccan package
3  * Copyright (C) 2008 Rusty Russell, Idris Soule
4  * Copyright (C) 2010 Rusty Russell, Idris Soule
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; either version 2 of the License, or (at your option)
9  * any later version.
10  *
11  *   This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * this program; if not, write to the Free Software Foundation, Inc., 51
18  * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 #include "ccanlint.h"
21 #include "../tools.h"
22 #include <unistd.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <err.h>
27 #include <ctype.h>
28 #include <ccan/str/str.h>
29 #include <ccan/str_talloc/str_talloc.h>
30 #include <ccan/talloc/talloc.h>
31 #include <ccan/opt/opt.h>
32 #include <ccan/foreach/foreach.h>
33 #include <ccan/grab_file/grab_file.h>
34 #include <ccan/cast/cast.h>
35 #include <ccan/tlist/tlist.h>
36 #include <ccan/strmap/strmap.h>
37
38 struct ccanlint_map {
39         STRMAP_MEMBERS(struct ccanlint *);
40 };
41
42 int verbose = 0;
43 static struct ccanlint_map tests;
44 bool safe_mode = false;
45 static bool targeting = false;
46 static unsigned int timeout;
47
48 /* These are overridden at runtime if we can find config.h */
49 const char *compiler = NULL;
50 const char *cflags = NULL;
51
52 const char *config_header;
53
54 #if 0
55 static void indent_print(const char *string)
56 {
57         while (*string) {
58                 unsigned int line = strcspn(string, "\n");
59                 printf("\t%.*s", line, string);
60                 if (string[line] == '\n') {
61                         printf("\n");
62                         line++;
63                 }
64                 string += line;
65         }
66 }
67 #endif
68
69 bool ask(const char *question)
70 {
71         char reply[80];
72
73         printf("%s ", question);
74         fflush(stdout);
75
76         return fgets(reply, sizeof(reply), stdin) != NULL
77                 && toupper(reply[0]) == 'Y';
78 }
79
80 /* Skip, but don't remove. */
81 static bool skip_test(struct dgraph_node *node, const char *why)
82 {
83         struct ccanlint *c = container_of(node, struct ccanlint, node);
84         c->skip = why;
85         return true;
86 }
87
88 static const char *dep_failed(struct manifest *m)
89 {
90         return "dependency couldn't run";
91 }
92
93 static bool cannot_run(struct dgraph_node *node, void *unused)
94 {
95         struct ccanlint *c = container_of(node, struct ccanlint, node);
96         c->can_run = dep_failed;
97         return true;
98 }
99
100 struct run_info {
101         bool quiet;
102         unsigned int score, total;
103         struct manifest *m;
104         const char *prefix;
105         bool pass;
106 };
107
108 static bool run_test(struct dgraph_node *n, struct run_info *run)
109 {
110         struct ccanlint *i = container_of(n, struct ccanlint, node);
111         unsigned int timeleft;
112         struct score *score;
113
114         if (i->done)
115                 return true;
116
117         score = talloc(run->m, struct score);
118         list_head_init(&score->per_file_errors);
119         score->error = NULL;
120         score->pass = false;
121         score->score = 0;
122         score->total = 1;
123
124         /* We can see skipped things in two cases:
125          * (1) _info excluded them (presumably because they fail).
126          * (2) A prerequisite failed.
127          */
128         if (i->skip) {
129                 if (verbose)
130                         printf("%s%s: skipped (%s)\n",
131                                run->prefix, i->name, i->skip);
132                 /* Pass us up to the test which failed, not us. */
133                 score->pass = true;
134                 goto out;
135         }
136
137         if (i->can_run) {
138                 i->skip = i->can_run(run->m);
139                 if (i->skip) {
140                         /* Test doesn't apply, or can't run?  That's OK. */
141                         if (verbose > 1)
142                                 printf("%s%s: skipped (%s)\n",
143                                        run->prefix, i->name, i->skip);
144                         /* Mark our dependencies to skip. */
145                         dgraph_traverse_from(&i->node, cannot_run, NULL);
146                         score->pass = true;
147                         score->total = 0;
148                         goto out;
149                 }
150         }
151
152         timeleft = timeout ? timeout : default_timeout_ms;
153         i->check(run->m, i->keep_results, &timeleft, score);
154         if (timeout && timeleft == 0) {
155                 i->skip = "timeout";
156                 if (verbose)
157                         printf("%s%s: skipped (%s)\n",
158                                run->prefix, i->name, i->skip);
159                 /* Mark our dependencies to skip. */
160                 dgraph_traverse_from(&i->node, skip_test,
161                                      "dependency timed out");
162                 score->pass = true;
163                 score->total = 0;
164                 goto out;
165         }
166
167         assert(score->score <= score->total);
168         if ((!score->pass && !run->quiet)
169             || (score->score < score->total && verbose)
170             || verbose > 1) {
171                 printf("%s%s (%s): %s",
172                        run->prefix, i->name, i->key,
173                        score->pass ? "PASS" : "FAIL");
174                 if (score->total > 1)
175                         printf(" (+%u/%u)", score->score, score->total);
176                 printf("\n");
177         }
178
179         if ((!run->quiet && !score->pass) || verbose) {
180                 if (score->error) {
181                         printf("%s%s", score->error,
182                                strends(score->error, "\n") ? "" : "\n");
183                 }
184         }
185         if (!run->quiet && score->score < score->total && i->handle)
186                 i->handle(run->m, score);
187
188         if (!score->pass) {
189                 /* Skip any tests which depend on this one. */
190                 dgraph_traverse_from(&i->node, skip_test, "dependency failed");
191         }
192
193 out:
194         run->score += score->score;
195         run->total += score->total;
196
197         /* FIXME: Free score. */
198         run->pass &= score->pass;
199         i->done = true;
200
201         if (!score->pass && i->compulsory) {
202                 warnx("%s%s failed", run->prefix, i->name);
203                 run->score = 0;
204                 return false;
205         }
206         return true;
207 }
208
209 static void register_test(struct ccanlint *test)
210 {
211         if (!strmap_add(&tests, test->key, test))
212                 err(1, "Adding test %s", test->key);
213         test->options = talloc_array(NULL, char *, 1);
214         test->options[0] = NULL;
215         dgraph_init_node(&test->node);
216 }
217
218 static bool get_test(const char *member, struct ccanlint *i,
219                      struct ccanlint **ret)
220 {
221         if (tlist_empty(&i->node.edge[DGRAPH_TO])) {
222                 *ret = i;
223                 return false;
224         }
225         return true;
226 }
227
228 /**
229  * get_next_test - retrieves the next test to be processed
230  **/
231 static inline struct ccanlint *get_next_test(void)
232 {
233         struct ccanlint *i = NULL;
234
235         strmap_iterate(&tests, get_test, &i);
236         if (i)
237                 return i;
238
239         if (strmap_empty(&tests))
240                 return NULL;
241
242         errx(1, "Can't make process; test dependency cycle");
243 }
244
245 static struct ccanlint *find_test(const char *key)
246 {
247         return strmap_get(&tests, key);
248 }
249
250 bool is_excluded(const char *name)
251 {
252         return find_test(name)->skip != NULL;
253 }
254
255 static bool init_deps(const char *member, struct ccanlint *c, void *unused)
256 {
257         char **deps = strsplit(NULL, c->needs, " ");
258         unsigned int i;
259
260         for (i = 0; deps[i]; i++) {
261                 struct ccanlint *dep;
262
263                 dep = find_test(deps[i]);
264                 if (!dep)
265                         errx(1, "BUG: unknown dep '%s' for %s",
266                              deps[i], c->key);
267                 dgraph_add_edge(&dep->node, &c->node);
268         }
269         talloc_free(deps);
270         return true;
271 }
272
273 static bool check_names(const char *member, struct ccanlint *c,
274                         struct ccanlint_map *names)
275 {
276         if (!strmap_add(names, c->name, c))
277                 err(1, "Duplicate name %s", c->name);
278         return true;
279 }
280
281 #undef REGISTER_TEST
282 #define REGISTER_TEST(name, ...) extern struct ccanlint name
283 #include "generated-testlist"
284
285 static void init_tests(void)
286 {
287         struct ccanlint_map names;
288
289         strmap_init(&tests);
290
291 #undef REGISTER_TEST
292 #define REGISTER_TEST(name) register_test(&name)
293 #include "generated-testlist"
294
295         strmap_iterate(&tests, init_deps, NULL);
296
297         /* Check for duplicate names. */
298         strmap_init(&names);
299         strmap_iterate(&tests, check_names, &names);
300         strmap_clear(&names);
301 }
302
303 static bool reset_test(struct dgraph_node *node, void *unused)
304 {
305         struct ccanlint *c = container_of(node, struct ccanlint, node);
306         c->skip = NULL;
307         c->done = false;
308         return true;
309 }
310
311 static void reset_tests(struct dgraph_node *all)
312 {
313         dgraph_traverse_to(all, reset_test, NULL);
314 }
315
316 static bool print_deps(const char *member, struct ccanlint *c, void *unused)
317 {
318         if (!tlist_empty(&c->node.edge[DGRAPH_FROM])) {
319                 struct dgraph_edge *e;
320
321                 printf("These depend on %s:\n", c->key);
322                 dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
323                         struct ccanlint *to = container_of(e->n[DGRAPH_TO],
324                                                            struct ccanlint,
325                                                            node);
326                         printf("\t%s\n", to->key);
327                 }
328         }
329         return true;
330 }
331
332 static void print_test_depends(void)
333 {
334         printf("Tests:\n");
335
336         strmap_iterate(&tests, print_deps, NULL);
337 }
338
339
340 static int show_tmpdir(const char *dir)
341 {
342         printf("You can find ccanlint working files in '%s'\n", dir);
343         return 0;
344 }
345
346 static bool keep_one_test(const char *member, struct ccanlint *c, void *unused)
347 {
348         c->keep_results = true;
349         return true;
350 }
351
352 static char *keep_test(const char *testname, void *unused)
353 {
354         if (streq(testname, "all")) {
355                 strmap_iterate(&tests, keep_one_test, NULL);
356         } else {
357                 struct ccanlint *i = find_test(testname);
358                 if (!i)
359                         errx(1, "No test %s to --keep", testname);
360                 keep_one_test(testname, i, NULL);
361         }
362
363         /* Don't automatically destroy temporary dir. */
364         talloc_set_destructor(temp_dir(NULL), show_tmpdir);
365         return NULL;
366 }
367
368 static bool remove_test(struct dgraph_node *node, const char *why)
369 {
370         struct ccanlint *c = container_of(node, struct ccanlint, node);
371         c->skip = why;
372         dgraph_clear_node(node);
373         return true;
374 }
375
376 static char *exclude_test(const char *testname, void *unused)
377 {
378         struct ccanlint *i = find_test(testname);
379         if (!i)
380                 return talloc_asprintf(NULL, "No test %s to --exclude",
381                                        testname);
382
383         /* Remove this, and everything which depends on it. */
384         dgraph_traverse_from(&i->node, remove_test, "excluded on command line");
385         remove_test(&i->node, "excluded on command line");
386         return NULL;
387 }
388
389 static void skip_test_and_deps(struct ccanlint *c, const char *why)
390 {
391         /* Skip this, and everything which depends on us. */
392         dgraph_traverse_from(&c->node, skip_test, why);
393         skip_test(&c->node, why);
394 }
395
396 static char *list_tests(void *arg)
397 {
398         struct ccanlint *i;
399
400         printf("Tests:\n");
401         /* This makes them print in topological order. */
402         while ((i = get_next_test()) != NULL) {
403                 printf("   %-25s %s\n", i->key, i->name);
404                 dgraph_clear_node(&i->node);
405                 strmap_del(&tests, i->key, NULL);
406         }
407         exit(0);
408 }
409
410 static bool draw_test(const char *member, struct ccanlint *c, const char *style)
411 {
412         /*
413          * todo: escape labels in case ccanlint test keys have
414          *       characters interpreted as GraphViz syntax.
415          */
416         printf("\t\"%p\" [label=\"%s\"%s]\n", c, c->key, style);
417         return true;
418 }
419
420 static void test_dgraph_vertices(const char *style)
421 {
422         strmap_iterate(&tests, draw_test, style);
423 }
424
425 static bool draw_edges(const char *member, struct ccanlint *c, void *unused)
426 {
427         struct dgraph_edge *e;
428
429         dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
430                 struct ccanlint *to = container_of(e->n[DGRAPH_TO],
431                                                    struct ccanlint,
432                                                    node);
433                 printf("\t\"%p\" -> \"%p\"\n", c->name, to->name);
434         }
435         return true;
436 }
437
438 static void test_dgraph_edges(void)
439 {
440         strmap_iterate(&tests, draw_edges, NULL);
441 }
442
443 static char *test_dependency_graph(void *arg)
444 {
445         puts("digraph G {");
446
447         test_dgraph_vertices("");
448         test_dgraph_edges();
449
450         puts("}");
451
452         exit(0);
453 }
454
455 /* Remove empty lines. */
456 static char **collapse(char **lines, unsigned int *nump)
457 {
458         unsigned int i, j;
459         for (i = j = 0; lines[i]; i++) {
460                 if (lines[i][0])
461                         lines[j++] = lines[i];
462         }
463         lines[j] = NULL;
464         if (nump)
465                 *nump = j;
466         return lines;
467 }
468
469
470 static void add_options(struct ccanlint *test, char **options,
471                         unsigned int num_options)
472 {
473         unsigned int num;
474
475         if (!test->options)
476                 num = 0;
477         else
478                 /* -1, because last one is NULL. */
479                 num = talloc_array_length(test->options) - 1;
480
481         test->options = talloc_realloc(NULL, test->options,
482                                        char *,
483                                        num + num_options + 1);
484         memcpy(&test->options[num], options, (num_options + 1)*sizeof(char *));
485 }
486
487 void add_info_options(struct ccan_file *info)
488 {
489         struct doc_section *d;
490         unsigned int i;
491         struct ccanlint *test;
492
493         list_for_each(get_ccan_file_docs(info), d, list) {
494                 if (!streq(d->type, "ccanlint"))
495                         continue;
496
497                 for (i = 0; i < d->num_lines; i++) {
498                         unsigned int num_words;
499                         char **words = collapse(strsplit(d, d->lines[i], " \t"),
500                                                 &num_words);
501                         if (num_words == 0)
502                                 continue;
503
504                         if (strncmp(words[0], "//", 2) == 0)
505                                 continue;
506
507                         test = find_test(words[0]);
508                         if (!test) {
509                                 warnx("%s: unknown ccanlint test '%s'",
510                                       info->fullname, words[0]);
511                                 continue;
512                         }
513
514                         if (!words[1]) {
515                                 warnx("%s: no argument to test '%s'",
516                                       info->fullname, words[0]);
517                                 continue;
518                         }
519
520                         /* Known failure? */
521                         if (strcasecmp(words[1], "FAIL") == 0) {
522                                 if (!targeting)
523                                         skip_test_and_deps(test,
524                                                            "excluded in _info"
525                                                            " file");
526                         } else {
527                                 if (!test->takes_options)
528                                         warnx("%s: %s doesn't take options",
529                                               info->fullname, words[0]);
530                                 add_options(test, words+1, num_words-1);
531                         }
532                 }
533         }
534 }
535
536 /* If options are of form "filename:<option>" they only apply to that file */
537 char **per_file_options(const struct ccanlint *test, struct ccan_file *f)
538 {
539         char **ret;
540         unsigned int i, j = 0;
541
542         /* Fast path. */
543         if (!test->options[0])
544                 return test->options;
545
546         ret = talloc_array(f, char *, talloc_array_length(test->options));
547         for (i = 0; test->options[i]; i++) {
548                 char *optname;
549
550                 if (!test->options[i] || !strchr(test->options[i], ':')) {
551                         optname = test->options[i];
552                 } else if (strstarts(test->options[i], f->name)
553                            && test->options[i][strlen(f->name)] == ':') {
554                         optname = test->options[i] + strlen(f->name) + 1;
555                 } else
556                         continue;
557
558                 /* FAIL overrides anything else. */
559                 if (streq(optname, "FAIL")) {
560                         ret = talloc_array(f, char *, 2);
561                         ret[0] = (char *)"FAIL";
562                         ret[1] = NULL;
563                         return ret;
564                 }
565                 ret[j++] = optname;
566         }
567         ret[j] = NULL;
568
569         /* Shrink it to size so talloc_array_length() works as expected. */
570         return talloc_realloc(NULL, ret, char *, j + 1);
571 }
572
573 static char *demangle_string(char *string)
574 {
575         unsigned int i;
576         const char mapfrom[] = "abfnrtv";
577         const char mapto[] = "\a\b\f\n\r\t\v";
578
579         if (!strchr(string, '"'))
580                 return NULL;
581         string = strchr(string, '"') + 1;
582         if (!strrchr(string, '"'))
583                 return NULL;
584         *strrchr(string, '"') = '\0';
585
586         for (i = 0; i < strlen(string); i++) {
587                 if (string[i] == '\\') {
588                         char repl;
589                         unsigned len = 0;
590                         const char *p = strchr(mapfrom, string[i+1]);
591                         if (p) {
592                                 repl = mapto[p - mapfrom];
593                                 len = 1;
594                         } else if (strlen(string+i+1) >= 3) {
595                                 if (string[i+1] == 'x') {
596                                         repl = (string[i+2]-'0')*16
597                                                 + string[i+3]-'0';
598                                         len = 3;
599                                 } else if (cisdigit(string[i+1])) {
600                                         repl = (string[i+2]-'0')*8*8
601                                                 + (string[i+3]-'0')*8
602                                                 + (string[i+4]-'0');
603                                         len = 3;
604                                 }
605                         }
606                         if (len == 0) {
607                                 repl = string[i+1];
608                                 len = 1;
609                         }
610
611                         string[i] = repl;
612                         memmove(string + i + 1, string + i + len + 1,
613                                 strlen(string + i + len + 1) + 1);
614                 }
615         }
616
617         return string;
618 }
619
620
621 static void read_config_header(void)
622 {
623         char *fname = talloc_asprintf(NULL, "%s/config.h", ccan_dir);
624         char **lines;
625         unsigned int i;
626
627         config_header = grab_file(NULL, fname, NULL);
628         if (!config_header) {
629                 talloc_free(fname);
630                 return;
631         }
632
633         lines = strsplit(config_header, config_header, "\n");
634         for (i = 0; i < talloc_array_length(lines) - 1; i++) {
635                 char *sym;
636                 const char **line = (const char **)&lines[i];
637
638                 if (!get_token(line, "#"))
639                         continue;
640                 if (!get_token(line, "define"))
641                         continue;
642                 sym = get_symbol_token(lines, line);
643                 if (streq(sym, "CCAN_COMPILER") && !compiler) {
644                         compiler = demangle_string(lines[i]);
645                         if (!compiler)
646                                 errx(1, "%s:%u:could not parse CCAN_COMPILER",
647                                      fname, i+1);
648                         if (verbose > 1)
649                                 printf("%s: compiler set to '%s'\n",
650                                        fname, compiler);
651                 } else if (streq(sym, "CCAN_CFLAGS") && !cflags) {
652                         cflags = demangle_string(lines[i]);
653                         if (!cflags)
654                                 errx(1, "%s:%u:could not parse CCAN_CFLAGS",
655                                      fname, i+1);
656                         if (verbose > 1)
657                                 printf("%s: compiler flags set to '%s'\n",
658                                        fname, cflags);
659                 }
660         }
661         if (!compiler)
662                 compiler = CCAN_COMPILER;
663         if (!cflags)
664                 compiler = CCAN_CFLAGS;
665 }
666
667 static char *opt_set_const_charp(const char *arg, const char **p)
668 {
669         return opt_set_charp(arg, cast_const2(char **, p));
670 }
671
672 static char *opt_set_target(const char *arg, struct dgraph_node *all)
673 {
674         struct ccanlint *t = find_test(arg);
675         if (!t)
676                 return talloc_asprintf(NULL, "unknown --target %s", arg);
677
678         targeting = true;
679         dgraph_add_edge(&t->node, all);
680         return NULL;
681 }
682
683 static bool run_tests(struct dgraph_node *all,
684                       bool summary,
685                       struct manifest *m,
686                       const char *prefix)
687 {
688         struct run_info run;
689
690         run.quiet = summary;
691         run.m = m;
692         run.prefix = prefix;
693         run.score = run.total = 0;
694         run.pass = true;
695
696         dgraph_traverse_to(all, run_test, &run);
697
698         printf("%sTotal score: %u/%u\n", prefix, run.score, run.total);
699         return run.pass;
700 }
701
702 static bool add_to_all(const char *member, struct ccanlint *c,
703                        struct dgraph_node *all)
704 {
705         dgraph_add_edge(&c->node, all);
706         return true;
707 }
708
709 int main(int argc, char *argv[])
710 {
711         bool summary = false, pass = true;
712         unsigned int i;
713         struct manifest *m;
714         const char *prefix = "";
715         char *dir = talloc_getcwd(NULL), *base_dir = dir, *testlink;
716         struct dgraph_node all;
717         
718         /* Empty graph node to which we attach everything else. */
719         dgraph_init_node(&all);
720
721         opt_register_early_noarg("--verbose|-v", opt_inc_intval, &verbose,
722                                  "verbose mode (up to -vvvv)");
723         opt_register_noarg("-n|--safe-mode", opt_set_bool, &safe_mode,
724                          "do not compile anything");
725         opt_register_noarg("-l|--list-tests", list_tests, NULL,
726                          "list tests ccanlint performs (and exit)");
727         opt_register_noarg("--test-dep-graph", test_dependency_graph, NULL,
728                          "print dependency graph of tests in Graphviz .dot format");
729         opt_register_arg("-k|--keep <testname>", keep_test, NULL, NULL,
730                          "keep results of <testname>"
731                          " (can be used multiple times, or 'all')");
732         opt_register_noarg("--summary|-s", opt_set_bool, &summary,
733                            "simply give one line summary");
734         opt_register_arg("-x|--exclude <testname>", exclude_test, NULL, NULL,
735                          "exclude <testname> (can be used multiple times)");
736         opt_register_arg("--timeout <milleseconds>", opt_set_uintval,
737                          NULL, &timeout,
738                          "ignore (terminate) tests that are slower than this");
739         opt_register_arg("-t|--target <testname>", opt_set_target, NULL, &all,
740                          "only run one test (and its prerequisites)");
741         opt_register_arg("--compiler <compiler>", opt_set_const_charp,
742                          NULL, &compiler, "set the compiler");
743         opt_register_arg("--cflags <flags>", opt_set_const_charp,
744                          NULL, &cflags, "set the compiler flags");
745         opt_register_noarg("-?|-h|--help", opt_usage_and_exit,
746                            "\nA program for checking and guiding development"
747                            " of CCAN modules.",
748                            "This usage message");
749
750         /* Do verbose before anything else... */
751         opt_early_parse(argc, argv, opt_log_stderr_exit);
752
753         /* We move into temporary directory, so gcov dumps its files there. */
754         if (chdir(temp_dir(talloc_autofree_context())) != 0)
755                 err(1, "Error changing to %s temporary dir", temp_dir(NULL));
756
757         init_tests();
758
759         if (verbose >= 3) {
760                 compile_verbose = true;
761                 print_test_depends();
762         }
763         if (verbose >= 4)
764                 tools_verbose = true;
765
766         opt_parse(&argc, argv, opt_log_stderr_exit);
767
768         if (!targeting)
769                 strmap_iterate(&tests, add_to_all, &all);
770
771         /* This links back to the module's test dir. */
772         testlink = talloc_asprintf(NULL, "%s/test", temp_dir(NULL));
773
774         /* Defaults to pwd. */
775         if (argc == 1) {
776                 i = 1;
777                 goto got_dir;
778         }
779
780         for (i = 1; i < argc; i++) {
781                 unsigned int score, total_score;
782
783                 dir = argv[i];
784
785                 if (dir[0] != '/')
786                         dir = talloc_asprintf_append(NULL, "%s/%s",
787                                                      base_dir, dir);
788                 while (strends(dir, "/"))
789                         dir[strlen(dir)-1] = '\0';
790
791         got_dir:
792                 if (dir != base_dir)
793                         prefix = talloc_append_string(talloc_basename(NULL,dir),
794                                                       ": ");
795
796                 m = get_manifest(talloc_autofree_context(), dir);
797
798                 /* FIXME: This has to come after we've got manifest. */
799                 if (i == 1)
800                         read_config_header();
801
802                 /* Create a symlink from temp dir back to src dir's
803                  * test directory. */
804                 unlink(testlink);
805                 if (symlink(talloc_asprintf(m, "%s/test", dir), testlink) != 0)
806                         err(1, "Creating test symlink in %s", temp_dir(NULL));
807
808                 score = total_score = 0;
809                 if (!run_tests(&all, summary, m, prefix))
810                         pass = false;
811
812                 reset_tests(&all);
813         }
814         return pass ? 0 : 1;
815 }