Commit 7a163ea2 authored by Rusty Russell's avatar Rusty Russell

ccanlint: rework so checks have more structure.

Previously each check returned a void *, but in fact most of them fell into
similar patterns.  So define 'struct score' and a helper to add files to it,
and use that.

Under these rules, you get 0/1 if you skip a test because a dependency failed
which in theory means your score (as a percentage) could drop if you fix
a test.
parent 71c95e84
...@@ -88,30 +88,37 @@ static const char *should_skip(struct manifest *m, struct ccanlint *i) ...@@ -88,30 +88,37 @@ static const char *should_skip(struct manifest *m, struct ccanlint *i)
static bool run_test(struct ccanlint *i, static bool run_test(struct ccanlint *i,
bool quiet, bool quiet,
unsigned int *score, unsigned int *running_score,
unsigned int *total_score, unsigned int *running_total,
struct manifest *m) struct manifest *m)
{ {
void *result; unsigned int timeleft;
unsigned int this_score, max_score, timeleft;
const struct dependent *d; const struct dependent *d;
const char *skip; const char *skip;
bool bad, good; struct score *score;
//one less test to run through //one less test to run through
list_for_each(&i->dependencies, d, node) list_for_each(&i->dependencies, d, node)
d->dependent->num_depends--; d->dependent->num_depends--;
score = talloc(m, struct score);
list_head_init(&score->per_file_errors);
score->error = NULL;
score->pass = false;
score->score = 0;
score->total = 1;
skip = should_skip(m, i); skip = should_skip(m, i);
if (skip) { if (skip) {
skip: skip:
if (verbose && !streq(skip, "not relevant to target")) if (verbose && !streq(skip, "not relevant to target"))
printf(" %s: skipped (%s)\n", i->name, skip); printf("%s: skipped (%s)\n", i->name, skip);
/* If we're skipping this because a prereq failed, we fail. */ /* If we're skipping this because a prereq failed, we fail:
* count it as a score of 1. */
if (i->skip_fail) if (i->skip_fail)
*total_score += i->total_score; (*running_total)++;
list_del(&i->list); list_del(&i->list);
list_add_tail(&finished_tests, &i->list); list_add_tail(&finished_tests, &i->list);
...@@ -121,55 +128,52 @@ static bool run_test(struct ccanlint *i, ...@@ -121,55 +128,52 @@ static bool run_test(struct ccanlint *i,
d->dependent->skip = "dependency was skipped"; d->dependent->skip = "dependency was skipped";
d->dependent->skip_fail = i->skip_fail; d->dependent->skip_fail = i->skip_fail;
} }
return true; return i->skip_fail ? false : true;
} }
timeleft = timeout ? timeout : default_timeout_ms; timeleft = timeout ? timeout : default_timeout_ms;
result = i->check(m, i->keep_results, &timeleft); i->check(m, i->keep_results, &timeleft, score);
if (timeout && timeleft == 0) { if (timeout && timeleft == 0) {
skip = "timeout"; skip = "timeout";
goto skip; goto skip;
} }
max_score = i->total_score; assert(score->score <= score->total);
if (!max_score) if ((!score->pass && !quiet)
max_score = 1; || (score->score < score->total && verbose)
|| verbose > 1) {
if (!result) printf("%s: %s", i->name, score->pass ? "PASS" : "FAIL");
this_score = max_score; if (score->total > 1)
else if (i->score) printf(" (+%u/%u)", score->score, score->total);
this_score = i->score(m, result);
else
this_score = 0;
bad = (this_score == 0);
good = (this_score >= max_score);
if (verbose || (!good && !quiet)) {
printf(" %s: %s", i->name,
bad ? "FAIL" : good ? "PASS" : "PARTIAL");
if (max_score > 1)
printf(" (+%u/%u)", this_score, max_score);
printf("\n"); printf("\n");
} }
if (!quiet && result) { if (!quiet && !score->pass) {
const char *desc; struct file_error *f;
if (i->describe && (desc = i->describe(m, result)) != NULL)
printf(" %s\n", desc); if (score->error)
printf("%s:\n", score->error);
list_for_each(&score->per_file_errors, f, list) {
if (f->line)
printf("%s:%u:%s\n",
f->file->fullname, f->line, f->error);
else if (f->file)
printf("%s:%s\n", f->file->fullname, f->error);
else
printf("%s\n", f->error);
}
if (i->handle) if (i->handle)
i->handle(m, result); i->handle(m, score);
} }
if (i->total_score) { *running_score += score->score;
*score += this_score; *running_total += score->total;
*total_score += i->total_score;
}
list_del(&i->list); list_del(&i->list);
list_add_tail(&finished_tests, &i->list); list_add_tail(&finished_tests, &i->list);
if (bad) { if (!score->pass) {
/* Skip any tests which depend on this one. */ /* Skip any tests which depend on this one. */
list_for_each(&i->dependencies, d, node) { list_for_each(&i->dependencies, d, node) {
if (d->dependent->skip) if (d->dependent->skip)
...@@ -178,7 +182,7 @@ static bool run_test(struct ccanlint *i, ...@@ -178,7 +182,7 @@ static bool run_test(struct ccanlint *i,
d->dependent->skip_fail = true; d->dependent->skip_fail = true;
} }
} }
return good; return score->pass;
} }
static void register_test(struct list_head *h, struct ccanlint *test, ...) static void register_test(struct list_head *h, struct ccanlint *test, ...)
...@@ -451,9 +455,6 @@ int main(int argc, char *argv[]) ...@@ -451,9 +455,6 @@ int main(int argc, char *argv[])
} }
/* If you don't pass the compulsory tests, you get a score of 0. */ /* If you don't pass the compulsory tests, you get a score of 0. */
if (verbose)
printf("Compulsory tests:\n");
while ((i = get_next_test(&compulsory_tests)) != NULL) { while ((i = get_next_test(&compulsory_tests)) != NULL) {
if (!run_test(i, summary, &score, &total_score, m)) { if (!run_test(i, summary, &score, &total_score, m)) {
printf("%sTotal score: 0/%u\n", prefix, total_score); printf("%sTotal score: 0/%u\n", prefix, total_score);
...@@ -462,9 +463,6 @@ int main(int argc, char *argv[]) ...@@ -462,9 +463,6 @@ int main(int argc, char *argv[])
} }
add_info_fails(m->info_file); add_info_fails(m->info_file);
if (verbose)
printf("\nNormal tests:\n");
while ((i = get_next_test(&normal_tests)) != NULL) while ((i = get_next_test(&normal_tests)) != NULL)
run_test(i, summary, &score, &total_score, m); run_test(i, summary, &score, &total_score, m);
......
...@@ -42,6 +42,20 @@ struct manifest { ...@@ -42,6 +42,20 @@ struct manifest {
struct manifest *get_manifest(const void *ctx, const char *dir); struct manifest *get_manifest(const void *ctx, const char *dir);
struct file_error {
struct list_node list;
struct ccan_file *file;
unsigned int line; /* 0 not to print */
const char *error;
};
struct score {
bool pass;
unsigned int score, total;
const char *error;
struct list_head per_file_errors;
};
struct ccanlint { struct ccanlint {
struct list_node list; struct list_node list;
...@@ -51,27 +65,18 @@ struct ccanlint { ...@@ -51,27 +65,18 @@ struct ccanlint {
/* Unique name of test */ /* Unique name of test */
const char *name; const char *name;
/* Total score that this test is worth. */
unsigned int total_score;
/* Can we run this test? Return string explaining why, if not. */ /* Can we run this test? Return string explaining why, if not. */
const char *(*can_run)(struct manifest *m); const char *(*can_run)(struct manifest *m);
/* If this returns non-NULL, it means the check failed. /* keep is set if you should keep the results.
* keep is set if you should keep the results. * If timeleft is set to 0, means it timed out.
* If timeleft is set to 0, means it timed out. */ * score is the result, and a talloc context freed after all our
void *(*check)(struct manifest *m, bool keep, unsigned int *timeleft); * depends are done. */
void (*check)(struct manifest *m,
/* The non-NULL return from check is passed to one of these: */ bool keep, unsigned int *timeleft, struct score *score);
/* So, what did this get out of the total_score? (NULL means 0). */
unsigned int (*score)(struct manifest *m, void *check_result);
/* Verbose description of what was wrong. */
const char *(*describe)(struct manifest *m, void *check_result);
/* Can we do something about it? (NULL if not) */ /* Can we do something about it? (NULL if not) */
void (*handle)(struct manifest *m, void *check_result); void (*handle)(struct manifest *m, struct score *score);
/* Internal use fields: */ /* Internal use fields: */
/* Who depends on us? */ /* Who depends on us? */
...@@ -184,11 +189,9 @@ char *get_symbol_token(void *ctx, const char **line); ...@@ -184,11 +189,9 @@ char *get_symbol_token(void *ctx, const char **line);
struct list_head *get_ccan_file_docs(struct ccan_file *f); struct list_head *get_ccan_file_docs(struct ccan_file *f);
/* Call the reporting on every line in the file. sofar contains /* Add an error about this file (and line, if non-zero) to the score struct */
* previous results. */ void score_file_error(struct score *, struct ccan_file *f, unsigned line,
char *report_on_lines(struct list_head *files, const char *error);
char *(*report)(const char *),
char *sofar);
/* Normal tests. */ /* Normal tests. */
extern struct ccanlint trailing_whitespace; extern struct ccanlint trailing_whitespace;
......
...@@ -33,42 +33,41 @@ static char *obj_list(const struct manifest *m) ...@@ -33,42 +33,41 @@ static char *obj_list(const struct manifest *m)
return list; return list;
} }
static void *do_build(struct manifest *m, static void do_build(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft,
struct score *score)
{ {
char *filename, *err; char *filename, *errstr;
if (list_empty(&m->c_files)) { if (list_empty(&m->c_files)) {
/* No files? No score, but we "pass". */ /* No files? No score, but we "pass". */
build.total_score = 0; score->total = 0;
return NULL; score->pass = true;
return;
} }
filename = link_objects(m, m->basename, false, obj_list(m), &err);
if (filename && keep) { filename = link_objects(m, m->basename, false, obj_list(m), &errstr);
if (!filename) {
score->error = "The object file didn't build";
score_file_error(score, NULL, 0, errstr);
return;
}
if (keep) {
char *realname = talloc_asprintf(m, "%s.o", m->dir); char *realname = talloc_asprintf(m, "%s.o", m->dir);
/* We leave this object file around, all built. */ /* We leave this object file around, all built. */
if (!move_file(filename, realname)) if (!move_file(filename, realname))
return talloc_asprintf(m, "Failed to rename %s to %s", err(1, "Renaming %s to %s", filename, realname);
filename, realname);
return NULL;
} }
return err; score->pass = true;
} score->score = score->total;
static const char *describe_build(struct manifest *m, void *check_result)
{
return talloc_asprintf(check_result,
"The object file for the module didn't build:\n"
"%s", (char *)check_result);
} }
struct ccanlint build = { struct ccanlint build = {
.key = "build", .key = "build",
.name = "Module can be built from object files", .name = "Module can be built from object files",
.total_score = 1,
.check = do_build, .check = do_build,
.describe = describe_build,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -21,42 +21,37 @@ static const char *can_build(struct manifest *m) ...@@ -21,42 +21,37 @@ static const char *can_build(struct manifest *m)
return NULL; return NULL;
} }
static void *check_objs_build(struct manifest *m, static void check_objs_build(struct manifest *m,
bool keep, unsigned int *timeleft) bool keep,
unsigned int *timeleft, struct score *score)
{ {
char *report = NULL;
struct ccan_file *i; struct ccan_file *i;
if (list_empty(&m->c_files))
score->total = 0;
list_for_each(&m->c_files, i, list) { list_for_each(&m->c_files, i, list) {
char *err; char *err;
char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name);
/* One point for each obj file. */
build_objs.total_score++;
i->compiled = maybe_temp_file(m, "", keep, fullfile); i->compiled = maybe_temp_file(m, "", keep, fullfile);
err = compile_object(m, fullfile, ccan_dir, "", i->compiled); err = compile_object(m, fullfile, ccan_dir, "", i->compiled);
if (err) { if (err) {
talloc_free(i->compiled); talloc_free(i->compiled);
if (report) score->error = "Compiling object files";
report = talloc_append_string(report, err); score_file_error(score, i, 0, err);
else
report = err;
} }
} }
return report; if (!score->error) {
} score->pass = true;
score->score = score->total;
static const char *describe_objs_build(struct manifest *m, void *check_result) }
{
return check_result;
} }
struct ccanlint build_objs = { struct ccanlint build_objs = {
.key = "build-objects", .key = "build-objects",
.name = "Module object files can be built", .name = "Module object files can be built",
.check = check_objs_build, .check = check_objs_build,
.describe = describe_objs_build,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -45,9 +45,9 @@ static char *lib_list(const struct manifest *m) ...@@ -45,9 +45,9 @@ static char *lib_list(const struct manifest *m)
return ret; return ret;
} }
static void *check_use_build(struct manifest *m, static void check_use_build(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
char *contents; char *contents;
char *tmpfile; char *tmpfile;
...@@ -58,8 +58,7 @@ static void *check_use_build(struct manifest *m, ...@@ -58,8 +58,7 @@ static void *check_use_build(struct manifest *m,
fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600); fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) if (fd < 0)
return talloc_asprintf(m, "Creating temporary file: %s", err(1, "Creating temporary file %s", tmpfile);
strerror(errno));
contents = talloc_asprintf(tmpfile, contents = talloc_asprintf(tmpfile,
"#include <ccan/%s/%s.h>\n" "#include <ccan/%s/%s.h>\n"
...@@ -68,30 +67,23 @@ static void *check_use_build(struct manifest *m, ...@@ -68,30 +67,23 @@ static void *check_use_build(struct manifest *m,
" return 0;\n" " return 0;\n"
"}\n", "}\n",
m->basename, m->basename); m->basename, m->basename);
if (write(fd, contents, strlen(contents)) != strlen(contents)) { if (write(fd, contents, strlen(contents)) != strlen(contents))
close(fd); err(1, "Failure writing to temporary file %s", tmpfile);
return "Failure writing to temporary file";
}
close(fd); close(fd);
return compile_and_link(m, tmpfile, ccan_dir, obj_list(m), "", score->error = compile_and_link(m, tmpfile, ccan_dir, obj_list(m), "",
lib_list(m), lib_list(m),
maybe_temp_file(m, "", keep, tmpfile)); maybe_temp_file(m, "", keep, tmpfile));
} if (!score->error) {
score->pass = true;
static const char *describe_use_build(struct manifest *m, void *check_result) score->score = score->total;
{ }
return talloc_asprintf(check_result,
"Linking against module:\n"
"%s", (char *)check_result);
} }
struct ccanlint check_build = { struct ccanlint check_build = {
.key = "check-link", .key = "check-link",
.name = "Module can be linked against trivial program", .name = "Module can be linked against trivial program",
.total_score = 1,
.check = check_use_build, .check = check_use_build,
.describe = describe_use_build,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -35,13 +35,12 @@ static bool expect_obj_file(const char *dir) ...@@ -35,13 +35,12 @@ static bool expect_obj_file(const char *dir)
return has_c_files; return has_c_files;
} }
static void *check_depends_built(struct manifest *m, static void check_depends_built(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct ccan_file *i; struct ccan_file *i;
struct stat st; struct stat st;
char *report = NULL;
list_for_each(&m->dep_dirs, i, list) { list_for_each(&m->dep_dirs, i, list) {
if (!expect_obj_file(i->fullname)) if (!expect_obj_file(i->fullname))
...@@ -49,9 +48,11 @@ static void *check_depends_built(struct manifest *m, ...@@ -49,9 +48,11 @@ static void *check_depends_built(struct manifest *m,
i->compiled = talloc_asprintf(i, "%s.o", i->fullname); i->compiled = talloc_asprintf(i, "%s.o", i->fullname);
if (stat(i->compiled, &st) != 0) { if (stat(i->compiled, &st) != 0) {
report = talloc_asprintf_append(report, score->error = "Dependencies are not built";
"object file %s\n", score_file_error(score, i, 0,
i->compiled); talloc_asprintf(score,
"object file %s",
i->compiled));
i->compiled = NULL; i->compiled = NULL;
} }
} }
...@@ -61,30 +62,21 @@ static void *check_depends_built(struct manifest *m, ...@@ -61,30 +62,21 @@ static void *check_depends_built(struct manifest *m,
&& (!list_empty(&m->run_tests) || !list_empty(&m->api_tests))) { && (!list_empty(&m->run_tests) || !list_empty(&m->api_tests))) {
char *tapobj = talloc_asprintf(m, "%s/ccan/tap.o", ccan_dir); char *tapobj = talloc_asprintf(m, "%s/ccan/tap.o", ccan_dir);
if (stat(tapobj, &st) != 0) { if (stat(tapobj, &st) != 0) {
report = talloc_asprintf_append(report, score->error = talloc_asprintf(score,
"object file %s" "tap object file not built");
" (for tests)\n",
tapobj);
} }
} }
return talloc_steal(m, report); if (!score->error) {
} score->pass = true;
score->score = score->total;
static const char *describe_depends_built(struct manifest *m, }
void *check_result)
{
return talloc_asprintf(check_result,
"The following dependencies are not built:\n"
"%s", (char *)check_result);
} }
struct ccanlint depends_built = { struct ccanlint depends_built = {
.key = "depends-built", .key = "depends-built",
.name = "Module's CCAN dependencies are already built", .name = "Module's CCAN dependencies are already built",
.total_score = 1,
.check = check_depends_built, .check = check_depends_built,
.describe = describe_depends_built,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -14,29 +14,24 @@ ...@@ -14,29 +14,24 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
static char *add_dep(char *sofar, struct manifest *m, const char *dep) static void add_dep(struct manifest *m, const char *dep, struct score *score)
{ {
struct stat st; struct stat st;
struct ccan_file *f; struct ccan_file *f;
f = new_ccan_file(m, ccan_dir, talloc_strdup(m, dep)); f = new_ccan_file(m, ccan_dir, talloc_strdup(m, dep));
if (stat(f->fullname, &st) != 0) { if (stat(f->fullname, &st) != 0) {
return talloc_asprintf_append(sofar, score->error = "Depends don't exist";
"ccan/%s: expected it in" score_file_error(score, f, 0, "could not stat");
" directory %s\n", } else
dep, f->fullname); list_add_tail(&m->dep_dirs, &f->list);
}
list_add_tail(&m->dep_dirs, &f->list);
return sofar;
} }
static void *check_depends_exist(struct manifest *m, static void check_depends_exist(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
unsigned int i; unsigned int i;
char *report = NULL;
char **deps; char **deps;
char *updir = talloc_strdup(m, m->dir); char *updir = talloc_strdup(m, m->dir);
...@@ -52,25 +47,18 @@ static void *check_depends_exist(struct manifest *m, ...@@ -52,25 +47,18 @@ static void *check_depends_exist(struct manifest *m,
if (!strstarts(deps[i], "ccan/")) if (!strstarts(deps[i], "ccan/"))
continue; continue;
report = add_dep(report, m, deps[i]); add_dep(m, deps[i], score);
}
if (!score->error) {
score->pass = true;
score->score = score->total;
} }
return report;
}
static const char *describe_depends_exist(struct manifest *m,
void *check_result)
{
return talloc_asprintf(check_result,
"The following dependencies are are expected:\n"
"%s", (char *)check_result);
} }
struct ccanlint depends_exist = { struct ccanlint depends_exist = {
.key = "depends-exist", .key = "depends-exist",
.name = "Module's CCAN dependencies are present", .name = "Module's CCAN dependencies are present",
.total_score = 1,
.check = check_depends_exist, .check = check_depends_exist,
.describe = describe_depends_exist,
}; };
REGISTER_TEST(depends_exist, NULL); REGISTER_TEST(depends_exist, NULL);
...@@ -32,12 +32,12 @@ static struct ccan_file *main_header(struct manifest *m) ...@@ -32,12 +32,12 @@ static struct ccan_file *main_header(struct manifest *m)
return f; return f;
} }
/* Should not happen: we depend on has_main_header */ /* Should not happen: we depend on has_main_header */
return NULL; abort();
} }
static void *check_includes_build(struct manifest *m, static void check_includes_build(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
char *contents; char *contents;
char *tmpsrc, *tmpobj; char *tmpsrc, *tmpobj;
...@@ -49,34 +49,29 @@ static void *check_includes_build(struct manifest *m, ...@@ -49,34 +49,29 @@ static void *check_includes_build(struct manifest *m,
fd = open(tmpsrc, O_WRONLY | O_CREAT | O_EXCL, 0600); fd = open(tmpsrc, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) if (fd < 0)
return talloc_asprintf(m, "Creating temporary file %s: %s", err(1, "Creating temporary file %s", tmpsrc);
tmpsrc, strerror(errno));
contents = talloc_asprintf(tmpsrc, "#include <ccan/%s/%s.h>\n", contents = talloc_asprintf(tmpsrc, "#include <ccan/%s/%s.h>\n",
m->basename, m->basename); m->basename, m->basename);
if (write(fd, contents, strlen(contents)) != strlen(contents)) { if (write(fd, contents, strlen(contents)) != strlen(contents))
close(fd); err(1, "writing to temporary file %s", tmpsrc);
return "Failure writing to temporary file";
}
close(fd); close(fd);
return compile_object(m, tmpsrc, ccan_dir, "", tmpobj); score->error = compile_object(m, tmpsrc, ccan_dir, "", tmpobj);
} if (score->error) {
score->error = talloc_asprintf(score,
static const char *describe_includes_build(struct manifest *m, "#include of the main header file:\n%s",
void *check_result) score->error);
{ } else {
return talloc_asprintf(check_result, score->pass = true;
"#include of the main header file:\n" score->score = score->total;
"%s", (char *)check_result); }
} }
struct ccanlint includes_build = { struct ccanlint includes_build = {
.key = "include-main", .key = "include-main",
.name = "Modules main header compiles", .name = "Modules main header compiles",
.total_score = 1,
.check = check_includes_build, .check = check_includes_build,
.describe = describe_includes_build,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -12,21 +12,20 @@ ...@@ -12,21 +12,20 @@
#include <ccan/noerr/noerr.h> #include <ccan/noerr/noerr.h>
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
static void *check_has_info(struct manifest *m, static void check_has_info(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft,
struct score *score)
{ {
if (m->info_file) if (m->info_file) {
return NULL; score->pass = true;
return m; score->score = score->total;
} } else {
score->error = "You have no _info file.\n\n"
static const char *describe_has_info(struct manifest *m, void *check_result)
{
return "You have no _info file.\n\n"
"The file _info contains the metadata for a ccan package: things\n" "The file _info contains the metadata for a ccan package: things\n"
"like the dependencies, the documentation for the package as a whole\n" "like the dependencies, the documentation for the package as a whole\n"
"and license information.\n"; "and license information.\n";
}
} }
static const char template[] = static const char template[] =
...@@ -55,7 +54,7 @@ static const char template[] = ...@@ -55,7 +54,7 @@ static const char template[] =
" return 1;\n" " return 1;\n"
"}\n"; "}\n";
static void create_info_template(struct manifest *m, void *check_result) static void create_info_template(struct manifest *m, struct score *score)
{ {
FILE *info; FILE *info;
const char *filename; const char *filename;
...@@ -79,7 +78,6 @@ struct ccanlint has_info = { ...@@ -79,7 +78,6 @@ struct ccanlint has_info = {
.key = "info", .key = "info",
.name = "Module has _info file", .name = "Module has _info file",
.check = check_has_info, .check = check_has_info,
.describe = describe_has_info,
.handle = create_info_template, .handle = create_info_template,
}; };
......
...@@ -12,35 +12,31 @@ ...@@ -12,35 +12,31 @@
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
#include <ccan/noerr/noerr.h> #include <ccan/noerr/noerr.h>
static void *check_has_main_header(struct manifest *m, static void check_has_main_header(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct ccan_file *f; struct ccan_file *f;
list_for_each(&m->h_files, f, list) { list_for_each(&m->h_files, f, list) {
if (strstarts(f->name, m->basename) if (strstarts(f->name, m->basename)
&& strlen(f->name) == strlen(m->basename) + 2) && strlen(f->name) == strlen(m->basename) + 2) {
return NULL; score->pass = true;
score->score = score->total;
return;
}
} }
return m; score->error = talloc_asprintf(score,
}
static const char *describe_has_main_header(struct manifest *m,
void *check_result)
{
return talloc_asprintf(m,
"You have no %s/%s.h header file.\n\n" "You have no %s/%s.h header file.\n\n"
"CCAN modules have a name, the same as the directory name. They're\n" "CCAN modules have a name, the same as the directory name. They're\n"
"expected to have an interface in the header of the same name.\n", "expected to have an interface in the header of the same name.\n",
m->basename, m->basename); m->basename, m->basename);
} }
struct ccanlint has_main_header = { struct ccanlint has_main_header = {
.key = "has-main-header", .key = "has-main-header",
.name = "Module has main header file", .name = "Module has main header file",
.check = check_has_main_header, .check = check_has_main_header,
.describe = describe_has_main_header,
}; };
REGISTER_TEST(has_main_header, NULL); REGISTER_TEST(has_main_header, NULL);
...@@ -139,30 +139,6 @@ static void add_files(struct manifest *m, const char *dir) ...@@ -139,30 +139,6 @@ static void add_files(struct manifest *m, const char *dir)
closedir(d); closedir(d);
} }
char *report_on_lines(struct list_head *files,
char *(*report)(const char *),
char *sofar)
{
struct ccan_file *f;
list_for_each(files, f, list) {
unsigned int i;
char **lines = get_ccan_file_lines(f);
for (i = 0; i < f->num_lines; i++) {
char *r = report(lines[i]);
if (!r)
continue;
sofar = talloc_asprintf_append(sofar,
"%s:%u:%s\n",
f->name, i+1, r);
talloc_free(r);
}
}
return sofar;
}
struct manifest *get_manifest(const void *ctx, const char *dir) struct manifest *get_manifest(const void *ctx, const char *dir)
{ {
struct manifest *m = talloc(ctx, struct manifest); struct manifest *m = talloc(ctx, struct manifest);
...@@ -574,3 +550,12 @@ enum line_compiled get_ccan_line_pp(struct pp_conditions *cond, ...@@ -574,3 +550,12 @@ enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
return ret; return ret;
} }
void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
const char *error)
{
struct file_error *fe = talloc(score, struct file_error);
fe->file = f;
fe->line = line;
fe->error = error;
list_add(&score->per_file_errors, &fe->list);
}
...@@ -25,8 +25,9 @@ static const char *can_run_coverage(struct manifest *m) ...@@ -25,8 +25,9 @@ static const char *can_run_coverage(struct manifest *m)
return NULL; return NULL;
} }
static char *build_module_objs_with_coverage(struct manifest *m, bool keep, static bool build_module_objs_with_coverage(struct manifest *m, bool keep,
char **modobjs) struct score *score,
char **modobjs)
{ {
struct ccan_file *i; struct ccan_file *i;
...@@ -39,13 +40,15 @@ static char *build_module_objs_with_coverage(struct manifest *m, bool keep, ...@@ -39,13 +40,15 @@ static char *build_module_objs_with_coverage(struct manifest *m, bool keep,
err = compile_object(m, fullfile, ccan_dir, "", err = compile_object(m, fullfile, ccan_dir, "",
i->cov_compiled); i->cov_compiled);
if (err) { if (err) {
score_file_error(score, i, 0, err);
talloc_free(i->cov_compiled); talloc_free(i->cov_compiled);
return err; i->cov_compiled = NULL;
return false;
} }
*modobjs = talloc_asprintf_append(*modobjs, *modobjs = talloc_asprintf_append(*modobjs,
" %s", i->cov_compiled); " %s", i->cov_compiled);
} }
return NULL; return true;
} }
static char *obj_list(const struct manifest *m, const char *modobjs) static char *obj_list(const struct manifest *m, const char *modobjs)
...@@ -101,97 +104,53 @@ static char *cov_compile(const void *ctx, ...@@ -101,97 +104,53 @@ static char *cov_compile(const void *ctx,
lib_list(m), file->cov_compiled); lib_list(m), file->cov_compiled);
if (errmsg) { if (errmsg) {
talloc_free(file->cov_compiled); talloc_free(file->cov_compiled);
file->cov_compiled = NULL;
return errmsg; return errmsg;
} }
return NULL; return NULL;
} }
struct compile_tests_result { /* FIXME: Coverage from testable examples as well. */
struct list_node list; static void do_compile_coverage_tests(struct manifest *m,
const char *filename; bool keep,
const char *description; unsigned int *timeleft,
const char *output; struct score *score)
};
static void *do_compile_coverage_tests(struct manifest *m,
bool keep,
unsigned int *timeleft)
{ {
struct list_head *list = talloc(m, struct list_head);
char *cmdout, *modobjs = NULL; char *cmdout, *modobjs = NULL;
struct ccan_file *i; struct ccan_file *i;
struct compile_tests_result *res;
list_head_init(list); if (!list_empty(&m->api_tests)
&& !build_module_objs_with_coverage(m, keep, score, &modobjs)) {
if (!list_empty(&m->api_tests)) { score->error = "Failed to compile module objects with coverage";
cmdout = build_module_objs_with_coverage(m, keep, &modobjs); return;
if (cmdout) {
res = talloc(list, struct compile_tests_result);
res->filename = "Module objects with coverage";
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
return list;
}
} }
list_for_each(&m->run_tests, i, list) { list_for_each(&m->run_tests, i, list) {
compile_tests.total_score++;
cmdout = cov_compile(m, m, i, NULL, keep); cmdout = cov_compile(m, m, i, NULL, keep);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile test with coverage";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
list_for_each(&m->api_tests, i, list) { list_for_each(&m->api_tests, i, list) {
compile_tests.total_score++;
cmdout = cov_compile(m, m, i, modobjs, keep); cmdout = cov_compile(m, m, i, modobjs, keep);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile test with coverage";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
if (!score->error) {
if (list_empty(list)) { score->pass = true;
talloc_free(list); score->score = score->total;
list = NULL;
} }
return list;
}
static const char *describe_compile_coverage_tests(struct manifest *m,
void *check_result)
{
struct list_head *list = check_result;
struct compile_tests_result *i;
char *descrip;
descrip = talloc_strdup(list,
"Compilation of tests for coverage failed:\n");
list_for_each(list, i, list)
descrip = talloc_asprintf_append(descrip, "%s %s\n%s",
i->filename, i->description,
i->output);
return descrip;
} }
struct ccanlint compile_coverage_tests = { struct ccanlint compile_coverage_tests = {
.key = "compile-coverage-tests", .key = "compile-coverage-tests",
.name = "Module tests compile with profiling", .name = "Module tests compile with profiling",
.check = do_compile_coverage_tests, .check = do_compile_coverage_tests,
.total_score = 1,
.describe = describe_compile_coverage_tests,
.can_run = can_run_coverage, .can_run = can_run_coverage,
}; };
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
static const char *can_build(struct manifest *m) static const char *can_run(struct manifest *m)
{ {
if (safe_mode) if (safe_mode)
return "Safe mode enabled"; return "Safe mode enabled";
...@@ -30,39 +30,35 @@ static char *compile(struct manifest *m, ...@@ -30,39 +30,35 @@ static char *compile(struct manifest *m,
cfile->compiled); cfile->compiled);
} }
static void *do_compile_test_helpers(struct manifest *m, static void do_compile_test_helpers(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft,
struct score *score)
{ {
char *cmdout = NULL;
struct ccan_file *i; struct ccan_file *i;
compile_tests.total_score = 0; if (list_empty(&m->other_test_c_files))
score->total = 0;
list_for_each(&m->other_test_c_files, i, list) { list_for_each(&m->other_test_c_files, i, list) {
compile_tests.total_score++; char *cmdout = compile(m, keep, i);
cmdout = compile(m, keep, i); if (cmdout) {
if (cmdout) score->error = "Failed to compile helper C files";
return talloc_asprintf(m, score_file_error(score, i, 0, cmdout);
"Failed to compile helper C" }
" code file %s:\n%s",
i->name, cmdout);
} }
return NULL;
}
static const char *describe_compile_test_helpers(struct manifest *m, if (!score->error) {
void *check_result) score->pass = true;
{ score->score = score->total;
return check_result; }
} }
struct ccanlint compile_test_helpers = { struct ccanlint compile_test_helpers = {
.key = "compile-helpers", .key = "compile-helpers",
.name = "Module test helper objects compile", .name = "Module test helper objects compile",
.total_score = 1,
.check = do_compile_test_helpers, .check = do_compile_test_helpers,
.describe = describe_compile_test_helpers, .can_run = can_run,
.can_run = can_build,
}; };
REGISTER_TEST(compile_test_helpers, &depends_built, &has_tests, NULL); REGISTER_TEST(compile_test_helpers, &depends_built, &has_tests, NULL);
...@@ -82,124 +82,64 @@ static char *compile(const void *ctx, ...@@ -82,124 +82,64 @@ static char *compile(const void *ctx,
return NULL; return NULL;
} }
struct compile_tests_result { static void do_compile_tests(struct manifest *m,
struct list_node list; bool keep,
const char *filename; unsigned int *timeleft, struct score *score)
const char *description;
const char *output;
};
static void *do_compile_tests(struct manifest *m,
bool keep,
unsigned int *timeleft)
{ {
struct list_head *list = talloc(m, struct list_head);
char *cmdout; char *cmdout;
struct ccan_file *i; struct ccan_file *i;
struct compile_tests_result *res;
list_head_init(list);
compile_tests.total_score = 0;
list_for_each(&m->compile_ok_tests, i, list) { list_for_each(&m->compile_ok_tests, i, list) {
compile_tests.total_score++; cmdout = compile(score, m, i, false, false, keep);
cmdout = compile(list, m, i, false, false, keep);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile tests";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
list_for_each(&m->run_tests, i, list) { list_for_each(&m->run_tests, i, list) {
compile_tests.total_score++; cmdout = compile(score, m, i, false, false, keep);
cmdout = compile(m, m, i, false, false, keep);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile tests";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
list_for_each(&m->api_tests, i, list) { list_for_each(&m->api_tests, i, list) {
compile_tests.total_score++; cmdout = compile(score, m, i, false, true, keep);
cmdout = compile(m, m, i, false, true, keep);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile tests";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile";
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
/* The compile fail tests are a bit weird, handle them separately */
if (score->error)
return;
list_for_each(&m->compile_fail_tests, i, list) { list_for_each(&m->compile_fail_tests, i, list) {
compile_tests.total_score++; cmdout = compile(score, m, i, false, false, false);
cmdout = compile(list, m, i, false, false, false);
if (cmdout) { if (cmdout) {
res = talloc(list, struct compile_tests_result); score->error = "Failed to compile without -DFAIL";
res->filename = i->name; score_file_error(score, i, 0, cmdout);
res->description = "failed to compile without -DFAIL"; return;
res->output = talloc_steal(res, cmdout); }
list_add_tail(list, &res->list); cmdout = compile(score, m, i, true, false, false);
} else { if (!cmdout) {
cmdout = compile(list, m, i, true, false, false); score->error = "Compiled successfully with -DFAIL?";
if (!cmdout) { score_file_error(score, i, 0, NULL);
res = talloc(list, struct compile_tests_result); return;
res->filename = i->name;
res->description = "compiled successfully"
" with -DFAIL";
res->output = "";
list_add_tail(list, &res->list);
}
} }
} }
if (list_empty(list)) { score->pass = true;
talloc_free(list); score->score = score->total;
list = NULL;
}
return list;
}
static unsigned int score_compile_tests(struct manifest *m,
void *check_result)
{
struct list_head *list = check_result;
struct compile_tests_result *i;
unsigned int score = compile_tests.total_score;
list_for_each(list, i, list)
score--;
return score;
}
static const char *describe_compile_tests(struct manifest *m,
void *check_result)
{
struct list_head *list = check_result;
struct compile_tests_result *i;
char *descrip = talloc_strdup(list, "Compilation tests failed:\n");
list_for_each(list, i, list)
descrip = talloc_asprintf_append(descrip, "%s %s\n%s",
i->filename, i->description,
i->output);
return descrip;
} }
struct ccanlint compile_tests = { struct ccanlint compile_tests = {
.key = "compile-tests", .key = "compile-tests",
.name = "Module tests compile", .name = "Module tests compile",
.score = score_compile_tests,
.total_score = 1,
.check = do_compile_tests, .check = do_compile_tests,
.describe = describe_compile_tests,
.can_run = can_build, .can_run = can_build,
}; };
......
...@@ -46,12 +46,11 @@ static bool has_dep(struct manifest *m, const char *depname, bool tap_ok) ...@@ -46,12 +46,11 @@ static bool has_dep(struct manifest *m, const char *depname, bool tap_ok)
return false; return false;
} }
static void *check_depends_accurate(struct manifest *m, static void check_depends_accurate(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct list_head *list; struct list_head *list;
char *report = talloc_strdup(m, "");
foreach_ptr(list, &m->c_files, &m->h_files, foreach_ptr(list, &m->c_files, &m->h_files,
&m->run_tests, &m->api_tests, &m->run_tests, &m->api_tests,
...@@ -61,7 +60,7 @@ static void *check_depends_accurate(struct manifest *m, ...@@ -61,7 +60,7 @@ static void *check_depends_accurate(struct manifest *m,
bool tap_ok; bool tap_ok;
/* Including ccan/tap is fine for tests. */ /* Including ccan/tap is fine for tests. */
tap_ok = (list != &m->c_files && list != &m->h_files); tap_ok = (list != &m->c_files && list != &m->h_files);
list_for_each(list, f, list) { list_for_each(list, f, list) {
unsigned int i; unsigned int i;
...@@ -79,35 +78,25 @@ static void *check_depends_accurate(struct manifest *m, ...@@ -79,35 +78,25 @@ static void *check_depends_accurate(struct manifest *m,
if (!strchr(strchr(p, '/') + 1, '/')) if (!strchr(strchr(p, '/') + 1, '/'))
continue; continue;
*strchr(strchr(p, '/') + 1, '/') = '\0'; *strchr(strchr(p, '/') + 1, '/') = '\0';
if (!has_dep(m, p, tap_ok)) if (has_dep(m, p, tap_ok))
report = talloc_asprintf_append(report, continue;
"%s:%u:%s\n", score->error = "Includes a ccan module"
f->name, i+1, lines[i]); " not listed in _info";
score_file_error(score, f, i+1, lines[i]);
} }
} }
} }
if (streq(report, "")) { if (!score->error) {
talloc_free(report); score->pass = true;
report = NULL; score->score = score->total;
} }
return report;
}
static const char *describe_depends_accurage(struct manifest *m,
void *check_result)
{
return talloc_asprintf(check_result,
"You include ccan modules you don't list as dependencies:\n"
"%s", (char *)check_result);
} }
struct ccanlint depends_accurate = { struct ccanlint depends_accurate = {
.key = "depends-accurate", .key = "depends-accurate",
.name = "Module's CCAN dependencies are the only ccan files #included", .name = "Module's CCAN dependencies are the only ccan files #included",
.total_score = 1,
.check = check_depends_accurate, .check = check_depends_accurate,
.describe = describe_depends_accurage,
}; };
REGISTER_TEST(depends_accurate, &depends_exist, NULL); REGISTER_TEST(depends_accurate, &depends_exist, NULL);
...@@ -15,6 +15,8 @@ static const char *can_run(struct manifest *m) ...@@ -15,6 +15,8 @@ static const char *can_run(struct manifest *m)
{ {
if (safe_mode) if (safe_mode)
return "Safe mode enabled"; return "Safe mode enabled";
if (list_empty(&m->examples))
return "No examples to compile";
return NULL; return NULL;
} }
...@@ -128,11 +130,6 @@ static char *compile(const void *ctx, ...@@ -128,11 +130,6 @@ static char *compile(const void *ctx,
return NULL; return NULL;
} }
struct score {
unsigned int score;
char *errors;
};
static char *start_main(char *ret, const char *why) static char *start_main(char *ret, const char *why)
{ {
return talloc_asprintf_append(ret, return talloc_asprintf_append(ret,
...@@ -434,22 +431,21 @@ static struct ccan_file *mangle_example(struct manifest *m, ...@@ -434,22 +431,21 @@ static struct ccan_file *mangle_example(struct manifest *m,
return f; return f;
} }
static void *build_examples(struct manifest *m, bool keep, static void build_examples(struct manifest *m, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct ccan_file *i; struct ccan_file *i;
struct score *score = talloc(m, struct score);
char **prev = NULL; char **prev = NULL;
score->score = 0; score->total = 0;
score->errors = talloc_strdup(score, ""); score->pass = true;
examples_compile.total_score = 0;
list_for_each(&m->examples, i, list) { list_for_each(&m->examples, i, list) {
char *ret, *ret1, *ret2 = NULL; char *ret, *ret1, *ret2 = NULL;
struct ccan_file *mangle1, *mangle2 = NULL; struct ccan_file *mangle1, *mangle2 = NULL;
char *err;
examples_compile.total_score++; score->total++;
/* Simplify our dumb parsing. */ /* Simplify our dumb parsing. */
strip_leading_whitespace(get_ccan_file_lines(i)); strip_leading_whitespace(get_ccan_file_lines(i));
ret = compile(i, m, i, keep); ret = compile(i, m, i, keep);
...@@ -481,64 +477,46 @@ static void *build_examples(struct manifest *m, bool keep, ...@@ -481,64 +477,46 @@ static void *build_examples(struct manifest *m, bool keep,
} }
} }
score->errors = talloc_asprintf_append(score->errors, score->pass = false;
"%s: tried standalone example:\n" score->error = "Compiling extracted examples failed";
"%s\n" if (!verbose) {
"Errors: %s\n\n", if (mangle2)
i->name, err = "Standalone, adding headers, "
get_ccan_file_contents(i), "and including previous "
ret); "example all failed";
score->errors = talloc_asprintf_append(score->errors, else
"%s: tried adding headers, wrappers:\n" err = "Standalone compile and"
"%s\n" " adding headers both failed";
"Errors: %s\n\n", } else {
i->name, err = talloc_asprintf("Standalone example:\n"
get_ccan_file_contents(mangle1), "%s\n"
ret1); "Errors: %s\n\n"
"Adding headers, wrappers:\n"
if (mangle2) { "%s\n"
score->errors = talloc_asprintf_append(score->errors, "Errors: %s\n\n",
"%s: tried combining with" get_ccan_file_contents(i),
" previous example:\n" ret,
get_ccan_file_contents(mangle1),
ret1);
if (mangle2)
err = talloc_asprintf_append(err,
"Combining with previous example:\n"
"%s\n" "%s\n"
"Errors: %s\n\n", "Errors: %s\n\n",
i->name,
get_ccan_file_contents(mangle2), get_ccan_file_contents(mangle2),
ret2); ret2);
} }
score_file_error(score, i, 0, err);
/* This didn't work, so not a candidate for combining. */ /* This didn't work, so not a candidate for combining. */
prev = NULL; prev = NULL;
} }
if (strcmp(score->errors, "") == 0) {
talloc_free(score);
return NULL;
}
return score;
}
static unsigned int score_examples(struct manifest *m, void *check_result)
{
struct score *score = check_result;
return score->score;
}
static const char *describe(struct manifest *m, void *check_result)
{
struct score *score = check_result;
if (verbose >= 2 && score->errors)
return talloc_asprintf(m, "Compile errors building examples:\n"
"%s", score->errors);
return NULL;
} }
struct ccanlint examples_compile = { struct ccanlint examples_compile = {
.key = "examples-compile", .key = "examples-compile",
.name = "Module examples compile", .name = "Module examples compile",
.score = score_examples,
.total_score = 3, /* This gets changed to # examples, if they exist */
.check = build_examples, .check = build_examples,
.describe = describe,
.can_run = can_run, .can_run = can_run,
}; };
......
...@@ -14,16 +14,16 @@ ...@@ -14,16 +14,16 @@
static const char *can_run(struct manifest *m) static const char *can_run(struct manifest *m)
{ {
struct list_head *list;
if (safe_mode) if (safe_mode)
return "Safe mode enabled"; return "Safe mode enabled";
return NULL; foreach_ptr(list, &m->examples, &m->mangled_examples)
if (!list_empty(list))
return NULL;
return "No examples";
} }
struct score {
unsigned int score;
char *errors;
};
/* Very dumb scanner, allocates %s-strings. */ /* Very dumb scanner, allocates %s-strings. */
static bool scan_forv(const void *ctx, static bool scan_forv(const void *ctx,
const char *input, const char *fmt, const va_list *args) const char *input, const char *fmt, const va_list *args)
...@@ -224,26 +224,21 @@ static char *unexpected(struct ccan_file *i, const char *input, ...@@ -224,26 +224,21 @@ static char *unexpected(struct ccan_file *i, const char *input,
return output; return output;
} }
static void *run_examples(struct manifest *m, bool keep, static void run_examples(struct manifest *m, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct ccan_file *i; struct ccan_file *i;
struct list_head *list; struct list_head *list;
struct score *score = talloc(m, struct score);
score->score = 0; score->total = 0;
score->errors = talloc_strdup(score, ""); score->pass = true;
examples_run.total_score = 0;
foreach_ptr(list, &m->examples, &m->mangled_examples) { foreach_ptr(list, &m->examples, &m->mangled_examples) {
list_for_each(list, i, list) { list_for_each(list, i, list) {
char **lines, *expect, *input, *output; char **lines, *expect, *input, *output;
unsigned int linenum = 0; unsigned int linenum = 0;
bool exact; bool exact;
if (i->compiled == NULL)
continue;
lines = get_ccan_file_lines(i); lines = get_ccan_file_lines(i);
for (expect = find_expect(i, lines, &input, &exact, for (expect = find_expect(i, lines, &input, &exact,
...@@ -252,52 +247,34 @@ static void *run_examples(struct manifest *m, bool keep, ...@@ -252,52 +247,34 @@ static void *run_examples(struct manifest *m, bool keep,
linenum++, linenum++,
expect = find_expect(i, lines, &input, expect = find_expect(i, lines, &input,
&exact, &linenum)) { &exact, &linenum)) {
examples_run.total_score++; char *err;
score->total++;
if (i->compiled == NULL)
continue;
output = unexpected(i, input, expect, exact); output = unexpected(i, input, expect, exact);
if (!output) if (!output) {
score->score++; score->score++;
else { continue;
score->errors = talloc_asprintf_append(
score->errors,
"%s: output '%s' didn't"
" %s '%s'\n",
i->name, output,
exact ? "match" : "contain",
expect);
} }
err = talloc_asprintf(score,
"output '%s' didn't"
" %s '%s'\n",
output,
exact
? "match" : "contain",
expect);
score_file_error(score, i, linenum+1, err);
score->pass = false;
} }
} }
} }
if (strcmp(score->errors, "") == 0) {
talloc_free(score);
return NULL;
}
return score;
}
static unsigned int score_examples(struct manifest *m, void *check_result)
{
struct score *score = check_result;
return score->score;
}
static const char *describe(struct manifest *m, void *check_result)
{
struct score *score = check_result;
if (verbose)
return talloc_asprintf(m, "Wrong output running examples:\n"
"%s", score->errors);
return NULL;
} }
struct ccanlint examples_run = { struct ccanlint examples_run = {
.key = "examples-run", .key = "examples-run",
.name = "Module examples with expected output give that output", .name = "Module examples with expected output give that output",
.score = score_examples,
.total_score = 3, /* This gets changed to # testable, if we run. */
.check = run_examples, .check = run_examples,
.describe = describe,
.can_run = can_run, .can_run = can_run,
}; };
......
...@@ -56,28 +56,22 @@ static char *add_example(struct manifest *m, struct ccan_file *source, ...@@ -56,28 +56,22 @@ static char *add_example(struct manifest *m, struct ccan_file *source,
} }
/* FIXME: We should have one example per function in header. */ /* FIXME: We should have one example per function in header. */
struct score { static void extract_examples(struct manifest *m,
bool info_example, header_example; bool keep,
char *error; unsigned int *timeleft,
}; struct score *score)
static void *extract_examples(struct manifest *m,
bool keep,
unsigned int *timeleft)
{ {
struct ccan_file *f; struct ccan_file *f;
struct doc_section *d; struct doc_section *d;
struct score *score = talloc(m, struct score); bool have_info_example = false, have_header_example = false;
score->info_example = score->header_example = false;
score->error = NULL;
score->total = 2;
list_for_each(get_ccan_file_docs(m->info_file), d, list) { list_for_each(get_ccan_file_docs(m->info_file), d, list) {
if (streq(d->type, "example")) { if (streq(d->type, "example")) {
score->error = add_example(m, m->info_file, keep, d); score->error = add_example(m, m->info_file, keep, d);
if (score->error) if (score->error)
return score; return;
score->info_example = true; have_info_example = true;
} }
} }
...@@ -91,57 +85,35 @@ static void *extract_examples(struct manifest *m, ...@@ -91,57 +85,35 @@ static void *extract_examples(struct manifest *m,
if (streq(d->type, "example")) { if (streq(d->type, "example")) {
score->error = add_example(m, f, keep, d); score->error = add_example(m, f, keep, d);
if (score->error) if (score->error)
return score; return;
score->header_example = true; have_header_example = true;
} }
} }
} }
return score;
}
static unsigned int score_examples(struct manifest *m, void *check_result)
{
struct score *score = check_result;
int total = 0;
if (score->error)
return 0;
total += score->info_example;
total += score->header_example;
return total;
}
static const char *describe_examples(struct manifest *m,
void *check_result)
{
struct score *score = check_result;
char *descrip = NULL;
if (score->error)
return score->error;
if (!score->info_example) if (!have_info_example && !have_header_example) {
descrip = talloc_asprintf(score, score->error = "You don't have any Example: sections";
"Your _info file has no module example.\n\n" score->score = 0;
"There should be an Example: section of the _info documentation\n" } else if (!have_info_example) {
"which provides a concise toy program which uses your module\n"); score->error = "You don't have an Example: section in _info";
score->score = 1;
if (!score->header_example) score->pass = true;
descrip = talloc_asprintf(score, } else if (!have_header_example) {
"%sMain header file file has no examples\n\n" score->error = talloc_asprintf(score,
"There should be an Example: section for each public function\n" "You don't have an Example: section in %s.h",
"demonstrating its use\n", descrip ? descrip : ""); m->basename);
score->score = 1;
return descrip; score->pass = true;
} else {
score->score = score->total;
score->pass = true;
}
} }
struct ccanlint has_examples = { struct ccanlint has_examples = {
.key = "has-examples", .key = "has-examples",
.name = "_info and header files have examples", .name = "_info and header files have examples",
.score = score_examples,
.check = extract_examples, .check = extract_examples,
.describe = describe_examples,
.total_score = 2,
}; };
REGISTER_TEST(has_examples, &has_info, NULL); REGISTER_TEST(has_examples, &has_info, NULL);
...@@ -15,38 +15,7 @@ ...@@ -15,38 +15,7 @@
#include <ccan/noerr/noerr.h> #include <ccan/noerr/noerr.h>
#include <ccan/grab_file/grab_file.h> #include <ccan/grab_file/grab_file.h>
struct info_docs static void create_info_template_doc(struct manifest *m, struct score *score)
{
bool summary;
bool description;
};
static void *check_has_info_documentation(struct manifest *m,
bool keep,
unsigned int *timeleft)
{
struct list_head *infodocs = get_ccan_file_docs(m->info_file);
struct doc_section *d;
struct info_docs id = { false, false };
list_for_each(infodocs, d, list) {
if (!streq(d->function, m->basename))
continue;
if (streq(d->type, "summary"))
id.summary = true;
if (streq(d->type, "description"))
id.description = true;
}
if (id.summary && id.description)
return NULL;
return talloc_memdup(m, &id, sizeof(id));
}
/* This is defined below. */
extern struct ccanlint has_info_documentation;
static void create_info_template_doc(struct manifest *m, void *check_result)
{ {
int fd = open("_info.new", O_WRONLY|O_CREAT|O_EXCL, 0666); int fd = open("_info.new", O_WRONLY|O_CREAT|O_EXCL, 0666);
FILE *new; FILE *new;
...@@ -87,42 +56,44 @@ static void create_info_template_doc(struct manifest *m, void *check_result) ...@@ -87,42 +56,44 @@ static void create_info_template_doc(struct manifest *m, void *check_result)
} }
} }
static const char *describe_has_info_documentation(struct manifest *m, static void check_has_info_documentation(struct manifest *m,
void *check_result) bool keep,
unsigned int *timeleft,
struct score *score)
{ {
struct info_docs *id = check_result; struct list_head *infodocs = get_ccan_file_docs(m->info_file);
char *reason = talloc_strdup(m, ""); struct doc_section *d;
bool summary = false, description = false;
if (!id->summary) { list_for_each(infodocs, d, list) {
has_info_documentation.handle = create_info_template_doc; if (!streq(d->function, m->basename))
reason = talloc_asprintf_append(reason, continue;
"Your _info file has no module documentation.\n\n" if (streq(d->type, "summary"))
summary = true;
if (streq(d->type, "description"))
description = true;
}
if (summary && description) {
score->score = score->total;
score->pass = true;
} else if (!summary) {
score->error = "_info file has no module documentation.\n\n"
"CCAN modules use /**-style comments for documentation: the\n" "CCAN modules use /**-style comments for documentation: the\n"
"overall documentation belongs in the _info metafile.\n"); "overall documentation belongs in the _info metafile.\n";
has_info_documentation.handle = create_info_template_doc;
} }
if (!id->description) else if (!description)
reason = talloc_asprintf_append(reason, score->error = "_info file has no module description.\n\n"
"Your _info file has no module description.\n\n"
"The lines after the first summary line in the _info file\n" "The lines after the first summary line in the _info file\n"
"documentation should describe the purpose and use of the\n" "documentation should describe the purpose and use of the\n"
"overall package\n"); "overall package\n";
return reason;
}
static unsigned int has_info_documentation_score(struct manifest *m,
void *check_result)
{
struct info_docs *id = check_result;
return (unsigned int)id->summary + id->description;
} }
struct ccanlint has_info_documentation = { struct ccanlint has_info_documentation = {
.key = "info-documentation", .key = "info-documentation",
.name = "Module has documentation in _info", .name = "Module has documentation in _info",
.total_score = 2,
.score = has_info_documentation_score,
.check = check_has_info_documentation, .check = check_has_info_documentation,
.describe = describe_has_info_documentation,
}; };
REGISTER_TEST(has_info_documentation, NULL); REGISTER_TEST(has_info_documentation, NULL);
...@@ -10,45 +10,20 @@ ...@@ -10,45 +10,20 @@
#include <err.h> #include <err.h>
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
static char test_is_not_dir[] = "test is not a directory"; static void handle_no_tests(struct manifest *m, struct score *score)
static void *check_has_tests(struct manifest *m,
bool keep,
unsigned int *timeleft)
{ {
struct stat st; FILE *run;
struct ccan_file *i;
char *test_dir = talloc_asprintf(m, "%s/test", m->dir); char *test_dir = talloc_asprintf(m, "%s/test", m->dir);
if (lstat(test_dir, &st) != 0) { printf(
if (errno != ENOENT) "CCAN modules have a directory called test/ which contains tests.\n"
err(1, "statting %s", test_dir);
return "You have no test directory";
}
if (!S_ISDIR(st.st_mode))
return test_is_not_dir;
if (list_empty(&m->api_tests)
&& list_empty(&m->run_tests)
&& list_empty(&m->compile_ok_tests)) {
if (list_empty(&m->compile_fail_tests))
return "You have no tests in the test directory";
else
return "You have no positive tests in the test directory";
}
return NULL;
}
static const char *describe_has_tests(struct manifest *m, void *check_result)
{
return talloc_asprintf(m, "%s\n\n"
"CCAN modules have a directory called test/ which contains tests.\n"
"There are four kinds of tests: api, run, compile_ok and compile_fail:\n" "There are four kinds of tests: api, run, compile_ok and compile_fail:\n"
"you can tell which type of test a C file is by its name, eg 'run.c'\n" "you can tell which type of test a C file is by its name, eg 'run.c'\n"
"and 'run-simple.c' are both run tests.\n\n" "and 'run-simple.c' are both run tests.\n\n"
"The simplest kind of test is a run test, which must compile with no\n" "The simplest kind of test is a run test, which must compile with no\n"
"warnings, and then run: it is expected to use libtap to report its\n" "warnings, and then run: it is expected to use ccan/tap to report its\n"
"results in a simple and portable format. It should #include the C\n" "results in a simple and portable format. It should #include the C\n"
"files from the module directly (so it can probe the internals): the\n" "files from the module directly (so it can probe the internals): the\n"
"module will not be linked in. The test will be run in a temporary\n" "module will not be linked in. The test will be run in a temporary\n"
...@@ -67,20 +42,8 @@ static const char *describe_has_tests(struct manifest *m, void *check_result) ...@@ -67,20 +42,8 @@ static const char *describe_has_tests(struct manifest *m, void *check_result)
"when it's not defined: this helps ensure unrelated errors don't make\n" "when it's not defined: this helps ensure unrelated errors don't make\n"
"compilation fail.\n\n" "compilation fail.\n\n"
"Note that the tests are not linked against the files in the\n" "Note that only API tests are linked against the files in the module!\n"
"above: you should directly #include those C files you want. This\n" );
"allows access to static functions and use special effects inside\n"
"test files\n", (char *)check_result);
}
static void handle_no_tests(struct manifest *m, void *check_result)
{
FILE *run;
struct ccan_file *i;
char *test_dir = talloc_asprintf(m, "%s/test", m->dir);
if (check_result == test_is_not_dir)
return;
if (!ask("Should I create a template test/run.c file for you?")) if (!ask("Should I create a template test/run.c file for you?"))
return; return;
...@@ -101,41 +64,70 @@ static void handle_no_tests(struct manifest *m, void *check_result) ...@@ -101,41 +64,70 @@ static void handle_no_tests(struct manifest *m, void *check_result)
fprintf(run, "#include <ccan/%s/%s>\n", fprintf(run, "#include <ccan/%s/%s>\n",
m->basename, i->name); m->basename, i->name);
} }
fputs("#include <ccan/tap/tap.h>\n", run); fprintf(run, "%s",
fputs("\n", run); "#include <ccan/tap/tap.h>\n\n"
"int main(void)\n"
fputs("int main(void)\n", run); "{\n"
fputs("{\n", run); " /* This is how many tests you plan to run */\n"
fputs("\t/* This is how many tests you plan to run */\n", run); " plan_tests(3);\n"
fputs("\tplan_tests(3);\n", run); "\n"
fputs("\n", run); " /* Simple thing we expect to succeed */\n"
fputs("\t/* Simple thing we expect to succeed */\n", run); " ok1(some_test())\n"
fputs("\tok1(some_test())\n", run); " /* Same, with an explicit description of the test. */\n"
fputs("\t/* Same, with an explicit description of the test. */\n", run); " ok(some_test(), \"%s with no args should return 1\", \"some_test\")\n"
fputs("\tok(some_test(), \"%s with no args should return 1\", \"some_test\")\n", run); " /* How to print out messages for debugging. */\n"
fputs("\t/* How to print out messages for debugging. */\n", run); " diag(\"Address of some_test is %p\", &some_test)\n"
fputs("\tdiag(\"Address of some_test is %p\", &some_test)\n", run); " /* Conditional tests must be explicitly skipped. */\n"
fputs("\t/* Conditional tests must be explicitly skipped. */\n", run); "#if HAVE_SOME_FEATURE\n"
fputs("#if HAVE_SOME_FEATURE\n", run); " ok1(test_some_feature())\n"
fputs("\tok1(test_some_feature())\n", run); "#else\n"
fputs("#else\n", run); " skip(1, \"Don\'t have SOME_FEATURE\")\n"
fputs("\tskip(1, \"Don\'t have SOME_FEATURE\")\n", run); "#endif\n"
fputs("#endif\n", run); "\n"
fputs("\n", run); " /* This exits depending on whether all tests passed */\n"
fputs("\t/* This exits depending on whether all tests passed */\n", run); " return exit_status();\n"
fputs("\treturn exit_status();\n", run); "}\n");
fputs("}\n", run);
fclose(run); fclose(run);
} }
static void check_has_tests(struct manifest *m,
bool keep,
unsigned int *timeleft, struct score *score)
{
struct stat st;
char *test_dir = talloc_asprintf(m, "%s/test", m->dir);
if (lstat(test_dir, &st) != 0) {
score->error = "No test directory";
if (errno != ENOENT)
err(1, "statting %s", test_dir);
has_tests.handle = handle_no_tests;
return;
}
if (!S_ISDIR(st.st_mode)) {
score->error = "test is not a directory";
return;
}
if (list_empty(&m->api_tests)
&& list_empty(&m->run_tests)
&& list_empty(&m->compile_ok_tests)) {
if (list_empty(&m->compile_fail_tests)) {
score->error = "No tests in test directory";
has_tests.handle = handle_no_tests;
} else
score->error = "No positive tests in test directory";
return;
}
score->pass = true;
score->score = score->total;
}
struct ccanlint has_tests = { struct ccanlint has_tests = {
.key = "has-tests", .key = "has-tests",
.name = "Module has tests", .name = "Module has tests",
.check = check_has_tests, .check = check_has_tests,
.describe = describe_has_tests,
.total_score = 1,
.handle = handle_no_tests,
}; };
REGISTER_TEST(has_tests, NULL); REGISTER_TEST(has_tests, NULL);
...@@ -17,12 +17,69 @@ ...@@ -17,12 +17,69 @@
static const char explain[] static const char explain[]
= "Headers usually start with the C preprocessor lines to prevent multiple\n" = "Headers usually start with the C preprocessor lines to prevent multiple\n"
"inclusions. These look like the following:\n" "inclusions. These look like the following:\n"
"#ifndef MY_HEADER_H\n" "#ifndef CCAN_<MODNAME>_H\n"
"#define MY_HEADER_H\n" "#define CCAN_<MODNAME>_H\n"
"...\n" "...\n"
"#endif /* MY_HEADER_H */\n"; "#endif /* CCAN_<MODNAME>_H */\n";
static char *report_idem(struct ccan_file *f, char *sofar) static void fix_name(char *name)
{
unsigned int i, j;
for (i = j = 0; name[i]; i++) {
if (isalnum(name[i]) || name[i] == '_')
name[j++] = toupper(name[i]);
}
name[j] = '\0';
}
static void handle_idem(struct manifest *m, struct score *score)
{
struct file_error *e;
list_for_each(&score->per_file_errors, e, list) {
char *name, *q, *tmpname;
FILE *out;
unsigned int i;
/* Main header gets CCAN_FOO_H, others CCAN_FOO_XXX_H */
if (strstarts(e->file->name, m->basename)
|| strlen(e->file->name) != strlen(m->basename) + 2)
name = talloc_asprintf(score, "CCAN_%s_H", m->basename);
else
name = talloc_asprintf(score, "CCAN_%s_%s_H",
m->basename, e->file->name);
fix_name(name);
q = talloc_asprintf(score,
"Should I wrap %s in #ifndef/#define %s for you?",
e->file->name, name);
if (!ask(q))
continue;
tmpname = maybe_temp_file(score, ".h", false, e->file->name);
out = fopen(tmpname, "w");
if (!out)
err(1, "Opening %s", tmpname);
if (fprintf(out, "#ifndef %s\n#define %s\n", name, name) < 0)
err(1, "Writing %s", tmpname);
for (i = 0; i < e->file->num_lines; i++)
if (fprintf(out, "%s\n", e->file->lines[i]) < 0)
err(1, "Writing %s", tmpname);
if (fprintf(out, "#endif /* %s */\n", name) < 0)
err(1, "Writing %s", tmpname);
if (fclose(out) != 0)
err(1, "Closing %s", tmpname);
if (!move_file(tmpname, e->file->fullname))
err(1, "Moving %s to %s", tmpname, e->file->fullname);
}
}
static bool check_idem(struct ccan_file *f, struct score *score)
{ {
struct line_info *line_info; struct line_info *line_info;
unsigned int i, first_preproc_line; unsigned int i, first_preproc_line;
...@@ -31,44 +88,48 @@ static char *report_idem(struct ccan_file *f, char *sofar) ...@@ -31,44 +88,48 @@ static char *report_idem(struct ccan_file *f, char *sofar)
line_info = get_ccan_line_info(f); line_info = get_ccan_line_info(f);
if (f->num_lines < 3) if (f->num_lines < 3)
/* FIXME: We assume small headers probably uninteresting. */ /* FIXME: We assume small headers probably uninteresting. */
return sofar; return true;
score->error = "Headers are not idempotent";
for (i = 0; i < f->num_lines; i++) { for (i = 0; i < f->num_lines; i++) {
if (line_info[i].type == DOC_LINE if (line_info[i].type == DOC_LINE
|| line_info[i].type == COMMENT_LINE) || line_info[i].type == COMMENT_LINE)
continue; continue;
if (line_info[i].type == CODE_LINE) if (line_info[i].type == CODE_LINE) {
return talloc_asprintf_append(sofar, score_file_error(score, f, i+1,
"%s:%u:expect first non-comment line to be #ifndef.\n", f->name, i+1); "Expect first non-comment line to be"
else if (line_info[i].type == PREPROC_LINE) " #ifndef.");
return false;
} else if (line_info[i].type == PREPROC_LINE)
break; break;
} }
/* No code at all? Don't complain. */ /* No code at all? Don't complain. */
if (i == f->num_lines) if (i == f->num_lines)
return sofar; return true;
first_preproc_line = i; first_preproc_line = i;
for (i = first_preproc_line+1; i < f->num_lines; i++) { for (i = first_preproc_line+1; i < f->num_lines; i++) {
if (line_info[i].type == DOC_LINE if (line_info[i].type == DOC_LINE
|| line_info[i].type == COMMENT_LINE) || line_info[i].type == COMMENT_LINE)
continue; continue;
if (line_info[i].type == CODE_LINE) if (line_info[i].type == CODE_LINE) {
return talloc_asprintf_append(sofar, score_file_error(score, f, i+1,
"%s:%u:expect second line to be #define.\n", f->name, i+1); "Expect second non-comment line to be"
else if (line_info[i].type == PREPROC_LINE) " #define.");
return false;
} else if (line_info[i].type == PREPROC_LINE)
break; break;
} }
/* No code at all? Weird. */ /* No code at all? Weird. */
if (i == f->num_lines) if (i == f->num_lines)
return sofar; return true;
/* We expect a condition on this line. */ /* We expect a condition on this line. */
if (!line_info[i].cond) { if (!line_info[i].cond) {
return talloc_asprintf_append(sofar, score_file_error(score, f, i+1, "Expected #ifndef");
"%s:%u:expected #ifndef.\n", return false;
f->name, first_preproc_line+1);
} }
line = f->lines[i]; line = f->lines[i];
...@@ -76,24 +137,27 @@ static char *report_idem(struct ccan_file *f, char *sofar) ...@@ -76,24 +137,27 @@ static char *report_idem(struct ccan_file *f, char *sofar)
/* We expect the condition to be ! IFDEF <symbol>. */ /* We expect the condition to be ! IFDEF <symbol>. */
if (line_info[i].cond->type != PP_COND_IFDEF if (line_info[i].cond->type != PP_COND_IFDEF
|| !line_info[i].cond->inverse) { || !line_info[i].cond->inverse) {
return talloc_asprintf_append(sofar, score_file_error(score, f, i+1, "Expected #ifndef");
"%s:%u:expected #ifndef.\n", return false;
f->name, first_preproc_line+1);
} }
/* And this to be #define <symbol> */ /* And this to be #define <symbol> */
if (!get_token(&line, "#")) if (!get_token(&line, "#"))
abort(); abort();
if (!get_token(&line, "define")) { if (!get_token(&line, "define")) {
return talloc_asprintf_append(sofar, char *str = talloc_asprintf(score,
"%s:%u:expected '#define %s'.\n", "expected '#define %s'",
f->name, i+1, line_info[i].cond->symbol); line_info[i].cond->symbol);
score_file_error(score, f, i+1, str);
return false;
} }
sym = get_symbol_token(f, &line); sym = get_symbol_token(f, &line);
if (!sym || !streq(sym, line_info[i].cond->symbol)) { if (!sym || !streq(sym, line_info[i].cond->symbol)) {
return talloc_asprintf_append(sofar, char *str = talloc_asprintf(score,
"%s:%u:expected '#define %s'.\n", "expected '#define %s'",
f->name, i+1, line_info[i].cond->symbol); line_info[i].cond->symbol);
score_file_error(score, f, i+1, str);
return false;
} }
/* Rest of code should all be covered by that conditional. */ /* Rest of code should all be covered by that conditional. */
...@@ -103,42 +167,35 @@ static char *report_idem(struct ccan_file *f, char *sofar) ...@@ -103,42 +167,35 @@ static char *report_idem(struct ccan_file *f, char *sofar)
|| line_info[i].type == COMMENT_LINE) || line_info[i].type == COMMENT_LINE)
continue; continue;
if (get_ccan_line_pp(line_info[i].cond, sym, &val, NULL) if (get_ccan_line_pp(line_info[i].cond, sym, &val, NULL)
!= NOT_COMPILED) != NOT_COMPILED) {
return talloc_asprintf_append(sofar, score_file_error(score, f, i+1, "code outside"
"%s:%u:code outside idempotent region.\n", " idempotent region");
f->name, i+1); return false;
}
} }
return sofar; return true;
} }
static void *check_idempotent(struct manifest *m, static void check_idempotent(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct ccan_file *f; struct ccan_file *f;
char *report = NULL;
list_for_each(&m->h_files, f, list)
report = report_idem(f, report);
return report; list_for_each(&m->h_files, f, list) {
} if (!check_idem(f, score))
return;
static const char *describe_idempotent(struct manifest *m, void *check_result) }
{ score->pass = true;
return talloc_asprintf(check_result, score->score = score->total;
"Some headers not idempotent:\n"
"%s\n%s", (char *)check_result,
explain);
} }
struct ccanlint idempotent = { struct ccanlint idempotent = {
.key = "idempotent", .key = "idempotent",
.name = "Module headers are #ifndef/#define wrapped", .name = "Module headers are #ifndef/#define wrapped",
.total_score = 1,
.check = check_idempotent, .check = check_idempotent,
.describe = describe_idempotent, .handle = handle_idem,
}; };
REGISTER_TEST(idempotent, NULL); REGISTER_TEST(idempotent, NULL);
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <ccan/str_talloc/str_talloc.h> #include <ccan/str_talloc/str_talloc.h>
#include <ccan/grab_file/grab_file.h> #include <ccan/grab_file/grab_file.h>
#include <ccan/str/str.h> #include <ccan/str/str.h>
#include <ccan/foreach/foreach.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
...@@ -16,15 +17,9 @@ ...@@ -16,15 +17,9 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
struct coverage_result { static bool find_source_file(const struct manifest *m, const char *filename)
float uncovered;
const char *what;
char *output;
};
static bool find_source_file(struct manifest *m, const char *filename)
{ {
struct ccan_file *i; const struct ccan_file *i;
list_for_each(&m->c_files, i, list) { list_for_each(&m->c_files, i, list) {
if (streq(i->fullname, filename)) if (streq(i->fullname, filename))
...@@ -37,12 +32,25 @@ static bool find_source_file(struct manifest *m, const char *filename) ...@@ -37,12 +32,25 @@ static bool find_source_file(struct manifest *m, const char *filename)
return false; return false;
} }
/* 1 point for 50%, 2 points for 75%, 3 points for 87.5%... */
static unsigned int score_coverage(float covered, unsigned total)
{
float thresh, uncovered = 1.0 - covered;
unsigned int i;
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? */ /* FIXME: Don't know how stable this is. Read cov files directly? */
static void analyze_coverage(struct manifest *m, static void analyze_coverage(struct manifest *m, bool full_gcov,
struct coverage_result *res, const char *output, const char *output, struct score *score)
bool full_gcov)
{ {
char **lines = strsplit(res, output, "\n", NULL); char **lines = strsplit(score, output, "\n", NULL);
float covered_lines = 0.0; float covered_lines = 0.0;
unsigned int i, total_lines = 0; unsigned int i, total_lines = 0;
bool lines_matter = false; bool lines_matter = false;
...@@ -81,17 +89,14 @@ static void analyze_coverage(struct manifest *m, ...@@ -81,17 +89,14 @@ static void analyze_coverage(struct manifest *m,
apostrophe = strchr(filename, '\''); apostrophe = strchr(filename, '\'');
*apostrophe = '\0'; *apostrophe = '\0';
if (lines_matter) { if (lines_matter) {
file = grab_file(res, filename, NULL); file = grab_file(score, filename, NULL);
if (!file) { if (!file) {
res->what = talloc_asprintf(res, score->error = talloc_asprintf(score,
"Reading %s", "Reading %s",
filename); filename);
res->output = talloc_strdup(res,
strerror(errno));
return; return;
} }
res->output = talloc_append_string(res->output, printf("%s", file);
file);
} }
if (tools_verbose) if (tools_verbose)
printf("Unlinking %s", filename); printf("Unlinking %s", filename);
...@@ -99,114 +104,65 @@ static void analyze_coverage(struct manifest *m, ...@@ -99,114 +104,65 @@ static void analyze_coverage(struct manifest *m,
} }
} }
score->pass = true;
/* Nothing covered? We can't tell if there's a source file which /* 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 * was never executed, or there really is no code to execute, so
* assume the latter: this test deserves no score. */ * assume the latter: this test deserves no score. */
if (total_lines == 0) { if (total_lines == 0)
res->uncovered = 1.0; score->total = score->score = 0;
run_coverage_tests.total_score = 0; else {
} else score->total = 5;
res->uncovered = 1.0 - covered_lines / total_lines; score->score = score_coverage(covered_lines / total_lines,
score->total);
}
} }
static void *do_run_coverage_tests(struct manifest *m, static void do_run_coverage_tests(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft, struct score *score)
{ {
struct coverage_result *res;
struct ccan_file *i; struct ccan_file *i;
char *cmdout; char *cmdout;
char *covcmd; char *covcmd;
bool ok; bool ok;
bool full_gcov = (verbose > 1); bool full_gcov = (verbose > 1);
struct list_head *list;
res = talloc(m, struct coverage_result);
res->what = NULL;
res->output = talloc_strdup(res, "");
res->uncovered = 1.0;
/* This tells gcov where we put those .gcno files. */ /* This tells gcov where we put those .gcno files. */
covcmd = talloc_asprintf(m, "gcov %s -o %s", covcmd = talloc_asprintf(m, "gcov %s -o %s",
full_gcov ? "" : "-n", full_gcov ? "" : "-n",
talloc_dirname(res, m->info_file->compiled)); talloc_dirname(score, m->info_file->compiled));
/* Run them all. */ /* Run them all. */
list_for_each(&m->run_tests, i, list) { foreach_ptr(list, &m->run_tests, &m->api_tests) {
cmdout = run_command(m, timeleft, i->cov_compiled); list_for_each(list, i, list) {
if (cmdout) { cmdout = run_command(m, timeleft, i->cov_compiled);
res->what = i->fullname; if (cmdout) {
res->output = talloc_steal(res, cmdout); score->error = "Running test with coverage";
return res; score_file_error(score, i, 0, cmdout);
} return;
covcmd = talloc_asprintf_append(covcmd, " %s", i->fullname); }
} covcmd = talloc_asprintf_append(covcmd, " %s",
i->fullname);
list_for_each(&m->api_tests, i, list) {
cmdout = run_command(m, timeleft, i->cov_compiled);
if (cmdout) {
res->what = i->fullname;
res->output = talloc_steal(res, cmdout);
return res;
} }
covcmd = talloc_asprintf_append(covcmd, " %s", i->fullname);
} }
/* Now run gcov: we want output even if it succeeds. */ /* Now run gcov: we want output even if it succeeds. */
cmdout = run_with_timeout(m, covcmd, &ok, timeleft); cmdout = run_with_timeout(m, covcmd, &ok, timeleft);
if (!ok) { if (!ok) {
res->what = "Running gcov"; score->error = talloc_asprintf(score, "Running gcov: %s",
res->output = talloc_steal(res, cmdout); cmdout);
return res; return;
}
analyze_coverage(m, res, cmdout, full_gcov);
return res;
}
/* 1 point for 50%, 2 points for 75%, 3 points for 87.5%... */
static unsigned int score_coverage(struct manifest *m, void *check_result)
{
struct coverage_result *res = check_result;
float thresh;
unsigned int i;
for (i = 0, thresh = 0.5;
i < run_coverage_tests.total_score;
i++, thresh /= 2) {
if (res->uncovered > thresh)
break;
} }
return i;
}
static const char *describe_run_coverage_tests(struct manifest *m,
void *check_result)
{
struct coverage_result *res = check_result;
bool full_gcov = (verbose > 1);
char *ret;
if (res->what)
return talloc_asprintf(m, "%s: %s", res->what, res->output);
if (!verbose)
return NULL;
ret = talloc_asprintf(m, "Tests achieved %0.2f%% coverage", analyze_coverage(m, full_gcov, cmdout, score);
(1.0 - res->uncovered) * 100);
if (full_gcov)
ret = talloc_asprintf_append(ret, "\n%s", res->output);
return ret;
} }
struct ccanlint run_coverage_tests = { struct ccanlint run_coverage_tests = {
.key = "test-coverage", .key = "test-coverage",
.name = "Code coverage of module tests", .name = "Code coverage of module tests",
.total_score = 5,
.score = score_coverage,
.check = do_run_coverage_tests, .check = do_run_coverage_tests,
.describe = describe_run_coverage_tests,
}; };
REGISTER_TEST(run_coverage_tests, &compile_coverage_tests, &run_tests, NULL); REGISTER_TEST(run_coverage_tests, &compile_coverage_tests, &run_tests, NULL);
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#include <tools/tools.h> #include <tools/tools.h>
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
#include <ccan/str/str.h> #include <ccan/str/str.h>
#include <ccan/foreach/foreach.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
...@@ -21,91 +22,43 @@ static const char *can_run(struct manifest *m) ...@@ -21,91 +22,43 @@ static const char *can_run(struct manifest *m)
return NULL; return NULL;
} }
struct run_tests_result { static void do_run_tests(struct manifest *m,
struct list_node list; bool keep,
struct ccan_file *file; unsigned int *timeleft,
const char *output; struct score *score)
};
static void *do_run_tests(struct manifest *m,
bool keep,
unsigned int *timeleft)
{ {
struct list_head *list = talloc(m, struct list_head); struct list_head *list;
struct run_tests_result *res;
struct ccan_file *i; struct ccan_file *i;
char *cmdout; char *cmdout;
list_head_init(list); score->total = 0;
run_tests.total_score = 0; foreach_ptr(list, &m->run_tests, &m->api_tests) {
list_for_each(list, i, list) {
list_for_each(&m->run_tests, i, list) { score->total++;
run_tests.total_score++; cmdout = run_command(m, timeleft, i->compiled);
cmdout = run_command(m, timeleft, i->compiled); if (cmdout)
if (cmdout) { score_file_error(score, i, 0, cmdout);
res = talloc(list, struct run_tests_result); else
res->file = i; score->score++;
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
}
}
list_for_each(&m->api_tests, i, list) {
run_tests.total_score++;
cmdout = run_command(m, timeleft, i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
} }
} }
if (list_empty(list)) { if (score->score == score->total)
talloc_free(list); score->pass = true;
list = NULL;
}
return list;
}
static unsigned int score_run_tests(struct manifest *m, void *check_result)
{
struct list_head *list = check_result;
struct run_tests_result *i;
unsigned int score = run_tests.total_score;
list_for_each(list, i, list)
score--;
return score;
}
static const char *describe_run_tests(struct manifest *m,
void *check_result)
{
struct list_head *list = check_result;
char *descrip = talloc_strdup(check_result, "Running tests failed:\n");
struct run_tests_result *i;
list_for_each(list, i, list)
descrip = talloc_asprintf_append(descrip, "Running %s:\n%s",
i->file->name, i->output);
return descrip;
} }
/* Gcc's warn_unused_result is fascist bullshit. */ /* Gcc's warn_unused_result is fascist bullshit. */
#define doesnt_matter() #define doesnt_matter()
static void run_under_debugger(struct manifest *m, void *check_result) static void run_under_debugger(struct manifest *m, struct score *score)
{ {
char *command; char *command;
struct list_head *list = check_result; struct file_error *first;
struct run_tests_result *first;
if (!ask("Should I run the first failing test under the debugger?")) if (!ask("Should I run the first failing test under the debugger?"))
return; return;
first = list_top(list, struct run_tests_result, list); first = list_top(&score->per_file_errors, struct file_error, list);
command = talloc_asprintf(m, "gdb -ex 'break tap.c:136' -ex 'run' %s", command = talloc_asprintf(m, "gdb -ex 'break tap.c:136' -ex 'run' %s",
first->file->compiled); first->file->compiled);
if (system(command)) if (system(command))
...@@ -115,12 +68,9 @@ static void run_under_debugger(struct manifest *m, void *check_result) ...@@ -115,12 +68,9 @@ static void run_under_debugger(struct manifest *m, void *check_result)
struct ccanlint run_tests = { struct ccanlint run_tests = {
.key = "run", .key = "run",
.name = "Module's run and api tests pass", .name = "Module's run and api tests pass",
.score = score_run_tests,
.total_score = 1,
.check = do_run_tests, .check = do_run_tests,
.describe = describe_run_tests, .handle = run_under_debugger,
.can_run = can_run, .can_run = can_run,
.handle = run_under_debugger
}; };
REGISTER_TEST(run_tests, &compile_tests, NULL); REGISTER_TEST(run_tests, &compile_tests, NULL);
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#include <tools/tools.h> #include <tools/tools.h>
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
#include <ccan/str/str.h> #include <ccan/str/str.h>
#include <ccan/foreach/foreach.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
...@@ -18,102 +19,55 @@ ...@@ -18,102 +19,55 @@
static const char *can_run_vg(struct manifest *m) static const char *can_run_vg(struct manifest *m)
{ {
unsigned int timeleft = default_timeout_ms; unsigned int timeleft = default_timeout_ms;
char *output = run_command(m, &timeleft, "valgrind -q --error-exitcode=0 true"); char *output = run_command(m, &timeleft,
"valgrind -q --error-exitcode=0 true");
if (output) if (output)
return talloc_asprintf(m, "No valgrind support: %s", output); return talloc_asprintf(m, "No valgrind support: %s", output);
return NULL; return NULL;
} }
struct run_tests_result { /* FIXME: Run examples, too! */
struct list_node list; static void do_run_tests_vg(struct manifest *m,
struct ccan_file *file;
const char *output;
};
static void *do_run_tests_vg(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft,
struct score *score)
{ {
struct list_head *list = talloc(m, struct list_head);
struct run_tests_result *res;
struct ccan_file *i; struct ccan_file *i;
struct list_head *list;
char *cmdout; char *cmdout;
list_head_init(list); score->total = 0;
run_tests_vg.total_score = 0; foreach_ptr(list, &m->run_tests, &m->api_tests) {
list_for_each(list, i, list) {
list_for_each(&m->run_tests, i, list) { score->total++;
run_tests_vg.total_score++; cmdout = run_command(m, timeleft,
cmdout = run_command(m, timeleft,
"valgrind -q --error-exitcode=100 %s",
i->compiled);
if (cmdout) {
res = talloc(list, struct run_tests_result);
res->file = i;
res->output = talloc_steal(res, cmdout);
list_add_tail(list, &res->list);
}
}
list_for_each(&m->api_tests, i, list) {
run_tests_vg.total_score++;
cmdout = run_command(m, timeleft,
"valgrind -q --error-exitcode=100 %s", "valgrind -q --error-exitcode=100 %s",
i->compiled); i->compiled);
if (cmdout) { if (cmdout) {
res = talloc(list, struct run_tests_result); score->error = "Running under valgrind";
res->file = i; score_file_error(score, i, 0, cmdout);
res->output = talloc_steal(res, cmdout); } else
list_add_tail(list, &res->list); score->score++;
} }
} }
if (list_empty(list)) { if (score->score == score->total)
talloc_free(list); score->pass = true;
list = NULL;
}
return list;
}
static unsigned int score_run_tests_vg(struct manifest *m, void *check_result)
{
struct list_head *list = check_result;
struct run_tests_result *i;
unsigned int score = run_tests_vg.total_score;
list_for_each(list, i, list)
score--;
return score;
}
static const char *describe_run_tests_vg(struct manifest *m,
void *check_result)
{
struct list_head *list = check_result;
char *descrip = talloc_strdup(check_result, "Running tests under valgrind failed:\n");
struct run_tests_result *i;
list_for_each(list, i, list)
descrip = talloc_asprintf_append(descrip, "Running %s:\n%s",
i->file->name, i->output);
return descrip;
} }
/* Gcc's warn_unused_result is fascist bullshit. */ /* Gcc's warn_unused_result is fascist bullshit. */
#define doesnt_matter() #define doesnt_matter()
static void run_under_debugger_vg(struct manifest *m, void *check_result) static void run_under_debugger_vg(struct manifest *m, struct score *score)
{ {
struct list_head *list = check_result; struct file_error *first;
struct run_tests_result *first;
char *command; char *command;
if (!ask("Should I run the first failing test under the debugger?")) if (!ask("Should I run the first failing test under the debugger?"))
return; return;
first = list_top(list, struct run_tests_result, list); first = list_top(&score->per_file_errors, struct file_error, list);
command = talloc_asprintf(m, "valgrind --db-attach=yes %s", command = talloc_asprintf(m, "valgrind --db-attach=yes %s",
first->file->compiled); first->file->compiled);
if (system(command)) if (system(command))
...@@ -123,11 +77,8 @@ static void run_under_debugger_vg(struct manifest *m, void *check_result) ...@@ -123,11 +77,8 @@ static void run_under_debugger_vg(struct manifest *m, void *check_result)
struct ccanlint run_tests_vg = { struct ccanlint run_tests_vg = {
.key = "valgrind-tests", .key = "valgrind-tests",
.name = "Module's run and api tests succeed under valgrind", .name = "Module's run and api tests succeed under valgrind",
.score = score_run_tests_vg,
.total_score = 1,
.check = do_run_tests_vg,
.describe = describe_run_tests_vg,
.can_run = can_run_vg, .can_run = can_run_vg,
.check = do_run_tests_vg,
.handle = run_under_debugger_vg .handle = run_under_debugger_vg
}; };
......
/* Trailing whitespace test. Almost embarrassing, but trivial. */ /* Trailing whitespace test. Almost embarrassing, but trivial. */
#include <tools/ccanlint/ccanlint.h> #include <tools/ccanlint/ccanlint.h>
#include <ccan/talloc/talloc.h> #include <ccan/talloc/talloc.h>
#include <ccan/foreach/foreach.h>
#include <ccan/str/str.h> #include <ccan/str/str.h>
/* FIXME: only print full analysis if verbose >= 2. */ /* FIXME: only print full analysis if verbose >= 2. */
static char *report_on_trailing_whitespace(const char *line) static char *get_trailing_whitespace(const char *line)
{ {
const char *e = strchr(line, 0); const char *e = strchr(line, 0);
while (e>line && (e[-1]==' ' || e[-1]=='\t')) while (e>line && (e[-1]==' ' || e[-1]=='\t'))
...@@ -20,36 +21,38 @@ static char *report_on_trailing_whitespace(const char *line) ...@@ -20,36 +21,38 @@ static char *report_on_trailing_whitespace(const char *line)
return talloc_asprintf(line, "'%s'", line); return talloc_asprintf(line, "'%s'", line);
} }
static void *check_trailing_whitespace(struct manifest *m, static void check_trailing_whitespace(struct manifest *m,
bool keep, bool keep,
unsigned int *timeleft) unsigned int *timeleft,
struct score *score)
{ {
char *report; struct list_head *list;
struct ccan_file *f;
report = report_on_lines(&m->c_files, report_on_trailing_whitespace, unsigned int i;
NULL);
report = report_on_lines(&m->h_files, report_on_trailing_whitespace, foreach_ptr(list, &m->c_files, &m->h_files) {
report); list_for_each(list, f, list) {
char **lines = get_ccan_file_lines(f);
return report; for (i = 0; i < f->num_lines; i++) {
} char *err = get_trailing_whitespace(lines[i]);
if (err) {
static const char *describe_trailing_whitespace(struct manifest *m, score->error = "Trailing whitespace"
void *check_result) "found";
{ score_file_error(score, f, i+1, err);
if (!verbose) }
return NULL; }
return talloc_asprintf(check_result, }
"Some source files have trailing whitespace:\n" }
"%s", (char *)check_result); if (!score->error) {
score->pass = true;
score->score = score->total;
}
} }
struct ccanlint trailing_whitespace = { struct ccanlint trailing_whitespace = {
.key = "trailing-whitespace", .key = "trailing-whitespace",
.name = "Module's source code has no trailing whitespace", .name = "Module's source code has no trailing whitespace",
.total_score = 1,
.check = check_trailing_whitespace, .check = check_trailing_whitespace,
.describe = describe_trailing_whitespace,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment