+ return strmap_get(&tests, key);
+}
+
+bool is_excluded(const char *name)
+{
+ return find_test(name)->skip != NULL;
+}
+
+static bool init_deps(const char *member UNNEEDED,
+ struct ccanlint *c, void *unused UNNEEDED)
+{
+ char **deps = tal_strsplit(NULL, c->needs, " ", STR_EMPTY_OK);
+ unsigned int i;
+
+ for (i = 0; deps[i]; i++) {
+ struct ccanlint *dep;
+
+ dep = find_test(deps[i]);
+ if (!dep)
+ errx(1, "BUG: unknown dep '%s' for %s",
+ deps[i], c->key);
+ dgraph_add_edge(&dep->node, &c->node);
+ }
+ tal_free(deps);
+ return true;
+}
+
+static bool check_names(const char *member UNNEEDED, struct ccanlint *c,
+ ccanlint_map_t *names)
+{
+ if (!strmap_add(names, c->name, c))
+ err(1, "Duplicate name %s", c->name);
+ return true;
+}
+
+static void init_tests(void)
+{
+ ccanlint_map_t names;
+ struct ccanlint **table;
+ size_t i, num;
+
+ strmap_init(&tests);
+
+ table = autodata_get(ccanlint_tests, &num);
+ for (i = 0; i < num; i++)
+ register_test(table[i]);
+ autodata_free(table);
+
+ strmap_iterate(&tests, init_deps, NULL);
+
+ /* Check for duplicate names. */
+ strmap_init(&names);
+ strmap_iterate(&tests, check_names, &names);
+ strmap_clear(&names);
+}
+
+static bool reset_test(struct dgraph_node *node, void *unused UNNEEDED)
+{
+ struct ccanlint *c = container_of(node, struct ccanlint, node);
+ c->skip = NULL;
+ c->done = false;
+ return true;
+}
+
+static void reset_tests(struct dgraph_node *all)
+{
+ dgraph_traverse_to(all, reset_test, NULL);
+}
+
+static bool print_deps(const char *member UNNEEDED,
+ struct ccanlint *c, void *unused UNNEEDED)
+{
+ if (!tlist_empty(&c->node.edge[DGRAPH_FROM])) {
+ struct dgraph_edge *e;
+
+ printf("These depend on %s:\n", c->key);
+ dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
+ struct ccanlint *to = container_of(e->n[DGRAPH_TO],
+ struct ccanlint,
+ node);
+ printf("\t%s\n", to->key);
+ }
+ }
+ return true;
+}
+
+static void print_test_depends(void)
+{
+ printf("Tests:\n");
+
+ strmap_iterate(&tests, print_deps, NULL);
+}
+
+
+static void show_tmpdir(const char *dir)
+{
+ printf("You can find ccanlint working files in '%s'\n", dir);
+}
+
+static char *keep_tests(void *unused UNNEEDED)
+{
+ keep_results = true;
+
+ /* Don't automatically destroy temporary dir. */
+ keep_temp_dir();
+ tal_add_destructor(temp_dir(), show_tmpdir);
+ return NULL;
+}
+
+static bool remove_test(struct dgraph_node *node, const char *why)
+{
+ struct ccanlint *c = container_of(node, struct ccanlint, node);
+ c->skip = why;
+ dgraph_clear_node(node);
+ return true;
+}
+
+static char *exclude_test(const char *testname, void *unused UNNEEDED)
+{
+ struct ccanlint *i = find_test(testname);
+ if (!i)
+ return tal_fmt(NULL, "No test %s to --exclude", testname);
+
+ /* Remove this, and everything which depends on it. */
+ dgraph_traverse_from(&i->node, remove_test, "excluded on command line");
+ remove_test(&i->node, "excluded on command line");
+ return NULL;
+}
+
+static void skip_test_and_deps(struct ccanlint *c, const char *why)
+{
+ /* Skip this, and everything which depends on us. */
+ dgraph_traverse_from(&c->node, skip_test, why);
+ skip_test(&c->node, why);
+}
+
+static char *list_tests(void *arg UNNEEDED)
+{
+ struct ccanlint *i;
+
+ printf("Tests:\n");
+ /* This makes them print in topological order. */
+ while ((i = get_next_test()) != NULL) {
+ printf(" %-25s %s\n", i->key, i->name);
+ dgraph_clear_node(&i->node);
+ strmap_del(&tests, i->key, NULL);
+ }
+ exit(0);
+}
+
+static bool draw_test(const char *member UNNEEDED,
+ struct ccanlint *c, const char *style)
+{
+ /*
+ * todo: escape labels in case ccanlint test keys have
+ * characters interpreted as GraphViz syntax.
+ */
+ printf("\t\"%p\" [label=\"%s\"%s]\n", c, c->key, style);
+ return true;
+}
+
+static void test_dgraph_vertices(const char *style)
+{
+ strmap_iterate(&tests, draw_test, style);
+}
+
+static bool draw_edges(const char *member UNNEEDED,
+ struct ccanlint *c, void *unused UNNEEDED)
+{
+ struct dgraph_edge *e;
+
+ dgraph_for_each_edge(&c->node, e, DGRAPH_FROM) {
+ struct ccanlint *to = container_of(e->n[DGRAPH_TO],
+ struct ccanlint,
+ node);
+ printf("\t\"%p\" -> \"%p\"\n", c->name, to->name);
+ }
+ return true;
+}
+
+static void test_dgraph_edges(void)
+{
+ strmap_iterate(&tests, draw_edges, NULL);
+}
+
+static char *test_dependency_graph(void *arg UNNEEDED)
+{
+ puts("digraph G {");
+
+ test_dgraph_vertices("");
+ test_dgraph_edges();
+
+ puts("}");
+
+ exit(0);
+}
+
+static void add_options(struct ccanlint *test, char **options,
+ unsigned int num_options)
+{
+ unsigned int num;
+
+ if (!test->options)
+ num = 0;
+ else
+ /* -1, because last one is NULL. */
+ num = tal_count(test->options) - 1;
+
+ tal_resize(&test->options, num + num_options + 1);
+ memcpy(&test->options[num], options, (num_options + 1)*sizeof(char *));
+}
+
+void add_info_options(struct manifest *m)
+{
+ unsigned int i;
+ char **info_options = get_ccanlint(m, m->dir, get_or_compile_info);
+
+ for (i = 0; info_options[i]; i++) {
+ char **words = tal_strsplit(m, info_options[i], " \t",
+ STR_NO_EMPTY);
+ struct ccanlint *test;
+
+ if (!words[0])
+ continue;
+
+ test = find_test(words[0]);
+ if (!test) {
+ warnx("%s: unknown ccanlint test '%s'",
+ m->info_file->fullname, words[0]);
+ continue;
+ }
+
+ if (!words[1]) {
+ warnx("%s: no argument to test '%s'",
+ m->info_file->fullname, words[0]);
+ continue;
+ }
+
+ /* Known failure? */
+ if (strcasecmp(words[1], "FAIL") == 0) {
+ if (!targeting)
+ skip_test_and_deps(test,
+ "excluded in _info"
+ " file");
+ } else {
+ if (!test->takes_options)
+ warnx("%s: %s doesn't take options",
+ m->info_file->fullname, words[0]);
+ add_options(test, words+1, tal_count(words)-1);