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