utf8: don't allow NUL in decoded strings.
[ccan] / tools / ccanlint / tests / tests_coverage.c
1 #include <tools/ccanlint/ccanlint.h>
2 #include <tools/tools.h>
3 #include <ccan/str/str.h>
4 #include <ccan/tal/path/path.h>
5 #include <ccan/tal/grab_file/grab_file.h>
6 #include <ccan/foreach/foreach.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <limits.h>
12 #include <errno.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <err.h>
16 #include <string.h>
17 #include <ctype.h>
18
19 static bool find_source_file(const struct manifest *m, const char *filename)
20 {
21         const struct ccan_file *i;
22
23         list_for_each(&m->c_files, i, list) {
24                 if (streq(i->fullname, filename))
25                         return true;
26         }
27         list_for_each(&m->h_files, i, list) {
28                 if (streq(i->fullname, filename))
29                         return true;
30         }
31         return false;
32 }
33
34 /* 1 point for 50%, 2 points for 75%, 3 points for 87.5%...  Bonus for 100%. */
35 static unsigned int score_coverage(float covered, unsigned total)
36 {
37         float thresh, uncovered = 1.0 - covered;
38         unsigned int i;
39
40         if (covered == 1.0)
41                 return total;
42
43         total--;
44         for (i = 0, thresh = 0.5; i < total; i++, thresh /= 2) {
45                 if (uncovered > thresh)
46                         break;
47         }
48         return i;
49 }
50
51
52 /* FIXME: Don't know how stable this is.  Read cov files directly? */
53 static void analyze_coverage(struct manifest *m, bool full_gcov,
54                              const char *output, struct score *score)
55 {
56         char **lines = tal_strsplit(score, output, "\n", STR_EMPTY_OK);
57         float covered_lines = 0.0;
58         unsigned int i, total_lines = 0;
59         bool lines_matter = false;
60
61         /*
62           Output looks like: (gcov 4.6.3)
63            File '../../../ccan/tdb2/private.h'
64            Lines executed:0.00% of 8
65            /home/ccan/ccan/tdb2/test/run-simple-delete.c:creating 'run-simple-delete.c.gcov'
66
67            File '../../../ccan/tdb2/tdb.c'
68            Lines executed:0.00% of 450
69
70          For gcov 4.7.2:
71
72            File '/home/dwg/src/ccan/ccan/rfc822/test/run-check-check.c'
73            Lines executed:100.00% of 19
74            Creating 'run-check-check.c.gcov'
75         */
76
77         for (i = 0; lines[i]; i++) {
78                 if (strstarts(lines[i], "File '")) {
79                         char *file = lines[i] + strlen("File '");
80                         file[strcspn(file, "'")] = '\0';
81                         if (find_source_file(m, file))
82                                 lines_matter = true;
83                         else
84                                 lines_matter = false;
85                 } else if (lines_matter
86                            && strstarts(lines[i], "Lines executed:")) {
87                         float ex;
88                         unsigned of;
89                         if (sscanf(lines[i], "Lines executed:%f%% of %u",
90                                    &ex, &of) != 2)
91                                 errx(1, "Could not parse line '%s'", lines[i]);
92                         total_lines += of;
93                         covered_lines += ex / 100.0 * of;
94                 } else if (full_gcov
95                            && (strstr(lines[i], ":creating '")
96                                || strstarts(lines[i], "Creating '"))) {
97                         char *file, *filename, *apostrophe;
98                         apostrophe = strchr(lines[i], '\'');
99                         filename = apostrophe + 1;
100                         apostrophe = strchr(filename, '\'');
101                         *apostrophe = '\0';
102                         if (lines_matter) {
103                                 file = grab_file(score, filename);
104                                 if (!file) {
105                                         score->error = tal_fmt(score,
106                                                                "Reading %s",
107                                                                filename);
108                                         return;
109                                 }
110                                 printf("%s", file);
111                         }
112                         if (tools_verbose)
113                                 printf("Unlinking %s", filename);
114                         unlink(filename);
115                 }
116         }
117
118         score->pass = true;
119
120         if (verbose > 1)
121                 printf("%u of %u lines covered\n",
122                        (unsigned)covered_lines, total_lines);
123
124         /* Nothing covered?  We can't tell if there's a source file which
125          * was never executed, or there really is no code to execute, so
126          * assume the latter: this test deserves no score. */
127         if (total_lines == 0)
128                 score->total = score->score = 0;
129         else {
130                 score->total = 6;
131                 score->score = score_coverage(covered_lines / total_lines,
132                                               score->total);
133         }
134 }
135
136 static void do_run_coverage_tests(struct manifest *m,
137                                   unsigned int *timeleft, struct score *score)
138 {
139         struct ccan_file *i;
140         char *cmdout, *outdir;
141         char *covargs;
142         bool full_gcov = (verbose > 1);
143         struct list_head *list;
144         bool ran_some = false;
145
146         /* This tells gcov where we put those .gcno files. */
147         outdir = path_dirname(score,
148                               m->info_file->compiled[COMPILE_NORMAL]);
149         covargs = tal_fmt(m, "%s -o %s", full_gcov ? "" : "-n", outdir);
150
151         /* Run them all. */
152         foreach_ptr(list, &m->run_tests, &m->api_tests) {
153                 list_for_each(list, i, list) {
154                         if (run_command(score, timeleft, &cmdout,
155                                         "%s", i->compiled[COMPILE_COVERAGE])) {
156                                 tal_append_fmt(&covargs, " %s", i->fullname);
157                         } else {
158                                 score_file_error(score, i, 0,
159                                                  "Running test with coverage"
160                                                  " failed: %s", cmdout);
161                                 return;
162                         }
163                         ran_some = true;
164                 }
165         }
166
167         /* No tests at all?  0 out of 0 for you... */
168         if (!ran_some) {
169                 score->total = score->score = 0;
170                 score->pass = true;
171                 return;
172         }
173
174         /* Now run gcov: we want output even if it succeeds. */
175         if (!run_gcov(score, timeleft, &cmdout, "%s", covargs)) {
176                 score->error = tal_fmt(score, "Running gcov: %s", cmdout);
177                 return;
178         }
179
180         analyze_coverage(m, full_gcov, cmdout, score);
181 }
182
183 struct ccanlint tests_coverage = {
184         .key = "tests_coverage",
185         .name = "Module's tests cover all the code",
186         .check = do_run_coverage_tests,
187         .needs = "tests_compile_coverage tests_pass"
188 };
189
190 REGISTER_TEST(tests_coverage);