Discussion:
[Linuxptp-devel] [PATCH 0/7] Test harness
Geoff Salmon
2013-01-03 23:44:39 UTC
Permalink
This is the test harness that was part of the previous patch set I
sent. It supports tap style tests and supports forking a child ptp4l
process. There's one example test and one test sending a GET
NULL_MANAGEMENT message and receiving the RESPONSE.

The test harness itself, tests/runtests.c, and the library for tap
output in tests/tap are slightly modified from the C TAP Harness
<http://www.eyrie.org/~eagle/software/c-tap-harness/> which is
distributed under a "BSD-style license".

Geoff Salmon (7):
tests: adds test harness
tests: add example test and readme
tests: put each test in process group and kill group after test run
tests: Add childproc utility for spawning ptp4l processes in tests
tests: use SOURCE/BUILD env variables set in runtests to find ptp4l
bin
tests: support sending messages to child ptp4l process
tests: test sending and receiving NULL_MANAGEMENT message

.gitignore | 7 +-
makefile | 5 +
tests/README.org | 74 +++
tests/TESTS | 2 +
tests/childproc.c | 279 ++++++++++
tests/childproc.h | 86 +++
tests/example-t.c | 27 +
tests/makefile | 62 +++
tests/null_management-t.c | 66 +++
tests/runtests.c | 1269 +++++++++++++++++++++++++++++++++++++++++++++
tests/tap/basic.c | 629 ++++++++++++++++++++++
tests/tap/basic.h | 134 +++++
tests/tap/float.c | 67 +++
tests/tap/float.h | 42 ++
tests/tap/libtap.sh | 246 +++++++++
tests/tap/macros.h | 88 ++++
tests/test.c | 81 +++
tests/test.h | 51 ++
18 files changed, 3213 insertions(+), 2 deletions(-)
create mode 100644 tests/README.org
create mode 100644 tests/TESTS
create mode 100644 tests/childproc.c
create mode 100644 tests/childproc.h
create mode 100644 tests/example-t.c
create mode 100644 tests/makefile
create mode 100644 tests/null_management-t.c
create mode 100644 tests/runtests.c
create mode 100644 tests/tap/basic.c
create mode 100644 tests/tap/basic.h
create mode 100644 tests/tap/float.c
create mode 100644 tests/tap/float.h
create mode 100644 tests/tap/libtap.sh
create mode 100644 tests/tap/macros.h
create mode 100644 tests/test.c
create mode 100644 tests/test.h
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:42 UTC
Permalink
This change is to ensure all processes started by a test are killed
when a test is finished. Future commits will add support for forking
ptp4l processes in tests and these should not outlive the individual
test or the runtests process.

Added a SIGINT handler to runtests to kill test processes even when
runtests itself is interrupted.
---
tests/runtests.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 69 insertions(+), 4 deletions(-)

diff --git a/tests/runtests.c b/tests/runtests.c
index c361def..385decc 100644
--- a/tests/runtests.c
+++ b/tests/runtests.c
@@ -139,6 +139,7 @@ enum plan_status {
#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+#define CHILDERR_PGID 103 /* Couldn't set process gid. */

/* Structure to hold data for a set of tests. */
struct testset {
@@ -204,6 +205,18 @@ Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)


+/* A process group id that will be killed when this process receives a
+ * SIGINT. Exists to ensure test processes and their children to not
+ * outlive the runtests process.
+ */
+volatile sig_atomic_t kill_pgid = 0;
+
+void int_handler(int i) {
+ if (kill_pgid)
+ kill(-kill_pgid, SIGINT);
+ _exit(0);
+}
+
/*
* Report a fatal error, including the results of strerror, and exit.
*/
@@ -345,15 +358,23 @@ test_start(const char *path, int *fd)
_exit(CHILDERR_STDERR);
if (dup2(errfd, 2) == -1)
_exit(CHILDERR_DUP);
- close(fds[0]);
if (dup2(fds[1], 1) == -1)
_exit(CHILDERR_DUP);
+ close(fds[0]);
+ close(fds[1]);
+
+ /* Change the child's process group so that the child and all
+ processes it forks can be killed after the test has
+ finished */
+ if (setpgid(0, 0) != 0)
+ _exit(CHILDERR_PGID);

/* Now, exec our process. */
if (execl(path, path, (char *) 0) == -1)
_exit(CHILDERR_EXEC);
} else {
/* In parent. Close the extra file descriptor. */
+ kill_pgid = child;
close(fds[1]);
}
*fd = fds[0];
@@ -760,6 +781,9 @@ test_analyze(struct testset *ts)
if (!ts->reported)
puts("ABORTED (can't open /dev/null)");
break;
+ case CHILDERR_PGID:
+ if (!ts->reported)
+ puts("ABORTED (can't set pgid)");
default:
test_summarize(ts, WEXITSTATUS(ts->status));
break;
@@ -825,6 +849,10 @@ test_run(struct testset *ts)
}
sysdie("waitpid for %u failed", (unsigned int) testpid);
}
+ /* kill all processes in the test's process group. This kills any
+ grand-child processes started by the test */
+ kill(-testpid, SIGINT);
+ kill_pgid = 0;
if (ts->all_skipped)
ts->aborted = 0;
status = test_analyze(ts);
@@ -1112,13 +1140,43 @@ static void
test_single(const char *program, const char *source, const char *build)
{
struct testset ts;
+ pid_t child;

memset(&ts, 0, sizeof(ts));
find_test(program, &ts, source, build);
- if (execl(ts.path, ts.path, (char *) 0) == -1)
- sysdie("cannot exec %s", ts.path);
-}

+ child = fork();
+ if (child == (pid_t) -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* Change the child's process group so that the child and all
+ processes it forks can be killed after the test has
+ finished */
+ if (setpgid(0, 0) != 0) {
+ printf("ABORTED (setpgid failed) %m\n");
+ _exit(CHILDERR_PGID);
+ }
+
+ /* Now, exec our process. */
+ if (execl(ts.path, ts.path, (char *) 0) == -1) {
+ printf("ABORTED (execution failed) %m\n");
+ _exit(CHILDERR_EXEC);
+ }
+ } else {
+ kill_pgid = child;
+ /* In parent. Wait for child then kill process group. */
+ if (waitpid(child, &ts.status, 0) == (pid_t) -1) {
+ printf("ABORTED (waitpid failed) %m\n");
+ sysdie("waitpid for %u failed", (unsigned int) child);
+ }
+ /* kill all processes in the test's process group. This kills any
+ grand-child processes started by the test */
+ kill(-child, SIGINT);
+ kill_pgid = 0;
+ }
+}

/*
* Main routine. Set the SOURCE and BUILD environment variables and then,
@@ -1175,6 +1233,13 @@ main(int argc, char *argv[])
sysdie("cannot set BUILD in the environment");
}

+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = int_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGINT, &sa, NULL);
+
if (single)
test_single(argv[0], source, build);
else {
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:41 UTC
Permalink
---
.gitignore | 1 +
tests/README.org | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/TESTS | 1 +
tests/example-t.c | 27 ++++++++++++++++++++
tests/makefile | 6 +++--
tests/test.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/test.h | 42 +++++++++++++++++++++++++++++++
7 files changed, 220 insertions(+), 2 deletions(-)
create mode 100644 tests/README.org
create mode 100644 tests/example-t.c
create mode 100644 tests/test.c
create mode 100644 tests/test.h

diff --git a/.gitignore b/.gitignore
index 886e1ea..b91a0a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
/pmc
/ptp4l
/tests/runtests
+/tests/example-t
diff --git a/tests/README.org b/tests/README.org
new file mode 100644
index 0000000..6088eaa
--- /dev/null
+++ b/tests/README.org
@@ -0,0 +1,74 @@
+* Tests
+
+This directory contains linuxptp's tests. They are TAP style tests
+executed by the runtests program.
+
+* Running tests
+
+To run the tests run
+
+#+BEGIN_EXAMPLE
+ make check
+#+END_EXAMPLE
+
+in the main directory. This will build everything and run the tests.
+
+* A test is failing, now what?
+
+If a tests is failing you'll see a message similar to the following.
+Here I've added a failing test to tests/example-t.c
+
+#+BEGIN_EXAMPLE
+ $ make check
+ ... build output ...
+ ./runtests TESTS
+
+ Running all tests listed in TESTS. If any tests fail, run the failing
+ test program with runtests -o to see more details.
+
+ example..........FAILED 2
+
+ Failed Set Fail/Total (%) Skip Stat Failing Tests
+ -------------------------- -------------- ---- ---- ------------------------
+ example 1/2 50% 0 0 2
+
+ Failed 1/2 tests, 50.00% okay.
+ Files=1, Tests=2, 0.00 seconds (0.00 usr + 0.00 sys = 0.00 CPU)
+#+END_EXAMPLE
+
+To investigate why a particular test is failing you can run a single
+test program to see it's output..
+
+#+BEGIN_EXAMPLE
+ $ tests/runtests -o tests/example
+ 1..2
+ ok 1 - example test
+ not ok 2 - another example test
+ # Looks like you failed 1 test of 2
+#+END_EXAMPLE
+
+Here we see that the second test in example is failing, so now you
+could look at the test code to determine the problem.
+
+Note that you could instead run "tests/example-t" directly in this
+case. However, the runtests harness sets some environment variables
+when it runs a test. Although example-t test doesn't use the
+variables, it's safer to use runtests to run an individual test.
+
+* Adding tests
+
+Before adding tests to an existing test program look at the
+tests/tap/basic.h header to see the ok and skip functions and their
+block variants. When you add or remove tests, be sure to update the
+number passed to plan() at the top of each test program. This tells
+the harness how many test results it should expect. If you don't do
+this, runtests will warn you that either tests are missing or that
+it's seeing invalid test numbers.
+
+** Adding new test program
+
+To add a new test program create a new *-t.c file. Add it to the
+tests/makefile in the same manner as the other tests. Add it to the
+.gitignore. Finally add it's name to the tests/TESTS file on a new
+line, minus the trailing "-t.c". The TESTS file tells the runtests
+harness which tests to run.
diff --git a/tests/TESTS b/tests/TESTS
index e69de29..33a9488 100644
--- a/tests/TESTS
+++ b/tests/TESTS
@@ -0,0 +1 @@
+example
diff --git a/tests/example-t.c b/tests/example-t.c
new file mode 100644
index 0000000..805aa33
--- /dev/null
+++ b/tests/example-t.c
@@ -0,0 +1,27 @@
+/**
+ * @file example-t.c
+ * @brief Example of a test
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "test.h"
+
+int main()
+{
+ plan(1);
+ ok(1, "example test");
+ return 0;
+}
diff --git a/tests/makefile b/tests/makefile
index dbb8238..c44883f 100644
--- a/tests/makefile
+++ b/tests/makefile
@@ -21,8 +21,8 @@ INC = -I..
CFLAGS = -Wall -Werror $(INC) $(DEBUG) $(EXTRA_CFLAGS)
LDLIBS = $(EXTRA_LDFLAGS)

-PRG = runtests
-TEST_OBJS = tap/basic.o
+PRG = runtests example-t
+TEST_OBJS = tap/basic.o test.o
OBJECTS = $(PRG:=.o) $(TEST_OBJS)
SRC = $(OBJECTS:.o=.c)
DEPEND = $(OBJECTS:.o=.d)
@@ -37,6 +37,8 @@ clean:
distclean: clean
rm -f $(PRG)

+example-t: example-t.o $(TEST_OBJS)
+
runtests: runtests.o

check: all
diff --git a/tests/test.c b/tests/test.c
new file mode 100644
index 0000000..4d63cde
--- /dev/null
+++ b/tests/test.c
@@ -0,0 +1,71 @@
+/**
+ * @file test.c
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "test.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+static void print_printable(FILE *f, const uint8_t *buf, size_t n)
+{
+ int i;
+ fputs(" ", f);
+ for (i = n; i < 16; i++)
+ fputs(" ", f);
+ for (i = 0; i < n; i++)
+ fputc(isprint(buf[i]) ? buf[i] : '.', f);
+ fputs("\n", f);
+}
+
+void print_buffer(FILE *f, const void *b, size_t n)
+{
+ const uint8_t *buf = b;
+ int i = 0;
+ while (n-- > 0) {
+ fprintf(f, "%02X ", *buf++);
+ i++;
+ if (i == 16 || n == 0) {
+ print_printable(f, buf - i, i);
+ i = 0;
+ }
+ }
+}
+
+int cmp_buffers(FILE *f, const void *b1, const void *b2, size_t n)
+{
+ int result = 0, i = 0;
+ const uint8_t *buf1 = b1;
+ const uint8_t *buf2 = b2;
+ while (n-- > 0) {
+ if (*buf1 == *buf2) {
+ fprintf(f, "%02X ", *buf1);
+ } else {
+ result = 1;
+ fprintf(f, "%02X!%02X ", *buf1, *buf2);
+ }
+ buf1++;
+ buf2++;
+ i++;
+ if (i == 16 || n == 0) {
+ print_printable(f, buf1 - i, i);
+ i = 0;
+ }
+ }
+ return result;
+}
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..295ae36
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,42 @@
+/**
+ * @file test.h
+ * @brief Utilities for tests
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_TESTS_TEST_H
+#define HAVE_TESTS_TEST_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "tap/basic.h"
+
+/*** Useful utilities when writing tests ***/
+
+/* Pretty prints bytes as 32 hex digits per line followed by the
+ * printable charaters.
+ */
+void print_buffer(FILE *f, const void* buf, size_t n);
+
+/* Pretty prints two buffers of the same size, highlighting where they
+ * differ.
+ */
+int cmp_buffers(FILE *f, const void* buf1, const void* buf2, size_t n);
+
+#endif
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:43 UTC
Permalink
---
tests/childproc.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/childproc.h | 68 ++++++++++++++++++++++++
tests/makefile | 2 +-
tests/test.h | 1 +
4 files changed, 225 insertions(+), 1 deletion(-)
create mode 100644 tests/childproc.c
create mode 100644 tests/childproc.h

diff --git a/tests/childproc.c b/tests/childproc.c
new file mode 100644
index 0000000..6029d05
--- /dev/null
+++ b/tests/childproc.c
@@ -0,0 +1,155 @@
+/**
+ * @file childproc.c
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "childproc.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <stddef.h>
+#include <poll.h>
+
+#define CHILDERR_DEV_NULL 100
+#define CHILDERR_DUP 101
+#define CHILDERR_EXEC 102
+
+struct childproc {
+ pid_t pid;
+ int running;
+};
+
+int childproc_new(struct childproc **proc)
+{
+ struct childproc *cp = malloc(sizeof(*cp));
+ *proc = 0;
+ if (cp == 0)
+ return -ENOMEM;
+ memset(cp, 0, sizeof(*cp));
+ *proc = cp;
+ return 0;
+}
+
+int childproc_free(struct childproc *proc)
+{
+ if (proc->running)
+ childproc_kill(proc, NULL);
+ free(proc);
+ return 0;
+}
+
+int childproc_kill(struct childproc *proc, int *status)
+{
+ if (!proc->running)
+ return -EALREADY;
+ if (kill(proc->pid, SIGINT) == -1)
+ return -errno;
+ if (waitpid(proc->pid, status, 0) == -1)
+ return -errno;
+ proc->running = 0;
+ return 0;
+}
+
+int childproc_spawn(struct childproc *proc, const char *config)
+{
+ int infds[2];
+ int nullfd;
+ pid_t child;
+ int len;
+ int err;
+
+ if (proc->running)
+ return -EALREADY;
+
+ if (pipe(infds) == -1)
+ return -errno;
+
+ child = fork();
+ if (child == -1) {
+ err = errno;
+ close(infds[0]);
+ close(infds[1]);
+ return -err;
+ } else if (child == 0) {
+ /* in child - redirect stdout and stderr to /dev/null
+ * and pipe to stdin */
+ if ((nullfd = open("/dev/null", O_WRONLY)) == -1)
+ _exit(CHILDERR_DEV_NULL);
+ if (dup2(nullfd, STDERR_FILENO) == -1)
+ _exit(CHILDERR_DUP);
+ if (dup2(nullfd, STDOUT_FILENO) == -1)
+ _exit(CHILDERR_DUP);
+ if (dup2(infds[0], STDIN_FILENO) == -1)
+ _exit(CHILDERR_DUP);
+ close(infds[0]);
+ close(infds[1]);
+
+ int argc = 0;
+ const char *argv[9];
+ memset(argv, 0, sizeof(argv));
+ argv[argc++] = "ptp4l";
+ argv[argc++] = "-S"; /* sw timestamps */
+ /* network interface */
+ argv[argc++] = "-i";
+ argv[argc++] = "lo";
+ if (config) {
+ /* config from stdin */
+ argv[argc++] = "-f";
+ argv[argc++] = "-";
+ }
+
+ execv("../ptp4l", (char * const *)argv);
+ /* if execv returns, an error occured */
+ _exit(CHILDERR_EXEC);
+ } else {
+ /* in parent */
+ close(infds[0]);
+ proc->running = 1;
+ proc->pid = child;
+
+ if (config) {
+ len = strlen(config);
+ if ((err = write(infds[1], config, len)) == -1) {
+ printf("failed to write config file %m\n");
+ goto kill_child;
+ }
+ if (err < len)
+ printf("Only able to partially write config file %d < %d\n", err, len);
+ }
+ close(infds[1]);
+ return 0;
+ }
+kill_child:
+ childproc_kill(proc, NULL);
+ return -1;
+}
+
+int childproc_pid(struct childproc *proc)
+{
+ if (proc->running)
+ return proc->pid;
+ return -1;
+}
diff --git a/tests/childproc.h b/tests/childproc.h
new file mode 100644
index 0000000..0991ade
--- /dev/null
+++ b/tests/childproc.h
@@ -0,0 +1,68 @@
+/**
+ * @file childproc.h
+ * @brief Utilities for forking a ptp4l process to test
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_TESTS_CHILDPROC_H
+#define HAVE_TESTS_CHILDPROC_H
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+struct childproc;
+
+/**
+ * Allocate a new childproc struct.
+ * @param proc New childproc is returned in this arg
+ * @return Zero on success, negative on failure
+ */
+int childproc_new(struct childproc **proc);
+
+/**
+ * Frees a childproc struct. Kills the process if it's running.
+ * @param proc The child proc to free
+ * @return Zero on success, negative on failure
+ */
+int childproc_free(struct childproc *proc);
+
+/**
+ * Forks a child ptp4l process.
+ *
+ * Fails if process is already running.
+ * @param proc The child proc to spawn
+ * @param config An optional config string that will be passed as the
+ * configuration file to the ptp4l process. Can be NULL.
+ * @return Zero on success, negative on failure
+ */
+int childproc_spawn(struct childproc *proc, const char *config);
+
+/**
+ * Kills a running child process by sending it a SIGINT.
+ * @param proc The child proc to kill
+ * @param status If not 0, is set to the process' status
+ * @return Zero on success, negative on failure
+ */
+int childproc_kill(struct childproc *proc, int *status);
+
+/**
+ * Returns a child process' pid.
+ * @param proc the childproc
+ * @return the pid
+ */
+int childproc_pid(struct childproc *proc);
+
+#endif
diff --git a/tests/makefile b/tests/makefile
index c44883f..d2e4406 100644
--- a/tests/makefile
+++ b/tests/makefile
@@ -22,7 +22,7 @@ CFLAGS = -Wall -Werror $(INC) $(DEBUG) $(EXTRA_CFLAGS)
LDLIBS = $(EXTRA_LDFLAGS)

PRG = runtests example-t
-TEST_OBJS = tap/basic.o test.o
+TEST_OBJS = tap/basic.o test.o childproc.o
OBJECTS = $(PRG:=.o) $(TEST_OBJS)
SRC = $(OBJECTS:.o=.c)
DEPEND = $(OBJECTS:.o=.d)
diff --git a/tests/test.h b/tests/test.h
index 295ae36..0e86a5a 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -26,6 +26,7 @@
#include <string.h>

#include "tap/basic.h"
+#include "childproc.h"

/*** Useful utilities when writing tests ***/
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:44 UTC
Permalink
Setting the SOURCE and BUILD variables in runtests allows test to
always find the source and build directories regardless of the cwd.
Use this to build an absolute path to the ptp4l binary so that
runtests be run from any directory. Users will not need to be in the
tests directory to run an individual test.
---
tests/README.org | 6 +++---
tests/childproc.c | 23 ++++++++++++++++++++++-
tests/makefile | 6 ++++--
3 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/tests/README.org b/tests/README.org
index 6088eaa..2d45617 100644
--- a/tests/README.org
+++ b/tests/README.org
@@ -40,7 +40,7 @@ To investigate why a particular test is failing you can run a single
test program to see it's output..

#+BEGIN_EXAMPLE
- $ tests/runtests -o tests/example
+ $ tests/runtests -o example
1..2
ok 1 - example test
not ok 2 - another example test
@@ -52,8 +52,8 @@ could look at the test code to determine the problem.

Note that you could instead run "tests/example-t" directly in this
case. However, the runtests harness sets some environment variables
-when it runs a test. Although example-t test doesn't use the
-variables, it's safer to use runtests to run an individual test.
+when it runs a test and ensures any child processes of the test are
+killed, so it's safer to use runtests to run an individual test.

* Adding tests

diff --git a/tests/childproc.c b/tests/childproc.c
index 6029d05..5478e17 100644
--- a/tests/childproc.c
+++ b/tests/childproc.c
@@ -80,10 +80,29 @@ int childproc_spawn(struct childproc *proc, const char *config)
pid_t child;
int len;
int err;
+ size_t path_size;
+ char *path;
+ char *build_path;

if (proc->running)
return -EALREADY;

+ /* build absolute path to ptp4l based on BUILD env var */
+ build_path = getenv("BUILD");
+ if (build_path == 0)
+ return -ENOENT;
+
+#define PTP4L_REL_PATH "/../ptp4l"
+ path_size = strlen(build_path) + sizeof(PTP4L_REL_PATH) + 1;
+ path = malloc(path_size);
+ if (path == 0)
+ return -ENOMEM;
+ err = snprintf(path, path_size, "%s" PTP4L_REL_PATH, build_path);
+ if (err < 0)
+ return err;
+ else if (err >= path_size)
+ return -ENAMETOOLONG;
+
if (pipe(infds) == -1)
return -errno;

@@ -121,12 +140,14 @@ int childproc_spawn(struct childproc *proc, const char *config)
argv[argc++] = "-";
}

- execv("../ptp4l", (char * const *)argv);
+ execv(path, (char * const *)argv);
/* if execv returns, an error occured */
_exit(CHILDERR_EXEC);
} else {
/* in parent */
close(infds[0]);
+ close(infds[0]);
+ free(path);
proc->running = 1;
proc->pid = child;

diff --git a/tests/makefile b/tests/makefile
index d2e4406..148b6f8 100644
--- a/tests/makefile
+++ b/tests/makefile
@@ -18,7 +18,10 @@
DEBUG =
CC = $(CROSS_COMPILE)gcc
INC = -I..
+DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
CFLAGS = -Wall -Werror $(INC) $(DEBUG) $(EXTRA_CFLAGS)
+# add SOURCE and BUILD symbols for runtests
+CFLAGS += -DSOURCE='"$(DIR)"' -DBUILD='"$(DIR)"'
LDLIBS = $(EXTRA_LDFLAGS)

PRG = runtests example-t
@@ -26,8 +29,7 @@ TEST_OBJS = tap/basic.o test.o childproc.o
OBJECTS = $(PRG:=.o) $(TEST_OBJS)
SRC = $(OBJECTS:.o=.c)
DEPEND = $(OBJECTS:.o=.d)
-srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
-VPATH = $(srcdir)
+VPATH = $(DIR)

all: $(PRG)
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:46 UTC
Permalink
The test itself is dead simple. The bytes of the message it sends and
the message it compares the response against are both hardcoded in
arrays in the test.

Hardcoded byte arrays isn't ideal, but attempting to reuse
msg_pre_send() and msg_post_recv() without individually linking
against many of ptp4l's object files required too many invasive changes.
---
.gitignore | 1 +
tests/TESTS | 1 +
tests/makefile | 3 ++-
tests/null_management-t.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
tests/test.c | 10 +++++++
tests/test.h | 8 ++++++
6 files changed, 88 insertions(+), 1 deletion(-)
create mode 100644 tests/null_management-t.c

diff --git a/.gitignore b/.gitignore
index b91a0a1..d8090c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
/ptp4l
/tests/runtests
/tests/example-t
+/tests/null_management-t
diff --git a/tests/TESTS b/tests/TESTS
index 33a9488..ed13221 100644
--- a/tests/TESTS
+++ b/tests/TESTS
@@ -1 +1,2 @@
example
+null_management
diff --git a/tests/makefile b/tests/makefile
index 148b6f8..a38fc3a 100644
--- a/tests/makefile
+++ b/tests/makefile
@@ -24,7 +24,7 @@ CFLAGS = -Wall -Werror $(INC) $(DEBUG) $(EXTRA_CFLAGS)
CFLAGS += -DSOURCE='"$(DIR)"' -DBUILD='"$(DIR)"'
LDLIBS = $(EXTRA_LDFLAGS)

-PRG = runtests example-t
+PRG = runtests example-t null_management-t
TEST_OBJS = tap/basic.o test.o childproc.o
OBJECTS = $(PRG:=.o) $(TEST_OBJS)
SRC = $(OBJECTS:.o=.c)
@@ -40,6 +40,7 @@ distclean: clean
rm -f $(PRG)

example-t: example-t.o $(TEST_OBJS)
+null_management-t: null_management-t.o $(TEST_OBJS)

runtests: runtests.o

diff --git a/tests/null_management-t.c b/tests/null_management-t.c
new file mode 100644
index 0000000..0947e25
--- /dev/null
+++ b/tests/null_management-t.c
@@ -0,0 +1,66 @@
+/**
+ * @file null_management-t.c
+ * @brief test NULL_MANAGEMENT
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "test.h"
+
+#include "msg.h"
+
+uint8_t get_null_mgmt[] = {
+ 0x0D, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x04, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x00
+};
+
+uint8_t resp_null_mgmt[] = {
+ 0x0D, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xFE, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x04, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x00
+};
+
+#include <unistd.h>
+
+int main()
+{
+ uint8_t buf[1500];
+ struct childproc *proc = test_child(0);
+ plan(1);
+
+ childproc_send(proc, get_null_mgmt, sizeof(get_null_mgmt));
+
+ int len = childproc_recv(proc, buf, sizeof(buf));
+ if (len < 0) {
+ skip("recv failed %d\n", len);
+ } else {
+ if (sizeof(resp_null_mgmt) == len)
+ ok(memcmp(resp_null_mgmt, buf, len) == 0,
+ "recv data matches");
+ else
+ ok(0, "recv len matches");
+
+ }
+ return 0;
+}
diff --git a/tests/test.c b/tests/test.c
index 4d63cde..cddc255 100644
--- a/tests/test.c
+++ b/tests/test.c
@@ -22,6 +22,16 @@
#include <stdio.h>
#include <ctype.h>

+struct childproc *test_child(const char *config)
+{
+ struct childproc *proc;
+ if (childproc_new(&proc) != 0)
+ exit(1);
+ if (childproc_spawn(proc, config) != 0)
+ exit(1);
+ return proc;
+}
+
static void print_printable(FILE *f, const uint8_t *buf, size_t n)
{
int i;
diff --git a/tests/test.h b/tests/test.h
index 0e86a5a..0615053 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -28,6 +28,14 @@
#include "tap/basic.h"
#include "childproc.h"

+/**
+ * Spawns a child ptp4l process. Succeeds or calls exit.
+ * @param config Optional config string that will be the config file
+ * for ptp4l
+ * @return the spawn test child
+ */
+struct childproc *test_child(const char *config);
+
/*** Useful utilities when writing tests ***/

/* Pretty prints bytes as 32 hex digits per line followed by the
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:45 UTC
Permalink
As implemented, there can only be one ptp4l child at a time because
it always binds the same /tmp/ptp4l socket.
---
tests/childproc.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
tests/childproc.h | 18 ++++++++++
2 files changed, 122 insertions(+), 1 deletion(-)

diff --git a/tests/childproc.c b/tests/childproc.c
index 5478e17..3ccaf96 100644
--- a/tests/childproc.c
+++ b/tests/childproc.c
@@ -33,17 +33,64 @@
#include <stddef.h>
#include <poll.h>

+#include "uds.h"
+
#define CHILDERR_DEV_NULL 100
#define CHILDERR_DUP 101
#define CHILDERR_EXEC 102

+/*
+ * A unix domain socket that the test uses to communicate with child
+ * processes.
+ */
+static int uds_skt = -1;
+
struct childproc {
pid_t pid;
int running;
+
+ /* address of child process' skt */
+ struct sockaddr_un addr;
+ socklen_t addr_len;
};

+#define TEST_SKT "/tmp/ptptest"
+#define UDS_FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) /*0660*/
+
+static int init_skt(const char *bind_name)
+{
+ int err;
+ struct sockaddr_un sa;
+ int skt = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (skt < 0) {
+ printf("Failed to create socket: %m\n");
+ return -errno;
+ }
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_LOCAL;
+ strncpy(sa.sun_path, bind_name, sizeof(sa.sun_path));
+ sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
+
+ unlink(bind_name);
+ if (bind(skt, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
+ err = errno;
+ printf("Failed to bind socket: %m\n");
+ close(skt);
+ return -err;
+ }
+ chmod(bind_name, UDS_FILEMODE);
+ return skt;
+}
+
int childproc_new(struct childproc **proc)
{
+ int skt;
+ if (uds_skt == -1) {
+ skt = init_skt(TEST_SKT);
+ if (skt < 0)
+ return skt;
+ uds_skt = skt;
+ }
struct childproc *cp = malloc(sizeof(*cp));
*proc = 0;
if (cp == 0)
@@ -73,6 +120,20 @@ int childproc_kill(struct childproc *proc, int *status)
return 0;
}

+/* Wait until an empty message can be sent to the process
+ * successfully implying it has bound its UDS socket. */
+static int wait_for_connect(struct childproc *proc)
+{
+ uint8_t buf[1];
+ int i;
+ for (i = 0; i < 10; i++) {
+ if (childproc_send(proc, buf, 0) == 0)
+ return 0;
+ usleep(100000);
+ }
+ return -1;
+}
+
int childproc_spawn(struct childproc *proc, const char *config)
{
int infds[2];
@@ -106,6 +167,15 @@ int childproc_spawn(struct childproc *proc, const char *config)
if (pipe(infds) == -1)
return -errno;

+ /* Set address of child process' UDS socket. To support
+ * multiple child processes, would need to configure
+ * different socket addresses for each child. */
+ memset(&proc->addr, 0, sizeof(proc->addr));
+ proc->addr.sun_family = AF_LOCAL;
+ strncpy(proc->addr.sun_path, UDS_PATH, sizeof(proc->addr.sun_path));
+ proc->addr.sun_path[sizeof(proc->addr.sun_path) - 1] = '\0';
+ proc->addr_len = offsetof(struct sockaddr_un, sun_path) + sizeof(UDS_PATH);
+
child = fork();
if (child == -1) {
err = errno;
@@ -125,6 +195,7 @@ int childproc_spawn(struct childproc *proc, const char *config)
_exit(CHILDERR_DUP);
close(infds[0]);
close(infds[1]);
+ close(uds_skt);

int argc = 0;
const char *argv[9];
@@ -161,7 +232,7 @@ int childproc_spawn(struct childproc *proc, const char *config)
printf("Only able to partially write config file %d < %d\n", err, len);
}
close(infds[1]);
- return 0;
+ return wait_for_connect(proc);
}
kill_child:
childproc_kill(proc, NULL);
@@ -174,3 +245,35 @@ int childproc_pid(struct childproc *proc)
return proc->pid;
return -1;
}
+
+ssize_t childproc_send(struct childproc *proc, uint8_t *buf, size_t len)
+{
+ ssize_t result;
+ if (!proc->running)
+ return -1;
+ result = sendto(uds_skt, buf, len, 0,
+ (struct sockaddr*) &proc->addr, proc->addr_len);
+ if (result == -1) {
+ result = -errno;
+ }
+ return result;
+}
+
+ssize_t childproc_recv(struct childproc *proc, uint8_t *buf, size_t len)
+{
+ struct sockaddr_un sa;
+ socklen_t sa_len = sizeof(sa);
+ ssize_t result;
+ if (!proc->running)
+ return -1;
+ result = recvfrom(uds_skt, buf, len, 0,
+ (struct sockaddr*) &sa, &sa_len);
+ if (result == -1) {
+ result = -errno;
+ } else if (sa_len != proc->addr_len ||
+ strcmp(proc->addr.sun_path, sa.sun_path) != 0) {
+ printf("Ignoring message from unknown sender\n");
+ return 0;
+ }
+ return result;
+}
diff --git a/tests/childproc.h b/tests/childproc.h
index 0991ade..a8825fe 100644
--- a/tests/childproc.h
+++ b/tests/childproc.h
@@ -65,4 +65,22 @@ int childproc_kill(struct childproc *proc, int *status);
*/
int childproc_pid(struct childproc *proc);

+/**
+ * Sends a message to a child process.
+ * @param proc the child process
+ * @param buf the message to send
+ * @param len the length of the message
+ * @return the number of bytes sent, or < 0 if an error occurred
+ */
+ssize_t childproc_send(struct childproc *proc, uint8_t *buf, size_t len);
+
+/**
+ * Receives a message from a child process.
+ * @param proc the child process
+ * @param buf the message to write the received message to
+ * @param len the size of the recv buf
+ * @return the number of bytes received, or < 0 if an error occurred
+ */
+ssize_t childproc_recv(struct childproc *proc, uint8_t *buf, size_t len);
+
#endif
--
1.8.0.2
Geoff Salmon
2013-01-03 23:44:40 UTC
Permalink
Adds test directory containing a harness for TAP style tests. Run
"make check" in the root directory to build and run the tests.
Currently there are no tests.
---
.gitignore | 5 +-
makefile | 5 +
tests/TESTS | 0
tests/makefile | 57 +++
tests/runtests.c | 1204 +++++++++++++++++++++++++++++++++++++++++++++++++++
tests/tap/basic.c | 629 +++++++++++++++++++++++++++
tests/tap/basic.h | 134 ++++++
tests/tap/float.c | 67 +++
tests/tap/float.h | 42 ++
tests/tap/libtap.sh | 246 +++++++++++
tests/tap/macros.h | 88 ++++
11 files changed, 2475 insertions(+), 2 deletions(-)
create mode 100644 tests/TESTS
create mode 100644 tests/makefile
create mode 100644 tests/runtests.c
create mode 100644 tests/tap/basic.c
create mode 100644 tests/tap/basic.h
create mode 100644 tests/tap/float.c
create mode 100644 tests/tap/float.h
create mode 100644 tests/tap/libtap.sh
create mode 100644 tests/tap/macros.h

diff --git a/.gitignore b/.gitignore
index e0710ad..886e1ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
-/*.d
-/*.o
+*.d
+*.o
/hwstamp_ctl
/phc2sys
/pmc
/ptp4l
+/tests/runtests
diff --git a/makefile b/makefile
index 53b9e4d..18a5e6b 100644
--- a/makefile
+++ b/makefile
@@ -71,10 +71,15 @@ install: $(PRG)

clean:
rm -f $(OBJECTS) $(DEPEND)
+ make -C tests clean

distclean: clean
rm -f $(PRG)
rm -f .version
+ make -C tests distclean
+
+check: all
+ make -C tests check

# Implicit rule to generate a C source file's dependencies.
%.d: %.c
diff --git a/tests/TESTS b/tests/TESTS
new file mode 100644
index 0000000..e69de29
diff --git a/tests/makefile b/tests/makefile
new file mode 100644
index 0000000..dbb8238
--- /dev/null
+++ b/tests/makefile
@@ -0,0 +1,57 @@
+#
+# Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+DEBUG =
+CC = $(CROSS_COMPILE)gcc
+INC = -I..
+CFLAGS = -Wall -Werror $(INC) $(DEBUG) $(EXTRA_CFLAGS)
+LDLIBS = $(EXTRA_LDFLAGS)
+
+PRG = runtests
+TEST_OBJS = tap/basic.o
+OBJECTS = $(PRG:=.o) $(TEST_OBJS)
+SRC = $(OBJECTS:.o=.c)
+DEPEND = $(OBJECTS:.o=.d)
+srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
+VPATH = $(srcdir)
+
+all: $(PRG)
+
+clean:
+ rm -f $(OBJECTS) $(DEPEND)
+
+distclean: clean
+ rm -f $(PRG)
+
+runtests: runtests.o
+
+check: all
+ ./runtests TESTS
+
+# Implicit rule to generate a C source file's dependencies.
+%.d: %.c
+ @echo DEPEND $<; \
+ rm -f $@; \
+ $(CC) -MM $(CPPFLAGS) $(CFLAGS) $< > $@.$$$$; \
+ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
+ rm -f $@.$$$$
+
+ifneq ($(MAKECMDGOALS), clean)
+ifneq ($(MAKECMDGOALS), distclean)
+-include $(DEPEND)
+endif
+endif
diff --git a/tests/runtests.c b/tests/runtests.c
new file mode 100644
index 0000000..c361def
--- /dev/null
+++ b/tests/runtests.c
@@ -0,0 +1,1204 @@
+/*
+ * Run a set of tests, reporting results.
+ *
+ * Usage:
+ *
+ * runtests [-b <build-dir>] [-s <source-dir>] <test-list>
+ * runtests -o [-b <build-dir>] [-s <source-dir>] <test>
+ *
+ * In the first case, expects a list of executables located in the given file,
+ * one line per executable. For each one, runs it as part of a test suite,
+ * reporting results. Test output should start with a line containing the
+ * number of tests (numbered from 1 to this number), optionally preceded by
+ * "1..", although that line may be given anywhere in the output. Each
+ * additional line should be in the following format:
+ *
+ * ok <number>
+ * not ok <number>
+ * ok <number> # skip
+ * not ok <number> # todo
+ *
+ * where <number> is the number of the test. An optional comment is permitted
+ * after the number if preceded by whitespace. ok indicates success, not ok
+ * indicates failure. "# skip" and "# todo" are a special cases of a comment,
+ * and must start with exactly that formatting. They indicate the test was
+ * skipped for some reason (maybe because it doesn't apply to this platform)
+ * or is testing something known to currently fail. The text following either
+ * "# skip" or "# todo" and whitespace is the reason.
+ *
+ * As a special case, the first line of the output may be in the form:
+ *
+ * 1..0 # skip some reason
+ *
+ * which indicates that this entire test case should be skipped and gives a
+ * reason.
+ *
+ * Any other lines are ignored, although for compliance with the TAP protocol
+ * all lines other than the ones in the above format should be sent to
+ * standard error rather than standard output and start with #.
+ *
+ * This is a subset of TAP as documented in Test::Harness::TAP or
+ * TAP::Parser::Grammar, which comes with Perl.
+ *
+ * If the -o option is given, instead run a single test and display all of its
+ * output. This is intended for use with failing tests so that the person
+ * running the test suite can get more details about what failed.
+ *
+ * If built with the C preprocessor symbols SOURCE and BUILD defined, C TAP
+ * Harness will export those values in the environment so that tests can find
+ * the source and build directory and will look for tests under both
+ * directories. These paths can also be set with the -b and -s command-line
+ * options, which will override anything set at build time.
+ *
+ * Any bug reports, bug fixes, and improvements are very much welcome and
+ * should be sent to the e-mail address below. This program is part of C TAP
+ * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
+ * Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+*/
+
+/* Required for fdopen(), getopt(), and putenv(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+# endif
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+/* sys/time.h must be included before sys/resource.h on some platforms. */
+#include <sys/resource.h>
+
+/* AIX doesn't have WCOREDUMP. */
+#ifndef WCOREDUMP
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
+#endif
+
+/*
+ * The source and build versions of the tests directory. This is used to set
+ * the SOURCE and BUILD environment variables and find test programs, if set.
+ * Normally, this should be set as part of the build process to the test
+ * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively.
+ */
+#ifndef SOURCE
+# define SOURCE NULL
+#endif
+#ifndef BUILD
+# define BUILD NULL
+#endif
+
+/* Test status codes. */
+enum test_status {
+ TEST_FAIL,
+ TEST_PASS,
+ TEST_SKIP,
+ TEST_INVALID
+};
+
+/* Indicates the state of our plan. */
+enum plan_status {
+ PLAN_INIT, /* Nothing seen yet. */
+ PLAN_FIRST, /* Plan seen before any tests. */
+ PLAN_PENDING, /* Test seen and no plan yet. */
+ PLAN_FINAL /* Plan seen after some tests. */
+};
+
+/* Error exit statuses for test processes. */
+#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
+#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
+#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+
+/* Structure to hold data for a set of tests. */
+struct testset {
+ char *file; /* The file name of the test. */
+ char *path; /* The path to the test program. */
+ enum plan_status plan; /* The status of our plan. */
+ unsigned long count; /* Expected count of tests. */
+ unsigned long current; /* The last seen test number. */
+ unsigned int length; /* The length of the last status message. */
+ unsigned long passed; /* Count of passing tests. */
+ unsigned long failed; /* Count of failing lists. */
+ unsigned long skipped; /* Count of skipped tests (passed). */
+ unsigned long allocated; /* The size of the results table. */
+ enum test_status *results; /* Table of results by test number. */
+ unsigned int aborted; /* Whether the set as aborted. */
+ int reported; /* Whether the results were reported. */
+ int status; /* The exit status of the test. */
+ unsigned int all_skipped; /* Whether all tests were skipped. */
+ char *reason; /* Why all tests were skipped. */
+
+ struct testset *next;
+};
+
+/* Structure to hold a linked list of test sets. */
+struct testlist {
+ struct testset *ts;
+ struct testlist *next;
+};
+
+/*
+ * Usage message. Should be used as a printf format with two arguments: the
+ * path to runtests, given twice.
+ */
+static const char usage_message[] = "\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
+ %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+\n\
+Options:\n\
+ -b <build-dir> Set the build directory to <build-dir>\n\
+ -o Run a single test rather than a list of tests\n\
+ -s <source-dir> Set the source directory to <source-dir>\n\
+\n\
+runtests normally runs each test listed in a file whose path is given as\n\
+its command-line argument. With the -o option, it instead runs a single\n\
+test and shows its complete output.\n";
+
+/*
+ * Header used for test output. %s is replaced by the file name of the list
+ * of tests.
+ */
+static const char banner[] = "\n\
+Running all tests listed in %s. If any tests fail, run the failing\n\
+test program with runtests -o to see more details.\n\n";
+
+/* Header for reports of failed tests. */
+static const char header[] = "\n\
+Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
+-------------------------- -------------- ---- ---- ------------------------";
+
+/* Include the file name and line number in malloc failures. */
+#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
+#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
+#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+
+
+/*
+ * Report a fatal error, including the results of strerror, and exit.
+ */
+static void
+sysdie(const char *format, ...)
+{
+ int oerrno;
+ va_list args;
+
+ oerrno = errno;
+ fflush(stdout);
+ fprintf(stderr, "runtests: ");
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_malloc(size_t size, const char *file, int line)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysdie("failed to malloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_realloc(void *p, size_t size, const char *file, int line)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysdie("failed to realloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error and exiting on failure.
+ */
+static char *
+x_strdup(const char *s, const char *file, int line)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysdie("failed to strdup %lu bytes at %s line %d",
+ (unsigned long) len, file, line);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Given a struct timeval, return the number of seconds it represents as a
+ * double. Use difftime() to convert a time_t to a double.
+ */
+static double
+tv_seconds(const struct timeval *tv)
+{
+ return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
+}
+
+
+/*
+ * Given two struct timevals, return the difference in seconds.
+ */
+static double
+tv_diff(const struct timeval *tv1, const struct timeval *tv0)
+{
+ return tv_seconds(tv1) - tv_seconds(tv0);
+}
+
+
+/*
+ * Given two struct timevals, return the sum in seconds as a double.
+ */
+static double
+tv_sum(const struct timeval *tv1, const struct timeval *tv2)
+{
+ return tv_seconds(tv1) + tv_seconds(tv2);
+}
+
+
+/*
+ * Given a pointer to a string, skip any leading whitespace and return a
+ * pointer to the first non-whitespace character.
+ */
+static const char *
+skip_whitespace(const char *p)
+{
+ while (isspace((unsigned char)(*p)))
+ p++;
+ return p;
+}
+
+
+/*
+ * Start a program, connecting its stdout to a pipe on our end and its stderr
+ * to /dev/null, and storing the file descriptor to read from in the two
+ * argument. Returns the PID of the new process. Errors are fatal.
+ */
+static pid_t
+test_start(const char *path, int *fd)
+{
+ int fds[2], errfd;
+ pid_t child;
+
+ if (pipe(fds) == -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't create pipe");
+ }
+ child = fork();
+ if (child == (pid_t) -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ errfd = open("/dev/null", O_WRONLY);
+ if (errfd < 0)
+ _exit(CHILDERR_STDERR);
+ if (dup2(errfd, 2) == -1)
+ _exit(CHILDERR_DUP);
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1)
+ _exit(CHILDERR_DUP);
+
+ /* Now, exec our process. */
+ if (execl(path, path, (char *) 0) == -1)
+ _exit(CHILDERR_EXEC);
+ } else {
+ /* In parent. Close the extra file descriptor. */
+ close(fds[1]);
+ }
+ *fd = fds[0];
+ return child;
+}
+
+
+/*
+ * Back up over the output saying what test we were executing.
+ */
+static void
+test_backspace(struct testset *ts)
+{
+ unsigned int i;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ for (i = 0; i < ts->length; i++)
+ putchar(' ');
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ ts->length = 0;
+}
+
+
+/*
+ * Read the plan line of test output, which should contain the range of test
+ * numbers. We may initialize the testset structure here if we haven't yet
+ * seen a test. Return true if initialization succeeded and the test should
+ * continue, false otherwise.
+ */
+static int
+test_plan(const char *line, struct testset *ts)
+{
+ unsigned long i;
+ long n;
+
+ /*
+ * Accept a plan without the leading 1.. for compatibility with older
+ * versions of runtests. This will only be allowed if we've not yet seen
+ * a test result.
+ */
+ line = skip_whitespace(line);
+ if (strncmp(line, "1..", 3) == 0)
+ line += 3;
+
+ /*
+ * Get the count, check it for validity, and initialize the struct. If we
+ * have something of the form "1..0 # skip foo", the whole file was
+ * skipped; record that. If we do skip the whole file, zero out all of
+ * our statistics, since they're no longer relevant. strtol is called
+ * with a second argument to advance the line pointer past the count to
+ * make it simpler to detect the # skip case.
+ */
+ n = strtol(line, (char **) &line, 10);
+ if (n == 0) {
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0) {
+ line = skip_whitespace(line + 4);
+ if (*line != '\0') {
+ ts->reason = xstrdup(line);
+ ts->reason[strlen(ts->reason) - 1] = '\0';
+ }
+ ts->all_skipped = 1;
+ ts->aborted = 1;
+ ts->count = 0;
+ ts->passed = 0;
+ ts->skipped = 0;
+ ts->failed = 0;
+ return 0;
+ }
+ }
+ }
+ if (n <= 0) {
+ puts("ABORTED (invalid test count)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ if (ts->plan == PLAN_INIT && ts->allocated == 0) {
+ ts->count = n;
+ ts->allocated = n;
+ ts->plan = PLAN_FIRST;
+ ts->results = xmalloc(ts->count * sizeof(enum test_status));
+ for (i = 0; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ } else if (ts->plan == PLAN_PENDING) {
+ if ((unsigned long) n < ts->count) {
+ printf("ABORTED (invalid test number %lu)\n", ts->count);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ ts->count = n;
+ if ((unsigned long) n > ts->allocated) {
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ ts->plan = PLAN_FINAL;
+ }
+ return 1;
+}
+
+
+/*
+ * Given a single line of output from a test, parse it and return the success
+ * status of that test. Anything printed to stdout not matching the form
+ * /^(not )?ok \d+/ is ignored. Sets ts->current to the test number that just
+ * reported status.
+ */
+static void
+test_checkline(const char *line, struct testset *ts)
+{
+ enum test_status status = TEST_PASS;
+ const char *bail;
+ char *end;
+ long number;
+ unsigned long i, current;
+ int outlen;
+
+ /* Before anything, check for a test abort. */
+ bail = strstr(line, "Bail out!");
+ if (bail != NULL) {
+ bail = skip_whitespace(bail + strlen("Bail out!"));
+ if (*bail != '\0') {
+ size_t length;
+
+ length = strlen(bail);
+ if (bail[length - 1] == '\n')
+ length--;
+ test_backspace(ts);
+ printf("ABORTED (%.*s)\n", (int) length, bail);
+ ts->reported = 1;
+ }
+ ts->aborted = 1;
+ return;
+ }
+
+ /*
+ * If the given line isn't newline-terminated, it was too big for an
+ * fgets(), which means ignore it.
+ */
+ if (line[strlen(line) - 1] != '\n')
+ return;
+
+ /* If the line begins with a hash mark, ignore it. */
+ if (line[0] == '#')
+ return;
+
+ /* If we haven't yet seen a plan, look for one. */
+ if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) {
+ if (!test_plan(line, ts))
+ return;
+ } else if (strncmp(line, "1..", 3) == 0) {
+ if (ts->plan == PLAN_PENDING) {
+ if (!test_plan(line, ts))
+ return;
+ } else {
+ puts("ABORTED (multiple plans)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+ }
+
+ /* Parse the line, ignoring something we can't parse. */
+ if (strncmp(line, "not ", 4) == 0) {
+ status = TEST_FAIL;
+ line += 4;
+ }
+ if (strncmp(line, "ok", 2) != 0)
+ return;
+ line = skip_whitespace(line + 2);
+ errno = 0;
+ number = strtol(line, &end, 10);
+ if (errno != 0 || end == line)
+ number = ts->current + 1;
+ current = number;
+ if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) {
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* We have a valid test result. Tweak the results array if needed. */
+ if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {
+ ts->plan = PLAN_PENDING;
+ if (current > ts->count)
+ ts->count = current;
+ if (current > ts->allocated) {
+ unsigned long n;
+
+ n = (ts->allocated == 0) ? 32 : ts->allocated * 2;
+ if (n < current)
+ n = current;
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < n; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ }
+
+ /*
+ * Handle directives. We should probably do something more interesting
+ * with unexpected passes of todo tests.
+ */
+ while (isdigit((unsigned char)(*line)))
+ line++;
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0)
+ status = TEST_SKIP;
+ if (strncasecmp(line, "todo", 4) == 0)
+ status = (status == TEST_FAIL) ? TEST_SKIP : TEST_FAIL;
+ }
+
+ /* Make sure that the test number is in range and not a duplicate. */
+ if (ts->results[current - 1] != TEST_INVALID) {
+ test_backspace(ts);
+ printf("ABORTED (duplicate test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* Good results. Increment our various counters. */
+ switch (status) {
+ case TEST_PASS: ts->passed++; break;
+ case TEST_FAIL: ts->failed++; break;
+ case TEST_SKIP: ts->skipped++; break;
+ case TEST_INVALID: break;
+ }
+ ts->current = current;
+ ts->results[current - 1] = status;
+ test_backspace(ts);
+ if (isatty(STDOUT_FILENO)) {
+ outlen = printf("%lu/%lu", current, ts->count);
+ ts->length = (outlen >= 0) ? outlen : 0;
+ fflush(stdout);
+ }
+}
+
+
+/*
+ * Print out a range of test numbers, returning the number of characters it
+ * took up. Takes the first number, the last number, the number of characters
+ * already printed on the line, and the limit of number of characters the line
+ * can hold. Add a comma and a space before the range if chars indicates that
+ * something has already been printed on the line, and print ... instead if
+ * chars plus the space needed would go over the limit (use a limit of 0 to
+ * disable this).
+ */
+static unsigned int
+test_print_range(unsigned long first, unsigned long last, unsigned int chars,
+ unsigned int limit)
+{
+ unsigned int needed = 0;
+ unsigned long n;
+
+ for (n = first; n > 0; n /= 10)
+ needed++;
+ if (last > first) {
+ for (n = last; n > 0; n /= 10)
+ needed++;
+ needed++;
+ }
+ if (chars > 0)
+ needed += 2;
+ if (limit > 0 && chars + needed > limit) {
+ needed = 0;
+ if (chars <= limit) {
+ if (chars > 0) {
+ printf(", ");
+ needed += 2;
+ }
+ printf("...");
+ needed += 3;
+ }
+ } else {
+ if (chars > 0)
+ printf(", ");
+ if (last > first)
+ printf("%lu-", first);
+ printf("%lu", last);
+ }
+ return needed;
+}
+
+
+/*
+ * Summarize a single test set. The second argument is 0 if the set exited
+ * cleanly, a positive integer representing the exit status if it exited
+ * with a non-zero status, and a negative integer representing the signal
+ * that terminated it if it was killed by a signal.
+ */
+static void
+test_summarize(struct testset *ts, int status)
+{
+ unsigned long i;
+ unsigned long missing = 0;
+ unsigned long failed = 0;
+ unsigned long first = 0;
+ unsigned long last = 0;
+
+ if (ts->aborted) {
+ fputs("ABORTED", stdout);
+ if (ts->count > 0)
+ printf(" (passed %lu/%lu)", ts->passed, ts->count - ts->skipped);
+ } else {
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ if (missing == 0)
+ fputs("MISSED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ missing++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (missing && !failed)
+ fputs("; ", stdout);
+ if (failed == 0)
+ fputs("FAILED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ failed++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ if (!missing && !failed) {
+ fputs(!status ? "ok" : "dubious", stdout);
+ if (ts->skipped > 0) {
+ if (ts->skipped == 1)
+ printf(" (skipped %lu test)", ts->skipped);
+ else
+ printf(" (skipped %lu tests)", ts->skipped);
+ }
+ }
+ }
+ if (status > 0)
+ printf(" (exit status %d)", status);
+ else if (status < 0)
+ printf(" (killed by signal %d%s)", -status,
+ WCOREDUMP(ts->status) ? ", core dumped" : "");
+ putchar('\n');
+}
+
+
+/*
+ * Given a test set, analyze the results, classify the exit status, handle a
+ * few special error messages, and then pass it along to test_summarize() for
+ * the regular output. Returns true if the test set ran successfully and all
+ * tests passed or were skipped, false otherwise.
+ */
+static int
+test_analyze(struct testset *ts)
+{
+ if (ts->reported)
+ return 0;
+ if (ts->all_skipped) {
+ if (ts->reason == NULL)
+ puts("skipped");
+ else
+ printf("skipped (%s)\n", ts->reason);
+ return 1;
+ } else if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
+ switch (WEXITSTATUS(ts->status)) {
+ case CHILDERR_DUP:
+ if (!ts->reported)
+ puts("ABORTED (can't dup file descriptors)");
+ break;
+ case CHILDERR_EXEC:
+ if (!ts->reported)
+ puts("ABORTED (execution failed -- not found?)");
+ break;
+ case CHILDERR_STDERR:
+ if (!ts->reported)
+ puts("ABORTED (can't open /dev/null)");
+ break;
+ default:
+ test_summarize(ts, WEXITSTATUS(ts->status));
+ break;
+ }
+ return 0;
+ } else if (WIFSIGNALED(ts->status)) {
+ test_summarize(ts, -WTERMSIG(ts->status));
+ return 0;
+ } else if (ts->plan != PLAN_FIRST && ts->plan != PLAN_FINAL) {
+ puts("ABORTED (no valid test plan)");
+ ts->aborted = 1;
+ return 0;
+ } else {
+ test_summarize(ts, 0);
+ return (ts->failed == 0);
+ }
+}
+
+
+/*
+ * Runs a single test set, accumulating and then reporting the results.
+ * Returns true if the test set was successfully run and all tests passed,
+ * false otherwise.
+ */
+static int
+test_run(struct testset *ts)
+{
+ pid_t testpid, child;
+ int outfd, status;
+ unsigned long i;
+ FILE *output;
+ char buffer[BUFSIZ];
+
+ /* Run the test program. */
+ testpid = test_start(ts->path, &outfd);
+ output = fdopen(outfd, "r");
+ if (!output) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("fdopen failed");
+ }
+
+ /* Pass each line of output to test_checkline(). */
+ while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
+ test_checkline(buffer, ts);
+ if (ferror(output) || ts->plan == PLAN_INIT)
+ ts->aborted = 1;
+ test_backspace(ts);
+
+ /*
+ * Consume the rest of the test output, close the output descriptor,
+ * retrieve the exit status, and pass that information to test_analyze()
+ * for eventual output.
+ */
+ while (fgets(buffer, sizeof(buffer), output))
+ ;
+ fclose(output);
+ child = waitpid(testpid, &ts->status, 0);
+ if (child == (pid_t) -1) {
+ if (!ts->reported) {
+ puts("ABORTED");
+ fflush(stdout);
+ }
+ sysdie("waitpid for %u failed", (unsigned int) testpid);
+ }
+ if (ts->all_skipped)
+ ts->aborted = 0;
+ status = test_analyze(ts);
+
+ /* Convert missing tests to failed tests. */
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ ts->failed++;
+ ts->results[i] = TEST_FAIL;
+ status = 0;
+ }
+ }
+ return status;
+}
+
+
+/* Summarize a list of test failures. */
+static void
+test_fail_summary(const struct testlist *fails)
+{
+ struct testset *ts;
+ unsigned int chars;
+ unsigned long i, first, last, total;
+
+ puts(header);
+
+ /* Failed Set Fail/Total (%) Skip Stat Failing (25)
+ -------------------------- -------------- ---- ---- -------------- */
+ for (; fails; fails = fails->next) {
+ ts = fails->ts;
+ total = ts->count - ts->skipped;
+ printf("%-26.26s %4lu/%-4lu %3.0f%% %4lu ", ts->file, ts->failed,
+ total, total ? (ts->failed * 100.0) / total : 0,
+ ts->skipped);
+ if (WIFEXITED(ts->status))
+ printf("%4d ", WEXITSTATUS(ts->status));
+ else
+ printf(" -- ");
+ if (ts->aborted) {
+ puts("aborted");
+ continue;
+ }
+ chars = 0;
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (first != 0 && i == last)
+ last = i + 1;
+ else {
+ if (first != 0)
+ chars += test_print_range(first, last, chars, 19);
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first != 0)
+ test_print_range(first, last, chars, 19);
+ putchar('\n');
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ if (ts->reason != NULL)
+ free(ts->reason);
+ free(ts);
+ }
+}
+
+
+/*
+ * Given the name of a test, a pointer to the testset struct, and the source
+ * and build directories, find the test. We try first relative to the current
+ * directory, then in the build directory (if not NULL), then in the source
+ * directory. In each of those directories, we first try a "-t" extension and
+ * then a ".t" extension. When we find an executable program, we fill in the
+ * path member of the testset struct. If none of those paths are executable,
+ * just fill in the name of the test with "-t" appended.
+ *
+ * The caller is responsible for freeing the path member of the testset
+ * struct.
+ */
+static void
+find_test(const char *name, struct testset *ts, const char *source,
+ const char *build)
+{
+ char *path;
+ const char *bases[4];
+ unsigned int i;
+
+ bases[0] = ".";
+ bases[1] = build;
+ bases[2] = source;
+ bases[3] = NULL;
+
+ for (i = 0; i < 3; i++) {
+ if (bases[i] == NULL)
+ continue;
+ path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
+ sprintf(path, "%s/%s-t", bases[i], name);
+ if (access(path, X_OK) != 0)
+ path[strlen(path) - 2] = '.';
+ if (access(path, X_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ if (path == NULL) {
+ path = xmalloc(strlen(name) + 3);
+ sprintf(path, "%s-t", name);
+ }
+ ts->path = path;
+}
+
+
+/*
+ * Run a batch of tests from a given file listing each test on a line by
+ * itself. Takes two additional parameters: the root of the source directory
+ * and the root of the build directory. Test programs will be first searched
+ * for in the current directory, then the build directory, then the source
+ * directory. The file must be rewindable. Returns true iff all tests
+ * passed.
+ */
+static int
+test_batch(const char *testlist, const char *source, const char *build)
+{
+ FILE *tests;
+ unsigned int length, i;
+ unsigned int longest = 0;
+ char buffer[BUFSIZ];
+ unsigned int line;
+ struct testset *ts, *tmp, *all_tests = 0;
+ struct timeval start, end;
+ struct rusage stats;
+ struct testlist *failhead = NULL;
+ struct testlist *failtail = NULL;
+ struct testlist *next;
+ unsigned long total = 0;
+ unsigned long passed = 0;
+ unsigned long skipped = 0;
+ unsigned long failed = 0;
+ unsigned long aborted = 0;
+
+ /*
+ * Open our file of tests to run and scan it, checking for lines that
+ * are too long and searching for the longest line.
+ */
+ if (strcmp(testlist, "-") == 0) {
+ tests = stdin;
+ } else {
+ tests = fopen(testlist, "r");
+ }
+ if (!tests)
+ sysdie("can't open %s", testlist);
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), tests)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%u: line too long\n", testlist, line);
+ exit(1);
+ }
+ if (length > longest)
+ longest = length;
+
+ ts = xmalloc(sizeof(struct testset));
+ memset(ts, 0, sizeof(struct testset));
+ buffer[length] = '\0';
+
+ ts->plan = PLAN_INIT;
+ ts->file = xstrdup(buffer);
+ ts->next = NULL;
+ if (all_tests) {
+ tmp = all_tests;
+ while (tmp->next)
+ tmp = tmp->next;
+ tmp->next = ts;
+ } else {
+ all_tests = ts;
+ }
+ }
+
+ /*
+ * Add two to longest and round up to the nearest tab stop. This is how
+ * wide the column for printing the current test name will be.
+ */
+ longest += 2;
+ if (longest % 8)
+ longest += 8 - (longest % 8);
+
+ /* Start the wall clock timer. */
+ gettimeofday(&start, NULL);
+
+ /*
+ * Now, plow through our tests again, running each one. Check line
+ * length again out of paranoia.
+ */
+ line = 0;
+ for(ts = all_tests; ts && (tmp = ts->next, 1); ts = tmp) {
+ line++;
+ length = strlen(ts->file) - 1;
+ fputs(ts->file, stdout);
+ for (i = length; i < longest; i++)
+ putchar('.');
+ if (isatty(STDOUT_FILENO))
+ fflush(stdout);
+ find_test(ts->file, ts, source, build);
+ ts->reason = NULL;
+ if (test_run(ts)) {
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ if (ts->reason != NULL)
+ free(ts->reason);
+ free(ts);
+ } else {
+ if (!failhead) {
+ failhead = xmalloc(sizeof(struct testset));
+ failhead->ts = ts;
+ failhead->next = NULL;
+ failtail = failhead;
+ } else {
+ failtail->next = xmalloc(sizeof(struct testset));
+ failtail = failtail->next;
+ failtail->ts = ts;
+ failtail->next = NULL;
+ }
+ }
+ aborted += ts->aborted;
+ total += ts->count + ts->all_skipped;
+ passed += ts->passed;
+ skipped += ts->skipped + ts->all_skipped;
+ failed += ts->failed;
+ }
+ total -= skipped;
+ fclose(tests);
+
+ /* Stop the timer and get our child resource statistics. */
+ gettimeofday(&end, NULL);
+ getrusage(RUSAGE_CHILDREN, &stats);
+
+ /* Print out our final results. */
+ if (failhead != NULL) {
+ test_fail_summary(failhead);
+ while (failhead != NULL) {
+ next = failhead->next;
+ free(failhead);
+ failhead = next;
+ }
+ }
+ putchar('\n');
+ if (aborted != 0) {
+ if (aborted == 1)
+ printf("Aborted %lu test set", aborted);
+ else
+ printf("Aborted %lu test sets", aborted);
+ printf(", passed %lu/%lu tests", passed, total);
+ }
+ else if (failed == 0)
+ fputs("All tests successful", stdout);
+ else
+ printf("Failed %lu/%lu tests, %.2f%% okay", failed, total,
+ (total - failed) * 100.0 / total);
+ if (skipped != 0) {
+ if (skipped == 1)
+ printf(", %lu test skipped", skipped);
+ else
+ printf(", %lu tests skipped", skipped);
+ }
+ puts(".");
+ printf("Files=%u, Tests=%lu", line, total);
+ printf(", %.2f seconds", tv_diff(&end, &start));
+ printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
+ tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
+ tv_sum(&stats.ru_utime, &stats.ru_stime));
+ return (failed == 0 && aborted == 0);
+}
+
+
+/*
+ * Run a single test case. This involves just running the test program after
+ * having done the environment setup and finding the test program.
+ */
+static void
+test_single(const char *program, const char *source, const char *build)
+{
+ struct testset ts;
+
+ memset(&ts, 0, sizeof(ts));
+ find_test(program, &ts, source, build);
+ if (execl(ts.path, ts.path, (char *) 0) == -1)
+ sysdie("cannot exec %s", ts.path);
+}
+
+
+/*
+ * Main routine. Set the SOURCE and BUILD environment variables and then,
+ * given a file listing tests, run each test listed.
+ */
+int
+main(int argc, char *argv[])
+{
+ int option;
+ int status = 0;
+ int single = 0;
+ char *source_env = NULL;
+ char *build_env = NULL;
+ const char *list;
+ const char *source = SOURCE;
+ const char *build = BUILD;
+
+ while ((option = getopt(argc, argv, "b:hos:")) != EOF) {
+ switch (option) {
+ case 'b':
+ build = optarg;
+ break;
+ case 'h':
+ printf(usage_message, argv[0], argv[0]);
+ exit(0);
+ break;
+ case 'o':
+ single = 1;
+ break;
+ case 's':
+ source = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ }
+ if (argc - optind != 1) {
+ fprintf(stderr, usage_message, argv[0], argv[0]);
+ exit(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (source != NULL) {
+ source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
+ sprintf(source_env, "SOURCE=%s", source);
+ if (putenv(source_env) != 0)
+ sysdie("cannot set SOURCE in the environment");
+ }
+ if (build != NULL) {
+ build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1);
+ sprintf(build_env, "BUILD=%s", build);
+ if (putenv(build_env) != 0)
+ sysdie("cannot set BUILD in the environment");
+ }
+
+ if (single)
+ test_single(argv[0], source, build);
+ else {
+ list = strrchr(argv[0], '/');
+ if (list == NULL)
+ list = argv[0];
+ else
+ list++;
+ if (strcmp(list, "-") == 0) {
+ printf(banner, "stdin");
+ } else {
+ printf(banner, list);
+ }
+ status = test_batch(argv[0], source, build) ? 0 : 1;
+ }
+
+ /* For valgrind cleanliness. */
+ if (source_env != NULL) {
+ putenv((char *) "SOURCE=");
+ free(source_env);
+ }
+ if (build_env != NULL) {
+ putenv((char *) "BUILD=");
+ free(build_env);
+ }
+ exit(status);
+}
diff --git a/tests/tap/basic.c b/tests/tap/basic.c
new file mode 100644
index 0000000..e8196fc
--- /dev/null
+++ b/tests/tap/basic.c
@@ -0,0 +1,629 @@
+/*
+ * Some utility routines for writing tests.
+ *
+ * Here are a variety of utility routines for writing tests compatible with
+ * the TAP protocol. All routines of the form ok() or is*() take a test
+ * number and some number of appropriate arguments, check to be sure the
+ * results match the expected output using the arguments, and print out
+ * something appropriate for that test number. Other utility routines help in
+ * constructing more complex tests, skipping tests, reporting errors, setting
+ * up the TAP output format, or finding things in the test environment.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+# include <direct.h>
+#else
+# include <sys/stat.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <tests/tap/basic.h>
+
+/* Windows provides mkdir and rmdir under different names. */
+#ifdef _WIN32
+# define mkdir(p, m) _mkdir(p)
+# define rmdir(p) _rmdir(p)
+#endif
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+unsigned long testnum = 1;
+
+/*
+ * Status information stored so that we can give a test summary at the end of
+ * the test case. We store the planned final test and the count of failures.
+ * We can get the highest test count from testnum.
+ *
+ * We also store the PID of the process that called plan() and only summarize
+ * results when that process exits, so as to not misreport results in forked
+ * processes.
+ *
+ * If _lazy is true, we're doing lazy planning and will print out the plan
+ * based on the last test number at the end of testing.
+ */
+static unsigned long _planned = 0;
+static unsigned long _failed = 0;
+static pid_t _process = 0;
+static int _lazy = 0;
+
+
+/*
+ * Our exit handler. Called on completion of the test to report a summary of
+ * results provided we're still in the original process. This also handles
+ * printing out the plan if we used plan_lazy(), although that's suppressed if
+ * we never ran a test (due to an early bail, for example).
+ */
+static void
+finish(void)
+{
+ unsigned long highest = testnum - 1;
+
+ if (_planned == 0 && !_lazy)
+ return;
+ fflush(stderr);
+ if (_process != 0 && getpid() == _process) {
+ if (_lazy && highest > 0) {
+ printf("1..%lu\n", highest);
+ _planned = highest;
+ }
+ if (_planned > highest)
+ printf("# Looks like you planned %lu test%s but only ran %lu\n",
+ _planned, (_planned > 1 ? "s" : ""), highest);
+ else if (_planned < highest)
+ printf("# Looks like you planned %lu test%s but ran %lu extra\n",
+ _planned, (_planned > 1 ? "s" : ""), highest - _planned);
+ else if (_failed > 0)
+ printf("# Looks like you failed %lu test%s of %lu\n", _failed,
+ (_failed > 1 ? "s" : ""), _planned);
+ else if (_planned > 1)
+ printf("# All %lu tests successful or skipped\n", _planned);
+ else
+ printf("# %lu test successful or skipped\n", _planned);
+ }
+}
+
+
+/*
+ * Initialize things. Turns on line buffering on stdout and then prints out
+ * the number of tests in the test suite.
+ */
+void
+plan(unsigned long count)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ fflush(stderr);
+ printf("1..%lu\n", count);
+ testnum = 1;
+ _planned = count;
+ _process = getpid();
+ atexit(finish);
+}
+
+
+/*
+ * Initialize things for lazy planning, where we'll automatically print out a
+ * plan at the end of the program. Turns on line buffering on stdout as well.
+ */
+void
+plan_lazy(void)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ testnum = 1;
+ _process = getpid();
+ _lazy = 1;
+ atexit(finish);
+}
+
+
+/*
+ * Skip the entire test suite and exits. Should be called instead of plan(),
+ * not after it, since it prints out a special plan line.
+ */
+void
+skip_all(const char *format, ...)
+{
+ fflush(stderr);
+ printf("1..0 # skip");
+ if (format != NULL) {
+ va_list args;
+
+ putchar(' ');
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ exit(0);
+}
+
+
+/*
+ * Print the test description.
+ */
+static void
+print_desc(const char *format, va_list args)
+{
+ printf(" - ");
+ vprintf(format, args);
+}
+
+
+/*
+ * Takes a boolean success value and assumes the test passes if that value
+ * is true and fails if that value is false.
+ */
+void
+ok(int success, const char *format, ...)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Same as ok(), but takes the format arguments as a va_list.
+ */
+void
+okv(int success, const char *format, va_list args)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL)
+ print_desc(format, args);
+ putchar('\n');
+}
+
+
+/*
+ * Skip a test.
+ */
+void
+skip(const char *reason, ...)
+{
+ fflush(stderr);
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Report the same status on the next count tests.
+ */
+void
+ok_block(unsigned long count, int status, const char *format, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("%sok %lu", status ? "" : "not ", testnum++);
+ if (!status)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Skip the next count tests.
+ */
+void
+skip_block(unsigned long count, const char *reason, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Takes an expected integer and a seen integer and assumes the test passes
+ * if those two numbers match.
+ */
+void
+is_int(long wanted, long seen, const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %ld\n# seen: %ld\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes a string and what the string should be, and assumes the test passes
+ * if those strings match (using strcmp).
+ */
+void
+is_string(const char *wanted, const char *seen, const char *format, ...)
+{
+ if (wanted == NULL)
+ wanted = "(null)";
+ if (seen == NULL)
+ seen = "(null)";
+ fflush(stderr);
+ if (strcmp(wanted, seen) == 0)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %s\n# seen: %s\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes an expected unsigned long and a seen unsigned long and assumes the
+ * test passes if the two numbers match. Otherwise, reports them in hex.
+ */
+void
+is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %lx\n# seen: %lx\n", (unsigned long) wanted,
+ (unsigned long) seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Bail out with an error.
+ */
+void
+bail(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ exit(1);
+}
+
+
+/*
+ * Bail out with an error, appending strerror(errno).
+ */
+void
+sysbail(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/*
+ * Report a diagnostic to stderr.
+ */
+void
+diag(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+}
+
+
+/*
+ * Report a diagnostic to stderr, appending strerror(errno).
+ */
+void
+sysdiag(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+}
+
+
+/*
+ * Allocate cleared memory, reporting a fatal error with bail on failure.
+ */
+void *
+bcalloc(size_t n, size_t size)
+{
+ void *p;
+
+ p = calloc(n, size);
+ if (p == NULL)
+ sysbail("failed to calloc %lu", (unsigned long)(n * size));
+ return p;
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+bmalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysbail("failed to malloc %lu", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+brealloc(void *p, size_t size)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysbail("failed to realloc %lu bytes", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error with bail on failure.
+ */
+char *
+bstrdup(const char *s)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysbail("failed to strdup %lu bytes", (unsigned long) len);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Copy up to n characters of a string, reporting a fatal error with bail on
+ * failure. Don't use the system strndup function, since it may not exist and
+ * the TAP library doesn't assume any portability support.
+ */
+char *
+bstrndup(const char *s, size_t n)
+{
+ const char *p;
+ char *copy;
+ size_t length;
+
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+ ;
+ length = p - s;
+ copy = malloc(length + 1);
+ if (p == NULL)
+ sysbail("failed to strndup %lu bytes", (unsigned long) length);
+ memcpy(copy, s, length);
+ copy[length] = '\0';
+ return copy;
+}
+
+
+/*
+ * Locate a test file. Given the partial path to a file, look under BUILD and
+ * then SOURCE for the file and return the full path to the file. Returns
+ * NULL if the file doesn't exist. A non-NULL return should be freed with
+ * test_file_path_free().
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_file_path(const char *file)
+{
+ char *base;
+ char *path = NULL;
+ size_t length;
+ const char *envs[] = { "BUILD", "SOURCE", NULL };
+ int i;
+
+ for (i = 0; envs[i] != NULL; i++) {
+ base = getenv(envs[i]);
+ if (base == NULL)
+ continue;
+ length = strlen(base) + 1 + strlen(file) + 1;
+ path = bmalloc(length);
+ sprintf(path, "%s/%s", base, file);
+ if (access(path, R_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_file_path(). This function exists primarily
+ * for Windows, where memory must be freed from the same library domain that
+ * it was allocated from.
+ */
+void
+test_file_path_free(char *path)
+{
+ if (path != NULL)
+ free(path);
+}
+
+
+/*
+ * Create a temporary directory, tmp, under BUILD if set and the current
+ * directory if it does not. Returns the path to the temporary directory in
+ * newly allocated memory, and calls bail on any failure. The return value
+ * should be freed with test_tmpdir_free.
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_tmpdir(void)
+{
+ const char *build;
+ char *path = NULL;
+ size_t length;
+
+ build = getenv("BUILD");
+ if (build == NULL)
+ build = ".";
+ length = strlen(build) + strlen("/tmp") + 1;
+ path = bmalloc(length);
+ sprintf(path, "%s/tmp", build);
+ if (access(path, X_OK) < 0)
+ if (mkdir(path, 0777) < 0)
+ sysbail("error creating temporary directory %s", path);
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_tmpdir() and attempt to remove the
+ * directory. If we can't delete the directory, don't worry; something else
+ * that hasn't yet cleaned up may still be using it.
+ */
+void
+test_tmpdir_free(char *path)
+{
+ rmdir(path);
+ if (path != NULL)
+ free(path);
+}
diff --git a/tests/tap/basic.h b/tests/tap/basic.h
new file mode 100644
index 0000000..fa4adaf
--- /dev/null
+++ b/tests/tap/basic.h
@@ -0,0 +1,134 @@
+/*
+ * Basic utility routines for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_BASIC_H
+#define TAP_BASIC_H 1
+
+#include <tests/tap/macros.h>
+#include <stdarg.h> /* va_list */
+#include <sys/types.h> /* size_t */
+
+/*
+ * Used for iterating through arrays. ARRAY_SIZE returns the number of
+ * elements in the array (useful for a < upper bound in a for loop) and
+ * ARRAY_END returns a pointer to the element past the end (ISO C99 makes it
+ * legal to refer to such a pointer as long as it's never dereferenced).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+
+BEGIN_DECLS
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+extern unsigned long testnum;
+
+/* Print out the number of tests and set standard output to line buffered. */
+void plan(unsigned long count);
+
+/*
+ * Prepare for lazy planning, in which the plan will be printed automatically
+ * at the end of the test program.
+ */
+void plan_lazy(void);
+
+/* Skip the entire test suite. Call instead of plan. */
+void skip_all(const char *format, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+
+/*
+ * Basic reporting functions. The okv() function is the same as ok() but
+ * takes the test description as a va_list to make it easier to reuse the
+ * reporting infrastructure when writing new tests.
+ */
+void ok(int success, const char *format, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void okv(int success, const char *format, va_list args);
+void skip(const char *reason, ...)
+ __attribute__((__format__(printf, 1, 2)));
+
+/* Report the same status on, or skip, the next count tests. */
+void ok_block(unsigned long count, int success, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void skip_block(unsigned long count, const char *reason, ...)
+ __attribute__((__format__(printf, 2, 3)));
+
+/* Check an expected value against a seen value. */
+void is_int(long wanted, long seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_string(const char *wanted, const char *seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+
+/* Bail out with an error. sysbail appends strerror(errno). */
+void bail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+void sysbail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+
+/* Report a diagnostic to stderr prefixed with #. */
+void diag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+void sysdiag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+
+/* Allocate memory, reporting a fatal error with bail on failure. */
+void *bcalloc(size_t, size_t)
+ __attribute__((__alloc_size__(1, 2), __malloc__));
+void *bmalloc(size_t)
+ __attribute__((__alloc_size__(1), __malloc__));
+void *brealloc(void *, size_t)
+ __attribute__((__alloc_size__(2), __malloc__));
+char *bstrdup(const char *)
+ __attribute__((__malloc__, __nonnull__));
+char *bstrndup(const char *, size_t)
+ __attribute__((__malloc__, __nonnull__));
+
+/*
+ * Find a test file under BUILD or SOURCE, returning the full path. The
+ * returned path should be freed with test_file_path_free().
+ */
+char *test_file_path(const char *file)
+ __attribute__((__malloc__, __nonnull__));
+void test_file_path_free(char *path);
+
+/*
+ * Create a temporary directory relative to BUILD and return the path. The
+ * returned path should be freed with test_tmpdir_free.
+ */
+char *test_tmpdir(void)
+ __attribute__((__malloc__));
+void test_tmpdir_free(char *path);
+
+END_DECLS
+
+#endif /* TAP_BASIC_H */
diff --git a/tests/tap/float.c b/tests/tap/float.c
new file mode 100644
index 0000000..67dd555
--- /dev/null
+++ b/tests/tap/float.c
@@ -0,0 +1,67 @@
+/*
+ * Utility routines for writing floating point tests.
+ *
+ * Currently provides only one function, which checks whether a double is
+ * equal to an expected value within a given epsilon. This is broken into a
+ * separate source file from the rest of the basic C TAP library because it
+ * may require linking with -lm on some platforms, and the package may not
+ * otherwise care about floating point.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/* Required for isnan() and isinf(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600
+# endif
+#endif
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/float.h>
+
+/*
+ * Takes an expected double and a seen double and assumes the test passes if
+ * those two numbers are within delta of each other.
+ */
+void
+is_double(double wanted, double seen, double epsilon, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ fflush(stderr);
+ if ((isnan(wanted) && isnan(seen))
+ || (isinf(wanted) && isinf(seen) && wanted == seen)
+ || fabs(wanted - seen) <= epsilon)
+ okv(1, format, args);
+ else {
+ printf("# wanted: %g\n# seen: %g\n", wanted, seen);
+ okv(0, format, args);
+ }
+}
diff --git a/tests/tap/float.h b/tests/tap/float.h
new file mode 100644
index 0000000..7464535
--- /dev/null
+++ b/tests/tap/float.h
@@ -0,0 +1,42 @@
+/*
+ * Floating point check function for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_FLOAT_H
+#define TAP_FLOAT_H 1
+
+#include <tests/tap/macros.h>
+
+BEGIN_DECLS
+
+/* Check an expected value against a seen value within epsilon. */
+void is_double(double wanted, double seen, double epsilon,
+ const char *format, ...)
+ __attribute__((__format__(printf, 4, 5)));
+
+END_DECLS
+
+#endif /* TAP_FLOAT_H */
diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh
new file mode 100644
index 0000000..f9347d8
--- /dev/null
+++ b/tests/tap/libtap.sh
@@ -0,0 +1,246 @@
+# Shell function library for test cases.
+#
+# Note that while many of the functions in this library could benefit from
+# using "local" to avoid possibly hammering global variables, Solaris /bin/sh
+# doesn't support local and this library aspires to be portable to Solaris
+# Bourne shell. Instead, all private variables are prefixed with "tap_".
+#
+# This file provides a TAP-compatible shell function library useful for
+# writing test cases. It is part of C TAP Harness, which can be found at
+# <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+#
+# Written by Russ Allbery <***@stanford.edu>
+# Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+# Copyright 2006, 2007, 2008
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+# Print out the number of test cases we expect to run.
+plan () {
+ count=1
+ planned="$1"
+ failed=0
+ echo "1..$1"
+ trap finish 0
+}
+
+# Prepare for lazy planning.
+plan_lazy () {
+ count=1
+ planned=0
+ failed=0
+ trap finish 0
+}
+
+# Report the test status on exit.
+finish () {
+ tap_highest=`expr "$count" - 1`
+ if [ "$planned" = 0 ] ; then
+ echo "1..$tap_highest"
+ planned="$tap_highest"
+ fi
+ tap_looks='# Looks like you'
+ if [ "$planned" -gt 0 ] ; then
+ if [ "$planned" -gt "$tap_highest" ] ; then
+ if [ "$planned" -gt 1 ] ; then
+ echo "$tap_looks planned $planned tests but only ran" \
+ "$tap_highest"
+ else
+ echo "$tap_looks planned $planned test but only ran" \
+ "$tap_highest"
+ fi
+ elif [ "$planned" -lt "$tap_highest" ] ; then
+ tap_extra=`expr "$tap_highest" - "$planned"`
+ if [ "$planned" -gt 1 ] ; then
+ echo "$tap_looks planned $planned tests but ran" \
+ "$tap_extra extra"
+ else
+ echo "$tap_looks planned $planned test but ran" \
+ "$tap_extra extra"
+ fi
+ elif [ "$failed" -gt 0 ] ; then
+ if [ "$failed" -gt 1 ] ; then
+ echo "$tap_looks failed $failed tests of $planned"
+ else
+ echo "$tap_looks failed $failed test of $planned"
+ fi
+ elif [ "$planned" -gt 1 ] ; then
+ echo "# All $planned tests successful or skipped"
+ else
+ echo "# $planned test successful or skipped"
+ fi
+ fi
+}
+
+# Skip the entire test suite. Should be run instead of plan.
+skip_all () {
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ echo "1..0 # skip $tap_desc"
+ else
+ echo "1..0 # skip"
+ fi
+ exit 0
+}
+
+# ok takes a test description and a command to run and prints success if that
+# command is successful, false otherwise. The count starts at 1 and is
+# updated each time ok is printed.
+ok () {
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ tap_desc=" - $tap_desc"
+ fi
+ shift
+ if "$@" ; then
+ echo ok "$count$tap_desc"
+ else
+ echo not ok "$count$tap_desc"
+ failed=`expr $failed + 1`
+ fi
+ count=`expr $count + 1`
+}
+
+# Skip the next test. Takes the reason why the test is skipped.
+skip () {
+ echo "ok $count # skip $*"
+ count=`expr $count + 1`
+}
+
+# Report the same status on a whole set of tests. Takes the count of tests,
+# the description, and then the command to run to determine the status.
+ok_block () {
+ tap_i=$count
+ tap_end=`expr $count + $1`
+ shift
+ while [ "$tap_i" -lt "$tap_end" ] ; do
+ ok "$@"
+ tap_i=`expr $tap_i + 1`
+ done
+}
+
+# Skip a whole set of tests. Takes the count and then the reason for skipping
+# the test.
+skip_block () {
+ tap_i=$count
+ tap_end=`expr $count + $1`
+ shift
+ while [ "$tap_i" -lt "$tap_end" ] ; do
+ skip "$@"
+ tap_i=`expr $tap_i + 1`
+ done
+}
+
+# Portable variant of printf '%s\n' "$*". In the majority of cases, this
+# function is slower than printf, because the latter is often implemented
+# as a builtin command. The value of the variable IFS is ignored.
+#
+# This macro must not be called via backticks inside double quotes, since this
+# will result in bizarre escaping behavior and lots of extra backslashes on
+# Solaris.
+puts () {
+ cat << EOH
+$@
+EOH
+}
+
+# Run a program expected to succeed, and print ok if it does and produces the
+# correct output. Takes the description, expected exit status, the expected
+# output, the command to run, and then any arguments for that command.
+# Standard output and standard error are combined when analyzing the output of
+# the command.
+#
+# If the command may contain system-specific error messages in its output,
+# add strip_colon_error before the command to post-process its output.
+ok_program () {
+ tap_desc="$1"
+ shift
+ tap_w_status="$1"
+ shift
+ tap_w_output="$1"
+ shift
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ if [ $tap_status = $tap_w_status ] \
+ && [ x"$tap_output" = x"$tap_w_output" ] ; then
+ ok "$tap_desc" true
+ else
+ echo "# saw: ($tap_status) $tap_output"
+ echo "# not: ($tap_w_status) $tap_w_output"
+ ok "$tap_desc" false
+ fi
+}
+
+# Strip a colon and everything after it off the output of a command, as long
+# as that colon comes after at least one whitespace character. (This is done
+# to avoid stripping the name of the program from the start of an error
+# message.) This is used to remove system-specific error messages (coming
+# from strerror, for example).
+strip_colon_error() {
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
+ puts "$tap_output"
+ return $tap_status
+}
+
+# Bail out with an error message.
+bail () {
+ echo 'Bail out!' "$@"
+ exit 1
+}
+
+# Output a diagnostic on standard error, preceded by the required # mark.
+diag () {
+ echo '#' "$@"
+}
+
+# Search for the given file first in $BUILD and then in $SOURCE and echo the
+# path where the file was found, or the empty string if the file wasn't
+# found.
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
+test_file_path () {
+ if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
+ puts "$BUILD/$1"
+ elif [ -n "$SOURCE" ] && [ -f "$SOURCE/$1" ] ; then
+ puts "$SOURCE/$1"
+ else
+ echo ''
+ fi
+}
+
+# Create $BUILD/tmp for use by tests for storing temporary files and return
+# the path (via standard output).
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
+test_tmpdir () {
+ if [ -z "$BUILD" ] ; then
+ tap_tmpdir="./tmp"
+ else
+ tap_tmpdir="$BUILD"/tmp
+ fi
+ if [ ! -d "$tap_tmpdir" ] ; then
+ mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
+ fi
+ puts "$tap_tmpdir"
+}
diff --git a/tests/tap/macros.h b/tests/tap/macros.h
new file mode 100644
index 0000000..33fee42
--- /dev/null
+++ b/tests/tap/macros.h
@@ -0,0 +1,88 @@
+/*
+ * Helpful macros for TAP header files.
+ *
+ * This is not, strictly speaking, related to TAP, but any TAP add-on is
+ * probably going to need these macros, so define them in one place so that
+ * everyone can pull them in.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_MACROS_H
+#define TAP_MACROS_H 1
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros), and only with gcc 2.96 can you use
+ * the attribute __malloc__. 2.96 is very old, so don't bother trying to get
+ * the other attributes to work with GCC versions between 2.7 and 2.96.
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does. For them, suppress warnings about unknown
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names. END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+#endif /* TAP_MACROS_H */
--
1.8.0.2
Geoff Salmon
2013-01-30 14:41:34 UTC
Permalink
Hi
Has anyone looked over these patches?

I've realized that I needed to link in fewer objects than I had
previously thought to use msg_pre_send() and msg_post_recv() from the
tests, so disregard the bit about "invasive changes" in the description
of the last patch.

- Geoff
Post by Geoff Salmon
This is the test harness that was part of the previous patch set I
sent. It supports tap style tests and supports forking a child ptp4l
process. There's one example test and one test sending a GET
NULL_MANAGEMENT message and receiving the RESPONSE.
The test harness itself, tests/runtests.c, and the library for tap
output in tests/tap are slightly modified from the C TAP Harness
<http://www.eyrie.org/~eagle/software/c-tap-harness/> which is
distributed under a "BSD-style license".
tests: adds test harness
tests: add example test and readme
tests: put each test in process group and kill group after test run
tests: Add childproc utility for spawning ptp4l processes in tests
tests: use SOURCE/BUILD env variables set in runtests to find ptp4l
bin
tests: support sending messages to child ptp4l process
tests: test sending and receiving NULL_MANAGEMENT message
.gitignore | 7 +-
makefile | 5 +
tests/README.org | 74 +++
tests/TESTS | 2 +
tests/childproc.c | 279 ++++++++++
tests/childproc.h | 86 +++
tests/example-t.c | 27 +
tests/makefile | 62 +++
tests/null_management-t.c | 66 +++
tests/runtests.c | 1269 +++++++++++++++++++++++++++++++++++++++++++++
tests/tap/basic.c | 629 ++++++++++++++++++++++
tests/tap/basic.h | 134 +++++
tests/tap/float.c | 67 +++
tests/tap/float.h | 42 ++
tests/tap/libtap.sh | 246 +++++++++
tests/tap/macros.h | 88 ++++
tests/test.c | 81 +++
tests/test.h | 51 ++
18 files changed, 3213 insertions(+), 2 deletions(-)
create mode 100644 tests/README.org
create mode 100644 tests/TESTS
create mode 100644 tests/childproc.c
create mode 100644 tests/childproc.h
create mode 100644 tests/example-t.c
create mode 100644 tests/makefile
create mode 100644 tests/null_management-t.c
create mode 100644 tests/runtests.c
create mode 100644 tests/tap/basic.c
create mode 100644 tests/tap/basic.h
create mode 100644 tests/tap/float.c
create mode 100644 tests/tap/float.h
create mode 100644 tests/tap/libtap.sh
create mode 100644 tests/tap/macros.h
create mode 100644 tests/test.c
create mode 100644 tests/test.h
Richard Cochran
2013-01-30 15:24:51 UTC
Permalink
Post by Geoff Salmon
Hi
Has anyone looked over these patches?
Not yet. Maybe next week though.

Sorry,
Richard
Richard Cochran
2013-04-08 18:30:57 UTC
Permalink
Post by Geoff Salmon
Hi
Has anyone looked over these patches?
Finally getting around to commenting on this series:

I guess my lack of response is somehow connected to my lack of
enthusiasm about this patch series. It is a bit sad to say it (looking
at the amount of work you put into building a co-process, etc), but I
don't think this approach buys us very much at all.

This test is really just checking that the messages are formatted
correctly. This can be verified more easily (and more objectively)
by using wireshark (and even automated with tools like tshark or
tcpdump). In fact, using wireshark has the advantage that all the
message types may be verified, and not just the management types.
So I really don't see this kind of testing as helpful.

Sorry,
Richard

Loading...