From 4bcba29f166e9a93c109a43c68c20d630f2c2465 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Thu, 5 Jul 2018 07:54:39 +0000 Subject: [PATCH] add C version of prove removes dependency on Perl to run tests --- Makefile | 7 +- t/ctap/prove.c | 359 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 t/ctap/prove.c diff --git a/Makefile b/Makefile index 705709b..0f71428 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,11 @@ lzma.o: lzma.c stest: $(ZPKGBIN) PATH=$(curdir)/t:$(curdir):$(PATH) t/$(T).t -test: $(ZPKGBIN) - PATH=$(curdir)/t:$(curdir):$(PATH) prove -e '' t/*.t +t/ctap/prove: t/ctap/prove.o + $(CC) $(CFLAGS) -o $@ $+ + +test: $(ZPKGBIN) t/ctap/prove + PATH=$(curdir)/t:$(curdir):$(PATH) t/ctap/prove t/*.t programs: elftype zpm-soname zpm-soneed zpm-addfile zpm-extract zpm-init \ zpm-vercmp zpm-findpkg zpm-merge diff --git a/t/ctap/prove.c b/t/ctap/prove.c new file mode 100644 index 0000000..8ff8a64 --- /dev/null +++ b/t/ctap/prove.c @@ -0,0 +1,359 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GREATER(x,y) ( ((x) > (y)) ? (x) : (y) ) + +struct result { + int test; /* i.e. the number of the test */ + char *name; /* name of the test, malloc */ + int pass; /* if the test passed */ + struct result *next; /* so we can make a linked list */ +}; + +struct testrun { + char *name; + int version; + int plan; + int ran; + int pass; + int fail; + int skip; + int todo, todo_pass, todo_fail; + int bailout; + struct result *results; + struct result *last; /* last result, so we append rather than push */ + int interactive; +}; + +int lookfor(char *needle, char *haystack, size_t n) { + return strncasecmp(needle, haystack, n) == 0; +} + +int rmatch(const regex_t *preg, const char *string, regmatch_t pmatch[]) { + return regexec(preg, string, 4, pmatch, 0) == 0; +} + +/* write n backspaces to stdout */ +void backup(int n) { + while (n--) putchar('\b'); +} + +void spaces(int n) { + while (n--) putchar(' '); +} + +int runone(struct testrun *run) { + int pipefd[2]; + pid_t cpid; + FILE *tap; + char *line; + ssize_t nread; + size_t len = 0; + int written = 0; + + if (pipe(pipefd) == -1) { + perror("pipe"); + exit(EXIT_FAILURE); + } + + cpid = fork(); + if (cpid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if (cpid == 0) { + /* child */ + close(pipefd[0]); /* close read end */ + close(1); /* close stdout */ + dup(pipefd[1]); /* set stdout to the write end of the pipe */ + execlp(run->name, run->name, NULL); + /* only gets here if exec failed */ + printf("Bail Out! exec %s failed: %s\n", run->name, strerror(errno)); + exit(EXIT_FAILURE); + } + + close(pipefd[1]); + /* parent continue on */ + tap = fdopen(pipefd[0], "r"); + + regex_t plan, diagnostic, bail, test, directive, version; + regmatch_t match[4]; + + regcomp(&plan, "^[[:digit:]]+\\.\\.([[:digit:]]+)", REG_EXTENDED); + regcomp(&diagnostic, "#[[:space:]]*(.+)", REG_EXTENDED); + regcomp(&bail, "^Bail Out![[:space:]]*(.+)", REG_EXTENDED); + regcomp(&version, "^TAP Version ([[:digit:]]+)", REG_EXTENDED); + + regcomp(&test, "^(not )?ok([[:space:]]*([[:digit:]]+))?", REG_EXTENDED); + regcomp(&directive, "#[[:space:]]*([^[:space:]]+)([[:space:]]+(.+))?", + REG_EXTENDED|REG_ICASE); + + + /* description: ok \d+([^#]+) */ + /* directive: #.+ */ + /* todo: # todo */ + /* skip: # skip */ + + /* bail out: ^Bail out! */ + /* diagnostic: ^#.+ */ + /* start of yaml: ^\s+--- */ + /* end of yaml: ^\s+... */ + /* between yaml lines must be indented */ + /* anything else is an error */ + + while ((nread = getline(&line, &len, tap)) != -1) { + /* version: ^TAP version \d+ */ + + /* plan: ^\d+\.\.\d+ */ + if (regexec(&plan, line, 2, match, 0) != REG_NOMATCH) { + run->plan = atoi(line+match[1].rm_so); + /* TODO plans can have a diag skip reason */ + } else + + /* ok: ^ok \d+ */ + /* not ok: ^not ok \d+ */ + if (rmatch(&test, line, match)) { + int test; + int todo = 0; + int skip = 0; + int pass = 0; + + run->ran++; + + if (run->interactive) { + backup(written); + written = printf("%d/%d", run->ran, run->plan); + fflush(stdout); + } + + + /* if the "not" match fails, the test passed */ + pass = match[1].rm_so == -1; + + /* test number */ + test = atoi(line+match[3].rm_so); + + /* directive */ + if (rmatch(&directive, line, match)) { + todo = lookfor("todo", line+match[1].rm_so, 4); + skip = lookfor("skip", line+match[1].rm_so, 4); + } + + /* check for diagnostics */ + if (test != run->ran) { + fprintf(stderr, "expected test %d, got %d\n", run->ran+1, test); + } + + /* pass, todo, or skip are all pass */ + if (pass || todo || skip) { + run->pass++; + } else { + run->fail++; + } + + if (todo && pass) { + run->todo_pass++; + } + + if (skip) { + run->skip++; + } + + if (!pass && !todo) { + struct result *r; + r = malloc(sizeof *r); + + r->test = run->ran; + r->pass = 0; + if (run->last) { + run->last->next = r; + } + if (!run->results) { + run->results = r; + } + run->last = r; + /* push test number onto fail list */ + } + + } + else if (rmatch(&diagnostic, line, match)) { + + } + else if (rmatch(&version, line, match)) { + run->version = atoi(line + match[1].rm_so); + } + else if (rmatch(&bail, line, match)) { + run->bailout = 1; + } + else { + printf("Unknown line: %s", line); + } + + /* not ok: ^not ok \d+ */ + /* description: ok \d+([^#]+) */ + /* directive: #.+ */ + /* todo: # todo */ + /* skip: # skip */ + + /* bail out: ^Bail out! */ + /* diagnostic: ^#.+ */ + /* start of yaml: ^\s+--- */ + /* end of yaml: ^\s+... */ + /* between yaml lines must be indented */ + /* anything else is an error */ + + } + + regfree(&plan); + regfree(&test); + + free(line); + fclose(tap); + wait(NULL); + + if (run->ran < run->plan) { + run->fail += run->plan - run->ran; + } + + if (run->interactive) { + backup(written); + spaces(written); + backup(written); + fflush(stdout); + } + + return 0; +} + +void json(struct testrun *total, struct testrun *runs, int nruns) { + if (total && runs && nruns) { + return; + } + return; +} + +int main(int ac, char *av[]) { + int maxnamelen = 0; + ssize_t len; + int i, j; + struct testrun total = { 0 }; + struct testrun run = { 0 }; + struct testrun clear = { 0 }; + struct testrun *runs; + int opt; + + /* + * -j json output + * -p progress output + * -i interactive, regardless of tty + * -s summary text + * -d detail text + */ + + int interactive = 0, json = 0, summary = 1, progress = 1, detail = 0; + + interactive = isatty(1); + + /* TODO -jj turn off everything else? */ + /* TODO -cC color escapes */ + while ((opt = getopt(ac, av, "jJpPiIsSdD")) != -1) { + switch (opt) { + case 'j': json = 1; break; + case 'J': json = 0; break; + case 'p': progress = 1; break; + case 'P': progress = 0; break; + case 'i': interactive = 1; break; + case 'I': interactive = 0; break; + case 's': summary = 1; break; + case 'S': summary = 0; break; + case 'd': detail = 1; break; + case 'D': detail = 0; break; + default: /* '?' */ + fprintf(stderr, "Usage: %s [-jpisd] file...\n", + av[0]); + exit(EXIT_FAILURE); + } + } + + runs = malloc(ac * sizeof *runs); + + for (i=optind; i 0 ) printf("not "); + printf("ok\n"); + } + } + + runs[i-optind] = run; + } + + if (summary) { + printf("ran: %d/%d, pass: %d, fail: %d, skip: %d", + total.ran, total.plan, total.pass, total.fail, + total.skip + ); + if (total.plan > 0) { + printf(", %.2f%% ok", + 100.0*(double)total.pass/(double)total.plan + ); + } + printf("\n"); + + } + + if (detail) { + printf("detail here\n"); + } + + if (json) { + printf("{ \"plan\": %d, \"ran\": %d, \"pass\": %d, \"fail\": %d }\n", + total.plan, total.ran, total.pass, total.fail + ); + } + +#if 0 + Files=8, Tests=508, 0 wallclock secs ( 0.08 usr + 0.01 sys = 0.09 CPU) +Result: PASS +#endif + + return total.fail ? EXIT_FAILURE : EXIT_SUCCESS; +} -- 2.40.0