add indentation to protocol.txt for readability
[beanstalkd.git] / ct / ct.c
blobe7b4e15ac453e99905f12f8252ef976b4e96e553
1 /* CT - simple-minded unit testing for C */
3 #include <signal.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <stdarg.h>
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 #include <fcntl.h>
13 #include <dirent.h>
14 #include <errno.h>
15 #include <sys/time.h>
16 #include <stdint.h>
17 #include <inttypes.h>
18 #include "internal.h"
19 #include "ct.h"
22 static char *curdir;
23 static int rjobfd = -1, wjobfd = -1;
24 int fail = 0; /* bool */
25 static int64 bstart, bdur;
26 static int btiming; /* bool */
27 static int64 bbytes;
28 enum { Second = 1000 * 1000 * 1000 };
29 enum { BenchTime = Second };
30 enum { MaxN = 1000 * 1000 * 1000 };
34 #ifdef __MACH__
35 # include <mach/mach_time.h>
37 static int64
38 nstime()
40 return (int64)mach_absolute_time();
43 #else
44 # include <time.h>
46 static int64
47 nstime()
49 struct timespec t;
50 clock_gettime(CLOCK_MONOTONIC, &t);
51 return (int64)(t.tv_sec)*Second + t.tv_nsec;
54 #endif
56 void
57 ctlogpn(const char *p, int n, const char *fmt, ...)
59 va_list arg;
61 printf("%s:%d: ", p, n);
62 va_start(arg, fmt);
63 vprintf(fmt, arg);
64 va_end(arg);
65 putchar('\n');
69 void
70 ctfail(void)
72 fail = 1;
76 void
77 ctfailnow(void)
79 fflush(NULL);
80 abort();
84 char *
85 ctdir(void)
87 return curdir;
91 void
92 ctresettimer(void)
94 bdur = 0;
95 bstart = nstime();
99 void
100 ctstarttimer(void)
102 if (!btiming) {
103 bstart = nstime();
104 btiming = 1;
109 void
110 ctstoptimer(void)
112 if (btiming) {
113 bdur += nstime() - bstart;
114 btiming = 0;
119 void
120 ctsetbytes(int n)
122 bbytes = (int64)n;
126 static void
127 die(int code, int err, const char *msg)
129 putc('\n', stderr);
131 if (msg && *msg) {
132 fputs(msg, stderr);
133 fputs(": ", stderr);
136 fputs(strerror(err), stderr);
137 putc('\n', stderr);
138 exit(code);
142 static int
143 tmpfd(void)
145 FILE *f = tmpfile();
146 if (!f) {
147 die(1, errno, "tmpfile");
149 return fileno(f);
153 static int
154 failed(int s)
156 return WIFSIGNALED(s) && (WTERMSIG(s) == SIGABRT);
160 static void
161 waittest(Test *ts)
163 Test *t;
164 int pid, stat;
166 pid = wait3(&stat, 0, 0);
167 if (pid == -1) {
168 die(3, errno, "wait");
170 killpg(pid, 9);
172 for (t=ts; t->f; t++) {
173 if (t->pid == pid) {
174 t->status = stat;
175 if (!t->status) {
176 putchar('.');
177 } else if (failed(t->status)) {
178 putchar('F');
179 } else {
180 putchar('E');
182 fflush(stdout);
188 static void
189 start(Test *t)
191 t->fd = tmpfd();
192 strcpy(t->dir, TmpDirPat);
193 if (mkdtemp(t->dir) == NULL) {
194 die(1, errno, "mkdtemp");
196 fflush(NULL);
197 t->pid = fork();
198 if (t->pid < 0) {
199 die(1, errno, "fork");
200 } else if (!t->pid) {
201 setpgid(0, 0);
202 if (dup2(t->fd, 1) == -1) {
203 die(3, errno, "dup2");
205 if (close(t->fd) == -1) {
206 die(3, errno, "fclose");
208 if (dup2(1, 2) == -1) {
209 die(3, errno, "dup2");
211 curdir = t->dir;
212 t->f();
213 if (fail) {
214 ctfailnow();
216 exit(0);
218 setpgid(t->pid, t->pid);
222 static void
223 runalltest(Test *ts, int limit)
225 int nrun = 0;
226 Test *t;
227 for (t=ts; t->f; t++) {
228 if (nrun >= limit) {
229 waittest(ts);
230 nrun--;
232 start(t);
233 nrun++;
235 for (; nrun; nrun--) {
236 waittest(ts);
241 static void
242 copyfd(FILE *out, int in)
244 ssize_t n;
245 char buf[1024]; /* arbitrary size */
247 while ((n = read(in, buf, sizeof(buf))) != 0) {
248 if (fwrite(buf, 1, n, out) != (size_t)n) {
249 die(3, errno, "fwrite");
256 Removes path and all of its children.
257 Writes errors to stderr and keeps going.
258 If path doesn't exist, rmtree returns silently.
260 static void
261 rmtree(char *path)
263 int r = unlink(path);
264 if (r == 0 || errno == ENOENT) {
265 return; /* success */
267 int unlinkerr = errno;
269 DIR *d = opendir(path);
270 if (!d) {
271 if (errno == ENOTDIR) {
272 fprintf(stderr, "ct: unlink: %s\n", strerror(unlinkerr));
273 } else {
274 perror("ct: opendir");
276 fprintf(stderr, "ct: path %s\n", path);
277 return;
279 struct dirent *ent;
280 while ((ent = readdir(d))) {
281 if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
282 continue;
284 int n = strlen(path) + 1 + strlen(ent->d_name);
285 char s[n+1];
286 sprintf(s, "%s/%s", path, ent->d_name);
287 rmtree(s);
289 closedir(d);
290 r = rmdir(path);
291 if (r == -1) {
292 perror("ct: rmdir");
293 fprintf(stderr, "ct: path %s\n", path);
298 static void
299 runbenchn(Benchmark *b, int n)
301 int outfd = tmpfd();
302 int durfd = tmpfd();
303 strcpy(b->dir, TmpDirPat);
304 if (mkdtemp(b->dir) == NULL) {
305 die(1, errno, "mkdtemp");
307 fflush(NULL);
308 int pid = fork();
309 if (pid < 0) {
310 die(1, errno, "fork");
311 } else if (!pid) {
312 setpgid(0, 0);
313 if (dup2(outfd, 1) == -1) {
314 die(3, errno, "dup2");
316 if (close(outfd) == -1) {
317 die(3, errno, "fclose");
319 if (dup2(1, 2) == -1) {
320 die(3, errno, "dup2");
322 curdir = b->dir;
323 ctstarttimer();
324 b->f(n);
325 ctstoptimer();
326 write(durfd, &bdur, sizeof bdur);
327 write(durfd, &bbytes, sizeof bbytes);
328 exit(0);
330 setpgid(pid, pid);
332 pid = waitpid(pid, &b->status, 0);
333 if (pid == -1) {
334 die(3, errno, "wait");
336 killpg(pid, 9);
337 rmtree(b->dir);
338 if (b->status != 0) {
339 putchar('\n');
340 lseek(outfd, 0, SEEK_SET);
341 copyfd(stdout, outfd);
342 return;
345 lseek(durfd, 0, SEEK_SET);
346 int r = read(durfd, &b->dur, sizeof b->dur);
347 if (r != sizeof b->dur) {
348 perror("read");
349 b->status = 1;
351 r = read(durfd, &b->bytes, sizeof b->bytes);
352 if (r != sizeof b->bytes) {
353 perror("read");
354 b->status = 1;
359 /* rounddown10 rounds a number down to the nearest power of 10. */
360 static int
361 rounddown10(int n)
363 int tens = 0;
364 /* tens = floor(log_10(n)) */
365 while (n >= 10) {
366 n = n / 10;
367 tens++;
369 /* result = 10**tens */
370 int i, result = 1;
371 for (i = 0; i < tens; i++) {
372 result *= 10;
374 return result;
378 /* roundup rounds n up to a number of the form [1eX, 2eX, 5eX]. */
379 static int
380 roundup(int n)
382 int base = rounddown10(n);
383 if (n == base)
384 return n;
385 if (n <= 2*base)
386 return 2 * base;
387 if (n <= 5*base)
388 return 5 * base;
389 return 10 * base;
393 static int
394 min(int a, int b)
396 if (a < b) {
397 return a;
399 return b;
403 static int
404 max(int a, int b)
406 if (a > b) {
407 return a;
409 return b;
413 static void
414 runbench(Benchmark *b)
416 printf("%s\t", b->name);
417 fflush(stdout);
418 int n = 1;
419 runbenchn(b, n);
420 while (b->status == 0 && b->dur < BenchTime && n < MaxN) {
421 int last = n;
422 /* Predict iterations/sec. */
423 int nsop = b->dur / n;
424 if (nsop == 0) {
425 n = MaxN;
426 } else {
427 n = BenchTime / nsop;
429 /* Run more iterations than we think we'll need for a second (1.5x).
430 Don't grow too fast in case we had timing errors previously.
431 Be sure to run at least one more than last time. */
432 n = max(min(n+n/2, 100*last), last+1);
433 /* Round up to something easy to read. */
434 n = roundup(n);
435 runbenchn(b, n);
437 if (b->status == 0) {
438 printf("%8d\t%10" PRId64 " ns/op", n, b->dur/n);
439 if (b->bytes > 0) {
440 double mbs = 0;
441 if (b->dur > 0) {
442 int64 sec = b->dur / 1000L / 1000L / 1000L;
443 int64 nsec = b->dur % 1000000000L;
444 double dur = (double)sec + (double)nsec*.0000000001;
445 mbs = ((double)b->bytes * (double)n / 1000000) / dur;
447 printf("\t%7.2f MB/s", mbs);
449 putchar('\n');
450 } else {
451 if (failed(b->status)) {
452 printf("failure");
453 } else {
454 printf("error");
455 if (WIFEXITED(b->status)) {
456 printf(" (exit status %d)", WEXITSTATUS(b->status));
458 if (WIFSIGNALED(b->status)) {
459 printf(" (signal %d)", WTERMSIG(b->status));
462 putchar('\n');
467 static void
468 runallbench(Benchmark *b)
470 for (; b->f; b++) {
471 runbench(b);
476 static int
477 report(Test *t)
479 int nfail = 0, nerr = 0;
481 putchar('\n');
482 for (; t->f; t++) {
483 rmtree(t->dir);
484 if (!t->status) {
485 continue;
488 printf("\n%s: ", t->name);
489 if (failed(t->status)) {
490 nfail++;
491 printf("failure");
492 } else {
493 nerr++;
494 printf("error");
495 if (WIFEXITED(t->status)) {
496 printf(" (exit status %d)", WEXITSTATUS(t->status));
498 if (WIFSIGNALED(t->status)) {
499 printf(" (signal %d)", WTERMSIG(t->status));
503 putchar('\n');
504 lseek(t->fd, 0, SEEK_SET);
505 copyfd(stdout, t->fd);
508 if (nfail || nerr) {
509 printf("\n%d failures; %d errors.\n", nfail, nerr);
510 } else {
511 printf("\nPASS\n");
513 return nfail || nerr;
517 static int
518 readtokens()
520 int n = 1;
521 char c, *s;
522 if ((s = strstr(getenv("MAKEFLAGS"), " --jobserver-fds="))) {
523 rjobfd = (int)strtol(s+17, &s, 10); /* skip " --jobserver-fds=" */
524 wjobfd = (int)strtol(s+1, NULL, 10); /* skip comma */
526 if (rjobfd >= 0) {
527 fcntl(rjobfd, F_SETFL, fcntl(rjobfd, F_GETFL)|O_NONBLOCK);
528 while (read(rjobfd, &c, 1) > 0) {
529 n++;
532 return n;
536 static void
537 writetokens(int n)
539 char c = '+';
540 if (wjobfd >= 0) {
541 fcntl(wjobfd, F_SETFL, fcntl(wjobfd, F_GETFL)|O_NONBLOCK);
542 for (; n>1; n--) {
543 write(wjobfd, &c, 1); /* ignore error; nothing we can do anyway */
550 main(int argc, char **argv)
552 int n = readtokens();
553 runalltest(ctmaintest, n);
554 writetokens(n);
555 int code = report(ctmaintest);
556 if (code != 0) {
557 return code;
559 if (argc == 2 && strcmp(argv[1], "-b") == 0) {
560 runallbench(ctmainbench);
562 return 0;