Files
beanstalkd/ct/ct.c
T
install b9bcb01322
Continous integration / Tests (push) Canceled after 0s
Testing clients / Build beanstalkd (push) Canceled after 0s
Continous integration / Docker build (push) Canceled after 0s
Testing clients / Test Pheanstalk PHP (push) Canceled after 0s
Testing clients / Test Greenstalk Python (3.9) (push) Canceled after 0s
Testing clients / Test Pystalk Python (3.10) (push) Canceled after 0s
Initialize
2026-10-15 10:52:58 +05:00

575 lines
11 KiB
C

/* CT - simple-minded unit testing for C */
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <stdint.h>
#include <inttypes.h>
#include "internal.h"
#include "ct.h"
static char *curdir;
static int rjobfd = -1, wjobfd = -1;
int fail = 0; /* bool */
static int64 bstart, bdur;
static int btiming; /* bool */
static int64 bbytes;
enum { Second = 1000 * 1000 * 1000 };
enum { BenchTime = Second };
enum { MaxN = 1000 * 1000 * 1000 };
#ifdef __MACH__
# include <mach/mach_time.h>
static int64
nstime()
{
return (int64)mach_absolute_time();
}
#else
# include <time.h>
static int64
nstime()
{
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (int64)(t.tv_sec)*Second + t.tv_nsec;
}
#endif
void
ctlogpn(const char *p, int n, const char *fmt, ...)
{
va_list arg;
printf("%s:%d: ", p, n);
va_start(arg, fmt);
vprintf(fmt, arg);
va_end(arg);
putchar('\n');
}
void
ctfail(void)
{
fail = 1;
}
void
ctfailnow(void)
{
fflush(NULL);
abort();
}
char *
ctdir(void)
{
return curdir;
}
void
ctresettimer(void)
{
bdur = 0;
bstart = nstime();
}
void
ctstarttimer(void)
{
if (!btiming) {
bstart = nstime();
btiming = 1;
}
}
void
ctstoptimer(void)
{
if (btiming) {
bdur += nstime() - bstart;
btiming = 0;
}
}
void
ctsetbytes(int n)
{
bbytes = (int64)n;
}
static void
die(int code, int err, const char *msg)
{
putc('\n', stderr);
if (msg && *msg) {
fputs(msg, stderr);
fputs(": ", stderr);
}
fputs(strerror(err), stderr);
putc('\n', stderr);
exit(code);
}
static int
tmpfd(void)
{
FILE *f = tmpfile();
if (!f) {
die(1, errno, "tmpfile");
}
return fileno(f);
}
static int
failed(int s)
{
return WIFSIGNALED(s) && (WTERMSIG(s) == SIGABRT);
}
static void
waittest(Test *ts)
{
Test *t;
int pid, stat;
pid = wait3(&stat, 0, 0);
if (pid == -1) {
die(3, errno, "wait");
}
killpg(pid, SIGKILL);
for (t=ts; t->f; t++) {
if (t->pid == pid) {
t->status = stat;
if (!t->status) {
putchar('.');
} else if (failed(t->status)) {
putchar('F');
} else {
putchar('E');
}
fflush(stdout);
}
}
}
static void
start(Test *t)
{
t->fd = tmpfd();
strcpy(t->dir, TmpDirPat);
if (mkdtemp(t->dir) == NULL) {
die(1, errno, "mkdtemp");
}
fflush(NULL);
t->pid = fork();
if (t->pid < 0) {
die(1, errno, "fork");
} else if (!t->pid) {
setpgid(0, 0);
if (dup2(t->fd, 1) == -1) {
die(3, errno, "dup2");
}
if (close(t->fd) == -1) {
die(3, errno, "fclose");
}
if (dup2(1, 2) == -1) {
die(3, errno, "dup2");
}
curdir = t->dir;
t->f();
if (fail) {
ctfailnow();
}
exit(0);
}
setpgid(t->pid, t->pid);
}
static void
runalltest(Test *ts, int limit)
{
int nrun = 0;
Test *t;
for (t=ts; t->f; t++) {
if (nrun >= limit) {
waittest(ts);
nrun--;
}
start(t);
nrun++;
}
for (; nrun; nrun--) {
waittest(ts);
}
}
static void
copyfd(FILE *out, int in)
{
ssize_t n;
char buf[1024]; /* arbitrary size */
while ((n = read(in, buf, sizeof(buf))) != 0) {
if (fwrite(buf, 1, n, out) != (size_t)n) {
die(3, errno, "fwrite");
}
}
}
/*
Removes path and all of its children.
Writes errors to stderr and keeps going.
If path doesn't exist, rmtree returns silently.
*/
static void
rmtree(char *path)
{
int r = unlink(path);
if (r == 0 || errno == ENOENT) {
return; /* success */
}
int unlinkerr = errno;
DIR *d = opendir(path);
if (!d) {
if (errno == ENOTDIR) {
fprintf(stderr, "ct: unlink: %s\n", strerror(unlinkerr));
} else {
perror("ct: opendir");
}
fprintf(stderr, "ct: path %s\n", path);
return;
}
struct dirent *ent;
while ((ent = readdir(d))) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
continue;
}
int n = strlen(path) + 1 + strlen(ent->d_name);
char s[n+1];
sprintf(s, "%s/%s", path, ent->d_name);
rmtree(s);
}
closedir(d);
r = rmdir(path);
if (r == -1) {
perror("ct: rmdir");
fprintf(stderr, "ct: path %s\n", path);
}
}
static void
runbenchn(Benchmark *b, int n)
{
int outfd = tmpfd();
int durfd = tmpfd();
strcpy(b->dir, TmpDirPat);
if (mkdtemp(b->dir) == NULL) {
die(1, errno, "mkdtemp");
}
fflush(NULL);
int pid = fork();
if (pid < 0) {
die(1, errno, "fork");
} else if (!pid) {
setpgid(0, 0);
if (dup2(outfd, 1) == -1) {
die(3, errno, "dup2");
}
if (close(outfd) == -1) {
die(3, errno, "fclose");
}
if (dup2(1, 2) == -1) {
die(3, errno, "dup2");
}
curdir = b->dir;
ctstarttimer();
b->f(n);
ctstoptimer();
if (write(durfd, &bdur, sizeof bdur) != sizeof bdur) {
die(3, errno, "write");
}
if (write(durfd, &bbytes, sizeof bbytes) != sizeof bbytes) {
die(3, errno, "write");
}
exit(0);
}
setpgid(pid, pid);
pid = waitpid(pid, &b->status, 0);
if (pid == -1) {
die(3, errno, "wait");
}
killpg(pid, SIGKILL);
rmtree(b->dir);
if (b->status != 0) {
putchar('\n');
lseek(outfd, 0, SEEK_SET);
copyfd(stdout, outfd);
return;
}
lseek(durfd, 0, SEEK_SET);
int r = read(durfd, &b->dur, sizeof b->dur);
if (r != sizeof b->dur) {
perror("read");
b->status = 1;
}
r = read(durfd, &b->bytes, sizeof b->bytes);
if (r != sizeof b->bytes) {
perror("read");
b->status = 1;
}
}
/* rounddown10 rounds a number down to the nearest power of 10. */
static int
rounddown10(int n)
{
int tens = 0;
/* tens = floor(log_10(n)) */
while (n >= 10) {
n = n / 10;
tens++;
}
/* result = 10**tens */
int i, result = 1;
for (i = 0; i < tens; i++) {
result *= 10;
}
return result;
}
/* roundup rounds n up to a number of the form [1eX, 2eX, 5eX]. */
static int
roundup(int n)
{
int base = rounddown10(n);
if (n <= base)
return base;
if (n <= 2*base)
return 2*base;
if (n <= 3*base)
return 3*base;
if (n <= 5*base)
return 5*base;
return 10*base;
}
static int
min(int a, int b)
{
if (a < b) {
return a;
}
return b;
}
static int
max(int a, int b)
{
if (a > b) {
return a;
}
return b;
}
static void
runbench(Benchmark *b)
{
printf("%s\t", b->name);
fflush(stdout);
int n = 1;
runbenchn(b, n);
while (b->status == 0 && b->dur < BenchTime && n < MaxN) {
int last = n;
/* Predict iterations/sec. */
int nsop = b->dur / n;
if (nsop == 0) {
n = MaxN;
} else {
n = BenchTime / nsop;
}
/* Run more iterations than we think we'll need for a second (1.2x).
Don't grow too fast in case we had timing errors previously.
Be sure to run at least one more than last time. */
n = max(min(n+n/5, 100*last), last+1);
/* Round up to something easy to read. */
n = roundup(n);
runbenchn(b, n);
}
if (b->status == 0) {
printf("%8d\t%10" PRId64 " ns/op", n, b->dur/n);
if (b->bytes > 0) {
double mbs = 0;
if (b->dur > 0) {
int64 sec = b->dur / 1000L / 1000L / 1000L;
int64 nsec = b->dur % 1000000000L;
double dur = (double)sec + (double)nsec*.0000000001;
mbs = ((double)b->bytes * (double)n / 1000000) / dur;
}
printf("\t%7.2f MB/s", mbs);
}
putchar('\n');
} else {
if (failed(b->status)) {
printf("failure");
} else {
printf("error");
if (WIFEXITED(b->status)) {
printf(" (exit status %d)", WEXITSTATUS(b->status));
}
if (WIFSIGNALED(b->status)) {
printf(" (signal %d)", WTERMSIG(b->status));
}
}
putchar('\n');
}
}
static void
runallbench(Benchmark *b)
{
for (; b->f; b++) {
runbench(b);
}
}
static int
report(Test *t)
{
int nfail = 0, nerr = 0;
putchar('\n');
for (; t->f; t++) {
rmtree(t->dir);
if (!t->status) {
continue;
}
printf("\n%s: ", t->name);
if (failed(t->status)) {
nfail++;
printf("failure");
} else {
nerr++;
printf("error");
if (WIFEXITED(t->status)) {
printf(" (exit status %d)", WEXITSTATUS(t->status));
}
if (WIFSIGNALED(t->status)) {
printf(" (signal %d)", WTERMSIG(t->status));
}
}
putchar('\n');
lseek(t->fd, 0, SEEK_SET);
copyfd(stdout, t->fd);
}
if (nfail || nerr) {
printf("\n%d failures; %d errors.\n", nfail, nerr);
} else {
printf("\nPASS\n");
}
return nfail || nerr;
}
static int
readtokens()
{
int n = 1;
char c, *s;
char *v = getenv("MAKEFLAGS");
if (v == NULL)
return n;
if ((s = strstr(v, " --jobserver-fds="))) {
rjobfd = (int)strtol(s+17, &s, 10); /* skip " --jobserver-fds=" */
wjobfd = (int)strtol(s+1, NULL, 10); /* skip comma */
}
if (rjobfd >= 0) {
fcntl(rjobfd, F_SETFL, fcntl(rjobfd, F_GETFL)|O_NONBLOCK);
while (read(rjobfd, &c, 1) > 0) {
n++;
}
}
return n;
}
static void
writetokens(int n)
{
char c = '+';
if (wjobfd >= 0) {
fcntl(wjobfd, F_SETFL, fcntl(wjobfd, F_GETFL)|O_NONBLOCK);
for (; n>1; n--) {
if (write(wjobfd, &c, 1) != 1) {
/* ignore error; nothing we can do anyway */
}
}
}
}
int
main(int argc, char **argv)
{
int n = readtokens();
runalltest(ctmaintest, n);
writetokens(n);
int code = report(ctmaintest);
if (code != 0) {
return code;
}
if (argc == 2 && strcmp(argv[1], "-b") == 0) {
runallbench(ctmainbench);
}
return 0;
}