---
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.1
Received 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