ccanlint: allow per-file restrictions, so we don't have to remove all tests.
authorRusty Russell <rusty@rustcorp.com.au>
Wed, 24 Aug 2011 03:22:46 +0000 (12:52 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 24 Aug 2011 03:22:46 +0000 (12:52 +0930)
For example, valgrind can't handle some things, so we may need to
switch it off, but we don't want to do that for every test.

So, start a standard format for per-file restrictions, eg:

    Ccanlint: tests_pass_valgrind test/foo.c:FAIL

For the moment, only tests_pass_valgrind takes options, so it has to
change: we now have a helper which returns the array of options
applicable to a given file.

tools/ccanlint/ccanlint.c
tools/ccanlint/ccanlint.h
tools/ccanlint/tests/tests_pass_valgrind.c

index 81f9614739b2242410bb7524634101ddb1e191da..1c060ba7db6bf61147a8716e47dafe63c130d509 100644 (file)
@@ -186,6 +186,8 @@ static bool run_test(struct ccanlint *i,
 static void register_test(struct list_head *h, struct ccanlint *test)
 {
        list_add(h, &test->list);
+       test->options = talloc_array(NULL, char *, 1);
+       test->options[0] = NULL;
 }
 
 /**
@@ -409,11 +411,30 @@ static char **collapse(char **lines, unsigned int *nump)
                if (lines[i][0])
                        lines[j++] = lines[i];
        }
+       lines[j] = NULL;
        if (nump)
                *nump = j;
        return lines;
 }
 
+
+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 = talloc_array_length(test->options) - 1;
+
+       test->options = talloc_realloc(NULL, test->options,
+                                      char *,
+                                      num + num_options + 1);
+       memcpy(&test->options[num], options, (num_options + 1)*sizeof(char *));
+}
+
 static void add_info_options(struct ccan_file *info, bool mark_fails)
 {
        struct doc_section *d;
@@ -425,9 +446,10 @@ static void add_info_options(struct ccan_file *info, bool mark_fails)
                        continue;
 
                for (i = 0; i < d->num_lines; i++) {
+                       unsigned int num_words;
                        char **words = collapse(strsplit(d, d->lines[i], " \t"),
-                                               NULL);
-                       if (!words[0])
+                                               &num_words);
+                       if (num_words == 0)
                                continue;
 
                        if (strncmp(words[0], "//", 2) == 0)
@@ -454,14 +476,49 @@ static void add_info_options(struct ccan_file *info, bool mark_fails)
                                if (!test->takes_options)
                                        warnx("%s: %s doesn't take options",
                                              info->fullname, words[0]);
-                               /* Copy line exactly into options. */
-                               test->options = strstr(d->lines[i], words[0])
-                                       + strlen(words[0]);
+                               add_options(test, words+1, num_words-1);
                        }
                }
        }
 }
 
+/* If options are of form "filename:<option>" they only apply to that file */
+char **per_file_options(const struct ccanlint *test, struct ccan_file *f)
+{
+       char **ret;
+       unsigned int i, j = 0;
+
+       /* Fast path. */
+       if (!test->options[0])
+               return test->options;
+
+       ret = talloc_array(f, char *, talloc_array_length(test->options));
+       for (i = 0; test->options[i]; i++) {
+               char *optname;
+
+               if (!test->options[i] || !strchr(test->options[i], ':')) {
+                       optname = test->options[i];
+               } else if (strstarts(test->options[i], f->name)
+                          && test->options[i][strlen(f->name)] == ':') {
+                       optname = test->options[i] + strlen(f->name) + 1;
+               } else
+                       continue;
+
+               /* FAIL overrides anything else. */
+               if (streq(optname, "FAIL")) {
+                       ret = talloc_array(f, char *, 2);
+                       ret[0] = (char *)"FAIL";
+                       ret[1] = NULL;
+                       return ret;
+               }
+               ret[j++] = optname;
+       }
+       ret[j] = NULL;
+
+       /* Shrink it to size so talloc_array_length() works as expected. */
+       return talloc_realloc(NULL, ret, char *, j + 1);
+}
+
 static bool depends_on(struct ccanlint *i, struct ccanlint *target)
 {
        const struct dependent *d;
index aec75ad97e524a3b42c19ac55244908903a2181f..c57f92037c6ba3a8197c8c48e102369f31db8dcd 100644 (file)
@@ -95,7 +95,7 @@ struct ccanlint {
        void (*handle)(struct manifest *m, struct score *score);
 
        /* Options from _info. */
-       char *options;
+       char **options;
        /* If not set, we'll give an error if they try to set options. */
        bool takes_options;
 
@@ -221,6 +221,9 @@ char *get_symbol_token(void *ctx, const char **line);
 /* Similarly for ->doc_sections */
 struct list_head *get_ccan_file_docs(struct ccan_file *f);
 
+/* Get NULL-terminated array options for this file for this test */
+char **per_file_options(const struct ccanlint *test, struct ccan_file *f);
+
 /* Append message about this file (and line, if non-zero) to the score->error */
 void score_file_error(struct score *, struct ccan_file *f, unsigned line,
                      const char *errorfmt, ...);
index 9622b300a20af1afe1023b33a980d42ccf3951fe..d248aaa85fea4c86782057b99ef286c7bf436bc9 100644 (file)
@@ -133,6 +133,19 @@ static char *analyze_output(const char *output, char **errs)
        return leaks;
 }
 
+static const char *concat(struct score *score, char *bits[])
+{
+       unsigned int i;
+       char *ret = talloc_strdup(score, "");
+
+       for (i = 0; bits[i]; i++) {
+               if (i)
+                       ret = talloc_append_string(ret, " ");
+               ret = talloc_append_string(ret, bits[i]);
+       }
+       return ret;
+}
+
 /* FIXME: Run examples, too! */
 static void do_run_tests_vg(struct manifest *m,
                             bool keep,
@@ -145,11 +158,19 @@ static void do_run_tests_vg(struct manifest *m,
 
        /* This is slow, so we run once but grab leak info. */
        score->total = 0;
+       score->pass = true;
        foreach_ptr(list, &m->run_tests, &m->api_tests) {
                list_for_each(list, i, list) {
                        char *output, *err, *log;
                        bool pass;
+                       const char *options;
+
                        score->total++;
+                       options = concat(score,
+                                        per_file_options(&tests_pass_valgrind,
+                                                         i));
+                       if (streq(options, "FAIL"))
+                               continue;
 
                        /* FIXME: Valgrind's output sucks.  XML is unreadable by
                         * humans *and* doesn't support children reporting. */
@@ -164,8 +185,7 @@ static void do_run_tests_vg(struct manifest *m,
                                           " --leak-check=full"
                                           " --log-fd=3 %s %s"
                                           " 3> %s",
-                                          tests_pass_valgrind.options ?
-                                          tests_pass_valgrind.options : "",
+                                          options,
                                           i->compiled, log);
                        output = grab_file(i, log, NULL);
                        /* No valgrind errors?  Expect it to pass... */
@@ -181,15 +201,13 @@ static void do_run_tests_vg(struct manifest *m,
                        } else {
                                i->leak_info = analyze_output(output, &err);
                        }
-                       if (err)
+                       if (err) {
                                score_file_error(score, i, 0, "%s", err);
-                       else
+                               score->pass = false;
+                       } else
                                score->score++;
                }
        }
-
-       if (score->score == score->total)
-               score->pass = true;
 }
 
 static void do_leakcheck_vg(struct manifest *m,
@@ -226,13 +244,18 @@ static void run_under_debugger_vg(struct manifest *m, struct score *score)
        struct file_error *first;
        char *command;
 
+       /* Don't ask anything if they suppressed tests. */
+       if (score->pass)
+               return;
+
        if (!ask("Should I run the first failing test under the debugger?"))
                return;
 
        first = list_top(&score->per_file_errors, struct file_error, list);
        command = talloc_asprintf(m, "valgrind --leak-check=full --db-attach=yes%s %s",
-                                 tests_pass_valgrind.options ?
-                                 tests_pass_valgrind.options : "",
+                                 concat(score,
+                                        per_file_options(&tests_pass_valgrind,
+                                                         first->file)),
                                  first->file->compiled);
        if (system(command))
                doesnt_matter();