From: Rusty Russell Date: Thu, 28 May 2009 04:14:35 +0000 (+0930) Subject: Move ccanlint tests into subdirectories. X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=8f61c0bccb152b2365baf70deac1e59264d7feb7 Move ccanlint tests into subdirectories. --- diff --git a/tools/ccanlint/Makefile b/tools/ccanlint/Makefile index 87ff2d0e..cd507e5d 100644 --- a/tools/ccanlint/Makefile +++ b/tools/ccanlint/Makefile @@ -1,32 +1,21 @@ -OBJS := tools/ccanlint/has_info.o \ - tools/ccanlint/has_main_header.o \ - tools/ccanlint/has_tests.o \ - tools/ccanlint/trailing_whitespace.o \ - tools/ccanlint/idempotent.o \ - tools/ccanlint/has_info_documentation.o \ +TEST_CFILES := $(wildcard tools/ccanlint/compulsory_tests/*.c) \ + $(wildcard tools/ccanlint/tests/*.c) +TEST_OBJS := $(TEST_CFILES:.c=.o) -FUTURE:=tools/ccanlint/if_have_not_ifdef.o \ - tools/ccanlint/needs_depends.o \ - tools/ccanlint/has_info_documentation.o \ - tools/ccanlint/has_header_documentation.o \ - tools/ccanlint/has_tests.o \ - tools/ccanlint/builds_ok.o \ - tools/ccanlint/builds_ok_all_have_variants.o \ - tools/ccanlint/run_tests.o \ - tools/ccanlint/test_coverage.o \ +CORE_OBJS := tools/ccanlint/ccanlint.o \ + tools/ccanlint/file_analysis.o \ + tools/doc_extract-core.o \ + ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o \ + ccan/talloc/talloc.o ccan/noerr/noerr.o + +OBJS := $(CORE_OBJS) $(TEST_OBJS) tools/ccanlint/generated-init-tests: $(OBJS) cat $(OBJS:.o=.c) | sed -n 's/^struct ccanlint \([A-Za-z0-9_]*\) = {/{ extern struct ccanlint \1; list_add(\&tests, \&\1.list); }/p' >$@ tools/ccanlint/ccanlint.o: tools/ccanlint/generated-init-tests -tools/ccanlint/ccanlint: \ - $(OBJS) \ - tools/ccanlint/ccanlint.o \ - tools/ccanlint/file_analysis.o \ - tools/doc_extract-core.o \ - ccan/str_talloc/str_talloc.o ccan/grab_file/grab_file.o \ - ccan/talloc/talloc.o ccan/noerr/noerr.o +tools/ccanlint/ccanlint: $(OBJS) ccanlint-clean: $(RM) tools/ccanlint/generated-init-tests diff --git a/tools/ccanlint/compulsory_tests/has_info.c b/tools/ccanlint/compulsory_tests/has_info.c new file mode 100644 index 00000000..e7f2622d --- /dev/null +++ b/tools/ccanlint/compulsory_tests/has_info.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void *check_has_info(struct manifest *m) +{ + if (m->info_file) + return NULL; + return m; +} + +static const char *describe_has_info(struct manifest *m, void *check_result) +{ + return "You have no _info.c file.\n\n" + "The file _info.c 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, void *check_result) +{ + FILE *info; + + if (!ask("Should I create a template _info.c file for you?")) + return; + + info = fopen("_info.c", "w"); + if (!info) + err(1, "Trying to create a template _info.c"); + + if (fprintf(info, template, m->basename) < 0) { + unlink_noerr("_info.c"); + err(1, "Writing template into _info.c"); + } + fclose(info); +} + +struct ccanlint has_info = { + .name = "Has _info.c file", + .check = check_has_info, + .describe = describe_has_info, + .handle = create_info_template, +}; diff --git a/tools/ccanlint/compulsory_tests/has_main_header.c b/tools/ccanlint/compulsory_tests/has_main_header.c new file mode 100644 index 00000000..978bfcb6 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/has_main_header.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void *check_has_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 NULL; + } + return m; +} + +static const char *describe_has_main_header(struct manifest *m, + void *check_result) +{ + return talloc_asprintf(m, + "You have no %s/%s.h header file.\n\n" + "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 has_main_header = { + .name = "No main header file", + .check = check_has_main_header, + .describe = describe_has_main_header, +}; diff --git a/tools/ccanlint/compulsory_tests/has_tests.c b/tools/ccanlint/compulsory_tests/has_tests.c new file mode 100644 index 00000000..a71a1569 --- /dev/null +++ b/tools/ccanlint/compulsory_tests/has_tests.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char test_is_not_dir[] = "test is not a directory"; + +static void *check_has_tests(struct manifest *m) +{ + struct stat st; + + if (lstat("test", &st) != 0) { + if (errno != ENOENT) + err(1, "statting test/"); + return "You have no test directory"; + } + + if (!S_ISDIR(st.st_mode)) + return test_is_not_dir; + + if (list_empty(&m->api_tests) + && list_empty(&m->run_tests) + && list_empty(&m->compile_ok_tests)) { + if (list_empty(&m->compile_fail_tests)) + return "You have no tests in the test directory"; + else + return "You have no positive tests in the test directory"; + } + return NULL; +} + +static const char *describe_has_tests(struct manifest *m, void *check_result) +{ + return talloc_asprintf(m, "%s\n\n" + "CCAN modules have a directory called test/ which contains tests.\n" + "There are four kinds of tests: api, run, compile_ok and compile_fail:\n" + "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 libtap 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.\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 the tests are not linked against the files in the\n" + "above: you should directly #include those C files you want. This\n" + "allows access to static functions and use special effects inside\n" + "test files\n", (char *)check_result); +} + +static void handle_no_tests(struct manifest *m, void *check_result) +{ + FILE *run; + struct ccan_file *i; + + if (check_result == test_is_not_dir) + return; + + if (!ask("Should I create a template test/run.c file for you?")) + return; + + if (mkdir("test", 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"); + + fputs("/* Include the main header first, to test it works */\n", run); + fprintf(run, "#include \"%s/%s.h\"\n", m->basename, m->basename); + fputs("/* Include the C files directly. */\n", run); + list_for_each(&m->c_files, i, list) + fprintf(run, "#include \"%s/%s\"\n", m->basename, i->name); + fputs("#include \"tap/tap.h\"\n", run); + fputs("\n", run); + + fputs("int main(void)\n", run); + fputs("{\n", run); + fputs("\t/* This is how many tests you plan to run */\n", run); + fputs("\tplan_tests(3);\n", run); + fputs("\n", run); + fputs("\t/* Simple thing we expect to succeed */\n", run); + fputs("\tok1(some_test())\n", run); + fputs("\t/* Same, with an explicit description of the test. */\n", run); + fputs("\tok(some_test(), \"%s with no args should return 1\", \"some_test\")\n", run); + fputs("\t/* How to print out messages for debugging. */\n", run); + fputs("\tdiag(\"Address of some_test is %p\", &some_test)\n", run); + fputs("\t/* Conditional tests must be explicitly skipped. */\n", run); + fputs("#if HAVE_SOME_FEATURE\n", run); + fputs("\tok1(test_some_feature())\n", run); + fputs("#else\n", run); + fputs("\tskip(1, \"Don\'t have SOME_FEATURE\")\n", run); + fputs("#endif\n", run); + fputs("\n", run); + fputs("\t/* This exits depending on whether all tests passed */\n", run); + fputs("\treturn exit_status();\n", run); + fputs("}\n", run); + + fclose(run); +} + +struct ccanlint has_tests = { + .name = "No tests", + .check = check_has_tests, + .describe = describe_has_tests, + .handle = handle_no_tests, +}; diff --git a/tools/ccanlint/has_info.c b/tools/ccanlint/has_info.c deleted file mode 100644 index 563eb2f6..00000000 --- a/tools/ccanlint/has_info.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "ccanlint.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void *check_has_info(struct manifest *m) -{ - if (m->info_file) - return NULL; - return m; -} - -static const char *describe_has_info(struct manifest *m, void *check_result) -{ - return "You have no _info.c file.\n\n" - "The file _info.c 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, void *check_result) -{ - FILE *info; - - if (!ask("Should I create a template _info.c file for you?")) - return; - - info = fopen("_info.c", "w"); - if (!info) - err(1, "Trying to create a template _info.c"); - - if (fprintf(info, template, m->basename) < 0) { - unlink_noerr("_info.c"); - err(1, "Writing template into _info.c"); - } - fclose(info); -} - -struct ccanlint has_info = { - .name = "Has _info.c file", - .check = check_has_info, - .describe = describe_has_info, - .handle = create_info_template, -}; diff --git a/tools/ccanlint/has_info_documentation.c b/tools/ccanlint/has_info_documentation.c deleted file mode 100644 index 3142b7b0..00000000 --- a/tools/ccanlint/has_info_documentation.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "ccanlint.h" -#include "../doc_extract.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct info_docs -{ - bool summary; - bool description; - bool example; -}; - -static void *check_has_info_documentation(struct manifest *m) -{ - struct list_head *infodocs = get_ccan_file_docs(m->info_file); - struct doc_section *d; - struct info_docs id = { false, false, false }; - - list_for_each(infodocs, d, list) { - if (!streq(d->function, m->basename)) - continue; - if (streq(d->type, "summary")) - id.summary = true; - if (streq(d->type, "description")) - id.description = true; - if (streq(d->type, "example")) - id.example = true; - } - - if (id.summary && id.description && id.example) - return NULL; - return talloc_memdup(m, &id, sizeof(id)); -} - -/* This is defined below. */ -extern struct ccanlint has_info_documentation; - -static void create_info_template_doc(struct manifest *m, void *check_result) -{ - int fd = open("_info.c.new", O_WRONLY|O_CREAT|O_EXCL, 0666); - FILE *new; - char *oldcontents; - - if (fd < 0 || !(new = fdopen(fd, "w"))) - err(1, "Creating _info.c.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.c.new"); - err(1, "Writing to _info.c.new to insert documentation"); - } - - oldcontents = grab_file(m, "_info.c", NULL); - if (!oldcontents) { - unlink_noerr("_info.c.new"); - err(1, "Reading _info.c"); - } - if (fprintf(new, "%s", oldcontents) < 0) { - unlink_noerr("_info.c.new"); - err(1, "Appending _info.c to _info.c.new"); - } - if (fclose(new) != 0) { - unlink_noerr("_info.c.new"); - err(1, "Closing _info.c.new"); - } - if (rename("_info.c.new", "_info.c") != 0) { - unlink_noerr("_info.c.new"); - err(1, "Renaming _info.c.new to _info.c"); - } -} - -static const char *describe_has_info_documentation(struct manifest *m, - void *check_result) -{ - struct info_docs *id = check_result; - char *reason = talloc_strdup(m, ""); - - if (!id->summary) { - has_info_documentation.handle = create_info_template_doc; - reason = talloc_asprintf_append(reason, - "Your _info.c has no module documentation.\n\n" - "CCAN modules use /**-style comments for documentation: the\n" - "overall documentation belongs in the _info.c metafile.\n"); - } - if (!id->description) - reason = talloc_asprintf_append(reason, - "Your _info.c has no module description.\n\n" - "The lines after the first summary line in the _info.c file\n" - "documentation should describe the purpose and use of the\n" - "overall package\n"); - if (!id->example) - reason = talloc_asprintf_append(reason, - "Your _info.c has no module example.\n\n" - "There should be an Example: section of the _info.c documentation\n" - "which provides a concise toy program which uses your module\n"); - return reason; -} - -static unsigned int has_info_documentation_score(struct manifest *m, - void *check_result) -{ - struct info_docs *id = check_result; - return id->summary + id->description + id->example; -} - -struct ccanlint has_info_documentation = { - .name = "Documentation in _info.c", - .total_score = 3, - .score = has_info_documentation_score, - .check = check_has_info_documentation, - .describe = describe_has_info_documentation, -}; diff --git a/tools/ccanlint/has_main_header.c b/tools/ccanlint/has_main_header.c deleted file mode 100644 index e1b71198..00000000 --- a/tools/ccanlint/has_main_header.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "ccanlint.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void *check_has_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 NULL; - } - return m; -} - -static const char *describe_has_main_header(struct manifest *m, - void *check_result) -{ - return talloc_asprintf(m, - "You have no %s/%s.h header file.\n\n" - "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 has_main_header = { - .name = "No main header file", - .check = check_has_main_header, - .describe = describe_has_main_header, -}; diff --git a/tools/ccanlint/has_tests.c b/tools/ccanlint/has_tests.c deleted file mode 100644 index efea3c06..00000000 --- a/tools/ccanlint/has_tests.c +++ /dev/null @@ -1,130 +0,0 @@ -#include "ccanlint.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static char test_is_not_dir[] = "test is not a directory"; - -static void *check_has_tests(struct manifest *m) -{ - struct stat st; - - if (lstat("test", &st) != 0) { - if (errno != ENOENT) - err(1, "statting test/"); - return "You have no test directory"; - } - - if (!S_ISDIR(st.st_mode)) - return test_is_not_dir; - - if (list_empty(&m->api_tests) - && list_empty(&m->run_tests) - && list_empty(&m->compile_ok_tests)) { - if (list_empty(&m->compile_fail_tests)) - return "You have no tests in the test directory"; - else - return "You have no positive tests in the test directory"; - } - return NULL; -} - -static const char *describe_has_tests(struct manifest *m, void *check_result) -{ - return talloc_asprintf(m, "%s\n\n" - "CCAN modules have a directory called test/ which contains tests.\n" - "There are four kinds of tests: api, run, compile_ok and compile_fail:\n" - "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 libtap 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.\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 the tests are not linked against the files in the\n" - "above: you should directly #include those C files you want. This\n" - "allows access to static functions and use special effects inside\n" - "test files\n", (char *)check_result); -} - -static void handle_no_tests(struct manifest *m, void *check_result) -{ - FILE *run; - struct ccan_file *i; - - if (check_result == test_is_not_dir) - return; - - if (!ask("Should I create a template test/run.c file for you?")) - return; - - if (mkdir("test", 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"); - - fputs("/* Include the main header first, to test it works */\n", run); - fprintf(run, "#include \"%s/%s.h\"\n", m->basename, m->basename); - fputs("/* Include the C files directly. */\n", run); - list_for_each(&m->c_files, i, list) - fprintf(run, "#include \"%s/%s\"\n", m->basename, i->name); - fputs("#include \"tap/tap.h\"\n", run); - fputs("\n", run); - - fputs("int main(void)\n", run); - fputs("{\n", run); - fputs("\t/* This is how many tests you plan to run */\n", run); - fputs("\tplan_tests(3);\n", run); - fputs("\n", run); - fputs("\t/* Simple thing we expect to succeed */\n", run); - fputs("\tok1(some_test())\n", run); - fputs("\t/* Same, with an explicit description of the test. */\n", run); - fputs("\tok(some_test(), \"%s with no args should return 1\", \"some_test\")\n", run); - fputs("\t/* How to print out messages for debugging. */\n", run); - fputs("\tdiag(\"Address of some_test is %p\", &some_test)\n", run); - fputs("\t/* Conditional tests must be explicitly skipped. */\n", run); - fputs("#if HAVE_SOME_FEATURE\n", run); - fputs("\tok1(test_some_feature())\n", run); - fputs("#else\n", run); - fputs("\tskip(1, \"Don\'t have SOME_FEATURE\")\n", run); - fputs("#endif\n", run); - fputs("\n", run); - fputs("\t/* This exits depending on whether all tests passed */\n", run); - fputs("\treturn exit_status();\n", run); - fputs("}\n", run); - - fclose(run); -} - -struct ccanlint has_tests = { - .name = "No tests", - .check = check_has_tests, - .describe = describe_has_tests, - .handle = handle_no_tests, -}; diff --git a/tools/ccanlint/idempotent.c b/tools/ccanlint/idempotent.c deleted file mode 100644 index fa65b05f..00000000 --- a/tools/ccanlint/idempotent.c +++ /dev/null @@ -1,139 +0,0 @@ -#include "ccanlint.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../tools.h" - -static const char explain[] -= "Headers usually start with the C preprocessor lines to prevent multiple\n" - "inclusions. These look like the following:\n" - "#ifndef MY_HEADER_H\n" - "#define MY_HEADER_H\n" - "...\n" - "#endif /* MY_HEADER_H */\n"; - -static char *report_idem(struct ccan_file *f, char *sofar) -{ - 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 sofar; - - 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) - return talloc_asprintf_append(sofar, - "%s:%u:expect first non-comment line to be #ifndef.\n", f->name, i+1); - else if (line_info[i].type == PREPROC_LINE) - break; - } - - /* No code at all? Don't complain. */ - if (i == f->num_lines) - return sofar; - - 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) - return talloc_asprintf_append(sofar, - "%s:%u:expect second line to be #define.\n", f->name, i+1); - else if (line_info[i].type == PREPROC_LINE) - break; - } - - /* No code at all? Weird. */ - if (i == f->num_lines) - return sofar; - - /* We expect a condition on this line. */ - if (!line_info[i].cond) { - return talloc_asprintf_append(sofar, - "%s:%u:expected #ifndef.\n", - f->name, first_preproc_line+1); - } - - 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) { - return talloc_asprintf_append(sofar, - "%s:%u:expected #ifndef.\n", - f->name, first_preproc_line+1); - } - - /* And this to be #define */ - if (!get_token(&line, "#")) - abort(); - if (!get_token(&line, "define")) { - return talloc_asprintf_append(sofar, - "%s:%u:expected '#define %s'.\n", - f->name, i+1, line_info[i].cond->symbol); - } - sym = get_symbol_token(f, &line); - if (!sym || !streq(sym, line_info[i].cond->symbol)) { - return talloc_asprintf_append(sofar, - "%s:%u:expected '#define %s'.\n", - f->name, i+1, line_info[i].cond->symbol); - } - - /* 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) - return talloc_asprintf_append(sofar, - "%s:%u:code outside idempotent region.\n", - f->name, i+1); - } - - return sofar; -} - -static void *check_idempotent(struct manifest *m) -{ - struct ccan_file *f; - char *report = NULL; - - list_for_each(&m->h_files, f, list) - report = report_idem(f, report); - - return report; -} - -static const char *describe_idempotent(struct manifest *m, void *check_result) -{ - return talloc_asprintf(check_result, - "Some headers not idempotent:\n" - "%s\n%s", (char *)check_result, - explain); -} - -struct ccanlint idempotent = { - .name = "Headers are #ifndef/#define idempotent wrapped", - .total_score = 1, - .check = check_idempotent, - .describe = describe_idempotent, -}; diff --git a/tools/ccanlint/tests/has_info_documentation.c b/tools/ccanlint/tests/has_info_documentation.c new file mode 100644 index 00000000..4202880e --- /dev/null +++ b/tools/ccanlint/tests/has_info_documentation.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct info_docs +{ + bool summary; + bool description; + bool example; +}; + +static void *check_has_info_documentation(struct manifest *m) +{ + struct list_head *infodocs = get_ccan_file_docs(m->info_file); + struct doc_section *d; + struct info_docs id = { false, false, false }; + + list_for_each(infodocs, d, list) { + if (!streq(d->function, m->basename)) + continue; + if (streq(d->type, "summary")) + id.summary = true; + if (streq(d->type, "description")) + id.description = true; + if (streq(d->type, "example")) + id.example = true; + } + + if (id.summary && id.description && id.example) + return NULL; + return talloc_memdup(m, &id, sizeof(id)); +} + +/* This is defined below. */ +extern struct ccanlint has_info_documentation; + +static void create_info_template_doc(struct manifest *m, void *check_result) +{ + int fd = open("_info.c.new", O_WRONLY|O_CREAT|O_EXCL, 0666); + FILE *new; + char *oldcontents; + + if (fd < 0 || !(new = fdopen(fd, "w"))) + err(1, "Creating _info.c.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.c.new"); + err(1, "Writing to _info.c.new to insert documentation"); + } + + oldcontents = grab_file(m, "_info.c", NULL); + if (!oldcontents) { + unlink_noerr("_info.c.new"); + err(1, "Reading _info.c"); + } + if (fprintf(new, "%s", oldcontents) < 0) { + unlink_noerr("_info.c.new"); + err(1, "Appending _info.c to _info.c.new"); + } + if (fclose(new) != 0) { + unlink_noerr("_info.c.new"); + err(1, "Closing _info.c.new"); + } + if (rename("_info.c.new", "_info.c") != 0) { + unlink_noerr("_info.c.new"); + err(1, "Renaming _info.c.new to _info.c"); + } +} + +static const char *describe_has_info_documentation(struct manifest *m, + void *check_result) +{ + struct info_docs *id = check_result; + char *reason = talloc_strdup(m, ""); + + if (!id->summary) { + has_info_documentation.handle = create_info_template_doc; + reason = talloc_asprintf_append(reason, + "Your _info.c has no module documentation.\n\n" + "CCAN modules use /**-style comments for documentation: the\n" + "overall documentation belongs in the _info.c metafile.\n"); + } + if (!id->description) + reason = talloc_asprintf_append(reason, + "Your _info.c has no module description.\n\n" + "The lines after the first summary line in the _info.c file\n" + "documentation should describe the purpose and use of the\n" + "overall package\n"); + if (!id->example) + reason = talloc_asprintf_append(reason, + "Your _info.c has no module example.\n\n" + "There should be an Example: section of the _info.c documentation\n" + "which provides a concise toy program which uses your module\n"); + return reason; +} + +static unsigned int has_info_documentation_score(struct manifest *m, + void *check_result) +{ + struct info_docs *id = check_result; + return id->summary + id->description + id->example; +} + +struct ccanlint has_info_documentation = { + .name = "Documentation in _info.c", + .total_score = 3, + .score = has_info_documentation_score, + .check = check_has_info_documentation, + .describe = describe_has_info_documentation, +}; diff --git a/tools/ccanlint/tests/idempotent.c b/tools/ccanlint/tests/idempotent.c new file mode 100644 index 00000000..0aa7d8b3 --- /dev/null +++ b/tools/ccanlint/tests/idempotent.c @@ -0,0 +1,139 @@ +#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 MY_HEADER_H\n" + "#define MY_HEADER_H\n" + "...\n" + "#endif /* MY_HEADER_H */\n"; + +static char *report_idem(struct ccan_file *f, char *sofar) +{ + 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 sofar; + + 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) + return talloc_asprintf_append(sofar, + "%s:%u:expect first non-comment line to be #ifndef.\n", f->name, i+1); + else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Don't complain. */ + if (i == f->num_lines) + return sofar; + + 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) + return talloc_asprintf_append(sofar, + "%s:%u:expect second line to be #define.\n", f->name, i+1); + else if (line_info[i].type == PREPROC_LINE) + break; + } + + /* No code at all? Weird. */ + if (i == f->num_lines) + return sofar; + + /* We expect a condition on this line. */ + if (!line_info[i].cond) { + return talloc_asprintf_append(sofar, + "%s:%u:expected #ifndef.\n", + f->name, first_preproc_line+1); + } + + 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) { + return talloc_asprintf_append(sofar, + "%s:%u:expected #ifndef.\n", + f->name, first_preproc_line+1); + } + + /* And this to be #define */ + if (!get_token(&line, "#")) + abort(); + if (!get_token(&line, "define")) { + return talloc_asprintf_append(sofar, + "%s:%u:expected '#define %s'.\n", + f->name, i+1, line_info[i].cond->symbol); + } + sym = get_symbol_token(f, &line); + if (!sym || !streq(sym, line_info[i].cond->symbol)) { + return talloc_asprintf_append(sofar, + "%s:%u:expected '#define %s'.\n", + f->name, i+1, line_info[i].cond->symbol); + } + + /* 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) + return talloc_asprintf_append(sofar, + "%s:%u:code outside idempotent region.\n", + f->name, i+1); + } + + return sofar; +} + +static void *check_idempotent(struct manifest *m) +{ + struct ccan_file *f; + char *report = NULL; + + list_for_each(&m->h_files, f, list) + report = report_idem(f, report); + + return report; +} + +static const char *describe_idempotent(struct manifest *m, void *check_result) +{ + return talloc_asprintf(check_result, + "Some headers not idempotent:\n" + "%s\n%s", (char *)check_result, + explain); +} + +struct ccanlint idempotent = { + .name = "Headers are #ifndef/#define idempotent wrapped", + .total_score = 1, + .check = check_idempotent, + .describe = describe_idempotent, +}; diff --git a/tools/ccanlint/tests/trailing_whitespace.c b/tools/ccanlint/tests/trailing_whitespace.c new file mode 100644 index 00000000..f522d316 --- /dev/null +++ b/tools/ccanlint/tests/trailing_whitespace.c @@ -0,0 +1,47 @@ +/* Trailing whitespace test. Almost embarrassing, but trivial. */ +#include +#include +#include + +static char *report_on_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) +{ + char *report; + + report = report_on_lines(&m->c_files, report_on_trailing_whitespace, + NULL); + report = report_on_lines(&m->h_files, report_on_trailing_whitespace, + report); + + return report; +} + +static const char *describe_trailing_whitespace(struct manifest *m, + void *check_result) +{ + return talloc_asprintf(check_result, + "Some source files have trailing whitespace:\n" + "%s", (char *)check_result); +} + +struct ccanlint trailing_whitespace = { + .name = "No lines with unnecessary trailing whitespace", + .total_score = 1, + .check = check_trailing_whitespace, + .describe = describe_trailing_whitespace, +}; diff --git a/tools/ccanlint/trailing_whitespace.c b/tools/ccanlint/trailing_whitespace.c deleted file mode 100644 index 96ab0d9d..00000000 --- a/tools/ccanlint/trailing_whitespace.c +++ /dev/null @@ -1,47 +0,0 @@ -/* Trailing whitespace test. Almost embarrassing, but trivial. */ -#include "ccanlint.h" -#include -#include - -static char *report_on_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) -{ - char *report; - - report = report_on_lines(&m->c_files, report_on_trailing_whitespace, - NULL); - report = report_on_lines(&m->h_files, report_on_trailing_whitespace, - report); - - return report; -} - -static const char *describe_trailing_whitespace(struct manifest *m, - void *check_result) -{ - return talloc_asprintf(check_result, - "Some source files have trailing whitespace:\n" - "%s", (char *)check_result); -} - -struct ccanlint trailing_whitespace = { - .name = "No lines with unnecessary trailing whitespace", - .total_score = 1, - .check = check_trailing_whitespace, - .describe = describe_trailing_whitespace, -};