--- Makefile | 20 +- test-common.c | 823 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test-common.h | 190 ++++++++++++++ tty.test.c | 26 ++ 4 files changed, 1055 insertions(+), 4 deletions(-) create mode 100644 test-common.c create mode 100644 test-common.h create mode 100644 tty.test.c diff --git a/Makefile b/Makefile index 0e421e7..005cf13 100644 --- a/Makefile +++ b/Makefile _AT_@ -1,7 +1,7 @@ include config.mk .SUFFIXES: -.SUFFIXES: .o .c +.SUFFIXES: .test .test.o .o .c HDR =\ arg.h\ _AT_@ -19,7 +19,8 @@ HDR =\ sha512-256.h\ text.h\ utf.h\ - util.h + util.h\ + test-common.h LIBUTF = libutf.a LIBUTFSRC =\ _AT_@ -181,9 +182,12 @@ BIN =\ xinstall\ yes +TEST =\ + tty.test + LIBUTFOBJ = $(LIBUTFSRC:.c=.o) LIBUTILOBJ = $(LIBUTILSRC:.c=.o) -OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ) +OBJ = $(BIN:=.o) $(TEST:=.o) test-common.o $(LIBUTFOBJ) $(LIBUTILOBJ) SRC = $(BIN:=.c) MAN = $(BIN:=.1) _AT_@ -193,12 +197,17 @@ $(BIN): $(LIB) $(@:=.o) $(OBJ): $(HDR) config.mk +$(TEST): $(_AT_:=.o) test-common.o + .o: $(CC) $(LDFLAGS) -o $_AT_ $< $(LIB) .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -o $_AT_ -c $< +.test.o.test: + $(CC) $(LDFLAGS) -o $_AT_ $< test-common.o + $(LIBUTF): $(LIBUTFOBJ) $(AR) rc $_AT_ $? $(RANLIB) $_AT_ _AT_@ -212,6 +221,9 @@ getconf.o: getconf.h getconf.h: getconf.sh ./getconf.sh > $_AT_ +check: $(TEST) $(BIN) + _AT_set -e; for f in $(TEST); do echo ./$$f; ./$$f; done + install: all mkdir -p $(DESTDIR)$(PREFIX)/bin cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin _AT_@ -271,7 +283,7 @@ sbase-box-uninstall: uninstall cd $(DESTDIR)$(PREFIX)/bin && rm -f sbase-box clean: - rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz + rm -f $(BIN) $(TEST) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz rm -f getconf.h .PHONY: all install uninstall dist sbase-box sbase-box-install sbase-box-uninstall clean diff --git a/test-common.c b/test-common.c new file mode 100644 index 0000000..458b094 --- /dev/null +++ b/test-common.c _AT_@ -0,0 +1,823 @@ +/* See LICENSE file for copyright and license details. */ +#include "test-common.h" + +struct Counter { + const char *name; + size_t value; +}; + +const char *test_file = NULL; +int test_line = 0; +int main_ret = 0; +int timeout = 10; +int pdeath_sig = SIGINT; +void (*atfork)(void) = NULL; + +static struct Counter counters[16]; +static size_t ncounters = 0; +static pid_t async_pids[1024]; +static size_t async_npids = 0; + +static void +eperror(const char *prefix) +{ + perror(prefix); + fflush(stderr); + exit(64); +} + +struct Process * +stdin_text(struct Process *proc, char *s) +{ + proc->input[0].data = s; + proc->input[0].flags &= ~IO_STREAM_BINARY; + return proc; +} + +struct Process * +stdin_bin(struct Process *proc, char *s, size_t n) +{ + proc->input[0].data = s; + proc->input[0].len = n; + proc->input[0].flags |= IO_STREAM_BINARY; + return proc; +} + +struct Process * +stdin_fds(struct Process *proc, int input_fd, int output_fd) +{ + proc->input[0].flags &= ~IO_STREAM_DATA; + proc->input[0].input_fd = input_fd; + proc->input[0].output_fd = output_fd; + return proc; +} + +struct Process * +stdin_type(struct Process *proc, int type) +{ + proc->input[0].flags &= ~IO_STREAM_CREATE_MASK; + proc->input[0].flags |= type; + return proc; +} + +struct Process * +stdout_fds(struct Process *proc, int input_fd, int output_fd) +{ + proc->output[0].flags &= ~IO_STREAM_DATA; + proc->output[0].input_fd = input_fd; + proc->output[0].output_fd = output_fd; + return proc; +} + +struct Process * +stdout_type(struct Process *proc, int type) +{ + proc->output[0].flags &= ~IO_STREAM_CREATE_MASK; + proc->output[0].flags |= type; + return proc; +} + +struct Process * +stderr_fds(struct Process *proc, int input_fd, int output_fd) +{ + proc->output[1].flags &= ~IO_STREAM_DATA; + proc->output[1].input_fd = input_fd; + proc->output[1].output_fd = output_fd; + return proc; +} + +struct Process * +stderr_type(struct Process *proc, int type) +{ + proc->output[1].flags &= ~IO_STREAM_CREATE_MASK; + proc->output[1].flags |= type; + return proc; +} + +struct Process * +set_preexec(struct Process *proc, void (*preexec)(struct Process *)) +{ + proc->preexec = preexec; + return proc; +} + +struct Process * +set_async(struct Process *proc) +{ + proc->flags |= PROCESS_ASYNC; + return proc; +} + +struct Process * +set_setsid(struct Process *proc) +{ + proc->flags |= PROCESS_SETSID; + return proc; +} + +void +push_counter(const char *name) +{ + if (ncounters == ELEMSOF(counters)) { + fprintf(stderr, "too many counters\n"); + fflush(stderr); + exit(64); + } + counters[ncounters++].name = name; +} + +void +set_counter(size_t value) +{ + if (!ncounters) { + fprintf(stderr, "no counters have been pushed\n"); + fflush(stderr); + exit(64); + } + counters[ncounters - 1].value = value; +} + +void +pop_counter(void) +{ + if (!ncounters) { + fprintf(stderr, "all counters have already been popped\n"); + fflush(stderr); + exit(64); + } + ncounters--; +} + +pid_t +async_fork(int *retp) +{ + pid_t pid; + + if (!retp) + retp = &(int){0}; + + if (async_npids == ELEMSOF(async_pids)) + *retp = async_join(); + + switch ((pid = fork())) { + case -1: + eperror("fork"); + case 0: + if (atfork) + atfork(); + async_npids = 0; +#ifdef PR_SET_PDEATHSIG + prctl(PR_SET_PDEATHSIG, pdeath_sig); +#endif + alarm(timeout); + break; + default: + async_pids[async_npids++] = pid; + break; + } + + return pid; +} + +int +async_join(void) +{ + int ret = 0, status; + + while (async_npids--) { + if (waitpid(async_pids[async_npids], &status, 0) != async_pids[async_npids]) + eperror("waitpid"); + ret |= WIFEXITED(status) ? WEXITSTATUS(status) : 64; + } + + main_ret |= status; + async_npids = 0; + return ret; +} + +void +start_process(struct Process *proc) +{ + struct InputStream *in; + struct OutputStream *out; + int exec_sig_pipe[2]; + int fd, minfd = 3, fds[2], status; + size_t i; + ssize_t r; + pid_t pid; + + if (!proc->file) + proc->file = proc->argv[0]; + + for (i = 0; i < proc->ninput; i++) + if (minfd <= proc->input[i].fd) + minfd = proc->input[i].fd + 1; + for (i = 0; i < proc->noutput; i++) + if (minfd <= proc->output[i].fd) + minfd = proc->output[i].fd + 1; + + if (pipe(exec_sig_pipe)) + eperror("pipe"); + + for (i = 0; i < 2; i++) { + fd = fcntl(exec_sig_pipe[i], F_DUPFD_CLOEXEC, minfd); + if (fd < 0) + eperror("fcntl F_DUPFD_CLOEXEC"); + close(exec_sig_pipe[i]); + exec_sig_pipe[i] = fd; + } + + for (i = 0; i < proc->ninput; i++) { + in = &proc->input[i]; + in->name = NULL; + if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_PIPE) { + if (pipe(fds)) + eperror("pipe"); + } else if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_STREAM) { + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_STREAM"); + } else if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_DGRAM) { + if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_DGRAM"); + } else if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_SEQPACKET) { + if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_SEQPACKET"); + } else if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_TTY_NOCTTY) { + in->name = strdup(openpt(0, fds)); + if (!in->name) + eperror("strdup"); + if (!(in->flags & IO_STREAM_MASTER)) + fd = fds[0], fds[0] = fds[1], fds[1] = fd; + } else if ((in->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_TTY_CTTY) { + in->name = strdup(openpt(1, fds)); + if (!in->name) + eperror("strdup"); + if (!(in->flags & IO_STREAM_MASTER)) + fd = fds[0], fds[0] = fds[1], fds[1] = fd; + } else { + goto precreated_input; + } + in->input_fd = fds[0]; + in->output_fd = fds[1]; + precreated_input: + if (in->flags & IO_STREAM_DATA) { + switch (pid = fork()) { + case -1: + eperror("fork"); + case 0: + break; + default: + if (waitpid(pid, &status, 0) != pid) + eperror("waitpid"); + if (status) + exit(64); + if (!(in->flags & IO_STREAM_KEEP_OPEN)) + close(in->output_fd); + continue; + } + if (atfork) + atfork(); + dealloc_process(proc); + close(in->input_fd); + close(exec_sig_pipe[0]); + close(exec_sig_pipe[1]); + while (i--) { + if (proc->input[i].input_fd > 0) + close(proc->input[i].input_fd); + if (proc->input[i].output_fd > 0) + close(proc->input[i].output_fd); + } + switch (pid = fork()) { + case -1: + eperror("fork"); + case 0: + exit(0); + default: + break; + } + while (in->len) { + r = write(in->output_fd, in->data, in->len); + if (r < 0) + eperror("write"); + in->data += r; + in->len -= (size_t)r; + } + close(in->output_fd); + exit(0); + } + } + + for (i = 0; i < proc->noutput; i++) { + out = &proc->output[i]; + out->name = NULL; + if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_PIPE) { + if (pipe(fds)) + eperror("pipe"); + } else if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_STREAM) { + if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_STREAM"); + } else if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_DGRAM) { + if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_DGRAM"); + } else if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_SOCK_SEQPACKET) { + if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, fds)) + eperror("socketpair PF_LOCAL SOCK_SEQPACKET"); + } else if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_TTY_NOCTTY) { + out->name = strdup(openpt(0, fds)); + if (!out->name) + eperror("strdup"); + if (out->flags & IO_STREAM_MASTER) + fd = fds[0], fds[0] = fds[1], fds[1] = fd; + } else if ((out->flags & IO_STREAM_CREATE_MASK) == IO_STREAM_TTY_CTTY) { + out->name = strdup(openpt(1, fds)); + if (!out->name) + eperror("strdup"); + if (out->flags & IO_STREAM_MASTER) + fd = fds[0], fds[0] = fds[1], fds[1] = fd; + } else { + continue; + } + out->input_fd = fds[0]; + out->output_fd = fds[1]; + } + + switch ((proc->pid = fork())) { + case -1: + eperror("fork"); + case 0: + break; + default: + if (atfork) + atfork(); + for (i = 0; i < proc->ninput; i++) + close(proc->input[i].input_fd); + for (i = 0; i < proc->noutput; i++) + close(proc->output[i].output_fd); + close(exec_sig_pipe[1]); + read(exec_sig_pipe[0], &(char){0}, 1); + if (clock_gettime(CLOCK_MONOTONIC, &proc->start_time)) + eperror("clock_gettime CLOCK_MONOTONIC"); + close(exec_sig_pipe[0]); + return; + } + +#ifdef PR_SET_PDEATHSIG + prctl(PR_SET_PDEATHSIG, proc->pdeath_sig); +#endif + + for (i = 0; i < proc->ninput; i++) { + fd = fcntl(proc->input[i].input_fd, F_DUPFD, minfd); + if (fd < 0) + eperror("fcntl F_DUPFD"); + close(proc->input[i].input_fd); + proc->input[i].input_fd = fd; + } + + for (i = 0; i < proc->noutput; i++) { + fd = fcntl(proc->output[i].output_fd, F_DUPFD, minfd); + if (fd < 0) + eperror("fcntl F_DUPFD"); + close(proc->output[i].output_fd); + proc->output[i].output_fd = fd; + } + + for (i = 0; i < proc->ninput; i++) { + if (dup2(proc->input[i].input_fd, proc->input[i].fd) != proc->input[i].fd) + eperror("dup2"); + close(proc->input[i].input_fd); + } + + for (i = 0; i < proc->noutput; i++) { + if (dup2(proc->output[i].output_fd, proc->output[i].fd) != proc->output[i].fd) + eperror("dup2"); + close(proc->output[i].output_fd); + } + + if (proc->flags & PROCESS_SETSID) + if (setsid() < 0) + eperror("setsid"); + + alarm(proc->timeout); + + if (proc->preexec) + proc->preexec(proc); + + dealloc_process(proc); + execvp(proc->file, (void *)proc->argv); + fprintf(stderr, "exec %s: %s\n", proc->file, strerror(errno)); + fflush(stderr); + exit(64); +} + +void +wait_process(struct Process *proc) +{ + struct pollfd *pfds = NULL; + struct OutputStream *out; + size_t i, j, p, n; + ssize_t r; + + if (!proc->noutput) + goto no_output; + + n = proc->noutput; + pfds = calloc(n, sizeof(*pfds)); + if (!pfds) + eperror("calloc"); + + for (i = j = 0; i < n; j++) { + if (proc->output[j].flags & IO_STREAM_DATA) { + pfds[i].fd = proc->output[j].input_fd; + pfds[i].events = POLLIN; + proc->output[j].data = NULL; + proc->output[j].len = 0; + proc->output[j].size = 0; + proc->output[j].error = 0; + i++; + } else { + n--; + } + } + + while (n) { + r = (ssize_t)poll(pfds, n, -1); + if (r < 0) + eperror("poll"); + for (j = 0; j < n;) { + for (i = 0; proc->output[i].input_fd != pfds[j].fd; i++); + out = &proc->output[i]; + if (out->len + 512 >= out->size) { + out->data = realloc(out->data, out->size += 8096); + if (!out->data) + eperror("realloc"); + } + errno = 0; + r = read(out->input_fd, &out->data[out->len], out->size - out->len); + if (r <= 0) + goto close_output; + p = out->len; + out->len += (size_t)r; + if (proc->output[i].head) { + for (; p < out->len; p++) { + if (proc->output[i].data[p] == '\n') { + if (!--proc->output[i].head) { + out->len = p + 1; + goto close_output; + } + } + } + } + j++; + continue; + close_output: + out->error = errno; + close(out->input_fd); + if (memchr(out->data, '\0', out->len)) + proc->output[i].flags |= IO_STREAM_BINARY; + out->size = out->len + 1; + out->data = realloc(out->data, out->size); + if (!out->data) + eperror("realloc"); + out->data[out->len] = '\0'; + memmove(&pfds[j], &pfds[j + 1], (--n - j) * sizeof(*pfds)); + } + } + +no_output: + if (waitpid(proc->pid, &proc->status, 0) != proc->pid) + eperror("waitpid"); + + if (clock_gettime(CLOCK_MONOTONIC, &proc->exit_time)) + eperror("clock_gettime CLOCK_MONOTONIC"); + proc->runtime.tv_sec = proc->exit_time.tv_sec - proc->start_time.tv_sec; + proc->runtime.tv_nsec = proc->exit_time.tv_nsec - proc->start_time.tv_nsec; + if (proc->runtime.tv_nsec < 0) { + proc->runtime.tv_sec -= 1; + proc->runtime.tv_nsec += 1000000000L; + } + + for (i = 0; i < proc->ninput; i++) + if (proc->input[i].flags & IO_STREAM_KEEP_OPEN) + close(proc->input[i].output_fd); + + free(pfds); +} + +void +dealloc_process(struct Process *proc) +{ + size_t i; + for (i = 0; i < proc->ninput; i++) + free(proc->input[i].name); + for (i = 0; i < proc->noutput; i++) { + if (proc->output[i].flags & IO_STREAM_DATA) + free(proc->output[i].data); + free(proc->output[i].name); + } +} + +const char * +openpt(int ctty, int master_slave[2]) +{ + const char *slave; + master_slave[0] = posix_openpt(O_RDWR | O_NOCTTY); + if (master_slave[0] < 0) + eperror("posix_openpt"); + if (grantpt(master_slave[0]) < 0) + eperror("grantpt"); + if (unlockpt(master_slave[0]) < 0) + eperror("unlockpt"); + slave = ptsname(master_slave[0]); + if (!slave) + eperror("ptsname"); + master_slave[1] = open(slave, O_RDWR | (ctty ? 0 : O_NOCTTY)); + if (master_slave[1] < 0) + eperror("open"); + return slave; +} + +static const char * +test_str(void) +{ + static char buf[8096]; + char *p = buf; + size_t i; + + p += sprintf(buf, "test at %s:%i", test_file, test_line); + switch (ncounters) { + case 0: + break; + case 1: + p += sprintf(p, ", with %s=%zu, ", counters[0].name, counters[0].value); + break; + case 2: + p += sprintf(p, ", with %s=%zu and %s=%zu, ", + counters[0].name, counters[0].value, counters[1].name, counters[1].value); + break; + default: + p = stpcpy(p, ", with"); + for (i = 0; i < ncounters; i++) { + if (i == ncounters - 1) + p = stpcpy(p, "and "); + p += sprintf(p, "%s=%zu, ", counters[i].name, counters[i].value); + } + break; + } + + return buf; +} + +int +check_runtime(struct Process *proc, double min, double max) +{ + double dur; + int ret; + + dur = proc->runtime.tv_nsec; + dur /= 1000000000.; + dur += proc->runtime.tv_sec; + + main_ret |= ret = (min <= dur && dur <= max); + if (ret) { + fprintf(stderr, "%s failed: runtime was %lfs, expected between %lf and %lf\n", + test_str(), dur, min, dur); + } + + return ret; +} + +static int +check_exit_(struct Process *proc, unsigned int *testp, va_list ap) +{ + int ok = 0, ret, min, max, value, status = proc->status; + size_t alts = 0; + char expected[8096]; + char *p = expected; + + goto beginning; + for (;; alts += 1) { + *testp = va_arg(ap, unsigned int); + beginning: + switch (*testp) { + case EXIT_VALUE: + value = va_arg(ap, int); + ok = WIFEXITED(status) && WEXITSTATUS(status) == value; + p += sprintf(p, ", exitstatus=%i", value); + break; + case EXIT_RANGE: + min = va_arg(ap, int); + max = va_arg(ap, int); + ok = WIFEXITED(status) && min <= WEXITSTATUS(status) && WEXITSTATUS(status) <= max; + p += sprintf(p, ", exitstatus=%i..%i", min, max); + break; + case SIGNAL_VALUE: + value = va_arg(ap, int); + ok = WIFSTOPPED(status) && WTERMSIG(status) == value; + p += sprintf(p, ", termsig=%i", value); + break; + case SIGNAL_RANGE: + min = va_arg(ap, int); + max = va_arg(ap, int); + ok = WIFSTOPPED(status) && min <= WTERMSIG(status) && WTERMSIG(status) <= max; + p += sprintf(p, ", termsig=%i..%i", min, max); + break; + default: + goto stop; + } + } +stop: + + if (!ok) { + fprintf(stderr, "%s failed: exit status was %s=%i, expected %s%s\n", + test_str(), WIFEXITED(status) ? "exitstatus" : WIFSTOPPED(status) ? "termsig" : "unknown", + WIFEXITED(status) ? WEXITSTATUS(status) : WIFSTOPPED(status) ? WTERMSIG(status) : status, + alts == 1 ? "" : "either of: ", &expected[2]); + } + + main_ret |= ret = !ok; + return ret; +} + +int +check_exit(struct Process *proc, ...) +{ + unsigned int test; + va_list ap; + va_start(ap, proc); + test = va_arg(ap, unsigned int); + return check_exit_(proc, &test, ap); + va_end(ap); +} + +static int +check_output_test(const char *data, size_t len, struct OutputStream *s, int flags) +{ + int beginning = (flags & OUTPUT_BEGINNING) == OUTPUT_BEGINNING; + int contains = (flags & OUTPUT_CONTAINS) == OUTPUT_CONTAINS; + int anycase = (flags & OUTPUT_ANYCASE) == OUTPUT_ANYCASE; + int at_beginning = 1; + size_t i, off, off_end; + + if (len > s->len) + return 0; + + off_end = contains ? s->len - len : 1; + for (off = 0; off < off_end; at_beginning = (s->data[off] == '\n'), off++) { + if (beginning && !at_beginning) + continue; + if (!anycase) { + for (i = 0; i < len; i++) + if (data[i] != s->data[i + off]) + break; + } else { + for (i = 0; i < len; i++) + if (tolower(data[i]) != tolower(s->data[i + off])) + break; + } + if (i != len) + continue; + if (beginning || i + off == s->len) + return 1; + } + + return 0; +} + +static int +check_output_(struct OutputStream *s, unsigned int *testp, va_list ap) +{ + char fd_str[1024]; + int ok = 0, ret; + const char *data; + size_t len; + + goto beginning; + for (;;) { + *testp = va_arg(ap, unsigned int); + beginning: + if ((*testp & CHECK_OP_MASK) != CHECK_OP_SOUT) + break; + if (*testp == OUTPUT_ERROR) { + ok = s->error == va_arg(ap, int); + continue; + } + do { + data = va_arg(ap, const char *); + if (!data) + break; + if ((*testp & OUTPUT_BINARY) == OUTPUT_BINARY) + len = va_arg(ap, size_t); + else + len = strlen(data); + if ((*testp & OUTPUT_TEXT_ONLY) == OUTPUT_TEXT_ONLY) + if (s->flags & IO_STREAM_BINARY) + continue; + ok |= check_output_test(data, len, s, *testp); + } while ((*testp & OUTPUT_MULTIPLE) == OUTPUT_MULTIPLE); + } + + if (!ok) { + switch (s->fd) { + case STDIN_FILENO: + stpcpy(fd_str, "stdin"); + break; + case STDOUT_FILENO: + stpcpy(fd_str, "stdout"); + break; + case STDERR_FILENO: + stpcpy(fd_str, "stderr"); + break; + default: + sprintf(fd_str, "fd %i", s->fd); + break; + } + + fprintf(stderr, "%s failed: output to %s did not match expectation, was ", test_str(), fd_str); + if (s->flags & IO_STREAM_BINARY) + fprintf(stderr, "binary of length %zu\n", s->len); + else + fprintf(stderr, "“%s”\n", s->data); + } + + main_ret |= ret = !ok; + return ret; +} + +int +check_output(struct OutputStream *s, ...) +{ + unsigned int test; + va_list ap; + va_start(ap, s); + test = va_arg(ap, unsigned int); + return check_output_(s, &test, ap); + va_end(ap); +} + +int +check(struct Process *proc, ...) +{ + unsigned int test; + double min, max; + va_list ap; + int fd, r, ret = 0; + struct OutputStream *out; + int (*callback)(struct Process *); + + if (!(proc->flags & PROCESS_ASYNC)) + start_process(proc); + wait_process(proc); + + va_start(ap, proc); + + for (;;) { + test = va_arg(ap, unsigned int); + again: + switch (test & CHECK_OP_MASK) { + case CHECK_OP_EXIT: + ret |= check_exit_(proc, &test, ap); + goto again; + case CHECK_OP_TIME: + min = va_arg(ap, double); + max = va_arg(ap, double); + ret |= check_runtime(proc, min, max); + break; + case CHECK_OP_SOUT: + case CHECK_OP_OUTF: + if ((test & CHECK_OP_MASK) == CHECK_OP_SOUT) { + fd = STDOUT_FILENO; + } else { + switch (test) { + case CHECK_STDOUT: + fd = STDOUT_FILENO; + break; + case CHECK_STDERR: + fd = STDERR_FILENO; + break; + default: + fd = va_arg(ap, int); + break; + } + test = va_arg(ap, unsigned int); + } + for (out = proc->output; out->fd != fd; out++); + fflush(stderr); + ret |= check_output_(out, &test, ap); + goto again; + case CHECK_OP_CALL: + callback = va_arg(ap, int (*)(struct Process *)); + ret |= r = callback(proc); + main_ret |= r; + if (r < 0) + goto stop; + break; + default: + goto stop; + } + } +stop: + + va_end(ap); + dealloc_process(proc); + return ret; +} diff --git a/test-common.h b/test-common.h new file mode 100644 index 0000000..a76d1aa --- /dev/null +++ b/test-common.h _AT_@ -0,0 +1,190 @@ +/* See LICENSE file for copyright and license details. */ +#ifdef __linux__ +# include <sys/prctl.h> +#endif +#include <sys/socket.h> +#include <sys/wait.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +enum { + SUCCESS = 0, + SUCCESS_TRUE = 0, /* e.g. true(1), diff(1) with identical files */ + SUCCESS_FALSE = 1, /* e.g. false(1), diff(1) with differing files */ + REGULAR_ERROR = 1, + BOOLEAN_ERROR = 2, /* e.g. error in diff(1) */ + PARTIAL_ERROR = 64, + SETUP_ERROR = 125, /* e.g. error before exec in env(1) */ + EXEC_ERROR = 126, /* e.g. error in exec in env(1), but not ENOENT */ + NOENT_ERROR = 127 /* e.g. error in exec in env(1), but only ENOENT */ +}; + +#define IO_STREAM_PIPE 0x0001U +#define IO_STREAM_SOCK_STREAM 0x0002U +#define IO_STREAM_SOCK_DGRAM 0x0003U +#define IO_STREAM_SOCK_SEQPACKET 0x0004U +#define IO_STREAM_TTY_NOCTTY 0x0005U +#define IO_STREAM_TTY_CTTY 0x0006U +#define IO_STREAM_CREATE_MASK 0x0007U + +#define IO_STREAM_DATA 0x0008U +#define IO_STREAM_BINARY 0x0010U +#define IO_STREAM_MASTER 0x0020U +#define IO_STREAM_KEEP_OPEN 0x0040U + +#define END 0x0000U +#define EXIT_VALUE 0x1000U +#define EXIT_RANGE 0x1001U +#define SIGNAL_VALUE 0x1002U +#define SIGNAL_RANGE 0x1003U +#define RUNTIME 0x2000U +#define OUTPUT_ERROR 0x30FFU +#define OUTPUT_EQUALS 0x3000U +#define OUTPUT_ANYCASE 0x3001U +#define OUTPUT_BEGINNING 0x3002U +#define OUTPUT_CONTAINS 0x3004U +#define OUTPUT_LINE (OUTPUT_BEGINNING | OUTPUT_CONTAINS) +#define OUTPUT_BINARY 0x3008U +#define OUTPUT_MULTIPLE 0x3010U +#define OUTPUT_TEXT_ONLY 0x3020U +#define CHECK_STDOUT 0x4001U +#define CHECK_STDERR 0x4002U +#define CHECK_FD 0x4003U +#define CALLBACK 0x5000U + +#define CHECK_OP_MASK 0xF000U +#define CHECK_OP_EXIT 0x1000U +#define CHECK_OP_TIME 0x2000U +#define CHECK_OP_SOUT 0x3000U +#define CHECK_OP_OUTF 0x4000U +#define CHECK_OP_CALL 0x5000U + +struct InputStream { + int fd; + int input_fd; + int output_fd; + int flags; + const char *data; + size_t len; + char *name; +}; + +struct OutputStream { + int fd; + int input_fd; + int output_fd; + int flags; + int error; + size_t head; + char *data; + size_t len; + size_t size; + char *name; +}; + +#define PROCESS_ASYNC 0x0001 +#define PROCESS_SETSID 0x0002 + +struct Process { + const char *file; + const char *const *argv; + pid_t pid; + int status; + int timeout; + int pdeath_sig; + int flags; + struct InputStream input[8]; + size_t ninput; + struct OutputStream output[8]; + size_t noutput; + struct timespec start_time; + struct timespec exit_time; + struct timespec runtime; + void (*preexec)(struct Process *); +}; + +extern const char *test_file; +extern int test_line; +extern int main_ret; +extern int timeout; +extern int pdeath_sig; +extern void (*atfork)(void); + +#define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) + +#define COUNTER(VAR, START, WHILE) push_counter(#VAR), VAR = 0; (WHILE) ? (set_counter(VAR), 1) : (pop_counter(), 0); (VAR)++ + +#define CMD(...)\ + (test_line = __LINE__,\ + test_file = __FILE__,\ + &(struct Process){\ + .file = NULL,\ + .argv = (const char *[]){__VA_ARGS__, NULL},\ + .timeout = timeout,\ + .pdeath_sig = pdeath_sig,\ + .flags = 0,\ + .input = {{\ + .fd = STDIN_FILENO,\ + .flags = IO_STREAM_PIPE | IO_STREAM_DATA,\ + .data = NULL,\ + .len = 0\ + }},\ + .ninput = 1,\ + .output = {{\ + .fd = STDOUT_FILENO,\ + .flags = IO_STREAM_PIPE | IO_STREAM_DATA,\ + .head = 0\ + }, {\ + .fd = STDERR_FILENO,\ + .flags = IO_STREAM_PIPE | IO_STREAM_DATA,\ + .head = 0\ + }},\ + .noutput = 2,\ + .preexec = NULL\ + }) + +#define STDIN_BIN(PROC, S) stdin_bin(PROC, S, sizeof(S) - 1) + +#define CHECK(PROC, ...) check(PROC, __VA_ARGS__, END) + +#define ASYNC(...) (async_fork(NULL) ? async_join() : (__VA_ARGS__, exit(main_ret), 64)) + +struct Process *stdin_text(struct Process *proc, char *s); +struct Process *stdin_bin(struct Process *proc, char *s, size_t n); +struct Process *stdin_fds(struct Process *proc, int input_fd, int output_fd); +struct Process *stdin_type(struct Process *proc, int type); +struct Process *stdout_fds(struct Process *proc, int input_fd, int output_fd); +struct Process *stdout_type(struct Process *proc, int type); +struct Process *stderr_fds(struct Process *proc, int input_fd, int output_fd); +struct Process *stderr_type(struct Process *proc, int type); +struct Process *set_preexec(struct Process *proc, void (*preexec)(struct Process *)); +struct Process *set_async(struct Process *proc); +struct Process *set_setsid(struct Process *proc); + +void push_counter(const char *name); +void set_counter(size_t value); +void pop_counter(void); + +pid_t async_fork(int *retp); +int async_join(void); + +void start_process(struct Process *proc); +void wait_process(struct Process *proc); +void dealloc_process(struct Process *proc); + +const char *openpt(int ctty, int master_slave[2]); + +int check_runtime(struct Process *proc, double min, double max); +int check_exit(struct Process *proc, ...); +int check_output(struct OutputStream *s, ...); +int check(struct Process *proc, ...); diff --git a/tty.test.c b/tty.test.c new file mode 100644 index 0000000..ba93870 --- /dev/null +++ b/tty.test.c _AT_@ -0,0 +1,26 @@ +/* See LICENSE file for copyright and license details. */ +#include "test-common.h" + +static char buf[1024]; + +static int +get_tty_name(struct Process *proc) +{ + stpcpy(stpcpy(buf, proc->input[0].name), "\n"); + return 0; +} + +int +main(void) +{ + alarm(timeout); + + CHECK(CMD("./tty", "-"), EXIT_VALUE, 2, CHECK_STDOUT, OUTPUT_EQUALS, ""); + CHECK(CMD("./tty", "x"), EXIT_VALUE, 2, CHECK_STDOUT, OUTPUT_EQUALS, ""); + CHECK(CMD("./tty", "---"), EXIT_VALUE, 2, CHECK_STDOUT, OUTPUT_EQUALS, ""); + CHECK(CMD("./tty"), EXIT_VALUE, 1, OUTPUT_EQUALS, "not a tty\n", CHECK_STDERR, OUTPUT_EQUALS, ""); + CHECK(stdin_type(CMD("./tty"), IO_STREAM_TTY_NOCTTY | IO_STREAM_KEEP_OPEN), CALLBACK, get_tty_name, EXIT_VALUE, 0, + CHECK_STDOUT, OUTPUT_EQUALS, buf, CHECK_STDOUT, OUTPUT_BEGINNING, "/dev/pts/", CHECK_STDERR, OUTPUT_EQUALS, ""); + + return main_ret; +} -- 2.11.1Received on Sat Jul 07 2018 - 23:26:29 CEST
This archive was generated by hypermail 2.3.0 : Sat Jul 07 2018 - 23:36:25 CEST