├── .gitignore ├── License ├── Makefile ├── Readme.md ├── ct ├── .gitignore ├── License ├── ct.c ├── ct.h ├── gen └── internal.h ├── hello.c ├── msg-test.c ├── msg.c └── msg.h /.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | hello 3 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright © 2010–2013 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -std=c99 -D_DEFAULT_SOURCE -Werror -Wall -Wextra -Wpedantic -Wwrite-strings -Wformat=2 2 | 3 | libs = msg.c 4 | objs = $(libs:.c=.o) 5 | 6 | tests = $(wildcard *-test.c) 7 | tobjs = $(tests:.c=.o) 8 | 9 | all: hello 10 | 11 | hello: hello.o $(objs) 12 | 13 | .PHONY: check 14 | check: ct/_ctcheck 15 | +ct/_ctcheck 16 | 17 | .PHONY: bench 18 | bench: ct/_ctcheck 19 | +ct/_ctcheck -b 20 | 21 | ct/ct.o: ct/ct.h 22 | 23 | $(tobjs): ct/ct.h 24 | 25 | ct/_ctcheck: ct/_ctcheck.o ct/ct.o $(objs) $(tobjs) 26 | 27 | ct/_ctcheck.c: $(tobjs) ct/gen 28 | ct/gen $(tobjs) > $@.part 29 | mv $@.part $@ 30 | 31 | .PHONY: clean 32 | clean: 33 | rm -f ct/_* *.o ct/*.o hello 34 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # CT 2 | 3 | **(Relatively) Easy Unit Testing for C** 4 | 5 | ## How to use 6 | 7 | 1. Copy subdirectory `ct` into your project. 8 | 2. Add some rules to your makefile. See [Makefile][] for an example. 9 | 3. Write some tests. See [msg-test.c][] for an example. 10 | Test function names begin with "cttest". 11 | 4. Run `make check` 12 | 13 | ## Behavior 14 | 15 | - The test runner runs each test in a separate process, so 16 | global state from one test will not affect another. 17 | - Each test is run in a new process group; all processes 18 | in the group will be killed after the test finishes. This 19 | means your test can fork without having to worry about 20 | cleaning up its descendants. 21 | - CT participates in GNU make's jobserver protocol. If you 22 | put a `+` in front of the `_ctcheck` command (as in the sample 23 | makefile) and run make with its `-jN` flag, for example 24 | `make -j16 check`, CT will run tests concurrently (and 25 | hopefully in parallel). 26 | - A scratch directory can be obtained by calling ctdir() 27 | inside the test. This directory will be removed by the test 28 | runner after the test finishes. 29 | - If you want to perform *test coverage analysis* using `gcov` 30 | please be aware that `gcov` is not necessarily multi-process-safe. 31 | If you get strange coverage data, try `-j1` and avoid forking in 32 | your test cases. 33 | 34 | ## Terminal Output 35 | 36 | Running `make -j4 check` in the example supplied looks like this: 37 | 38 | ``` 39 | $ make -j4 check 40 | cc -Werror -Wall -Wformat=2 -c -o msg-test.o msg-test.c 41 | cc -Werror -Wall -Wformat=2 -c -o ct/ct.o ct/ct.c 42 | cc -Werror -Wall -Wformat=2 -c -o msg.o msg.c 43 | ct/gen msg-test.o > ct/_ctcheck.c.part 44 | mv ct/_ctcheck.c.part ct/_ctcheck.c 45 | cc -Werror -Wall -Wformat=2 -c -o ct/_ctcheck.o ct/_ctcheck.c 46 | cc ct/_ctcheck.o ct/ct.o msg.o msg-test.o -o ct/_ctcheck 47 | ct/_ctcheck 48 | ....... 49 | 50 | PASS 51 | ``` 52 | 53 | Remove some of the return statements in msg-test.c to see 54 | what various errors and failures look like. 55 | 56 | ## Releases 57 | 58 | There will be no releases of this tool. Just clone the latest source from git 59 | and copy it into your project. If you want to update, copy the newer source 60 | into your project. 61 | 62 | ## History 63 | 64 | Inspired by [CUT][] 2.1 by Sam Falvo and Billy Tanksley. 65 | Also with ideas from the [Go testing package][gotesting] and [gotest][]. 66 | Also stole some benchmark hints from [testingbee][] by Dustin Sallings. 67 | 68 | [CUT]: https://web.archive.org/web/20130318233307/http://falvotech.com/content/cut/ 69 | [Makefile]: https://github.com/kr/ct/blob/main/Makefile 70 | [msg-test.c]: https://github.com/kr/ct/blob/main/msg-test.c 71 | [gotesting]: http://golang.org/pkg/testing/ 72 | [gotest]: https://golang.org/cmd/go/#hdr-Test_packages 73 | [testingbee]: https://github.com/dustin/testingbee 74 | -------------------------------------------------------------------------------- /ct/.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | /_* 3 | -------------------------------------------------------------------------------- /ct/License: -------------------------------------------------------------------------------- 1 | Copyright © 2010–2013 Keith Rarick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /ct/ct.c: -------------------------------------------------------------------------------- 1 | /* CT - simple-minded unit testing for C */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "internal.h" 20 | #include "ct.h" 21 | 22 | 23 | static char *curdir; 24 | static int rjobfd = -1, wjobfd = -1; 25 | int fail = 0; /* bool */ 26 | static int64 bstart, bdur; 27 | static int btiming; /* bool */ 28 | static int64 bbytes; 29 | static char *argv0; 30 | static const char *testpat = "*"; /* match everything by default */ 31 | static const char *benchpat = ""; /* match nothing by default */ 32 | static uint64_t count = 1; 33 | enum { Second = 1000 * 1000 * 1000 }; 34 | enum { BenchTime = Second }; 35 | enum { MaxN = 1000 * 1000 * 1000 }; 36 | 37 | 38 | 39 | #ifdef __MACH__ 40 | # include 41 | 42 | static int64 43 | nstime() 44 | { 45 | return (int64)mach_absolute_time(); 46 | } 47 | 48 | #else 49 | # include 50 | 51 | static int64 52 | nstime() 53 | { 54 | struct timespec t; 55 | clock_gettime(CLOCK_MONOTONIC, &t); 56 | return (int64)(t.tv_sec)*Second + t.tv_nsec; 57 | } 58 | 59 | #endif 60 | 61 | void 62 | ctlogpn(const char *p, int n, const char *fmt, ...) 63 | { 64 | va_list arg; 65 | 66 | printf("%s:%d: ", p, n); 67 | va_start(arg, fmt); 68 | vprintf(fmt, arg); 69 | va_end(arg); 70 | putchar('\n'); 71 | } 72 | 73 | 74 | void 75 | ctfail(void) 76 | { 77 | fail = 1; 78 | } 79 | 80 | 81 | void 82 | ctfailnow(void) 83 | { 84 | fflush(NULL); 85 | abort(); 86 | } 87 | 88 | 89 | char * 90 | ctdir(void) 91 | { 92 | return curdir; 93 | } 94 | 95 | 96 | void 97 | ctresettimer(void) 98 | { 99 | bdur = 0; 100 | bstart = nstime(); 101 | } 102 | 103 | 104 | void 105 | ctstarttimer(void) 106 | { 107 | if (!btiming) { 108 | bstart = nstime(); 109 | btiming = 1; 110 | } 111 | } 112 | 113 | 114 | void 115 | ctstoptimer(void) 116 | { 117 | if (btiming) { 118 | bdur += nstime() - bstart; 119 | btiming = 0; 120 | } 121 | } 122 | 123 | 124 | void 125 | ctsetbytes(int n) 126 | { 127 | bbytes = (int64)n; 128 | } 129 | 130 | 131 | static void 132 | die(int code, int err, const char *msg) 133 | { 134 | putc('\n', stderr); 135 | 136 | if (msg && *msg) { 137 | fputs(msg, stderr); 138 | fputs(": ", stderr); 139 | } 140 | 141 | fputs(strerror(err), stderr); 142 | putc('\n', stderr); 143 | exit(code); 144 | } 145 | 146 | 147 | static int 148 | tmpfd(void) 149 | { 150 | FILE *f = tmpfile(); 151 | if (!f) { 152 | die(1, errno, "tmpfile"); 153 | } 154 | return fileno(f); 155 | } 156 | 157 | 158 | static int 159 | failed(int s) 160 | { 161 | return WIFSIGNALED(s) && (WTERMSIG(s) == SIGABRT); 162 | } 163 | 164 | 165 | static void 166 | waittest(Test *ts) 167 | { 168 | Test *t; 169 | int pid, stat; 170 | 171 | pid = wait3(&stat, 0, 0); 172 | if (pid == -1) { 173 | die(3, errno, "wait"); 174 | } 175 | killpg(pid, SIGKILL); 176 | 177 | for (t=ts; t->f; t++) { 178 | if (t->pid == pid) { 179 | t->status = stat; 180 | } 181 | } 182 | } 183 | 184 | 185 | static void 186 | start(Test *t) 187 | { 188 | t->fd = tmpfd(); 189 | strcpy(t->dir, TmpDirPat); 190 | if (mkdtemp(t->dir) == NULL) { 191 | die(1, errno, "mkdtemp"); 192 | } 193 | fflush(NULL); 194 | t->pid = fork(); 195 | if (t->pid < 0) { 196 | die(1, errno, "fork"); 197 | } else if (!t->pid) { 198 | setpgid(0, 0); 199 | if (dup2(t->fd, 1) == -1) { 200 | die(3, errno, "dup2"); 201 | } 202 | if (close(t->fd) == -1) { 203 | die(3, errno, "fclose"); 204 | } 205 | if (dup2(1, 2) == -1) { 206 | die(3, errno, "dup2"); 207 | } 208 | curdir = t->dir; 209 | t->f(); 210 | if (fail) { 211 | ctfailnow(); 212 | } 213 | exit(0); 214 | } 215 | setpgid(t->pid, t->pid); 216 | } 217 | 218 | 219 | static void 220 | runalltest(Test *ts, int limit) 221 | { 222 | int nrun = 0; 223 | Test *t; 224 | for (t=ts; t->f; t++) { 225 | if (fnmatch(testpat, t->name, 0) != 0) { 226 | continue; 227 | } 228 | if (nrun >= limit) { 229 | waittest(ts); 230 | nrun--; 231 | } 232 | start(t); 233 | nrun++; 234 | } 235 | for (; nrun; nrun--) { 236 | waittest(ts); 237 | } 238 | } 239 | 240 | 241 | static void 242 | copyfd(FILE *out, int in) 243 | { 244 | ssize_t n; 245 | char buf[1024]; /* arbitrary size */ 246 | 247 | while ((n = read(in, buf, sizeof(buf))) != 0) { 248 | if (fwrite(buf, 1, n, out) != (size_t)n) { 249 | die(3, errno, "fwrite"); 250 | } 251 | } 252 | } 253 | 254 | 255 | /* 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. 259 | */ 260 | static void 261 | rmtree(char *path) 262 | { 263 | int r = unlink(path); 264 | if (r == 0 || errno == ENOENT) { 265 | return; /* success */ 266 | } 267 | int unlinkerr = errno; 268 | 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"); 275 | } 276 | fprintf(stderr, "ct: path %s\n", path); 277 | return; 278 | } 279 | struct dirent *ent; 280 | while ((ent = readdir(d))) { 281 | if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { 282 | continue; 283 | } 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); 288 | } 289 | closedir(d); 290 | r = rmdir(path); 291 | if (r == -1) { 292 | perror("ct: rmdir"); 293 | fprintf(stderr, "ct: path %s\n", path); 294 | } 295 | } 296 | 297 | 298 | static void 299 | runbenchn(Benchmark *b, int n) 300 | { 301 | int outfd = tmpfd(); 302 | int durfd = tmpfd(); 303 | strcpy(b->dir, TmpDirPat); 304 | if (mkdtemp(b->dir) == NULL) { 305 | die(1, errno, "mkdtemp"); 306 | } 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"); 315 | } 316 | if (close(outfd) == -1) { 317 | die(3, errno, "fclose"); 318 | } 319 | if (dup2(1, 2) == -1) { 320 | die(3, errno, "dup2"); 321 | } 322 | curdir = b->dir; 323 | ctstarttimer(); 324 | b->f(n); 325 | ctstoptimer(); 326 | if (write(durfd, &bdur, sizeof bdur) != sizeof bdur) { 327 | die(3, errno, "write"); 328 | } 329 | if (write(durfd, &bbytes, sizeof bbytes) != sizeof bbytes) { 330 | die(3, errno, "write"); 331 | } 332 | exit(0); 333 | } 334 | setpgid(pid, pid); 335 | 336 | pid = waitpid(pid, &b->status, 0); 337 | if (pid == -1) { 338 | die(3, errno, "wait"); 339 | } 340 | killpg(pid, SIGKILL); 341 | rmtree(b->dir); 342 | if (b->status != 0) { 343 | putchar('\n'); 344 | lseek(outfd, 0, SEEK_SET); 345 | copyfd(stdout, outfd); 346 | return; 347 | } 348 | 349 | lseek(durfd, 0, SEEK_SET); 350 | int r = read(durfd, &b->dur, sizeof b->dur); 351 | if (r != sizeof b->dur) { 352 | perror("read"); 353 | b->status = 1; 354 | } 355 | r = read(durfd, &b->bytes, sizeof b->bytes); 356 | if (r != sizeof b->bytes) { 357 | perror("read"); 358 | b->status = 1; 359 | } 360 | } 361 | 362 | 363 | /* rounddown10 rounds a number down to the nearest power of 10. */ 364 | static int 365 | rounddown10(int n) 366 | { 367 | int tens = 0; 368 | /* tens = floor(log_10(n)) */ 369 | while (n >= 10) { 370 | n = n / 10; 371 | tens++; 372 | } 373 | /* result = 10**tens */ 374 | int i, result = 1; 375 | for (i = 0; i < tens; i++) { 376 | result *= 10; 377 | } 378 | return result; 379 | } 380 | 381 | 382 | /* roundup rounds n up to a number of the form [1eX, 2eX, 5eX]. */ 383 | static int 384 | roundup(int n) 385 | { 386 | int base = rounddown10(n); 387 | if (n <= base) 388 | return base; 389 | if (n <= 2*base) 390 | return 2*base; 391 | if (n <= 3*base) 392 | return 3*base; 393 | if (n <= 5*base) 394 | return 5*base; 395 | return 10*base; 396 | } 397 | 398 | 399 | static int 400 | min(int a, int b) 401 | { 402 | if (a < b) { 403 | return a; 404 | } 405 | return b; 406 | } 407 | 408 | 409 | static int 410 | max(int a, int b) 411 | { 412 | if (a > b) { 413 | return a; 414 | } 415 | return b; 416 | } 417 | 418 | 419 | static void 420 | runbench(Benchmark *b) 421 | { 422 | // Add the prefix to make it work with benchstat. 423 | printf("Benchmark%s\t", b->name+8); 424 | fflush(stdout); 425 | int n = 1; 426 | runbenchn(b, n); 427 | while (b->status == 0 && b->dur < BenchTime && n < MaxN) { 428 | int last = n; 429 | /* Predict iterations/sec. */ 430 | int nsop = b->dur / n; 431 | if (nsop == 0) { 432 | n = MaxN; 433 | } else { 434 | n = BenchTime / nsop; 435 | } 436 | /* Run more iterations than we think we'll need for a second (1.2x). 437 | Don't grow too fast in case we had timing errors previously. 438 | Be sure to run at least one more than last time. */ 439 | n = max(min(n+n/5, 100*last), last+1); 440 | /* Round up to something easy to read. */ 441 | n = roundup(n); 442 | runbenchn(b, n); 443 | } 444 | if (b->status == 0) { 445 | printf("%8d\t%10" PRId64 " ns/op", n, b->dur/n); 446 | if (b->bytes > 0) { 447 | double mbs = 0; 448 | if (b->dur > 0) { 449 | int64 sec = b->dur / 1000L / 1000L / 1000L; 450 | int64 nsec = b->dur % 1000000000L; 451 | double dur = (double)sec + (double)nsec*.0000000001; 452 | mbs = ((double)b->bytes * (double)n / 1000000) / dur; 453 | } 454 | printf("\t%7.2f MB/s", mbs); 455 | } 456 | putchar('\n'); 457 | } else { 458 | if (failed(b->status)) { 459 | printf("failure"); 460 | } else { 461 | printf("error"); 462 | if (WIFEXITED(b->status)) { 463 | printf(" (exit status %d)", WEXITSTATUS(b->status)); 464 | } 465 | if (WIFSIGNALED(b->status)) { 466 | printf(" (signal %d)", WTERMSIG(b->status)); 467 | } 468 | } 469 | putchar('\n'); 470 | } 471 | } 472 | 473 | 474 | static void 475 | runallbench(Benchmark *b) 476 | { 477 | for (; b->f; b++) { 478 | if (fnmatch(benchpat, b->name, 0) != 0) { 479 | continue; 480 | } 481 | uint64_t runs = 0; 482 | while (runs++ < count) { 483 | runbench(b); 484 | } 485 | } 486 | } 487 | 488 | 489 | static int 490 | report(Test *t) 491 | { 492 | int nfail = 0, nerr = 0; 493 | 494 | for (; t->f; t++) { 495 | rmtree(t->dir); 496 | if (!t->status) { 497 | continue; 498 | } 499 | 500 | printf("\n%s: ", t->name); 501 | if (failed(t->status)) { 502 | nfail++; 503 | printf("failure"); 504 | } else { 505 | nerr++; 506 | printf("error"); 507 | if (WIFEXITED(t->status)) { 508 | printf(" (exit status %d)", WEXITSTATUS(t->status)); 509 | } 510 | if (WIFSIGNALED(t->status)) { 511 | printf(" (signal %d)", WTERMSIG(t->status)); 512 | } 513 | } 514 | 515 | putchar('\n'); 516 | lseek(t->fd, 0, SEEK_SET); 517 | copyfd(stdout, t->fd); 518 | } 519 | 520 | if (nfail || nerr) { 521 | printf("\n%d failures; %d errors.\n", nfail, nerr); 522 | } 523 | return nfail || nerr; 524 | } 525 | 526 | 527 | static int 528 | readtokens() 529 | { 530 | int n = 1; 531 | char c, *s; 532 | char *v = getenv("MAKEFLAGS"); 533 | if (v == NULL) 534 | return n; 535 | if ((s = strstr(v, " --jobserver-fds="))) { 536 | rjobfd = (int)strtol(s+17, &s, 10); /* skip " --jobserver-fds=" */ 537 | wjobfd = (int)strtol(s+1, NULL, 10); /* skip comma */ 538 | } 539 | if (rjobfd >= 0) { 540 | fcntl(rjobfd, F_SETFL, fcntl(rjobfd, F_GETFL)|O_NONBLOCK); 541 | while (read(rjobfd, &c, 1) > 0) { 542 | n++; 543 | } 544 | } 545 | return n; 546 | } 547 | 548 | 549 | static void 550 | writetokens(int n) 551 | { 552 | char c = '+'; 553 | if (wjobfd >= 0) { 554 | fcntl(wjobfd, F_SETFL, fcntl(wjobfd, F_GETFL)|O_NONBLOCK); 555 | for (; n>1; n--) { 556 | if (write(wjobfd, &c, 1) != 1) { 557 | /* ignore error; nothing we can do anyway */ 558 | } 559 | } 560 | } 561 | } 562 | 563 | 564 | static void 565 | usage() 566 | { 567 | fprintf(stderr, "Usage: %s [-test pat] [-bench pat] [-b] [-count n]\n" 568 | "Flags:\n" 569 | " -test pattern\n" 570 | " Run only those tests that match the pattern, using the same\n" 571 | " 'glob' rules as the shell uses for file names. Default is to\n" 572 | " run all tests (equivalent to -test '*').\n" 573 | " -bench pattern\n" 574 | " Run only those benchmarks that match the pattern, using the\n" 575 | " same 'glob' rules as the shell uses for file names. Default\n" 576 | " is to run no benchmarks (equivalent to -bench '').\n" 577 | " -b\n" 578 | " Run all benchmarks (equivalent to -bench '*').\n" 579 | " -count n\n" 580 | " Run each benchmark n times (default 1).\n", 581 | argv0 582 | ); 583 | } 584 | 585 | 586 | #define badflagf(fmt, ...) do {\ 587 | fprintf(stderr, fmt "\nRun '%s -h' for details.\n", __VA_ARGS__, argv0);\ 588 | exit(2);\ 589 | } while (0) 590 | 591 | 592 | static void 593 | parseflags(char **argv) 594 | { 595 | char *flag; 596 | while ((flag = *argv++)) { 597 | if (strcmp(flag, "-h") == 0) { 598 | usage(); 599 | exit(0); 600 | } else if (strcmp(flag, "-test") == 0) { 601 | testpat = *argv++; 602 | if (!testpat) { 603 | badflagf("Flag %s requires an argument.", flag); 604 | } 605 | } else if (strcmp(flag, "-bench") == 0) { 606 | benchpat = *argv++; 607 | if (!benchpat) { 608 | badflagf("Flag %s requires an argument.", flag); 609 | } 610 | } else if (strcmp(flag, "-b") == 0) { 611 | benchpat = "*"; 612 | } else if (strcmp(flag, "-count") == 0) { 613 | char *cstr = *argv++; 614 | uint64_t c = strtoul(cstr, NULL, 10); 615 | if (errno) 616 | badflagf("Cannot parse %s as count parameter", cstr); 617 | count = c; 618 | } else { 619 | badflagf("Unknown flag: %s", flag); 620 | } 621 | } 622 | } 623 | 624 | 625 | int 626 | main(int argc, char **argv) 627 | { 628 | (void)(argc); /* quiet warning about unused variable */ 629 | argv0 = *argv++; 630 | parseflags(argv); 631 | int n = readtokens(); 632 | runalltest(ctmaintest, n); 633 | writetokens(n); 634 | int code = report(ctmaintest); 635 | if (code != 0) { 636 | return code; 637 | } 638 | runallbench(ctmainbench); 639 | return 0; 640 | } 641 | -------------------------------------------------------------------------------- /ct/ct.h: -------------------------------------------------------------------------------- 1 | char *ctdir(void); 2 | void ctfail(void); 3 | void ctfailnow(void); 4 | void ctresettimer(void); 5 | void ctstarttimer(void); 6 | void ctstoptimer(void); 7 | void ctsetbytes(int); 8 | void ctlogpn(const char*, int, const char*, ...) __attribute__((format(printf, 3, 4))); 9 | #define ctlog(...) ctlogpn(__FILE__, __LINE__, __VA_ARGS__) 10 | #define assert(x) do if (!(x)) {\ 11 | ctlog("%s", "test: " #x);\ 12 | ctfailnow();\ 13 | } while (0) 14 | #define assertf(x, ...) do if (!(x)) {\ 15 | ctlog("%s", "test: " #x);\ 16 | ctlog(__VA_ARGS__);\ 17 | ctfailnow();\ 18 | } while (0) 19 | -------------------------------------------------------------------------------- /ct/gen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | fixsyms() { 6 | if test "`uname -s|tr A-Z a-z`" = darwin 7 | then egrep -v [.] | egrep ^_ | sed s/^_// 8 | else cat 9 | fi 10 | } 11 | 12 | syms() { 13 | prefix=$1 14 | shift 15 | for f in "$@" 16 | do nm $f 17 | done | cut -d ' ' -f 3 | fixsyms | egrep ^$prefix 18 | } 19 | 20 | ts=`syms cttest "$@" || true` 21 | bs=`syms ctbench "$@" || true` 22 | 23 | printf '#include \n' 24 | printf '#include "internal.h"\n' 25 | 26 | for t in $ts 27 | do printf 'void %s(void);\n' $t 28 | done 29 | 30 | for b in $bs 31 | do printf 'void %s(int);\n' $b 32 | done 33 | 34 | printf 'Test ctmaintest[] = {\n' 35 | for t in $ts 36 | do printf ' {%s, "%s", 0, 0, 0, TmpDirPat},\n' $t $t 37 | done 38 | printf ' {.f = 0},\n' 39 | printf '};\n' 40 | 41 | printf 'Benchmark ctmainbench[] = {\n' 42 | for b in $bs 43 | do printf ' {%s, "%s", 0, 0, 0, TmpDirPat},\n' $b $b 44 | done 45 | printf ' {.f = 0},\n' 46 | printf '};\n' 47 | -------------------------------------------------------------------------------- /ct/internal.h: -------------------------------------------------------------------------------- 1 | /* include */ 2 | 3 | #define TmpDirPat "/tmp/ct.XXXXXX" 4 | 5 | typedef int64_t int64; 6 | typedef struct Test Test; 7 | typedef struct Benchmark Benchmark; 8 | 9 | struct Test { 10 | void (*f)(void); 11 | const char *name; 12 | int status; 13 | int fd; 14 | int pid; 15 | char dir[sizeof TmpDirPat]; 16 | }; 17 | 18 | struct Benchmark { 19 | void (*f)(int); 20 | const char *name; 21 | int status; 22 | int64 dur; 23 | int64 bytes; 24 | char dir[sizeof TmpDirPat]; 25 | }; 26 | 27 | extern Test ctmaintest[]; 28 | extern Benchmark ctmainbench[]; 29 | -------------------------------------------------------------------------------- /hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "msg.h" 3 | 4 | int 5 | main() { 6 | set_message("hello, world"); 7 | printf("%s\n", get_message()); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /msg-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "msg.h" 7 | #include "ct/ct.h" 8 | 9 | void 10 | cttestset() 11 | { 12 | const char *m; 13 | 14 | set_message("foo"); 15 | m = get_message(); 16 | assert(strcmp("foo", m) == 0); 17 | } 18 | 19 | void 20 | cttestdefault() 21 | { 22 | const char *m; 23 | 24 | m = get_message(); 25 | assert(strcmp("default message", m) == 0); 26 | } 27 | 28 | 29 | void 30 | cttestfailure() 31 | { 32 | return; /* remove this line to see a failure */ 33 | assert(1 == 2); 34 | } 35 | 36 | void 37 | cttestfmt() 38 | { 39 | return; /* remove this line to see a failure with formatting */ 40 | int n = 1; 41 | assertf(n == 2, "n is %d", n); 42 | } 43 | 44 | void 45 | cttestsegfault() 46 | { 47 | return; /* remove this line to see a segfault error */ 48 | *(volatile int*)0 = 0; 49 | } 50 | 51 | void 52 | cttesttmpdir() 53 | { 54 | assert(chdir(ctdir()) == 0); 55 | assert(open("x", O_CREAT|O_RDWR, 0777)); 56 | } 57 | 58 | void 59 | cttestexit() 60 | { 61 | return; /* remove this line to see an exit error */ 62 | exit(2); 63 | } 64 | 65 | void 66 | cttestfail() 67 | { 68 | return; /* remove this line to see multiple failures */ 69 | ctlog("first"); 70 | ctfail(); 71 | ctlog("second"); 72 | ctfail(); 73 | ctlog("third"); 74 | } 75 | 76 | void 77 | ctbenchprintf(int n) 78 | { 79 | int i; 80 | const char *m = get_message(); 81 | for (i = 0; i < n; i++) { 82 | printf("%s\n", m); 83 | } 84 | } 85 | 86 | void 87 | ctbenchprintsz(int n) 88 | { 89 | int i; 90 | const char *m = get_message(); 91 | ctsetbytes(strlen(m)+1); 92 | for (i = 0; i < n; i++) { 93 | printf("%s\n", m); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /msg.c: -------------------------------------------------------------------------------- 1 | /* msg.c - keep track of a message */ 2 | 3 | #include "msg.h" 4 | 5 | const char *msg = "default message"; 6 | 7 | void 8 | set_message(const char *m) 9 | { 10 | msg = m; 11 | } 12 | 13 | const char * 14 | get_message(void) 15 | { 16 | return msg; 17 | } 18 | -------------------------------------------------------------------------------- /msg.h: -------------------------------------------------------------------------------- 1 | /* msg.h - keep track of a message */ 2 | 3 | void set_message(const char *m); 4 | 5 | const char *get_message(void); 6 | --------------------------------------------------------------------------------