]> git.ozlabs.org Git - ccan/blob - tools/ccanlint/ccanlint.c
ccanlint: fix --compiler and --cflags options to apply to _info files as well.
[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 "../read_config_header.h"
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <err.h>
28 #include <ctype.h>
29 #include <ccan/str/str.h>
30 #include <ccan/take/take.h>
31 #include <ccan/opt/opt.h>
32 #include <ccan/foreach/foreach.h>
33 #include <ccan/cast/cast.h>
34 #include <ccan/tlist/tlist.h>
35 #include <ccan/tal/path/path.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 bool keep_results = false;
46 static bool targeting = false;
47 static unsigned int timeout;
48
49 const char *config_header;
50
51 const char *ccan_dir;
52
53 #if 0
54 static void indent_print(const char *string)
55 {
56         while (*string) {
57                 unsigned int line = strcspn(string, "\n");
58                 printf("\t%.*s", line, string);
59                 if (string[line] == '\n') {
60                         printf("\n");
61                         line++;
62                 }
63                 string += line;
64         }
65 }
66 #endif
67
68 bool ask(const char *question)
69 {
70         char reply[80];
71
72         printf("%s ", question);
73         fflush(stdout);
74
75         return fgets(reply, sizeof(reply), stdin) != NULL
76                 && toupper(reply[0]) == 'Y';
77 }
78
79 /* Skip, but don't remove. */
80 static bool skip_test(struct dgraph_node *node, const char *why)
81 {
82         struct ccanlint *c = container_of(node, struct ccanlint, node);
83         c->skip = why;
84         return true;
85 }
86
87 static const char *dep_failed(struct manifest *m)
88 {
89         return "dependency couldn't run";
90 }
91
92 static bool cannot_run(struct dgraph_node *node, void *unused)
93 {
94         struct ccanlint *c = container_of(node, struct ccanlint, node);
95         c->can_run = dep_failed;
96         return true;
97 }
98
99 struct run_info {
100         bool quiet;
101         unsigned int score, total;
102         struct manifest *m;
103         const char *prefix;
104         bool pass;
105 };
106
107 static bool run_test(struct dgraph_node *n, struct run_info *run)
108 {
109         struct ccanlint *i = container_of(n, struct ccanlint, node);
110         unsigned int timeleft;
111         struct score *score;
112
113         if (i->done)
114                 return true;
115
116         score = tal(run->m, struct score);
117         list_head_init(&score->per_file_errors);
118         score->error = NULL;
119         score->pass = false;
120         score->score = 0;
121         score->total = 1;
122
123         /* We can see skipped things in two cases:
124          * (1) _info excluded them (presumably because they fail).
125          * (2) A prerequisite failed.
126          */
127         if (i->skip) {
128                 if (verbose)
129                         printf("%s%s: skipped (%s)\n",
130                                run->prefix, i->name, i->skip);
131                 /* Pass us up to the test which failed, not us. */
132                 score->pass = true;
133                 goto out;
134         }
135
136         if (i->can_run) {
137                 i->skip = i->can_run(run->m);
138                 if (i->skip) {
139                         /* Test doesn't apply, or can't run?  That's OK. */
140                         if (verbose > 1)
141                                 printf("%s%s: skipped (%s)\n",
142                                        run->prefix, i->name, i->skip);
143                         /* Mark our dependencies to skip. */
144                         dgraph_traverse_from(&i->node, cannot_run, NULL);
145                         score->pass = true;
146                         score->total = 0;
147                         goto out;
148                 }
149         }
150
151         timeleft = timeout ? timeout : default_timeout_ms;
152         i->check(run->m, &timeleft, score);
153         if (timeout && timeleft == 0) {
154                 i->skip = "timeout";
155                 if (verbose)
156                         printf("%s%s: skipped (%s)\n",
157                                run->prefix, i->name, i->skip);
158                 /* Mark our dependencies to skip. */
159                 dgraph_traverse_from(&i->node, skip_test,
160                                      "dependency timed out");
161                 score->pass = true;
162                 score->total = 0;
163                 goto out;
164         }
165
166         assert(score->score <= score->total);
167         if ((!score->pass && !run->quiet)
168             || (score->score < score->total && verbose)
169             || verbose > 1) {
170                 printf("%s%s (%s): %s",
171                        run->prefix, i->name, i->key,
172                        score->pass ? "PASS" : "FAIL");
173                 if (score->total > 1)
174                         printf(" (+%u/%u)", score->score, score->total);
175                 printf("\n");
176         }
177
178         if ((!run->quiet && !score->pass) || verbose) {
179                 if (score->error) {
180                         printf("%s%s", score->error,
181                                strends(score->error, "\n") ? "" : "\n");
182                 }
183         }
184         if (!run->quiet && score->score < score->total && i->handle)
185                 i->handle(run->m, score);
186
187         if (!score->pass) {
188                 /* Skip any tests which depend on this one. */
189                 dgraph_traverse_from(&i->node, skip_test, "dependency failed");
190         }
191
192 out:
193         run->score += score->score;
194         run->total += score->total;
195
196         /* FIXME: Free score. */
197         run->pass &= score->pass;
198         i->done = true;
199
200         if (!score->pass && i->compulsory) {
201                 warnx("%s%s failed", run->prefix, i->name);
202                 run->score = 0;
203                 return false;
204         }
205         return true;
206 }
207
208 static void register_test(struct ccanlint *test)
209 {
210         if (!strmap_add(&tests, test->key, test))
211                 err(1, "Adding test %s", test->key);
212         test->options = tal_arr(NULL, char *, 1);
213         test->options[0] = NULL;
214         dgraph_init_node(&test->node);
215 }
216
217 static bool get_test(const char *member, struct ccanlint *i,
218                      struct ccanlint **ret)
219 {
220         if (tlist_empty(&i->node.edge[DGRAPH_TO])) {
221                 *ret = i;
222                 return false;
223         }
224         return true;
225 }
226
227 /**
228  * get_next_test - retrieves the next test to be processed
229  **/
230 static inline struct ccanlint *get_next_test(void)
231 {
232         struct ccanlint *i = NULL;
233
234         strmap_iterate(&tests, get_test, &i);
235         if (i)
236                 return i;
237
238         if (strmap_empty(&tests))
239                 return NULL;
240
241         errx(1, "Can't make process; test dependency cycle");
242 }
243
244 static struct ccanlint *find_test(const char *key)
245 {
246         return strmap_get(&tests, key);
247 }
248
249 bool is_excluded(const char *name)
250 {
251         return find_test(name)->skip != NULL;
252 }
253
254 static bool init_deps(const char *member, struct ccanlint *c, void *unused)
255 {
256         char **deps = tal_strsplit(NULL, c->needs, " ", STR_EMPTY_OK);
257         unsigned int i;
258
259         for (i = 0; deps[i]; i++) {
260                 struct ccanlint *dep;
261
262                 dep = find_test(deps[i]);
263                 if (!dep)
264                         errx(1, "BUG: unknown dep '%s' for %s",
265                              deps[i], c->key);
266                 dgraph_add_edge(&dep->node, &c->node);
267         }
268         tal_free(deps);
269         return true;
270 }
271
272 static bool check_names(const char *member, struct ccanlint *c,
273                         struct ccanlint_map *names)
274 {
275         if (!strmap_add(names, c->name, c))
276                 err(1, "Duplicate name %s", c->name);
277         return true;
278 }
279
280 static void init_tests(void)
281 {
282         struct ccanlint_map names;
283         struct ccanlint **table;
284         size_t i, num;
285
286         strmap_init(&tests);
287
288         table = autodata_get(ccanlint_tests, &num);
289         for (i = 0; i < num; i++)
290                 register_test(table[i]);
291         autodata_free(table);
292
293         strmap_iterate(&tests, init_deps, NULL);
294
295         /* Check for duplicate names. */
296         strmap_init(&names);
297         strmap_iterate(&tests, check_names, &names);
298         strmap_clear(&names);
299 }
300
301 static bool reset_test(struct dgraph_node *node, void *unused)
302 {
303         struct ccanlint *c = container_of(node, struct ccanlint, node);
304         c->skip = NULL;
305         c->done = false;
306         return true;
307 }
308
309 static void reset_tests(struct dgraph_node *all)
310 {
311         dgraph_traverse_to(all, reset_test, NULL);
312 }
313
314 static bool print_deps(const char *member, struct ccanlint *c, void *unused)
315 {
316         if (!tlist_empty(&c->node.edge[DGRAPH_FROM])) {
317                 struct dgraph_edge *e;
318
319                 printf("These depend on %s:\n", c->key);
320                 dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
321                         struct ccanlint *to = container_of(e->n[DGRAPH_TO],
322                                                            struct ccanlint,
323                                                            node);
324                         printf("\t%s\n", to->key);
325                 }
326         }
327         return true;
328 }
329
330 static void print_test_depends(void)
331 {
332         printf("Tests:\n");
333
334         strmap_iterate(&tests, print_deps, NULL);
335 }
336
337
338 static void show_tmpdir(const char *dir)
339 {
340         printf("You can find ccanlint working files in '%s'\n", dir);
341 }
342
343 static char *keep_tests(void *unused)
344 {
345         keep_results = true;
346
347         /* Don't automatically destroy temporary dir. */
348         keep_temp_dir();
349         tal_add_destructor(temp_dir(), show_tmpdir);
350         return NULL;
351 }
352
353 static bool remove_test(struct dgraph_node *node, const char *why)
354 {
355         struct ccanlint *c = container_of(node, struct ccanlint, node);
356         c->skip = why;
357         dgraph_clear_node(node);
358         return true;
359 }
360
361 static char *exclude_test(const char *testname, void *unused)
362 {
363         struct ccanlint *i = find_test(testname);
364         if (!i)
365                 return tal_fmt(NULL, "No test %s to --exclude", testname);
366
367         /* Remove this, and everything which depends on it. */
368         dgraph_traverse_from(&i->node, remove_test, "excluded on command line");
369         remove_test(&i->node, "excluded on command line");
370         return NULL;
371 }
372
373 static void skip_test_and_deps(struct ccanlint *c, const char *why)
374 {
375         /* Skip this, and everything which depends on us. */
376         dgraph_traverse_from(&c->node, skip_test, why);
377         skip_test(&c->node, why);
378 }
379
380 static char *list_tests(void *arg)
381 {
382         struct ccanlint *i;
383
384         printf("Tests:\n");
385         /* This makes them print in topological order. */
386         while ((i = get_next_test()) != NULL) {
387                 printf("   %-25s %s\n", i->key, i->name);
388                 dgraph_clear_node(&i->node);
389                 strmap_del(&tests, i->key, NULL);
390         }
391         exit(0);
392 }
393
394 static bool draw_test(const char *member, struct ccanlint *c, const char *style)
395 {
396         /*
397          * todo: escape labels in case ccanlint test keys have
398          *       characters interpreted as GraphViz syntax.
399          */
400         printf("\t\"%p\" [label=\"%s\"%s]\n", c, c->key, style);
401         return true;
402 }
403
404 static void test_dgraph_vertices(const char *style)
405 {
406         strmap_iterate(&tests, draw_test, style);
407 }
408
409 static bool draw_edges(const char *member, struct ccanlint *c, void *unused)
410 {
411         struct dgraph_edge *e;
412
413         dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
414                 struct ccanlint *to = container_of(e->n[DGRAPH_TO],
415                                                    struct ccanlint,
416                                                    node);
417                 printf("\t\"%p\" -> \"%p\"\n", c->name, to->name);
418         }
419         return true;
420 }
421
422 static void test_dgraph_edges(void)
423 {
424         strmap_iterate(&tests, draw_edges, NULL);
425 }
426
427 static char *test_dependency_graph(void *arg)
428 {
429         puts("digraph G {");
430
431         test_dgraph_vertices("");
432         test_dgraph_edges();
433
434         puts("}");
435
436         exit(0);
437 }
438
439 static void add_options(struct ccanlint *test, char **options,
440                         unsigned int num_options)
441 {
442         unsigned int num;
443
444         if (!test->options)
445                 num = 0;
446         else
447                 /* -1, because last one is NULL. */
448                 num = tal_count(test->options) - 1;
449
450         tal_resize(&test->options, num + num_options + 1);
451         memcpy(&test->options[num], options, (num_options + 1)*sizeof(char *));
452 }
453
454 void add_info_options(struct ccan_file *info)
455 {
456         struct doc_section *d;
457         unsigned int i;
458         struct ccanlint *test;
459
460         list_for_each(get_ccan_file_docs(info), d, list) {
461                 if (!streq(d->type, "ccanlint"))
462                         continue;
463
464                 for (i = 0; i < d->num_lines; i++) {
465                         char **words = tal_strsplit(d, d->lines[i], " \t",
466                                                     STR_NO_EMPTY);
467                         if (!words[0])
468                                 continue;
469
470                         if (strncmp(words[0], "//", 2) == 0)
471                                 continue;
472
473                         test = find_test(words[0]);
474                         if (!test) {
475                                 warnx("%s: unknown ccanlint test '%s'",
476                                       info->fullname, words[0]);
477                                 continue;
478                         }
479
480                         if (!words[1]) {
481                                 warnx("%s: no argument to test '%s'",
482                                       info->fullname, words[0]);
483                                 continue;
484                         }
485
486                         /* Known failure? */
487                         if (strcasecmp(words[1], "FAIL") == 0) {
488                                 if (!targeting)
489                                         skip_test_and_deps(test,
490                                                            "excluded in _info"
491                                                            " file");
492                         } else {
493                                 if (!test->takes_options)
494                                         warnx("%s: %s doesn't take options",
495                                               info->fullname, words[0]);
496                                 add_options(test, words+1, tal_count(words)-1);
497                         }
498                 }
499         }
500 }
501
502 /* If options are of form "filename:<option>" they only apply to that file */
503 char **per_file_options(const struct ccanlint *test, struct ccan_file *f)
504 {
505         char **ret;
506         unsigned int i, j = 0;
507
508         /* Fast path. */
509         if (!test->options[0])
510                 return test->options;
511
512         ret = tal_arr(f, char *, tal_count(test->options));
513         for (i = 0; test->options[i]; i++) {
514                 char *optname;
515
516                 if (!test->options[i] || !strchr(test->options[i], ':')) {
517                         optname = test->options[i];
518                 } else if (strstarts(test->options[i], f->name)
519                            && test->options[i][strlen(f->name)] == ':') {
520                         optname = test->options[i] + strlen(f->name) + 1;
521                 } else
522                         continue;
523
524                 /* FAIL overrides anything else. */
525                 if (streq(optname, "FAIL")) {
526                         ret = tal_arr(f, char *, 2);
527                         ret[0] = (char *)"FAIL";
528                         ret[1] = NULL;
529                         return ret;
530                 }
531                 ret[j++] = optname;
532         }
533         ret[j] = NULL;
534
535         /* Shrink it to size so tal_array_length() works as expected. */
536         tal_resize(&ret, j + 1);
537         return ret;
538 }
539
540 static char *opt_set_const_charp(const char *arg, const char **p)
541 {
542         return opt_set_charp(arg, cast_const2(char **, p));
543 }
544
545 static char *opt_set_target(const char *arg, struct dgraph_node *all)
546 {
547         struct ccanlint *t = find_test(arg);
548         if (!t)
549                 return tal_fmt(NULL, "unknown --target %s", arg);
550
551         targeting = true;
552         dgraph_add_edge(&t->node, all);
553         return NULL;
554 }
555
556 static bool run_tests(struct dgraph_node *all,
557                       bool summary,
558                       struct manifest *m,
559                       const char *prefix)
560 {
561         struct run_info run;
562
563         run.quiet = summary;
564         run.m = m;
565         run.prefix = prefix;
566         run.score = run.total = 0;
567         run.pass = true;
568
569         dgraph_traverse_to(all, run_test, &run);
570
571         printf("%sTotal score: %u/%u\n", prefix, run.score, run.total);
572         return run.pass;
573 }
574
575 static bool add_to_all(const char *member, struct ccanlint *c,
576                        struct dgraph_node *all)
577 {
578         /* If we're excluded on cmdline, don't add. */
579         if (!c->skip)
580                 dgraph_add_edge(&c->node, all);
581         return true;
582 }
583
584 static bool test_module(struct dgraph_node *all,
585                         const char *dir, const char *prefix, bool summary)
586 {
587         struct manifest *m = get_manifest(autofree(), dir);
588         char *testlink = path_join(NULL, temp_dir(), "test");
589
590         /* Create a symlink from temp dir back to src dir's
591          * test directory. */
592         unlink(testlink);
593         if (symlink(path_join(m, dir, "test"), testlink) != 0)
594                 err(1, "Creating test symlink in %s", temp_dir());
595
596         return run_tests(all, summary, m, prefix);
597 }
598
599 int main(int argc, char *argv[])
600 {
601         bool summary = false, pass = true;
602         unsigned int i;
603         const char *prefix = "";
604         char *cwd = path_cwd(NULL), *dir;
605         struct dgraph_node all;
606         const char *override_compiler = NULL, *override_cflags = NULL;
607         
608         /* Empty graph node to which we attach everything else. */
609         dgraph_init_node(&all);
610
611         opt_register_early_noarg("--verbose|-v", opt_inc_intval, &verbose,
612                                  "verbose mode (up to -vvvv)");
613         opt_register_noarg("-n|--safe-mode", opt_set_bool, &safe_mode,
614                          "do not compile anything");
615         opt_register_noarg("-l|--list-tests", list_tests, NULL,
616                          "list tests ccanlint performs (and exit)");
617         opt_register_noarg("--test-dep-graph", test_dependency_graph, NULL,
618                          "print dependency graph of tests in Graphviz .dot format");
619         opt_register_noarg("-k|--keep", keep_tests, NULL,
620                          "do not delete ccanlint working files");
621         opt_register_noarg("--summary|-s", opt_set_bool, &summary,
622                            "simply give one line summary");
623         opt_register_arg("-x|--exclude <testname>", exclude_test, NULL, NULL,
624                          "exclude <testname> (can be used multiple times)");
625         opt_register_arg("--timeout <milleseconds>", opt_set_uintval,
626                          NULL, &timeout,
627                          "ignore (terminate) tests that are slower than this");
628         opt_register_arg("-t|--target <testname>", opt_set_target, NULL, &all,
629                          "only run one test (and its prerequisites)");
630         opt_register_arg("--compiler <compiler>", opt_set_const_charp,
631                          NULL, &override_compiler, "set the compiler");
632         opt_register_arg("--cflags <flags>", opt_set_const_charp,
633                          NULL, &override_cflags, "set the compiler flags");
634         opt_register_noarg("-?|-h|--help", opt_usage_and_exit,
635                            "\nA program for checking and guiding development"
636                            " of CCAN modules.",
637                            "This usage message");
638
639         /* Do verbose before anything else... */
640         opt_early_parse(argc, argv, opt_log_stderr_exit);
641
642         /* We move into temporary directory, so gcov dumps its files there. */
643         if (chdir(temp_dir()) != 0)
644                 err(1, "Error changing to %s temporary dir", temp_dir());
645
646         init_tests();
647
648         if (verbose >= 3) {
649                 compile_verbose = true;
650                 print_test_depends();
651         }
652         if (verbose >= 4)
653                 tools_verbose = true;
654
655         opt_parse(&argc, argv, opt_log_stderr_exit);
656
657         if (!targeting)
658                 strmap_iterate(&tests, add_to_all, &all);
659
660         if (argc == 1)
661                 dir = cwd;
662         else
663                 dir = path_join(NULL, cwd, argv[1]);
664
665         ccan_dir = find_ccan_dir(dir);
666         if (!ccan_dir)
667                 errx(1, "Cannot find ccan/ base directory in %s", dir);
668         config_header = read_config_header(ccan_dir, verbose > 1);
669
670         /* We do this after read_config_header has set compiler & cflags */
671         if (override_cflags)
672                 cflags = override_cflags;
673         if (override_compiler)
674                 compiler = override_compiler;
675
676         if (argc == 1)
677                 pass = test_module(&all, cwd, "", summary);
678         else {
679                 for (i = 1; i < argc; i++) {
680                         dir = path_canon(NULL,
681                                          take(path_join(NULL, cwd, argv[i])));
682
683                         prefix = path_join(NULL, ccan_dir, "ccan");
684                         prefix = path_rel(NULL, take(prefix), dir);
685                         prefix = tal_strcat(NULL, take(prefix), ": ");
686
687                         pass &= test_module(&all, dir, prefix, summary);
688                         reset_tests(&all);
689                 }
690         }
691         return pass ? 0 : 1;
692 }