Initialize
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
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
This commit is contained in:
@@ -0,0 +1,574 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user