From: Rusty Russell Date: Fri, 7 Jan 2011 11:50:13 +0000 (+1030) Subject: ccanlint: rename files to match keys X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=051db34fb275491d4d5dfa5bf7970e8e525766d8 ccanlint: rename files to match keys --- diff --git a/tools/ccanlint/compulsory_tests/build.c b/tools/ccanlint/compulsory_tests/build.c deleted file mode 100644 index 9d697a30..00000000 --- a/tools/ccanlint/compulsory_tests/build.c +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "build.h" - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static char *obj_list(const struct manifest *m) -{ - char *list = talloc_strdup(m, ""); - struct ccan_file *i; - - /* Objects from all the C files. */ - list_for_each(&m->c_files, i, list) - list = talloc_asprintf_append(list, "%s ", i->compiled); - - return list; -} - -char *build_module(struct manifest *m, bool keep, char **errstr) -{ - char *name = link_objects(m, m->basename, false, obj_list(m), errstr); - if (name) { - if (keep) { - char *realname = talloc_asprintf(m, "%s.o", m->dir); - /* We leave this object file around, all built. */ - if (!move_file(name, realname)) - err(1, "Renaming %s to %s", name, realname); - name = realname; - } - } - return name; -} - -static void do_build(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - char *errstr; - - if (list_empty(&m->c_files)) { - /* No files? No score, but we "pass". */ - score->total = 0; - score->pass = true; - return; - } - - m->compiled = build_module(m, keep, &errstr); - if (!m->compiled) { - score_file_error(score, NULL, 0, errstr); - return; - } - - score->pass = true; - score->score = score->total; -} - -struct ccanlint module_builds = { - .key = "module_builds", - .name = "Module can be built from object files", - .check = do_build, - .can_run = can_build, - .needs = "objects_build" -}; - -REGISTER_TEST(module_builds); diff --git a/tools/ccanlint/compulsory_tests/build_objs.c b/tools/ccanlint/compulsory_tests/build_objs.c deleted file mode 100644 index 13d34a14..00000000 --- a/tools/ccanlint/compulsory_tests/build_objs.c +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static void check_objs_build(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - struct ccan_file *i; - bool errors = false, warnings = false; - - if (list_empty(&m->c_files)) - score->total = 0; - else - score->total = 2; - - list_for_each(&m->c_files, i, list) { - char *output; - char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); - - i->compiled = maybe_temp_file(m, "", keep, fullfile); - if (!compile_object(score, fullfile, ccan_dir, "", i->compiled, - &output)) { - talloc_free(i->compiled); - score->error = "Compiling object files"; - score_file_error(score, i, 0, output); - errors = true; - } else if (!streq(output, "")) { - score->error = "Compiling object files gave warnings"; - score_file_error(score, i, 0, output); - warnings = true; - } - } - - if (!errors) { - score->pass = true; - score->score = score->total - warnings; - } -} - -struct ccanlint objects_build = { - .key = "objects_build", - .name = "Module object files can be built", - .check = check_objs_build, - .can_run = can_build, - .needs = "depends_exist" -}; - -REGISTER_TEST(objects_build); diff --git a/tools/ccanlint/compulsory_tests/check_build.c b/tools/ccanlint/compulsory_tests/check_build.c deleted file mode 100644 index 83b1c780..00000000 --- a/tools/ccanlint/compulsory_tests/check_build.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static char *obj_list(const struct manifest *m) -{ - char *list = talloc_strdup(m, ""); - struct manifest *i; - - /* Other CCAN deps. */ - list_for_each(&m->deps, i, list) { - if (i->compiled) - list = talloc_asprintf_append(list, "%s ", - i->compiled); - } - return list; -} - -static char *lib_list(const struct manifest *m) -{ - unsigned int i, num; - char **libs = get_libs(m, ".", &num, &m->info_file->compiled); - char *ret = talloc_strdup(m, ""); - - for (i = 0; i < num; i++) - ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); - return ret; -} - -static void check_use_build(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - char *contents; - char *tmpfile, *cmdout; - char *basename = talloc_asprintf(m, "%s/example.c", m->dir); - int fd; - - tmpfile = maybe_temp_file(m, ".c", keep, basename); - - fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) - err(1, "Creating temporary file %s", tmpfile); - - contents = talloc_asprintf(tmpfile, - "#include \n" - "int main(void)\n" - "{\n" - " return 0;\n" - "}\n", - m->basename, m->basename); - if (write(fd, contents, strlen(contents)) != strlen(contents)) - err(1, "Failure writing to temporary file %s", tmpfile); - close(fd); - - if (compile_and_link(score, tmpfile, ccan_dir, obj_list(m), "", - lib_list(m), - maybe_temp_file(m, "", keep, tmpfile), - &cmdout)) { - score->pass = true; - score->score = score->total; - } else { - score->error = cmdout; - } -} - -struct ccanlint module_links = { - .key = "module_links", - .name = "Module can be linked against trivial program", - .check = check_use_build, - .can_run = can_build, - .needs = "module_builds depends_build" -}; - -REGISTER_TEST(module_links); diff --git a/tools/ccanlint/compulsory_tests/check_depends_built.c b/tools/ccanlint/compulsory_tests/check_depends_built.c deleted file mode 100644 index bb1f6ca3..00000000 --- a/tools/ccanlint/compulsory_tests/check_depends_built.c +++ /dev/null @@ -1,111 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "build.h" - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static bool expect_obj_file(struct manifest *m) -{ - /* If it has C files, we expect an object file built from them. */ - return !list_empty(&m->c_files); -} - -static char *build_subdir_objs(struct manifest *m) -{ - struct ccan_file *i; - - list_for_each(&m->c_files, i, list) { - char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); - char *output; - - i->compiled = maybe_temp_file(m, "", false, fullfile); - if (!compile_object(m, fullfile, ccan_dir, "", i->compiled, - &output)) { - talloc_free(i->compiled); - i->compiled = NULL; - return talloc_asprintf(m, - "Dependency %s" - " did not build:\n%s", - m->basename, output); - } - } - return NULL; -} - -char *build_submodule(struct manifest *m) -{ - char *errstr; - struct stat st; - - if (m->compiled) - return NULL; - - if (!expect_obj_file(m)) - return NULL; - - m->compiled = talloc_asprintf(m, "%s.o", m->dir); - if (stat(m->compiled, &st) == 0) - return NULL; - - if (verbose >= 2) - printf(" Building dependency %s\n", m->dir); - - errstr = build_subdir_objs(m); - if (errstr) - return errstr; - - m->compiled = build_module(m, false, &errstr); - if (!m->compiled) - return errstr; - return NULL; -} - -static void check_depends_built(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - struct manifest *i; - - list_for_each(&m->deps, i, list) { - char *errstr = build_submodule(i); - - if (errstr) { - score->error = talloc_asprintf(score, - "Dependency %s" - " did not build:\n%s", - i->basename, errstr); - return; - } - } - - score->pass = true; - score->score = score->total; -} - -struct ccanlint depends_build = { - .key = "depends_build", - .name = "Module's CCAN dependencies can be found or built", - .check = check_depends_built, - .can_run = can_build, - .needs = "depends_exist" -}; - -REGISTER_TEST(depends_build); diff --git a/tools/ccanlint/compulsory_tests/check_depends_exist.c b/tools/ccanlint/compulsory_tests/check_depends_exist.c deleted file mode 100644 index 12eaf021..00000000 --- a/tools/ccanlint/compulsory_tests/check_depends_exist.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool add_dep(struct manifest *m, const char *dep, struct score *score) -{ - struct stat st; - struct manifest *subm; - char *dir = talloc_asprintf(m, "%s/%s", ccan_dir, dep); - - /* FIXME: get_manifest has a tendency to exit. */ - if (stat(dir, &st) != 0) { - score->error - = talloc_asprintf(m, - "Could not stat dependency %s: %s", - dir, strerror(errno)); - return false; - } - subm = get_manifest(m, dir); - list_add_tail(&m->deps, &subm->list); - return true; -} - -static void check_depends_exist(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - unsigned int i; - char **deps; - char *updir = talloc_strdup(m, m->dir); - - *strrchr(updir, '/') = '\0'; - - if (safe_mode) - deps = get_safe_ccan_deps(m, m->dir, true, - &m->info_file->compiled); - else - deps = get_deps(m, m->dir, true, &m->info_file->compiled); - - for (i = 0; deps[i]; i++) { - if (!strstarts(deps[i], "ccan/")) - continue; - - if (!add_dep(m, deps[i], score)) - return; - } - - /* We may need libtap for testing, unless we're "tap" */ - if (!streq(m->basename, "tap") - && (!list_empty(&m->run_tests) || !list_empty(&m->api_tests))) { - if (!add_dep(m, "ccan/tap", score)) - return; - } - - score->pass = true; - score->score = score->total; -} - -struct ccanlint depends_exist = { - .key = "depends_exist", - .name = "Module's CCAN dependencies can be found", - .check = check_depends_exist, - .needs = "info_exists" -}; - -REGISTER_TEST(depends_exist); diff --git a/tools/ccanlint/compulsory_tests/check_includes_build.c b/tools/ccanlint/compulsory_tests/check_includes_build.c deleted file mode 100644 index 296b0a39..00000000 --- a/tools/ccanlint/compulsory_tests/check_includes_build.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static struct ccan_file *main_header(struct manifest *m) -{ - struct ccan_file *f; - - list_for_each(&m->h_files, f, list) { - if (strstarts(f->name, m->basename) - && strlen(f->name) == strlen(m->basename) + 2) - return f; - } - /* Should not happen: we depend on has_main_header */ - abort(); -} - -static void check_includes_build(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - char *contents; - char *tmpsrc, *tmpobj, *cmdout; - int fd; - struct ccan_file *mainh = main_header(m); - - tmpsrc = maybe_temp_file(m, "-included.c", keep, mainh->fullname); - tmpobj = maybe_temp_file(m, ".o", keep, tmpsrc); - - fd = open(tmpsrc, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) - err(1, "Creating temporary file %s", tmpsrc); - - contents = talloc_asprintf(tmpsrc, "#include \n", - m->basename, m->basename); - if (write(fd, contents, strlen(contents)) != strlen(contents)) - err(1, "writing to temporary file %s", tmpsrc); - close(fd); - - if (compile_object(score, tmpsrc, ccan_dir, "", tmpobj, &cmdout)) { - score->pass = true; - score->score = score->total; - } else { - score->error = talloc_asprintf(score, - "#include of the main header file:\n%s", - cmdout); - } -} - -struct ccanlint main_header_compiles = { - .key = "main_header_compiles", - .name = "Modules main header compiles", - .check = check_includes_build, - .can_run = can_build, - .needs = "depends_exist main_header_exists" -}; - -REGISTER_TEST(main_header_compiles); diff --git a/tools/ccanlint/compulsory_tests/depends_build.c b/tools/ccanlint/compulsory_tests/depends_build.c new file mode 100644 index 00000000..bb1f6ca3 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/depends_build.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "build.h" + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static bool expect_obj_file(struct manifest *m) +{ + /* If it has C files, we expect an object file built from them. */ + return !list_empty(&m->c_files); +} + +static char *build_subdir_objs(struct manifest *m) +{ + struct ccan_file *i; + + list_for_each(&m->c_files, i, list) { + char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); + char *output; + + i->compiled = maybe_temp_file(m, "", false, fullfile); + if (!compile_object(m, fullfile, ccan_dir, "", i->compiled, + &output)) { + talloc_free(i->compiled); + i->compiled = NULL; + return talloc_asprintf(m, + "Dependency %s" + " did not build:\n%s", + m->basename, output); + } + } + return NULL; +} + +char *build_submodule(struct manifest *m) +{ + char *errstr; + struct stat st; + + if (m->compiled) + return NULL; + + if (!expect_obj_file(m)) + return NULL; + + m->compiled = talloc_asprintf(m, "%s.o", m->dir); + if (stat(m->compiled, &st) == 0) + return NULL; + + if (verbose >= 2) + printf(" Building dependency %s\n", m->dir); + + errstr = build_subdir_objs(m); + if (errstr) + return errstr; + + m->compiled = build_module(m, false, &errstr); + if (!m->compiled) + return errstr; + return NULL; +} + +static void check_depends_built(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct manifest *i; + + list_for_each(&m->deps, i, list) { + char *errstr = build_submodule(i); + + if (errstr) { + score->error = talloc_asprintf(score, + "Dependency %s" + " did not build:\n%s", + i->basename, errstr); + return; + } + } + + score->pass = true; + score->score = score->total; +} + +struct ccanlint depends_build = { + .key = "depends_build", + .name = "Module's CCAN dependencies can be found or built", + .check = check_depends_built, + .can_run = can_build, + .needs = "depends_exist" +}; + +REGISTER_TEST(depends_build); diff --git a/tools/ccanlint/compulsory_tests/depends_exist.c b/tools/ccanlint/compulsory_tests/depends_exist.c new file mode 100644 index 00000000..12eaf021 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/depends_exist.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool add_dep(struct manifest *m, const char *dep, struct score *score) +{ + struct stat st; + struct manifest *subm; + char *dir = talloc_asprintf(m, "%s/%s", ccan_dir, dep); + + /* FIXME: get_manifest has a tendency to exit. */ + if (stat(dir, &st) != 0) { + score->error + = talloc_asprintf(m, + "Could not stat dependency %s: %s", + dir, strerror(errno)); + return false; + } + subm = get_manifest(m, dir); + list_add_tail(&m->deps, &subm->list); + return true; +} + +static void check_depends_exist(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + unsigned int i; + char **deps; + char *updir = talloc_strdup(m, m->dir); + + *strrchr(updir, '/') = '\0'; + + if (safe_mode) + deps = get_safe_ccan_deps(m, m->dir, true, + &m->info_file->compiled); + else + deps = get_deps(m, m->dir, true, &m->info_file->compiled); + + for (i = 0; deps[i]; i++) { + if (!strstarts(deps[i], "ccan/")) + continue; + + if (!add_dep(m, deps[i], score)) + return; + } + + /* We may need libtap for testing, unless we're "tap" */ + if (!streq(m->basename, "tap") + && (!list_empty(&m->run_tests) || !list_empty(&m->api_tests))) { + if (!add_dep(m, "ccan/tap", score)) + return; + } + + score->pass = true; + score->score = score->total; +} + +struct ccanlint depends_exist = { + .key = "depends_exist", + .name = "Module's CCAN dependencies can be found", + .check = check_depends_exist, + .needs = "info_exists" +}; + +REGISTER_TEST(depends_exist); diff --git a/tools/ccanlint/compulsory_tests/has_info.c b/tools/ccanlint/compulsory_tests/has_info.c deleted file mode 100644 index a9a6c1c9..00000000 --- a/tools/ccanlint/compulsory_tests/has_info.c +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void check_has_info(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - if (m->info_file) { - score->pass = true; - score->score = score->total; - } else { - score->error = "You have no _info file.\n\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" - "and license information.\n"; - } -} - -static const char template[] = - "#include \n" - "#include \"config.h\"\n" - "\n" - "/**\n" - " * %s - YOUR-ONE-LINE-DESCRIPTION-HERE\n" - " *\n" - " * This code ... YOUR-BRIEF-SUMMARY-HERE\n" - " *\n" - " * Example:\n" - " * FULLY-COMPILABLE-INDENTED-TRIVIAL-BUT-USEFUL-EXAMPLE-HERE\n" - " */\n" - "int main(int argc, char *argv[])\n" - "{\n" - " /* Expect exactly one argument */\n" - " if (argc != 2)\n" - " return 1;\n" - "\n" - " if (strcmp(argv[1], \"depends\") == 0) {\n" - " PRINTF-CCAN-PACKAGES-YOU-NEED-ONE-PER-LINE-IF-ANY\n" - " return 0;\n" - " }\n" - "\n" - " return 1;\n" - "}\n"; - -static void create_info_template(struct manifest *m, struct score *score) -{ - FILE *info; - const char *filename; - - if (!ask("Should I create a template _info file for you?")) - return; - - filename = talloc_asprintf(m, "%s/%s", m->dir, "_info"); - info = fopen(filename, "w"); - if (!info) - err(1, "Trying to create a template _info in %s", filename); - - if (fprintf(info, template, m->basename) < 0) { - unlink_noerr(filename); - err(1, "Writing template into %s", filename); - } - fclose(info); -} - -struct ccanlint info_exists = { - .key = "info_exists", - .name = "Module has _info file", - .check = check_has_info, - .handle = create_info_template, - .needs = "" -}; - -REGISTER_TEST(info_exists); diff --git a/tools/ccanlint/compulsory_tests/has_main_header.c b/tools/ccanlint/compulsory_tests/has_main_header.c deleted file mode 100644 index 68ea1359..00000000 --- a/tools/ccanlint/compulsory_tests/has_main_header.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void check_has_main_header(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - struct ccan_file *f; - - list_for_each(&m->h_files, f, list) { - if (strstarts(f->name, m->basename) - && strlen(f->name) == strlen(m->basename) + 2) { - score->pass = true; - score->score = score->total; - return; - } - } - score->error = talloc_asprintf(score, - "You have no %s/%s.h header file.\n\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", - m->basename, m->basename); -} - -struct ccanlint main_header_exists = { - .key = "main_header_exists", - .name = "Module has main header file", - .check = check_has_main_header, - .needs = "" -}; - -REGISTER_TEST(main_header_exists); diff --git a/tools/ccanlint/compulsory_tests/info_exists.c b/tools/ccanlint/compulsory_tests/info_exists.c new file mode 100644 index 00000000..a9a6c1c9 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/info_exists.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void check_has_info(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + if (m->info_file) { + score->pass = true; + score->score = score->total; + } else { + score->error = "You have no _info file.\n\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" + "and license information.\n"; + } +} + +static const char template[] = + "#include \n" + "#include \"config.h\"\n" + "\n" + "/**\n" + " * %s - YOUR-ONE-LINE-DESCRIPTION-HERE\n" + " *\n" + " * This code ... YOUR-BRIEF-SUMMARY-HERE\n" + " *\n" + " * Example:\n" + " * FULLY-COMPILABLE-INDENTED-TRIVIAL-BUT-USEFUL-EXAMPLE-HERE\n" + " */\n" + "int main(int argc, char *argv[])\n" + "{\n" + " /* Expect exactly one argument */\n" + " if (argc != 2)\n" + " return 1;\n" + "\n" + " if (strcmp(argv[1], \"depends\") == 0) {\n" + " PRINTF-CCAN-PACKAGES-YOU-NEED-ONE-PER-LINE-IF-ANY\n" + " return 0;\n" + " }\n" + "\n" + " return 1;\n" + "}\n"; + +static void create_info_template(struct manifest *m, struct score *score) +{ + FILE *info; + const char *filename; + + if (!ask("Should I create a template _info file for you?")) + return; + + filename = talloc_asprintf(m, "%s/%s", m->dir, "_info"); + info = fopen(filename, "w"); + if (!info) + err(1, "Trying to create a template _info in %s", filename); + + if (fprintf(info, template, m->basename) < 0) { + unlink_noerr(filename); + err(1, "Writing template into %s", filename); + } + fclose(info); +} + +struct ccanlint info_exists = { + .key = "info_exists", + .name = "Module has _info file", + .check = check_has_info, + .handle = create_info_template, + .needs = "" +}; + +REGISTER_TEST(info_exists); diff --git a/tools/ccanlint/compulsory_tests/main_header_compiles.c b/tools/ccanlint/compulsory_tests/main_header_compiles.c new file mode 100644 index 00000000..296b0a39 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/main_header_compiles.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static struct ccan_file *main_header(struct manifest *m) +{ + struct ccan_file *f; + + list_for_each(&m->h_files, f, list) { + if (strstarts(f->name, m->basename) + && strlen(f->name) == strlen(m->basename) + 2) + return f; + } + /* Should not happen: we depend on has_main_header */ + abort(); +} + +static void check_includes_build(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + char *contents; + char *tmpsrc, *tmpobj, *cmdout; + int fd; + struct ccan_file *mainh = main_header(m); + + tmpsrc = maybe_temp_file(m, "-included.c", keep, mainh->fullname); + tmpobj = maybe_temp_file(m, ".o", keep, tmpsrc); + + fd = open(tmpsrc, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + err(1, "Creating temporary file %s", tmpsrc); + + contents = talloc_asprintf(tmpsrc, "#include \n", + m->basename, m->basename); + if (write(fd, contents, strlen(contents)) != strlen(contents)) + err(1, "writing to temporary file %s", tmpsrc); + close(fd); + + if (compile_object(score, tmpsrc, ccan_dir, "", tmpobj, &cmdout)) { + score->pass = true; + score->score = score->total; + } else { + score->error = talloc_asprintf(score, + "#include of the main header file:\n%s", + cmdout); + } +} + +struct ccanlint main_header_compiles = { + .key = "main_header_compiles", + .name = "Modules main header compiles", + .check = check_includes_build, + .can_run = can_build, + .needs = "depends_exist main_header_exists" +}; + +REGISTER_TEST(main_header_compiles); diff --git a/tools/ccanlint/compulsory_tests/main_header_exists.c b/tools/ccanlint/compulsory_tests/main_header_exists.c new file mode 100644 index 00000000..68ea1359 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/main_header_exists.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void check_has_main_header(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct ccan_file *f; + + list_for_each(&m->h_files, f, list) { + if (strstarts(f->name, m->basename) + && strlen(f->name) == strlen(m->basename) + 2) { + score->pass = true; + score->score = score->total; + return; + } + } + score->error = talloc_asprintf(score, + "You have no %s/%s.h header file.\n\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", + m->basename, m->basename); +} + +struct ccanlint main_header_exists = { + .key = "main_header_exists", + .name = "Module has main header file", + .check = check_has_main_header, + .needs = "" +}; + +REGISTER_TEST(main_header_exists); diff --git a/tools/ccanlint/compulsory_tests/module_builds.c b/tools/ccanlint/compulsory_tests/module_builds.c new file mode 100644 index 00000000..9d697a30 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/module_builds.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "build.h" + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static char *obj_list(const struct manifest *m) +{ + char *list = talloc_strdup(m, ""); + struct ccan_file *i; + + /* Objects from all the C files. */ + list_for_each(&m->c_files, i, list) + list = talloc_asprintf_append(list, "%s ", i->compiled); + + return list; +} + +char *build_module(struct manifest *m, bool keep, char **errstr) +{ + char *name = link_objects(m, m->basename, false, obj_list(m), errstr); + if (name) { + if (keep) { + char *realname = talloc_asprintf(m, "%s.o", m->dir); + /* We leave this object file around, all built. */ + if (!move_file(name, realname)) + err(1, "Renaming %s to %s", name, realname); + name = realname; + } + } + return name; +} + +static void do_build(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + char *errstr; + + if (list_empty(&m->c_files)) { + /* No files? No score, but we "pass". */ + score->total = 0; + score->pass = true; + return; + } + + m->compiled = build_module(m, keep, &errstr); + if (!m->compiled) { + score_file_error(score, NULL, 0, errstr); + return; + } + + score->pass = true; + score->score = score->total; +} + +struct ccanlint module_builds = { + .key = "module_builds", + .name = "Module can be built from object files", + .check = do_build, + .can_run = can_build, + .needs = "objects_build" +}; + +REGISTER_TEST(module_builds); diff --git a/tools/ccanlint/compulsory_tests/module_links.c b/tools/ccanlint/compulsory_tests/module_links.c new file mode 100644 index 00000000..83b1c780 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/module_links.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static char *obj_list(const struct manifest *m) +{ + char *list = talloc_strdup(m, ""); + struct manifest *i; + + /* Other CCAN deps. */ + list_for_each(&m->deps, i, list) { + if (i->compiled) + list = talloc_asprintf_append(list, "%s ", + i->compiled); + } + return list; +} + +static char *lib_list(const struct manifest *m) +{ + unsigned int i, num; + char **libs = get_libs(m, ".", &num, &m->info_file->compiled); + char *ret = talloc_strdup(m, ""); + + for (i = 0; i < num; i++) + ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); + return ret; +} + +static void check_use_build(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + char *contents; + char *tmpfile, *cmdout; + char *basename = talloc_asprintf(m, "%s/example.c", m->dir); + int fd; + + tmpfile = maybe_temp_file(m, ".c", keep, basename); + + fd = open(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + err(1, "Creating temporary file %s", tmpfile); + + contents = talloc_asprintf(tmpfile, + "#include \n" + "int main(void)\n" + "{\n" + " return 0;\n" + "}\n", + m->basename, m->basename); + if (write(fd, contents, strlen(contents)) != strlen(contents)) + err(1, "Failure writing to temporary file %s", tmpfile); + close(fd); + + if (compile_and_link(score, tmpfile, ccan_dir, obj_list(m), "", + lib_list(m), + maybe_temp_file(m, "", keep, tmpfile), + &cmdout)) { + score->pass = true; + score->score = score->total; + } else { + score->error = cmdout; + } +} + +struct ccanlint module_links = { + .key = "module_links", + .name = "Module can be linked against trivial program", + .check = check_use_build, + .can_run = can_build, + .needs = "module_builds depends_build" +}; + +REGISTER_TEST(module_links); diff --git a/tools/ccanlint/compulsory_tests/objects_build.c b/tools/ccanlint/compulsory_tests/objects_build.c new file mode 100644 index 00000000..13d34a14 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/objects_build.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static void check_objs_build(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct ccan_file *i; + bool errors = false, warnings = false; + + if (list_empty(&m->c_files)) + score->total = 0; + else + score->total = 2; + + list_for_each(&m->c_files, i, list) { + char *output; + char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); + + i->compiled = maybe_temp_file(m, "", keep, fullfile); + if (!compile_object(score, fullfile, ccan_dir, "", i->compiled, + &output)) { + talloc_free(i->compiled); + score->error = "Compiling object files"; + score_file_error(score, i, 0, output); + errors = true; + } else if (!streq(output, "")) { + score->error = "Compiling object files gave warnings"; + score_file_error(score, i, 0, output); + warnings = true; + } + } + + if (!errors) { + score->pass = true; + score->score = score->total - warnings; + } +} + +struct ccanlint objects_build = { + .key = "objects_build", + .name = "Module object files can be built", + .check = check_objs_build, + .can_run = can_build, + .needs = "depends_exist" +}; + +REGISTER_TEST(objects_build); diff --git a/tools/ccanlint/tests/build-coverage.c b/tools/ccanlint/tests/build-coverage.c deleted file mode 100644 index 115ae94a..00000000 --- a/tools/ccanlint/tests/build-coverage.c +++ /dev/null @@ -1,153 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Note: we already test safe_mode in run_tests.c */ -static const char *can_run_coverage(struct manifest *m) -{ - unsigned int timeleft = default_timeout_ms; - char *output; - - if (!run_command(m, &timeleft, &output, "gcov -h")) - return talloc_asprintf(m, "No gcov support: %s", output); - return NULL; -} - -static bool build_module_objs_with_coverage(struct manifest *m, bool keep, - struct score *score, - char **modobjs) -{ - struct ccan_file *i; - - *modobjs = talloc_strdup(m, ""); - list_for_each(&m->c_files, i, list) { - char *err; - char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); - - i->cov_compiled = maybe_temp_file(m, "", keep, fullfile); - if (!compile_object(m, fullfile, ccan_dir, "", - i->cov_compiled, &err)) { - score_file_error(score, i, 0, err); - talloc_free(i->cov_compiled); - i->cov_compiled = NULL; - return false; - } - *modobjs = talloc_asprintf_append(*modobjs, - " %s", i->cov_compiled); - } - return true; -} - -/* FIXME: Merge this into one place. */ -static char *obj_list(const struct manifest *m, const char *modobjs) -{ - char *list = talloc_strdup(m, ""); - struct ccan_file *i; - struct manifest *subm; - - /* Objects from any other C files. */ - list_for_each(&m->other_test_c_files, i, list) - list = talloc_asprintf_append(list, " %s", i->compiled); - - if (modobjs) - list = talloc_append_string(list, modobjs); - - /* Other ccan modules (don't need coverage versions of those). */ - list_for_each(&m->deps, subm, list) { - if (subm->compiled) - list = talloc_asprintf_append(list, " %s", - subm->compiled); - } - - return list; -} - -static char *lib_list(const struct manifest *m) -{ - unsigned int i, num; - char **libs = get_libs(m, m->dir, &num, &m->info_file->compiled); - char *ret = talloc_strdup(m, ""); - - for (i = 0; i < num; i++) - ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); - return ret; -} - -static char *cov_compile(const void *ctx, - struct manifest *m, - struct ccan_file *file, - const char *modobjs, - bool keep) -{ - char *output; - - file->cov_compiled = maybe_temp_file(ctx, "", keep, file->fullname); - if (!compile_and_link(ctx, file->fullname, ccan_dir, - obj_list(m, modobjs), - COVERAGE_CFLAGS, - lib_list(m), file->cov_compiled, &output)) { - talloc_free(file->cov_compiled); - file->cov_compiled = NULL; - return output; - } - talloc_free(output); - return NULL; -} - -/* FIXME: Coverage from testable examples as well. */ -static void do_compile_coverage_tests(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - char *cmdout, *modobjs = NULL; - struct ccan_file *i; - - if (!list_empty(&m->api_tests) - && !build_module_objs_with_coverage(m, keep, score, &modobjs)) { - score->error = "Failed to compile module objects with coverage"; - return; - } - - list_for_each(&m->run_tests, i, list) { - cmdout = cov_compile(m, m, i, NULL, keep); - if (cmdout) { - score->error = "Failed to compile test with coverage"; - score_file_error(score, i, 0, cmdout); - } - } - - list_for_each(&m->api_tests, i, list) { - cmdout = cov_compile(m, m, i, modobjs, keep); - if (cmdout) { - score->error = "Failed to compile test with coverage"; - score_file_error(score, i, 0, cmdout); - } - } - if (!score->error) { - score->pass = true; - score->score = score->total; - } -} - -struct ccanlint tests_compile_coverage = { - .key = "tests_compile_coverage", - .name = "Module tests compile with " COVERAGE_CFLAGS, - .check = do_compile_coverage_tests, - .can_run = can_run_coverage, - .needs = "tests_compile" -}; - -REGISTER_TEST(tests_compile_coverage); diff --git a/tools/ccanlint/tests/compile_test_helpers.c b/tools/ccanlint/tests/compile_test_helpers.c deleted file mode 100644 index 0ad5a7e6..00000000 --- a/tools/ccanlint/tests/compile_test_helpers.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_run(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static bool compile(struct manifest *m, - bool keep, - struct ccan_file *cfile, - char **output) -{ - cfile->compiled = maybe_temp_file(m, ".o", keep, cfile->fullname); - return compile_object(m, cfile->fullname, ccan_dir, "", - cfile->compiled, output); -} - -static void do_compile_test_helpers(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct ccan_file *i; - bool errors = false, warnings = false; - - if (list_empty(&m->other_test_c_files)) - score->total = 0; - else - score->total = 2; - - list_for_each(&m->other_test_c_files, i, list) { - char *cmdout; - - if (!compile(m, keep, i, &cmdout)) { - errors = true; - score->error = "Failed to compile helper C files"; - score_file_error(score, i, 0, cmdout); - } else if (!streq(cmdout, "")) { - warnings = true; - score->error = "Helper C files gave warnings"; - score_file_error(score, i, 0, cmdout); - } - } - - if (!errors) { - score->pass = true; - score->score = score->total - warnings; - } -} - -struct ccanlint tests_helpers_compile = { - .key = "tests_helpers_compile", - .name = "Module test helper objects compile", - .check = do_compile_test_helpers, - .can_run = can_run, - .needs = "depends_build tests_exist" -}; - -REGISTER_TEST(tests_helpers_compile); diff --git a/tools/ccanlint/tests/compile_tests.c b/tools/ccanlint/tests/compile_tests.c deleted file mode 100644 index 4d868679..00000000 --- a/tools/ccanlint/tests/compile_tests.c +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_build(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -/* FIXME: Merge this into one place. */ -static char *obj_list(const struct manifest *m, bool link_with_module) -{ - char *list = talloc_strdup(m, ""); - struct ccan_file *i; - struct manifest *subm; - - /* Objects from any other C files. */ - list_for_each(&m->other_test_c_files, i, list) - list = talloc_asprintf_append(list, " %s", i->compiled); - - /* Our own object files. */ - if (link_with_module) - list_for_each(&m->c_files, i, list) - list = talloc_asprintf_append(list, " %s", i->compiled); - - /* Other ccan modules. */ - list_for_each(&m->deps, subm, list) { - if (subm->compiled) - list = talloc_asprintf_append(list, " %s", - subm->compiled); - } - - return list; -} - -static char *lib_list(const struct manifest *m) -{ - unsigned int i, num; - char **libs = get_libs(m, m->dir, &num, &m->info_file->compiled); - char *ret = talloc_strdup(m, ""); - - for (i = 0; i < num; i++) - ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); - return ret; -} - -static bool compile(const void *ctx, - struct manifest *m, - struct ccan_file *file, - bool fail, - bool link_with_module, - bool keep, char **output) -{ - file->compiled = maybe_temp_file(ctx, "", keep, file->fullname); - if (!compile_and_link(ctx, file->fullname, ccan_dir, - obj_list(m, link_with_module), - fail ? "-DFAIL" : "", - lib_list(m), file->compiled, output)) { - talloc_free(file->compiled); - return false; - } - return true; -} - -static void do_compile_tests(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - char *cmdout; - struct ccan_file *i; - struct list_head *list; - bool errors = false, warnings = false; - - foreach_ptr(list, &m->compile_ok_tests, &m->run_tests, &m->api_tests) { - list_for_each(list, i, list) { - if (!compile(score, m, i, false, list == &m->api_tests, - keep, &cmdout)) { - score->error = "Failed to compile tests"; - score_file_error(score, i, 0, cmdout); - errors = true; - } else if (!streq(cmdout, "")) { - score->error = "Test compiled with warnings"; - score_file_error(score, i, 0, cmdout); - warnings = true; - } - } - } - - /* The compile fail tests are a bit weird, handle them separately */ - if (errors) - return; - - /* For historical reasons, "fail" often means "gives warnings" */ - list_for_each(&m->compile_fail_tests, i, list) { - if (!compile(score, m, i, false, false, false, &cmdout)) { - score->error = "Failed to compile without -DFAIL"; - score_file_error(score, i, 0, cmdout); - return; - } - if (!streq(cmdout, "")) { - score->error = "Compile with warnigns without -DFAIL"; - score_file_error(score, i, 0, cmdout); - return; - } - if (compile(score, m, i, true, false, false, &cmdout) - && streq(cmdout, "")) { - score->error = "Compiled successfully with -DFAIL?"; - score_file_error(score, i, 0, NULL); - return; - } - } - - score->pass = true; - score->total = 2; - score->score = 1 + !warnings; -} - -struct ccanlint tests_compile = { - .key = "tests_compile", - .name = "Module tests compile", - .check = do_compile_tests, - .can_run = can_build, - .needs = "tests_helpers_compile objects_build" -}; - -REGISTER_TEST(tests_compile); diff --git a/tools/ccanlint/tests/examples_exist.c b/tools/ccanlint/tests/examples_exist.c new file mode 100644 index 00000000..947d76a4 --- /dev/null +++ b/tools/ccanlint/tests/examples_exist.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Creates and adds an example file. */ +static char *add_example(struct manifest *m, struct ccan_file *source, + bool keep, + struct doc_section *example) +{ + char *name; + unsigned int i; + int fd; + struct ccan_file *f; + + name = talloc_asprintf(m, "%s/example-%s-%s.c", + talloc_dirname(m, + source->fullname), + source->name, + example->function); + /* example->function == 'struct foo' */ + while (strchr(name, ' ')) + *strchr(name, ' ') = '_'; + + name = maybe_temp_file(m, ".c", keep, name); + f = new_ccan_file(m, talloc_dirname(m, name), talloc_basename(m, name)); + talloc_steal(f, name); + list_add_tail(&m->examples, &f->list); + + fd = open(f->fullname, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) + return talloc_asprintf(m, "Creating temporary file %s: %s", + f->fullname, strerror(errno)); + + for (i = 0; i < example->num_lines; i++) { + if (write(fd, example->lines[i], strlen(example->lines[i])) + != strlen(example->lines[i]) + || write(fd, "\n", 1) != 1) { + close(fd); + return "Failure writing to temporary file"; + } + } + close(fd); + return NULL; +} + +/* FIXME: We should have one example per function in header. */ +static void extract_examples(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct ccan_file *f, *mainh = NULL; /* gcc complains uninitialized */ + struct doc_section *d; + bool have_info_example = false, have_header_example = false; + + score->total = 2; + list_for_each(get_ccan_file_docs(m->info_file), d, list) { + if (streq(d->type, "example")) { + score->error = add_example(m, m->info_file, keep, d); + if (score->error) + return; + have_info_example = true; + } + } + + /* Check main header. */ + list_for_each(&m->h_files, f, list) { + if (!strstarts(f->name, m->basename) + || strlen(f->name) != strlen(m->basename) + 2) + continue; + + mainh = f; + list_for_each(get_ccan_file_docs(f), d, list) { + if (streq(d->type, "example")) { + score->error = add_example(m, f, keep, d); + if (score->error) + return; + have_header_example = true; + } + } + } + + if (have_info_example && have_header_example) { + score->score = score->total; + score->pass = true; + return; + } + + score->error = "Expect examples in header and _info"; + if (!have_info_example) + score_file_error(score, m->info_file, 0, "No Example: section"); + if (!have_header_example) + score_file_error(score, mainh, 0, "No Example: section"); + + score->score = have_info_example + have_header_example; + /* We pass if we find any example. */ + score->pass = score->score != 0; +} + +struct ccanlint examples_exist = { + .key = "examples_exist", + .name = "_info and main header file have Example: sections", + .check = extract_examples, + .needs = "info_exists" +}; + +REGISTER_TEST(examples_exist); diff --git a/tools/ccanlint/tests/has_examples.c b/tools/ccanlint/tests/has_examples.c deleted file mode 100644 index 947d76a4..00000000 --- a/tools/ccanlint/tests/has_examples.c +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Creates and adds an example file. */ -static char *add_example(struct manifest *m, struct ccan_file *source, - bool keep, - struct doc_section *example) -{ - char *name; - unsigned int i; - int fd; - struct ccan_file *f; - - name = talloc_asprintf(m, "%s/example-%s-%s.c", - talloc_dirname(m, - source->fullname), - source->name, - example->function); - /* example->function == 'struct foo' */ - while (strchr(name, ' ')) - *strchr(name, ' ') = '_'; - - name = maybe_temp_file(m, ".c", keep, name); - f = new_ccan_file(m, talloc_dirname(m, name), talloc_basename(m, name)); - talloc_steal(f, name); - list_add_tail(&m->examples, &f->list); - - fd = open(f->fullname, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) - return talloc_asprintf(m, "Creating temporary file %s: %s", - f->fullname, strerror(errno)); - - for (i = 0; i < example->num_lines; i++) { - if (write(fd, example->lines[i], strlen(example->lines[i])) - != strlen(example->lines[i]) - || write(fd, "\n", 1) != 1) { - close(fd); - return "Failure writing to temporary file"; - } - } - close(fd); - return NULL; -} - -/* FIXME: We should have one example per function in header. */ -static void extract_examples(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct ccan_file *f, *mainh = NULL; /* gcc complains uninitialized */ - struct doc_section *d; - bool have_info_example = false, have_header_example = false; - - score->total = 2; - list_for_each(get_ccan_file_docs(m->info_file), d, list) { - if (streq(d->type, "example")) { - score->error = add_example(m, m->info_file, keep, d); - if (score->error) - return; - have_info_example = true; - } - } - - /* Check main header. */ - list_for_each(&m->h_files, f, list) { - if (!strstarts(f->name, m->basename) - || strlen(f->name) != strlen(m->basename) + 2) - continue; - - mainh = f; - list_for_each(get_ccan_file_docs(f), d, list) { - if (streq(d->type, "example")) { - score->error = add_example(m, f, keep, d); - if (score->error) - return; - have_header_example = true; - } - } - } - - if (have_info_example && have_header_example) { - score->score = score->total; - score->pass = true; - return; - } - - score->error = "Expect examples in header and _info"; - if (!have_info_example) - score_file_error(score, m->info_file, 0, "No Example: section"); - if (!have_header_example) - score_file_error(score, mainh, 0, "No Example: section"); - - score->score = have_info_example + have_header_example; - /* We pass if we find any example. */ - score->pass = score->score != 0; -} - -struct ccanlint examples_exist = { - .key = "examples_exist", - .name = "_info and main header file have Example: sections", - .check = extract_examples, - .needs = "info_exists" -}; - -REGISTER_TEST(examples_exist); diff --git a/tools/ccanlint/tests/has_info_documentation.c b/tools/ccanlint/tests/has_info_documentation.c deleted file mode 100644 index a5316fe0..00000000 --- a/tools/ccanlint/tests/has_info_documentation.c +++ /dev/null @@ -1,102 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern struct ccanlint info_documentation_exists; - -static void create_info_template_doc(struct manifest *m, struct score *score) -{ - int fd = open("_info.new", O_WRONLY|O_CREAT|O_EXCL, 0666); - FILE *new; - char *oldcontents; - - if (fd < 0 || !(new = fdopen(fd, "w"))) - err(1, "Creating _info.new to insert documentation"); - - if (fprintf(new, - "/**\n" - " * %s - [[ONE LINE DESCRIPTION HERE]]\n" - " *\n" - " * Paragraphs why %s exists and where to use it.\n" - " *\n" - " * Followed by an Example: section with a standalone\n" - " * (trivial and usually useless) program\n" - " */\n", m->basename, m->basename) < 0) { - unlink_noerr("_info.new"); - err(1, "Writing to _info.new to insert documentation"); - } - - oldcontents = grab_file(m, "_info", NULL); - if (!oldcontents) { - unlink_noerr("_info.new"); - err(1, "Reading _info"); - } - if (fprintf(new, "%s", oldcontents) < 0) { - unlink_noerr("_info.new"); - err(1, "Appending _info to _info.new"); - } - if (fclose(new) != 0) { - unlink_noerr("_info.new"); - err(1, "Closing _info.new"); - } - if (!move_file("_info.new", "_info")) { - unlink_noerr("_info.new"); - err(1, "Renaming _info.new to _info"); - } -} - -static void check_info_documentation_exists(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct list_head *infodocs = get_ccan_file_docs(m->info_file); - struct doc_section *d; - bool summary = false, description = false; - - list_for_each(infodocs, d, list) { - if (!streq(d->function, m->basename)) - continue; - 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" - "overall documentation belongs in the _info metafile.\n"; - info_documentation_exists.handle = create_info_template_doc; - } else if (!description) { - score->error = "_info file has no module description.\n\n" - "The lines after the first summary line in the _info file\n" - "documentation should describe the purpose and use of the\n" - "overall package\n"; - } -} - -struct ccanlint info_documentation_exists = { - .key = "info_documentation_exists", - .name = "Module has documentation in _info", - .check = check_info_documentation_exists, - .needs = "info_exists" -}; - -REGISTER_TEST(info_documentation_exists); diff --git a/tools/ccanlint/tests/has_tests.c b/tools/ccanlint/tests/has_tests.c deleted file mode 100644 index bdfe49d5..00000000 --- a/tools/ccanlint/tests/has_tests.c +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern struct ccanlint tests_exist; - -static void handle_no_tests(struct manifest *m, struct score *score) -{ - FILE *run; - struct ccan_file *i; - char *test_dir = talloc_asprintf(m, "%s/test", m->dir); - - printf( - "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" - "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" - - "The simplest kind of test is a run test, which must compile with no\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" - "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" - "directory, with the test directory symlinked under test/.\n\n" - - "api tests are just like a run test, except it is a guarantee of API\n" - "stability: this test should pass on all future versions of the\n" - "module. They *are* linked to the module, since they should only\n" - "test the API, not the internal state.\n\n" - - "compile_ok tests are a subset of run tests: they must compile and\n" - "link, but aren't run.\n\n" - - "compile_fail tests are tests which should fail to compile (or emit\n" - "warnings) or link when FAIL is defined, but should compile and link\n" - "when it's not defined: this helps ensure unrelated errors don't make\n" - "compilation fail.\n\n" - - "Note that only API tests are linked against the files in the module!\n" - ); - - if (!ask("Should I create a template test/run.c file for you?")) - return; - - if (mkdir(test_dir, 0700) != 0) { - if (errno != EEXIST) - err(1, "Creating test/ directory"); - } - - run = fopen("test/run.c", "w"); - if (!run) - err(1, "Trying to create a test/run.c"); - - fprintf(run, "#include \n", m->basename, m->basename); - if (!list_empty(&m->c_files)) { - fputs("/* Include the C files directly. */\n", run); - list_for_each(&m->c_files, i, list) - fprintf(run, "#include \n", - m->basename, i->name); - } - fprintf(run, "%s", - "#include \n\n" - "int main(void)\n" - "{\n" - " /* This is how many tests you plan to run */\n" - " plan_tests(3);\n" - "\n" - " /* Simple thing we expect to succeed */\n" - " ok1(some_test())\n" - " /* Same, with an explicit description of the test. */\n" - " ok(some_test(), \"%s with no args should return 1\", \"some_test\")\n" - " /* How to print out messages for debugging. */\n" - " diag(\"Address of some_test is %p\", &some_test)\n" - " /* Conditional tests must be explicitly skipped. */\n" - "#if HAVE_SOME_FEATURE\n" - " ok1(test_some_feature())\n" - "#else\n" - " skip(1, \"Don\'t have SOME_FEATURE\")\n" - "#endif\n" - "\n" - " /* This exits depending on whether all tests passed */\n" - " return exit_status();\n" - "}\n"); - fclose(run); -} - -static void check_tests_exist(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); - tests_exist.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"; - tests_exist.handle = handle_no_tests; - } else - score->error = "No positive tests in test directory"; - return; - } - score->pass = true; - score->score = score->total; -} - -struct ccanlint tests_exist = { - .key = "tests_exist", - .name = "Module has test directory with tests in it", - .check = check_tests_exist, - .needs = "" -}; - -REGISTER_TEST(tests_exist); diff --git a/tools/ccanlint/tests/headers_idempotent.c b/tools/ccanlint/tests/headers_idempotent.c new file mode 100644 index 00000000..340a0698 --- /dev/null +++ b/tools/ccanlint/tests/headers_idempotent.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char explain[] += "Headers usually start with the C preprocessor lines to prevent multiple\n" + "inclusions. These look like the following:\n" + "#ifndef CCAN__H\n" + "#define CCAN__H\n" + "...\n" + "#endif /* CCAN__H */\n"; + +static void fix_name(char *name) +{ + unsigned int i; + + for (i = 0; name[i]; i++) { + if (isalnum(name[i])) + name[i] = toupper(name[i]); + else + name[i] = '_'; + } +} + +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", + 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; + unsigned int i, first_preproc_line; + const char *line, *sym; + + line_info = get_ccan_line_info(f); + if (f->num_lines < 3) + /* FIXME: We assume small headers probably uninteresting. */ + return true; + + for (i = 0; i < f->num_lines; i++) { + if (line_info[i].type == DOC_LINE + || line_info[i].type == COMMENT_LINE) + continue; + if (line_info[i].type == CODE_LINE) { + score_file_error(score, f, i+1, + "Expect first non-comment line to be" + " #ifndef."); + return false; + } else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Don't complain. */ + if (i == f->num_lines) + return true; + + first_preproc_line = i; + for (i = first_preproc_line+1; i < f->num_lines; i++) { + if (line_info[i].type == DOC_LINE + || line_info[i].type == COMMENT_LINE) + continue; + if (line_info[i].type == CODE_LINE) { + score_file_error(score, f, i+1, + "Expect second non-comment line to be" + " #define."); + return false; + } else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Weird. */ + if (i == f->num_lines) + return true; + + /* We expect a condition on this line. */ + if (!line_info[i].cond) { + score_file_error(score, f, i+1, "Expected #ifndef"); + return false; + } + + line = f->lines[i]; + + /* We expect the condition to be ! IFDEF . */ + if (line_info[i].cond->type != PP_COND_IFDEF + || !line_info[i].cond->inverse) { + score_file_error(score, f, i+1, "Expected #ifndef"); + return false; + } + + /* And this to be #define */ + if (!get_token(&line, "#")) + abort(); + if (!get_token(&line, "define")) { + char *str = talloc_asprintf(score, + "expected '#define %s'", + line_info[i].cond->symbol); + score_file_error(score, f, i+1, str); + return false; + } + sym = get_symbol_token(f, &line); + if (!sym || !streq(sym, line_info[i].cond->symbol)) { + char *str = talloc_asprintf(score, + "expected '#define %s'", + 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. */ + for (i++; i < f->num_lines; i++) { + unsigned int val = 0; + if (line_info[i].type == DOC_LINE + || line_info[i].type == COMMENT_LINE) + continue; + if (get_ccan_line_pp(line_info[i].cond, sym, &val, NULL) + != NOT_COMPILED) { + score_file_error(score, f, i+1, "code outside" + " idempotent region"); + return false; + } + } + + return true; +} + +static void check_idempotent(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct ccan_file *f; + + list_for_each(&m->h_files, f, list) { + if (!check_idem(f, score)) + score->error = "Headers are not idempotent"; + } + if (!score->error) { + score->pass = true; + score->score = score->total; + } +} + +struct ccanlint headers_idempotent = { + .key = "headers_idempotent", + .name = "Module headers are #ifndef/#define wrapped", + .check = check_idempotent, + .handle = handle_idem, + .needs = "" +}; + +REGISTER_TEST(headers_idempotent); diff --git a/tools/ccanlint/tests/idempotent.c b/tools/ccanlint/tests/idempotent.c deleted file mode 100644 index 340a0698..00000000 --- a/tools/ccanlint/tests/idempotent.c +++ /dev/null @@ -1,204 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char explain[] -= "Headers usually start with the C preprocessor lines to prevent multiple\n" - "inclusions. These look like the following:\n" - "#ifndef CCAN__H\n" - "#define CCAN__H\n" - "...\n" - "#endif /* CCAN__H */\n"; - -static void fix_name(char *name) -{ - unsigned int i; - - for (i = 0; name[i]; i++) { - if (isalnum(name[i])) - name[i] = toupper(name[i]); - else - name[i] = '_'; - } -} - -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", - 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; - unsigned int i, first_preproc_line; - const char *line, *sym; - - line_info = get_ccan_line_info(f); - if (f->num_lines < 3) - /* FIXME: We assume small headers probably uninteresting. */ - return true; - - for (i = 0; i < f->num_lines; i++) { - if (line_info[i].type == DOC_LINE - || line_info[i].type == COMMENT_LINE) - continue; - if (line_info[i].type == CODE_LINE) { - score_file_error(score, f, i+1, - "Expect first non-comment line to be" - " #ifndef."); - return false; - } else if (line_info[i].type == PREPROC_LINE) - break; - } - - /* No code at all? Don't complain. */ - if (i == f->num_lines) - return true; - - first_preproc_line = i; - for (i = first_preproc_line+1; i < f->num_lines; i++) { - if (line_info[i].type == DOC_LINE - || line_info[i].type == COMMENT_LINE) - continue; - if (line_info[i].type == CODE_LINE) { - score_file_error(score, f, i+1, - "Expect second non-comment line to be" - " #define."); - return false; - } else if (line_info[i].type == PREPROC_LINE) - break; - } - - /* No code at all? Weird. */ - if (i == f->num_lines) - return true; - - /* We expect a condition on this line. */ - if (!line_info[i].cond) { - score_file_error(score, f, i+1, "Expected #ifndef"); - return false; - } - - line = f->lines[i]; - - /* We expect the condition to be ! IFDEF . */ - if (line_info[i].cond->type != PP_COND_IFDEF - || !line_info[i].cond->inverse) { - score_file_error(score, f, i+1, "Expected #ifndef"); - return false; - } - - /* And this to be #define */ - if (!get_token(&line, "#")) - abort(); - if (!get_token(&line, "define")) { - char *str = talloc_asprintf(score, - "expected '#define %s'", - line_info[i].cond->symbol); - score_file_error(score, f, i+1, str); - return false; - } - sym = get_symbol_token(f, &line); - if (!sym || !streq(sym, line_info[i].cond->symbol)) { - char *str = talloc_asprintf(score, - "expected '#define %s'", - 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. */ - for (i++; i < f->num_lines; i++) { - unsigned int val = 0; - if (line_info[i].type == DOC_LINE - || line_info[i].type == COMMENT_LINE) - continue; - if (get_ccan_line_pp(line_info[i].cond, sym, &val, NULL) - != NOT_COMPILED) { - score_file_error(score, f, i+1, "code outside" - " idempotent region"); - return false; - } - } - - return true; -} - -static void check_idempotent(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - struct ccan_file *f; - - list_for_each(&m->h_files, f, list) { - if (!check_idem(f, score)) - score->error = "Headers are not idempotent"; - } - if (!score->error) { - score->pass = true; - score->score = score->total; - } -} - -struct ccanlint headers_idempotent = { - .key = "headers_idempotent", - .name = "Module headers are #ifndef/#define wrapped", - .check = check_idempotent, - .handle = handle_idem, - .needs = "" -}; - -REGISTER_TEST(headers_idempotent); diff --git a/tools/ccanlint/tests/info_documentation_exists.c b/tools/ccanlint/tests/info_documentation_exists.c new file mode 100644 index 00000000..a5316fe0 --- /dev/null +++ b/tools/ccanlint/tests/info_documentation_exists.c @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct ccanlint info_documentation_exists; + +static void create_info_template_doc(struct manifest *m, struct score *score) +{ + int fd = open("_info.new", O_WRONLY|O_CREAT|O_EXCL, 0666); + FILE *new; + char *oldcontents; + + if (fd < 0 || !(new = fdopen(fd, "w"))) + err(1, "Creating _info.new to insert documentation"); + + if (fprintf(new, + "/**\n" + " * %s - [[ONE LINE DESCRIPTION HERE]]\n" + " *\n" + " * Paragraphs why %s exists and where to use it.\n" + " *\n" + " * Followed by an Example: section with a standalone\n" + " * (trivial and usually useless) program\n" + " */\n", m->basename, m->basename) < 0) { + unlink_noerr("_info.new"); + err(1, "Writing to _info.new to insert documentation"); + } + + oldcontents = grab_file(m, "_info", NULL); + if (!oldcontents) { + unlink_noerr("_info.new"); + err(1, "Reading _info"); + } + if (fprintf(new, "%s", oldcontents) < 0) { + unlink_noerr("_info.new"); + err(1, "Appending _info to _info.new"); + } + if (fclose(new) != 0) { + unlink_noerr("_info.new"); + err(1, "Closing _info.new"); + } + if (!move_file("_info.new", "_info")) { + unlink_noerr("_info.new"); + err(1, "Renaming _info.new to _info"); + } +} + +static void check_info_documentation_exists(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct list_head *infodocs = get_ccan_file_docs(m->info_file); + struct doc_section *d; + bool summary = false, description = false; + + list_for_each(infodocs, d, list) { + if (!streq(d->function, m->basename)) + continue; + 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" + "overall documentation belongs in the _info metafile.\n"; + info_documentation_exists.handle = create_info_template_doc; + } else if (!description) { + score->error = "_info file has no module description.\n\n" + "The lines after the first summary line in the _info file\n" + "documentation should describe the purpose and use of the\n" + "overall package\n"; + } +} + +struct ccanlint info_documentation_exists = { + .key = "info_documentation_exists", + .name = "Module has documentation in _info", + .check = check_info_documentation_exists, + .needs = "info_exists" +}; + +REGISTER_TEST(info_documentation_exists); diff --git a/tools/ccanlint/tests/license.c b/tools/ccanlint/tests/license.c deleted file mode 100644 index 965a159f..00000000 --- a/tools/ccanlint/tests/license.c +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct ccanlint has_license; - -static struct doc_section *find_license(const struct manifest *m) -{ - struct doc_section *d; - - list_for_each(m->info_file->doc_sections, d, list) { - if (!streq(d->function, m->basename)) - continue; - if (streq(d->type, "license")) - return d; - } - return NULL; -} - -static const char *expected_link(const struct manifest *m, - struct doc_section *d) -{ - if (streq(d->lines[0], "GPL") - || streq(d->lines[0], "GPLv3") - || streq(d->lines[0], "GPLv3 or later") - || streq(d->lines[0], "GPLv3 (or later)") - || streq(d->lines[0], "GPL (3 or any later version)")) - return "../../licenses/GPL-3"; - if (streq(d->lines[0], "GPLv2") - || streq(d->lines[0], "GPLv2 or later") - || streq(d->lines[0], "GPLv2 (or later)") - || streq(d->lines[0], "GPL (2 or any later version)")) - return "../../licenses/GPL-3"; - if (streq(d->lines[0], "LGPL") - || streq(d->lines[0], "LGPLv3") - || streq(d->lines[0], "LGPLv3 or later") - || streq(d->lines[0], "LGPLv3 (or later)") - || streq(d->lines[0], "LGPL (3 or any later version)")) - return "../../licenses/LGPL-3"; - if (streq(d->lines[0], "LGPLv2") - || streq(d->lines[0], "LGPLv2 or later") - || streq(d->lines[0], "LGPLv2 (or later)") - || streq(d->lines[0], "LGPL (2 or any later version)")) - return "../../licenses/LGPL-2.1"; - if (streq(d->lines[0], "BSD") - || streq(d->lines[0], "BSD-MIT") - || streq(d->lines[0], "MIT")) - return "../../licenses/BSD-MIT"; - return NULL; -} - -static void handle_license_link(struct manifest *m, struct score *score) -{ - const char *link = talloc_asprintf(m, "%s/LICENSE", m->dir); - struct doc_section *d = find_license(m); - const char *ldest = expected_link(m, d); - char *q; - - printf( - "Most modules want a copy of their license, so usually we create a\n" - "LICENSE symlink into ../../licenses to avoid too many copies.\n"); - - /* FIXME: make ask printf-like */ - q = talloc_asprintf(m, "Set up link to %s (license is %s)?", - ldest, d->lines[0]); - if (ask(q)) { - if (symlink(ldest, link) != 0) - err(1, "Creating symlink %s -> %s", link, ldest); - } -} - -static void check_has_license(struct manifest *m, - bool keep, - unsigned int *timeleft, struct score *score) -{ - char buf[PATH_MAX]; - ssize_t len; - char *license = talloc_asprintf(m, "%s/LICENSE", m->dir); - const char *expected; - struct doc_section *d; - - d = find_license(m); - if (!d) { - score->error = "No License: tag in _info"; - return; - } - expected = expected_link(m, d); - - len = readlink(license, buf, sizeof(buf)); - if (len < 0) { - /* Could be a real file... OK if not a standard license. */ - if (errno == EINVAL) { - if (!expected) { - score->pass = true; - return; - } - score->error - = talloc_asprintf(score, - "License in _info is '%s'," - " expect LICENSE symlink '%s'", - d->lines[0], expected); - return; - } - if (errno == ENOENT) { - score->error = "LICENSE does not exist"; - if (expected) - has_license.handle = handle_license_link; - return; - } - err(1, "readlink on %s", license); - } - if (len >= sizeof(buf)) - errx(1, "Reading symlink %s gave huge result", license); - - buf[len] = '\0'; - - if (!strstarts(buf, "../../licenses/")) { - score->error = talloc_asprintf(score, - "Expected symlink to" - " ../../licenses/..." - " not %s", buf); - return; - } - - if (!expected) { - score->error = talloc_asprintf(score, - "License in _info is unknown '%s'," - " but LICENSE symlink is '%s'", - d->lines[0], buf); - return; - } - - if (!streq(buf, expected)) { - score->error = talloc_asprintf(score, - "Expected symlink to %s not %s", - expected, buf); - return; - } - score->pass = true; - score->score = score->total; -} - -struct ccanlint license_exists = { - .key = "license_exists", - .name = "Module has License: entry in _info, and LICENSE symlink/file", - .check = check_has_license, - .needs = "info_exists" -}; - -REGISTER_TEST(license_exists); diff --git a/tools/ccanlint/tests/license_exists.c b/tools/ccanlint/tests/license_exists.c new file mode 100644 index 00000000..965a159f --- /dev/null +++ b/tools/ccanlint/tests/license_exists.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ccanlint has_license; + +static struct doc_section *find_license(const struct manifest *m) +{ + struct doc_section *d; + + list_for_each(m->info_file->doc_sections, d, list) { + if (!streq(d->function, m->basename)) + continue; + if (streq(d->type, "license")) + return d; + } + return NULL; +} + +static const char *expected_link(const struct manifest *m, + struct doc_section *d) +{ + if (streq(d->lines[0], "GPL") + || streq(d->lines[0], "GPLv3") + || streq(d->lines[0], "GPLv3 or later") + || streq(d->lines[0], "GPLv3 (or later)") + || streq(d->lines[0], "GPL (3 or any later version)")) + return "../../licenses/GPL-3"; + if (streq(d->lines[0], "GPLv2") + || streq(d->lines[0], "GPLv2 or later") + || streq(d->lines[0], "GPLv2 (or later)") + || streq(d->lines[0], "GPL (2 or any later version)")) + return "../../licenses/GPL-3"; + if (streq(d->lines[0], "LGPL") + || streq(d->lines[0], "LGPLv3") + || streq(d->lines[0], "LGPLv3 or later") + || streq(d->lines[0], "LGPLv3 (or later)") + || streq(d->lines[0], "LGPL (3 or any later version)")) + return "../../licenses/LGPL-3"; + if (streq(d->lines[0], "LGPLv2") + || streq(d->lines[0], "LGPLv2 or later") + || streq(d->lines[0], "LGPLv2 (or later)") + || streq(d->lines[0], "LGPL (2 or any later version)")) + return "../../licenses/LGPL-2.1"; + if (streq(d->lines[0], "BSD") + || streq(d->lines[0], "BSD-MIT") + || streq(d->lines[0], "MIT")) + return "../../licenses/BSD-MIT"; + return NULL; +} + +static void handle_license_link(struct manifest *m, struct score *score) +{ + const char *link = talloc_asprintf(m, "%s/LICENSE", m->dir); + struct doc_section *d = find_license(m); + const char *ldest = expected_link(m, d); + char *q; + + printf( + "Most modules want a copy of their license, so usually we create a\n" + "LICENSE symlink into ../../licenses to avoid too many copies.\n"); + + /* FIXME: make ask printf-like */ + q = talloc_asprintf(m, "Set up link to %s (license is %s)?", + ldest, d->lines[0]); + if (ask(q)) { + if (symlink(ldest, link) != 0) + err(1, "Creating symlink %s -> %s", link, ldest); + } +} + +static void check_has_license(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + char buf[PATH_MAX]; + ssize_t len; + char *license = talloc_asprintf(m, "%s/LICENSE", m->dir); + const char *expected; + struct doc_section *d; + + d = find_license(m); + if (!d) { + score->error = "No License: tag in _info"; + return; + } + expected = expected_link(m, d); + + len = readlink(license, buf, sizeof(buf)); + if (len < 0) { + /* Could be a real file... OK if not a standard license. */ + if (errno == EINVAL) { + if (!expected) { + score->pass = true; + return; + } + score->error + = talloc_asprintf(score, + "License in _info is '%s'," + " expect LICENSE symlink '%s'", + d->lines[0], expected); + return; + } + if (errno == ENOENT) { + score->error = "LICENSE does not exist"; + if (expected) + has_license.handle = handle_license_link; + return; + } + err(1, "readlink on %s", license); + } + if (len >= sizeof(buf)) + errx(1, "Reading symlink %s gave huge result", license); + + buf[len] = '\0'; + + if (!strstarts(buf, "../../licenses/")) { + score->error = talloc_asprintf(score, + "Expected symlink to" + " ../../licenses/..." + " not %s", buf); + return; + } + + if (!expected) { + score->error = talloc_asprintf(score, + "License in _info is unknown '%s'," + " but LICENSE symlink is '%s'", + d->lines[0], buf); + return; + } + + if (!streq(buf, expected)) { + score->error = talloc_asprintf(score, + "Expected symlink to %s not %s", + expected, buf); + return; + } + score->pass = true; + score->score = score->total; +} + +struct ccanlint license_exists = { + .key = "license_exists", + .name = "Module has License: entry in _info, and LICENSE symlink/file", + .check = check_has_license, + .needs = "info_exists" +}; + +REGISTER_TEST(license_exists); diff --git a/tools/ccanlint/tests/no_trailing_whitespace.c b/tools/ccanlint/tests/no_trailing_whitespace.c new file mode 100644 index 00000000..a66fd74c --- /dev/null +++ b/tools/ccanlint/tests/no_trailing_whitespace.c @@ -0,0 +1,60 @@ +/* Trailing whitespace test. Almost embarrassing, but trivial. */ +#include +#include +#include +#include + +/* FIXME: only print full analysis if verbose >= 2. */ +static char *get_trailing_whitespace(const char *line) +{ + const char *e = strchr(line, 0); + while (e>line && (e[-1]==' ' || e[-1]=='\t')) + e--; + if (*e == 0) + return NULL; //there were no trailing spaces + if (e == line) + return NULL; //the line only consists of spaces + + if (strlen(line) > 20) + return talloc_asprintf(line, "...'%s'", + line + strlen(line) - 20); + return talloc_asprintf(line, "'%s'", line); +} + +static void check_trailing_whitespace(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct list_head *list; + struct ccan_file *f; + unsigned int i; + + foreach_ptr(list, &m->c_files, &m->h_files) { + list_for_each(list, f, list) { + char **lines = get_ccan_file_lines(f); + for (i = 0; i < f->num_lines; i++) { + char *err = get_trailing_whitespace(lines[i]); + if (err) { + score->error = "Trailing whitespace" + " found"; + score_file_error(score, f, i+1, err); + } + } + } + } + if (!score->error) { + score->pass = true; + score->score = score->total; + } +} + +struct ccanlint no_trailing_whitespace = { + .key = "no_trailing_whitespace", + .name = "Module's source code has no trailing whitespace", + .check = check_trailing_whitespace, + .needs = "" +}; + + +REGISTER_TEST(no_trailing_whitespace); diff --git a/tools/ccanlint/tests/run-coverage.c b/tools/ccanlint/tests/run-coverage.c deleted file mode 100644 index af8d6f21..00000000 --- a/tools/ccanlint/tests/run-coverage.c +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 = strsplit(score, output, "\n", NULL); - float covered_lines = 0.0; - unsigned int i, total_lines = 0; - bool lines_matter = false; - - /* - Output looks like: - 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 (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 '")) { - char *file, *filename, *apostrophe; - apostrophe = strchr(lines[i], '\''); - filename = apostrophe + 1; - apostrophe = strchr(filename, '\''); - *apostrophe = '\0'; - if (lines_matter) { - file = grab_file(score, filename, NULL); - if (!file) { - score->error = talloc_asprintf(score, - "Reading %s", - filename); - return; - } - printf("%s", file); - } - if (tools_verbose) - printf("Unlinking %s", filename); - unlink(filename); - } - } - - score->pass = true; - - /* 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, - bool keep, - unsigned int *timeleft, struct score *score) -{ - struct ccan_file *i; - char *cmdout; - char *covcmd; - bool full_gcov = (verbose > 1); - struct list_head *list; - - /* This tells gcov where we put those .gcno files. */ - covcmd = talloc_asprintf(m, "gcov %s -o %s", - full_gcov ? "" : "-n", - talloc_dirname(score, m->info_file->compiled)); - - /* 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->cov_compiled)) { - covcmd = talloc_asprintf_append(covcmd, " %s", - i->fullname); - } else { - score->error = "Running test with coverage"; - score_file_error(score, i, 0, cmdout); - return; - } - } - } - - /* Now run gcov: we want output even if it succeeds. */ - if (!run_command(score, timeleft, &cmdout, "%s", covcmd)) { - score->error = talloc_asprintf(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); diff --git a/tools/ccanlint/tests/run_tests.c b/tools/ccanlint/tests/run_tests.c deleted file mode 100644 index 0edfad26..00000000 --- a/tools/ccanlint/tests/run_tests.c +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char *can_run(struct manifest *m) -{ - if (safe_mode) - return "Safe mode enabled"; - return NULL; -} - -static void do_run_tests(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct list_head *list; - struct ccan_file *i; - char *cmdout; - - score->total = 0; - foreach_ptr(list, &m->run_tests, &m->api_tests) { - list_for_each(list, i, list) { - score->total++; - if (run_command(m, timeleft, &cmdout, "%s", - i->compiled)) - score->score++; - else - score_file_error(score, i, 0, cmdout); - } - } - - if (score->score == score->total) - score->pass = true; -} - -/* Gcc's warn_unused_result is fascist bullshit. */ -#define doesnt_matter() - -static void run_under_debugger(struct manifest *m, struct score *score) -{ - char *command; - struct file_error *first; - - if (!ask("Should I run the first failing test under the debugger?")) - return; - - first = list_top(&score->per_file_errors, struct file_error, list); - command = talloc_asprintf(m, "gdb -ex 'break tap.c:136' -ex 'run' %s", - first->file->compiled); - if (system(command)) - doesnt_matter(); -} - -struct ccanlint tests_pass = { - .key = "tests_pass", - .name = "Module's run and api tests pass", - .check = do_run_tests, - .handle = run_under_debugger, - .can_run = can_run, - .needs = "tests_compile" -}; - -REGISTER_TEST(tests_pass); diff --git a/tools/ccanlint/tests/run_tests_valgrind.c b/tools/ccanlint/tests/run_tests_valgrind.c deleted file mode 100644 index 30a806eb..00000000 --- a/tools/ccanlint/tests/run_tests_valgrind.c +++ /dev/null @@ -1,201 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct ccanlint run_tests_vg; - -/* Note: we already test safe_mode in run_tests.c */ -static const char *can_run_vg(struct manifest *m) -{ - unsigned int timeleft = default_timeout_ms; - char *output; - - if (!run_command(m, &timeleft, &output, - "valgrind -q --error-exitcode=0 true")) - return talloc_asprintf(m, "No valgrind support: %s", output); - return NULL; -} - -/* Example output: -==2749== Conditional jump or move depends on uninitialised value(s) -==2749== at 0x4026C60: strnlen (mc_replace_strmem.c:263) -==2749== by 0x40850E3: vfprintf (vfprintf.c:1614) -==2749== by 0x408EACF: printf (printf.c:35) -==2749== by 0x8048465: main (in /tmp/foo) -==2749== -==2749== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1 -==2749== at 0x4025BD3: malloc (vg_replace_malloc.c:236) -==2749== by 0x8048444: main (in /tmp/foo) -==2749== -*/ - -static bool blank_line(const char *line) -{ - return line[strspn(line, "=0123456789 ")] == '\0'; -} - -static char *get_leaks(const char *output, char **errs) -{ - char *leaks = talloc_strdup(output, ""); - unsigned int i, num; - char **lines = strsplit(output, output, "\n", &num); - - *errs = talloc_strdup(output, ""); - for (i = 0; i < num; i++) { - if (strstr(lines[i], " lost ")) { - /* A leak... */ - if (strstr(lines[i], " definitely lost ")) { - /* Definite leak, report. */ - while (lines[i] && !blank_line(lines[i])) { - leaks = talloc_append_string(leaks, - lines[i]); - leaks = talloc_append_string(leaks, - "\n"); - i++; - } - } else - /* Not definite, ignore. */ - while (lines[i] && !blank_line(lines[i])) - i++; - } else { - /* A real error. */ - while (lines[i] && !blank_line(lines[i])) { - *errs = talloc_append_string(*errs, lines[i]); - *errs = talloc_append_string(*errs, "\n"); - i++; - } - } - } - if (!leaks[0]) { - talloc_free(leaks); - leaks = NULL; - } - if (!(*errs)[0]) { - talloc_free(*errs); - *errs = NULL; - } - return leaks; -} - -/* FIXME: Run examples, too! */ -static void do_run_tests_vg(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct ccan_file *i; - struct list_head *list; - char *cmdout; - - /* This is slow, so we run once but grab leak info. */ - score->total = 0; - foreach_ptr(list, &m->run_tests, &m->api_tests) { - list_for_each(list, i, list) { - char *output, *err; - score->total++; - /* FIXME: Valgrind's output sucks. XML is unreadable by - * humans, and you can't have both. */ - run_command(score, timeleft, &cmdout, - "valgrind -q --error-exitcode=101" - " --leak-check=full" - " --log-fd=3 %s %s" - " 3> valgrind.log", - run_tests_vg.options ? - run_tests_vg.options : "", - i->compiled); - output = grab_file(i, "valgrind.log", NULL); - if (!output || output[0] == '\0') { - err = NULL; - } else { - i->leak_info = get_leaks(output, &err); - } - if (err) - score_file_error(score, i, 0, err); - else - score->score++; - } - } - - if (score->score == score->total) - score->pass = true; -} - -static void do_leakcheck_vg(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct ccan_file *i; - struct list_head *list; - bool leaks = false; - - foreach_ptr(list, &m->run_tests, &m->api_tests) { - list_for_each(list, i, list) { - if (i->leak_info) { - score_file_error(score, i, 0, i->leak_info); - leaks = true; - } - } - } - - if (!leaks) { - score->score = 1; - score->pass = true; - } -} - -/* Gcc's warn_unused_result is fascist bullshit. */ -#define doesnt_matter() - -static void run_under_debugger_vg(struct manifest *m, struct score *score) -{ - struct file_error *first; - char *command; - - if (!ask("Should I run the first failing test under the debugger?")) - return; - - first = list_top(&score->per_file_errors, struct file_error, list); - command = talloc_asprintf(m, "valgrind --db-attach=yes%s %s", - run_tests_vg.options ? - run_tests_vg.options : "", - first->file->compiled); - if (system(command)) - doesnt_matter(); -} - -struct ccanlint tests_pass_valgrind = { - .key = "tests_pass_valgrind", - .name = "Module's run and api tests succeed under valgrind", - .can_run = can_run_vg, - .check = do_run_tests_vg, - .handle = run_under_debugger_vg, - .takes_options = true, - .needs = "tests_pass" -}; - -REGISTER_TEST(tests_pass_valgrind); - -struct ccanlint tests_pass_valgrind_noleaks = { - .key = "tests_pass_valgrind_noleaks", - .name = "Module's run and api tests leak memory", - .check = do_leakcheck_vg, - .needs = "tests_pass_valgrind" -}; - -REGISTER_TEST(tests_pass_valgrind_noleaks); diff --git a/tools/ccanlint/tests/tests_compile.c b/tools/ccanlint/tests/tests_compile.c new file mode 100644 index 00000000..4d868679 --- /dev/null +++ b/tools/ccanlint/tests/tests_compile.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_build(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +/* FIXME: Merge this into one place. */ +static char *obj_list(const struct manifest *m, bool link_with_module) +{ + char *list = talloc_strdup(m, ""); + struct ccan_file *i; + struct manifest *subm; + + /* Objects from any other C files. */ + list_for_each(&m->other_test_c_files, i, list) + list = talloc_asprintf_append(list, " %s", i->compiled); + + /* Our own object files. */ + if (link_with_module) + list_for_each(&m->c_files, i, list) + list = talloc_asprintf_append(list, " %s", i->compiled); + + /* Other ccan modules. */ + list_for_each(&m->deps, subm, list) { + if (subm->compiled) + list = talloc_asprintf_append(list, " %s", + subm->compiled); + } + + return list; +} + +static char *lib_list(const struct manifest *m) +{ + unsigned int i, num; + char **libs = get_libs(m, m->dir, &num, &m->info_file->compiled); + char *ret = talloc_strdup(m, ""); + + for (i = 0; i < num; i++) + ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); + return ret; +} + +static bool compile(const void *ctx, + struct manifest *m, + struct ccan_file *file, + bool fail, + bool link_with_module, + bool keep, char **output) +{ + file->compiled = maybe_temp_file(ctx, "", keep, file->fullname); + if (!compile_and_link(ctx, file->fullname, ccan_dir, + obj_list(m, link_with_module), + fail ? "-DFAIL" : "", + lib_list(m), file->compiled, output)) { + talloc_free(file->compiled); + return false; + } + return true; +} + +static void do_compile_tests(struct manifest *m, + bool keep, + unsigned int *timeleft, struct score *score) +{ + char *cmdout; + struct ccan_file *i; + struct list_head *list; + bool errors = false, warnings = false; + + foreach_ptr(list, &m->compile_ok_tests, &m->run_tests, &m->api_tests) { + list_for_each(list, i, list) { + if (!compile(score, m, i, false, list == &m->api_tests, + keep, &cmdout)) { + score->error = "Failed to compile tests"; + score_file_error(score, i, 0, cmdout); + errors = true; + } else if (!streq(cmdout, "")) { + score->error = "Test compiled with warnings"; + score_file_error(score, i, 0, cmdout); + warnings = true; + } + } + } + + /* The compile fail tests are a bit weird, handle them separately */ + if (errors) + return; + + /* For historical reasons, "fail" often means "gives warnings" */ + list_for_each(&m->compile_fail_tests, i, list) { + if (!compile(score, m, i, false, false, false, &cmdout)) { + score->error = "Failed to compile without -DFAIL"; + score_file_error(score, i, 0, cmdout); + return; + } + if (!streq(cmdout, "")) { + score->error = "Compile with warnigns without -DFAIL"; + score_file_error(score, i, 0, cmdout); + return; + } + if (compile(score, m, i, true, false, false, &cmdout) + && streq(cmdout, "")) { + score->error = "Compiled successfully with -DFAIL?"; + score_file_error(score, i, 0, NULL); + return; + } + } + + score->pass = true; + score->total = 2; + score->score = 1 + !warnings; +} + +struct ccanlint tests_compile = { + .key = "tests_compile", + .name = "Module tests compile", + .check = do_compile_tests, + .can_run = can_build, + .needs = "tests_helpers_compile objects_build" +}; + +REGISTER_TEST(tests_compile); diff --git a/tools/ccanlint/tests/tests_compile_coverage.c b/tools/ccanlint/tests/tests_compile_coverage.c new file mode 100644 index 00000000..115ae94a --- /dev/null +++ b/tools/ccanlint/tests/tests_compile_coverage.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Note: we already test safe_mode in run_tests.c */ +static const char *can_run_coverage(struct manifest *m) +{ + unsigned int timeleft = default_timeout_ms; + char *output; + + if (!run_command(m, &timeleft, &output, "gcov -h")) + return talloc_asprintf(m, "No gcov support: %s", output); + return NULL; +} + +static bool build_module_objs_with_coverage(struct manifest *m, bool keep, + struct score *score, + char **modobjs) +{ + struct ccan_file *i; + + *modobjs = talloc_strdup(m, ""); + list_for_each(&m->c_files, i, list) { + char *err; + char *fullfile = talloc_asprintf(m, "%s/%s", m->dir, i->name); + + i->cov_compiled = maybe_temp_file(m, "", keep, fullfile); + if (!compile_object(m, fullfile, ccan_dir, "", + i->cov_compiled, &err)) { + score_file_error(score, i, 0, err); + talloc_free(i->cov_compiled); + i->cov_compiled = NULL; + return false; + } + *modobjs = talloc_asprintf_append(*modobjs, + " %s", i->cov_compiled); + } + return true; +} + +/* FIXME: Merge this into one place. */ +static char *obj_list(const struct manifest *m, const char *modobjs) +{ + char *list = talloc_strdup(m, ""); + struct ccan_file *i; + struct manifest *subm; + + /* Objects from any other C files. */ + list_for_each(&m->other_test_c_files, i, list) + list = talloc_asprintf_append(list, " %s", i->compiled); + + if (modobjs) + list = talloc_append_string(list, modobjs); + + /* Other ccan modules (don't need coverage versions of those). */ + list_for_each(&m->deps, subm, list) { + if (subm->compiled) + list = talloc_asprintf_append(list, " %s", + subm->compiled); + } + + return list; +} + +static char *lib_list(const struct manifest *m) +{ + unsigned int i, num; + char **libs = get_libs(m, m->dir, &num, &m->info_file->compiled); + char *ret = talloc_strdup(m, ""); + + for (i = 0; i < num; i++) + ret = talloc_asprintf_append(ret, "-l%s ", libs[i]); + return ret; +} + +static char *cov_compile(const void *ctx, + struct manifest *m, + struct ccan_file *file, + const char *modobjs, + bool keep) +{ + char *output; + + file->cov_compiled = maybe_temp_file(ctx, "", keep, file->fullname); + if (!compile_and_link(ctx, file->fullname, ccan_dir, + obj_list(m, modobjs), + COVERAGE_CFLAGS, + lib_list(m), file->cov_compiled, &output)) { + talloc_free(file->cov_compiled); + file->cov_compiled = NULL; + return output; + } + talloc_free(output); + return NULL; +} + +/* FIXME: Coverage from testable examples as well. */ +static void do_compile_coverage_tests(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + char *cmdout, *modobjs = NULL; + struct ccan_file *i; + + if (!list_empty(&m->api_tests) + && !build_module_objs_with_coverage(m, keep, score, &modobjs)) { + score->error = "Failed to compile module objects with coverage"; + return; + } + + list_for_each(&m->run_tests, i, list) { + cmdout = cov_compile(m, m, i, NULL, keep); + if (cmdout) { + score->error = "Failed to compile test with coverage"; + score_file_error(score, i, 0, cmdout); + } + } + + list_for_each(&m->api_tests, i, list) { + cmdout = cov_compile(m, m, i, modobjs, keep); + if (cmdout) { + score->error = "Failed to compile test with coverage"; + score_file_error(score, i, 0, cmdout); + } + } + if (!score->error) { + score->pass = true; + score->score = score->total; + } +} + +struct ccanlint tests_compile_coverage = { + .key = "tests_compile_coverage", + .name = "Module tests compile with " COVERAGE_CFLAGS, + .check = do_compile_coverage_tests, + .can_run = can_run_coverage, + .needs = "tests_compile" +}; + +REGISTER_TEST(tests_compile_coverage); diff --git a/tools/ccanlint/tests/tests_coverage.c b/tools/ccanlint/tests/tests_coverage.c new file mode 100644 index 00000000..af8d6f21 --- /dev/null +++ b/tools/ccanlint/tests/tests_coverage.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 = strsplit(score, output, "\n", NULL); + float covered_lines = 0.0; + unsigned int i, total_lines = 0; + bool lines_matter = false; + + /* + Output looks like: + 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 (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 '")) { + char *file, *filename, *apostrophe; + apostrophe = strchr(lines[i], '\''); + filename = apostrophe + 1; + apostrophe = strchr(filename, '\''); + *apostrophe = '\0'; + if (lines_matter) { + file = grab_file(score, filename, NULL); + if (!file) { + score->error = talloc_asprintf(score, + "Reading %s", + filename); + return; + } + printf("%s", file); + } + if (tools_verbose) + printf("Unlinking %s", filename); + unlink(filename); + } + } + + score->pass = true; + + /* 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, + bool keep, + unsigned int *timeleft, struct score *score) +{ + struct ccan_file *i; + char *cmdout; + char *covcmd; + bool full_gcov = (verbose > 1); + struct list_head *list; + + /* This tells gcov where we put those .gcno files. */ + covcmd = talloc_asprintf(m, "gcov %s -o %s", + full_gcov ? "" : "-n", + talloc_dirname(score, m->info_file->compiled)); + + /* 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->cov_compiled)) { + covcmd = talloc_asprintf_append(covcmd, " %s", + i->fullname); + } else { + score->error = "Running test with coverage"; + score_file_error(score, i, 0, cmdout); + return; + } + } + } + + /* Now run gcov: we want output even if it succeeds. */ + if (!run_command(score, timeleft, &cmdout, "%s", covcmd)) { + score->error = talloc_asprintf(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); diff --git a/tools/ccanlint/tests/tests_exist.c b/tools/ccanlint/tests/tests_exist.c new file mode 100644 index 00000000..bdfe49d5 --- /dev/null +++ b/tools/ccanlint/tests/tests_exist.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct ccanlint tests_exist; + +static void handle_no_tests(struct manifest *m, struct score *score) +{ + FILE *run; + struct ccan_file *i; + char *test_dir = talloc_asprintf(m, "%s/test", m->dir); + + printf( + "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" + "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" + + "The simplest kind of test is a run test, which must compile with no\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" + "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" + "directory, with the test directory symlinked under test/.\n\n" + + "api tests are just like a run test, except it is a guarantee of API\n" + "stability: this test should pass on all future versions of the\n" + "module. They *are* linked to the module, since they should only\n" + "test the API, not the internal state.\n\n" + + "compile_ok tests are a subset of run tests: they must compile and\n" + "link, but aren't run.\n\n" + + "compile_fail tests are tests which should fail to compile (or emit\n" + "warnings) or link when FAIL is defined, but should compile and link\n" + "when it's not defined: this helps ensure unrelated errors don't make\n" + "compilation fail.\n\n" + + "Note that only API tests are linked against the files in the module!\n" + ); + + if (!ask("Should I create a template test/run.c file for you?")) + return; + + if (mkdir(test_dir, 0700) != 0) { + if (errno != EEXIST) + err(1, "Creating test/ directory"); + } + + run = fopen("test/run.c", "w"); + if (!run) + err(1, "Trying to create a test/run.c"); + + fprintf(run, "#include \n", m->basename, m->basename); + if (!list_empty(&m->c_files)) { + fputs("/* Include the C files directly. */\n", run); + list_for_each(&m->c_files, i, list) + fprintf(run, "#include \n", + m->basename, i->name); + } + fprintf(run, "%s", + "#include \n\n" + "int main(void)\n" + "{\n" + " /* This is how many tests you plan to run */\n" + " plan_tests(3);\n" + "\n" + " /* Simple thing we expect to succeed */\n" + " ok1(some_test())\n" + " /* Same, with an explicit description of the test. */\n" + " ok(some_test(), \"%s with no args should return 1\", \"some_test\")\n" + " /* How to print out messages for debugging. */\n" + " diag(\"Address of some_test is %p\", &some_test)\n" + " /* Conditional tests must be explicitly skipped. */\n" + "#if HAVE_SOME_FEATURE\n" + " ok1(test_some_feature())\n" + "#else\n" + " skip(1, \"Don\'t have SOME_FEATURE\")\n" + "#endif\n" + "\n" + " /* This exits depending on whether all tests passed */\n" + " return exit_status();\n" + "}\n"); + fclose(run); +} + +static void check_tests_exist(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); + tests_exist.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"; + tests_exist.handle = handle_no_tests; + } else + score->error = "No positive tests in test directory"; + return; + } + score->pass = true; + score->score = score->total; +} + +struct ccanlint tests_exist = { + .key = "tests_exist", + .name = "Module has test directory with tests in it", + .check = check_tests_exist, + .needs = "" +}; + +REGISTER_TEST(tests_exist); diff --git a/tools/ccanlint/tests/tests_helpers_compile.c b/tools/ccanlint/tests/tests_helpers_compile.c new file mode 100644 index 00000000..0ad5a7e6 --- /dev/null +++ b/tools/ccanlint/tests/tests_helpers_compile.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_run(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static bool compile(struct manifest *m, + bool keep, + struct ccan_file *cfile, + char **output) +{ + cfile->compiled = maybe_temp_file(m, ".o", keep, cfile->fullname); + return compile_object(m, cfile->fullname, ccan_dir, "", + cfile->compiled, output); +} + +static void do_compile_test_helpers(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct ccan_file *i; + bool errors = false, warnings = false; + + if (list_empty(&m->other_test_c_files)) + score->total = 0; + else + score->total = 2; + + list_for_each(&m->other_test_c_files, i, list) { + char *cmdout; + + if (!compile(m, keep, i, &cmdout)) { + errors = true; + score->error = "Failed to compile helper C files"; + score_file_error(score, i, 0, cmdout); + } else if (!streq(cmdout, "")) { + warnings = true; + score->error = "Helper C files gave warnings"; + score_file_error(score, i, 0, cmdout); + } + } + + if (!errors) { + score->pass = true; + score->score = score->total - warnings; + } +} + +struct ccanlint tests_helpers_compile = { + .key = "tests_helpers_compile", + .name = "Module test helper objects compile", + .check = do_compile_test_helpers, + .can_run = can_run, + .needs = "depends_build tests_exist" +}; + +REGISTER_TEST(tests_helpers_compile); diff --git a/tools/ccanlint/tests/tests_pass.c b/tools/ccanlint/tests/tests_pass.c new file mode 100644 index 00000000..0edfad26 --- /dev/null +++ b/tools/ccanlint/tests/tests_pass.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *can_run(struct manifest *m) +{ + if (safe_mode) + return "Safe mode enabled"; + return NULL; +} + +static void do_run_tests(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct list_head *list; + struct ccan_file *i; + char *cmdout; + + score->total = 0; + foreach_ptr(list, &m->run_tests, &m->api_tests) { + list_for_each(list, i, list) { + score->total++; + if (run_command(m, timeleft, &cmdout, "%s", + i->compiled)) + score->score++; + else + score_file_error(score, i, 0, cmdout); + } + } + + if (score->score == score->total) + score->pass = true; +} + +/* Gcc's warn_unused_result is fascist bullshit. */ +#define doesnt_matter() + +static void run_under_debugger(struct manifest *m, struct score *score) +{ + char *command; + struct file_error *first; + + if (!ask("Should I run the first failing test under the debugger?")) + return; + + first = list_top(&score->per_file_errors, struct file_error, list); + command = talloc_asprintf(m, "gdb -ex 'break tap.c:136' -ex 'run' %s", + first->file->compiled); + if (system(command)) + doesnt_matter(); +} + +struct ccanlint tests_pass = { + .key = "tests_pass", + .name = "Module's run and api tests pass", + .check = do_run_tests, + .handle = run_under_debugger, + .can_run = can_run, + .needs = "tests_compile" +}; + +REGISTER_TEST(tests_pass); diff --git a/tools/ccanlint/tests/tests_pass_valgrind.c b/tools/ccanlint/tests/tests_pass_valgrind.c new file mode 100644 index 00000000..30a806eb --- /dev/null +++ b/tools/ccanlint/tests/tests_pass_valgrind.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ccanlint run_tests_vg; + +/* Note: we already test safe_mode in run_tests.c */ +static const char *can_run_vg(struct manifest *m) +{ + unsigned int timeleft = default_timeout_ms; + char *output; + + if (!run_command(m, &timeleft, &output, + "valgrind -q --error-exitcode=0 true")) + return talloc_asprintf(m, "No valgrind support: %s", output); + return NULL; +} + +/* Example output: +==2749== Conditional jump or move depends on uninitialised value(s) +==2749== at 0x4026C60: strnlen (mc_replace_strmem.c:263) +==2749== by 0x40850E3: vfprintf (vfprintf.c:1614) +==2749== by 0x408EACF: printf (printf.c:35) +==2749== by 0x8048465: main (in /tmp/foo) +==2749== +==2749== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1 +==2749== at 0x4025BD3: malloc (vg_replace_malloc.c:236) +==2749== by 0x8048444: main (in /tmp/foo) +==2749== +*/ + +static bool blank_line(const char *line) +{ + return line[strspn(line, "=0123456789 ")] == '\0'; +} + +static char *get_leaks(const char *output, char **errs) +{ + char *leaks = talloc_strdup(output, ""); + unsigned int i, num; + char **lines = strsplit(output, output, "\n", &num); + + *errs = talloc_strdup(output, ""); + for (i = 0; i < num; i++) { + if (strstr(lines[i], " lost ")) { + /* A leak... */ + if (strstr(lines[i], " definitely lost ")) { + /* Definite leak, report. */ + while (lines[i] && !blank_line(lines[i])) { + leaks = talloc_append_string(leaks, + lines[i]); + leaks = talloc_append_string(leaks, + "\n"); + i++; + } + } else + /* Not definite, ignore. */ + while (lines[i] && !blank_line(lines[i])) + i++; + } else { + /* A real error. */ + while (lines[i] && !blank_line(lines[i])) { + *errs = talloc_append_string(*errs, lines[i]); + *errs = talloc_append_string(*errs, "\n"); + i++; + } + } + } + if (!leaks[0]) { + talloc_free(leaks); + leaks = NULL; + } + if (!(*errs)[0]) { + talloc_free(*errs); + *errs = NULL; + } + return leaks; +} + +/* FIXME: Run examples, too! */ +static void do_run_tests_vg(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct ccan_file *i; + struct list_head *list; + char *cmdout; + + /* This is slow, so we run once but grab leak info. */ + score->total = 0; + foreach_ptr(list, &m->run_tests, &m->api_tests) { + list_for_each(list, i, list) { + char *output, *err; + score->total++; + /* FIXME: Valgrind's output sucks. XML is unreadable by + * humans, and you can't have both. */ + run_command(score, timeleft, &cmdout, + "valgrind -q --error-exitcode=101" + " --leak-check=full" + " --log-fd=3 %s %s" + " 3> valgrind.log", + run_tests_vg.options ? + run_tests_vg.options : "", + i->compiled); + output = grab_file(i, "valgrind.log", NULL); + if (!output || output[0] == '\0') { + err = NULL; + } else { + i->leak_info = get_leaks(output, &err); + } + if (err) + score_file_error(score, i, 0, err); + else + score->score++; + } + } + + if (score->score == score->total) + score->pass = true; +} + +static void do_leakcheck_vg(struct manifest *m, + bool keep, + unsigned int *timeleft, + struct score *score) +{ + struct ccan_file *i; + struct list_head *list; + bool leaks = false; + + foreach_ptr(list, &m->run_tests, &m->api_tests) { + list_for_each(list, i, list) { + if (i->leak_info) { + score_file_error(score, i, 0, i->leak_info); + leaks = true; + } + } + } + + if (!leaks) { + score->score = 1; + score->pass = true; + } +} + +/* Gcc's warn_unused_result is fascist bullshit. */ +#define doesnt_matter() + +static void run_under_debugger_vg(struct manifest *m, struct score *score) +{ + struct file_error *first; + char *command; + + if (!ask("Should I run the first failing test under the debugger?")) + return; + + first = list_top(&score->per_file_errors, struct file_error, list); + command = talloc_asprintf(m, "valgrind --db-attach=yes%s %s", + run_tests_vg.options ? + run_tests_vg.options : "", + first->file->compiled); + if (system(command)) + doesnt_matter(); +} + +struct ccanlint tests_pass_valgrind = { + .key = "tests_pass_valgrind", + .name = "Module's run and api tests succeed under valgrind", + .can_run = can_run_vg, + .check = do_run_tests_vg, + .handle = run_under_debugger_vg, + .takes_options = true, + .needs = "tests_pass" +}; + +REGISTER_TEST(tests_pass_valgrind); + +struct ccanlint tests_pass_valgrind_noleaks = { + .key = "tests_pass_valgrind_noleaks", + .name = "Module's run and api tests leak memory", + .check = do_leakcheck_vg, + .needs = "tests_pass_valgrind" +}; + +REGISTER_TEST(tests_pass_valgrind_noleaks); diff --git a/tools/ccanlint/tests/trailing_whitespace.c b/tools/ccanlint/tests/trailing_whitespace.c deleted file mode 100644 index a66fd74c..00000000 --- a/tools/ccanlint/tests/trailing_whitespace.c +++ /dev/null @@ -1,60 +0,0 @@ -/* Trailing whitespace test. Almost embarrassing, but trivial. */ -#include -#include -#include -#include - -/* FIXME: only print full analysis if verbose >= 2. */ -static char *get_trailing_whitespace(const char *line) -{ - const char *e = strchr(line, 0); - while (e>line && (e[-1]==' ' || e[-1]=='\t')) - e--; - if (*e == 0) - return NULL; //there were no trailing spaces - if (e == line) - return NULL; //the line only consists of spaces - - if (strlen(line) > 20) - return talloc_asprintf(line, "...'%s'", - line + strlen(line) - 20); - return talloc_asprintf(line, "'%s'", line); -} - -static void check_trailing_whitespace(struct manifest *m, - bool keep, - unsigned int *timeleft, - struct score *score) -{ - struct list_head *list; - struct ccan_file *f; - unsigned int i; - - foreach_ptr(list, &m->c_files, &m->h_files) { - list_for_each(list, f, list) { - char **lines = get_ccan_file_lines(f); - for (i = 0; i < f->num_lines; i++) { - char *err = get_trailing_whitespace(lines[i]); - if (err) { - score->error = "Trailing whitespace" - " found"; - score_file_error(score, f, i+1, err); - } - } - } - } - if (!score->error) { - score->pass = true; - score->score = score->total; - } -} - -struct ccanlint no_trailing_whitespace = { - .key = "no_trailing_whitespace", - .name = "Module's source code has no trailing whitespace", - .check = check_trailing_whitespace, - .needs = "" -}; - - -REGISTER_TEST(no_trailing_whitespace);