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
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)
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
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.
22 #include "../read_config_header.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>
39 STRMAP_MEMBERS(struct ccanlint *);
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;
49 const char *config_header;
54 static void indent_print(const char *string)
57 unsigned int line = strcspn(string, "\n");
58 printf("\t%.*s", line, string);
59 if (string[line] == '\n') {
68 bool ask(const char *question)
72 printf("%s ", question);
75 return fgets(reply, sizeof(reply), stdin) != NULL
76 && toupper(reply[0]) == 'Y';
79 /* Skip, but don't remove. */
80 static bool skip_test(struct dgraph_node *node, const char *why)
82 struct ccanlint *c = container_of(node, struct ccanlint, node);
87 static const char *dep_failed(struct manifest *m)
89 return "dependency couldn't run";
92 static bool cannot_run(struct dgraph_node *node, void *unused)
94 struct ccanlint *c = container_of(node, struct ccanlint, node);
95 c->can_run = dep_failed;
101 unsigned int score, total;
107 static bool run_test(struct dgraph_node *n, struct run_info *run)
109 struct ccanlint *i = container_of(n, struct ccanlint, node);
110 unsigned int timeleft;
116 score = tal(run->m, struct score);
117 list_head_init(&score->per_file_errors);
123 /* We can see skipped things in two cases:
124 * (1) _info excluded them (presumably because they fail).
125 * (2) A prerequisite failed.
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. */
137 i->skip = i->can_run(run->m);
139 /* Test doesn't apply, or can't run? That's OK. */
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);
151 timeleft = timeout ? timeout : default_timeout_ms;
152 i->check(run->m, &timeleft, score);
153 if (timeout && timeleft == 0) {
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");
166 assert(score->score <= score->total);
167 if ((!score->pass && !run->quiet)
168 || (score->score < score->total && verbose)
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);
178 if ((!run->quiet && !score->pass) || verbose) {
180 printf("%s%s", score->error,
181 strends(score->error, "\n") ? "" : "\n");
184 if (!run->quiet && score->score < score->total && i->handle)
185 i->handle(run->m, score);
188 /* Skip any tests which depend on this one. */
189 dgraph_traverse_from(&i->node, skip_test, "dependency failed");
193 run->score += score->score;
194 run->total += score->total;
196 /* FIXME: Free score. */
197 run->pass &= score->pass;
200 if (!score->pass && i->compulsory) {
201 warnx("%s%s failed", run->prefix, i->name);
208 static void register_test(struct ccanlint *test)
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);
217 static bool get_test(const char *member, struct ccanlint *i,
218 struct ccanlint **ret)
220 if (tlist_empty(&i->node.edge[DGRAPH_TO])) {
228 * get_next_test - retrieves the next test to be processed
230 static inline struct ccanlint *get_next_test(void)
232 struct ccanlint *i = NULL;
234 strmap_iterate(&tests, get_test, &i);
238 if (strmap_empty(&tests))
241 errx(1, "Can't make process; test dependency cycle");
244 static struct ccanlint *find_test(const char *key)
246 return strmap_get(&tests, key);
249 bool is_excluded(const char *name)
251 return find_test(name)->skip != NULL;
254 static bool init_deps(const char *member, struct ccanlint *c, void *unused)
256 char **deps = tal_strsplit(NULL, c->needs, " ", STR_EMPTY_OK);
259 for (i = 0; deps[i]; i++) {
260 struct ccanlint *dep;
262 dep = find_test(deps[i]);
264 errx(1, "BUG: unknown dep '%s' for %s",
266 dgraph_add_edge(&dep->node, &c->node);
272 static bool check_names(const char *member, struct ccanlint *c,
273 struct ccanlint_map *names)
275 if (!strmap_add(names, c->name, c))
276 err(1, "Duplicate name %s", c->name);
280 static void init_tests(void)
282 struct ccanlint_map names;
283 struct ccanlint **table;
288 table = autodata_get(ccanlint_tests, &num);
289 for (i = 0; i < num; i++)
290 register_test(table[i]);
291 autodata_free(table);
293 strmap_iterate(&tests, init_deps, NULL);
295 /* Check for duplicate names. */
297 strmap_iterate(&tests, check_names, &names);
298 strmap_clear(&names);
301 static bool reset_test(struct dgraph_node *node, void *unused)
303 struct ccanlint *c = container_of(node, struct ccanlint, node);
309 static void reset_tests(struct dgraph_node *all)
311 dgraph_traverse_to(all, reset_test, NULL);
314 static bool print_deps(const char *member, struct ccanlint *c, void *unused)
316 if (!tlist_empty(&c->node.edge[DGRAPH_FROM])) {
317 struct dgraph_edge *e;
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],
324 printf("\t%s\n", to->key);
330 static void print_test_depends(void)
334 strmap_iterate(&tests, print_deps, NULL);
338 static void show_tmpdir(const char *dir)
340 printf("You can find ccanlint working files in '%s'\n", dir);
343 static char *keep_tests(void *unused)
347 /* Don't automatically destroy temporary dir. */
349 tal_add_destructor(temp_dir(), show_tmpdir);
353 static bool remove_test(struct dgraph_node *node, const char *why)
355 struct ccanlint *c = container_of(node, struct ccanlint, node);
357 dgraph_clear_node(node);
361 static char *exclude_test(const char *testname, void *unused)
363 struct ccanlint *i = find_test(testname);
365 return tal_fmt(NULL, "No test %s to --exclude", testname);
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");
373 static void skip_test_and_deps(struct ccanlint *c, const char *why)
375 /* Skip this, and everything which depends on us. */
376 dgraph_traverse_from(&c->node, skip_test, why);
377 skip_test(&c->node, why);
380 static char *list_tests(void *arg)
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);
394 static bool draw_test(const char *member, struct ccanlint *c, const char *style)
397 * todo: escape labels in case ccanlint test keys have
398 * characters interpreted as GraphViz syntax.
400 printf("\t\"%p\" [label=\"%s\"%s]\n", c, c->key, style);
404 static void test_dgraph_vertices(const char *style)
406 strmap_iterate(&tests, draw_test, style);
409 static bool draw_edges(const char *member, struct ccanlint *c, void *unused)
411 struct dgraph_edge *e;
413 dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
414 struct ccanlint *to = container_of(e->n[DGRAPH_TO],
417 printf("\t\"%p\" -> \"%p\"\n", c->name, to->name);
422 static void test_dgraph_edges(void)
424 strmap_iterate(&tests, draw_edges, NULL);
427 static char *test_dependency_graph(void *arg)
431 test_dgraph_vertices("");
439 static void add_options(struct ccanlint *test, char **options,
440 unsigned int num_options)
447 /* -1, because last one is NULL. */
448 num = tal_count(test->options) - 1;
450 tal_resize(&test->options, num + num_options + 1);
451 memcpy(&test->options[num], options, (num_options + 1)*sizeof(char *));
454 void add_info_options(struct ccan_file *info)
456 struct doc_section *d;
458 struct ccanlint *test;
460 list_for_each(get_ccan_file_docs(info), d, list) {
461 if (!streq(d->type, "ccanlint"))
464 for (i = 0; i < d->num_lines; i++) {
465 char **words = tal_strsplit(d, d->lines[i], " \t",
470 if (strncmp(words[0], "//", 2) == 0)
473 test = find_test(words[0]);
475 warnx("%s: unknown ccanlint test '%s'",
476 info->fullname, words[0]);
481 warnx("%s: no argument to test '%s'",
482 info->fullname, words[0]);
487 if (strcasecmp(words[1], "FAIL") == 0) {
489 skip_test_and_deps(test,
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);
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)
506 unsigned int i, j = 0;
509 if (!test->options[0])
510 return test->options;
512 ret = tal_arr(f, char *, tal_count(test->options));
513 for (i = 0; test->options[i]; i++) {
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;
524 /* FAIL overrides anything else. */
525 if (streq(optname, "FAIL")) {
526 ret = tal_arr(f, char *, 2);
527 ret[0] = (char *)"FAIL";
535 /* Shrink it to size so tal_array_length() works as expected. */
536 tal_resize(&ret, j + 1);
540 static char *opt_set_const_charp(const char *arg, const char **p)
542 return opt_set_charp(arg, cast_const2(char **, p));
545 static char *opt_set_target(const char *arg, struct dgraph_node *all)
547 struct ccanlint *t = find_test(arg);
549 return tal_fmt(NULL, "unknown --target %s", arg);
552 dgraph_add_edge(&t->node, all);
556 static bool run_tests(struct dgraph_node *all,
566 run.score = run.total = 0;
569 dgraph_traverse_to(all, run_test, &run);
571 printf("%sTotal score: %u/%u\n", prefix, run.score, run.total);
575 static bool add_to_all(const char *member, struct ccanlint *c,
576 struct dgraph_node *all)
578 /* If we're excluded on cmdline, don't add. */
580 dgraph_add_edge(&c->node, all);
584 static bool test_module(struct dgraph_node *all,
585 const char *dir, const char *prefix, bool summary)
587 struct manifest *m = get_manifest(autofree(), dir);
588 char *testlink = path_join(NULL, temp_dir(), "test");
590 /* Create a symlink from temp dir back to src dir's
593 if (symlink(path_join(m, dir, "test"), testlink) != 0)
594 err(1, "Creating test symlink in %s", temp_dir());
596 return run_tests(all, summary, m, prefix);
599 int main(int argc, char *argv[])
601 bool summary = false, pass = true;
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;
608 /* Empty graph node to which we attach everything else. */
609 dgraph_init_node(&all);
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,
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"
637 "This usage message");
639 /* Do verbose before anything else... */
640 opt_early_parse(argc, argv, opt_log_stderr_exit);
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());
649 compile_verbose = true;
650 print_test_depends();
653 tools_verbose = true;
655 opt_parse(&argc, argv, opt_log_stderr_exit);
658 strmap_iterate(&tests, add_to_all, &all);
663 dir = path_join(NULL, cwd, argv[1]);
665 ccan_dir = find_ccan_dir(dir);
667 errx(1, "Cannot find ccan/ base directory in %s", dir);
668 config_header = read_config_header(ccan_dir, verbose > 1);
670 /* We do this after read_config_header has set compiler & cflags */
672 cflags = override_cflags;
673 if (override_compiler)
674 compiler = override_compiler;
677 pass = test_module(&all, cwd, "", summary);
679 for (i = 1; i < argc; i++) {
680 dir = path_canon(NULL,
681 take(path_join(NULL, cwd, argv[i])));
683 prefix = path_join(NULL, ccan_dir, "ccan");
684 prefix = path_rel(NULL, take(prefix), dir);
685 prefix = tal_strcat(NULL, take(prefix), ": ");
687 pass &= test_module(&all, dir, prefix, summary);