The following utilities are tested:
- basename(1)
- dirname(1)
- echo(1)
- false(1)
- link(1)
- printenv(1)
- sleep(1)
- test(1)
- time(1)
- true(1)
- tty(1)
- uname(1)
- unexpand(1)
- unlink(1)
- whoami(1)
- yes(1)

Some tests contain "#ifdef TODO", these tests current
fail, but there are patches submitted for most of them.
There are not patches submitted for fixing the
"#ifdef TODO"s in expand.test.c and unexpand.test.c.

Signed-off-by: Mattias Andrée <[email protected]>
---
 Makefile        |  45 ++++-
 basename.test.c |  68 +++++++
 dirname.test.c  |  55 ++++++
 echo.test.c     |  51 ++++++
 expand.test.c   |  92 ++++++++++
 false.test.c    |  32 ++++
 link.test.c     |  58 ++++++
 printenv.test.c |  79 ++++++++
 sleep.test.c    |  53 ++++++
 test-common.c   | 560 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 test-common.h   | 219 ++++++++++++++++++++++
 test.test.c     | 408 +++++++++++++++++++++++++++++++++++++++++
 time.test.c     | 218 ++++++++++++++++++++++
 true.test.c     |  31 ++++
 tty.test.c      |  44 +++++
 uname.test.c    | 283 ++++++++++++++++++++++++++++
 unexpand.test.c |  97 ++++++++++
 unlink.test.c   |  56 ++++++
 whoami.test.c   |  38 ++++
 yes.test.c      | 131 +++++++++++++
 20 files changed, 2614 insertions(+), 4 deletions(-)
 create mode 100644 basename.test.c
 create mode 100644 dirname.test.c
 create mode 100644 echo.test.c
 create mode 100644 expand.test.c
 create mode 100644 false.test.c
 create mode 100644 link.test.c
 create mode 100644 printenv.test.c
 create mode 100644 sleep.test.c
 create mode 100644 test-common.c
 create mode 100644 test-common.h
 create mode 100644 test.test.c
 create mode 100644 time.test.c
 create mode 100644 true.test.c
 create mode 100644 tty.test.c
 create mode 100644 uname.test.c
 create mode 100644 unexpand.test.c
 create mode 100644 unlink.test.c
 create mode 100644 whoami.test.c
 create mode 100644 yes.test.c

diff --git a/Makefile b/Makefile
index 0e421e7..b83058f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 include config.mk
 
 .SUFFIXES:
-.SUFFIXES: .o .c
+.SUFFIXES: .test .test.o .o .c
 
 HDR =\
        arg.h\
@@ -19,7 +19,8 @@ HDR =\
        sha512-256.h\
        text.h\
        utf.h\
-       util.h
+       util.h\
+       test-common.h
 
 LIBUTF = libutf.a
 LIBUTFSRC =\
@@ -181,9 +182,28 @@ BIN =\
        xinstall\
        yes
 
+TEST =\
+       basename.test\
+       dirname.test\
+       echo.test\
+       expand.test\
+       false.test\
+       link.test\
+       printenv.test\
+       sleep.test\
+       test.test\
+       time.test\
+       true.test\
+       tty.test\
+       uname.test\
+       unexpand.test\
+       unlink.test\
+       whoami.test\
+       yes.test
+
 LIBUTFOBJ = $(LIBUTFSRC:.c=.o)
 LIBUTILOBJ = $(LIBUTILSRC:.c=.o)
-OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ)
+OBJ = $(BIN:=.o) $(TEST:=.o) test-common.o $(LIBUTFOBJ) $(LIBUTILOBJ)
 SRC = $(BIN:=.c)
 MAN = $(BIN:=.1)
 
@@ -193,12 +213,17 @@ $(BIN): $(LIB) $(@:=.o)
 
 $(OBJ): $(HDR) config.mk
 
+$(TEST): $(@:=.o) test-common.o
+
 .o:
        $(CC) $(LDFLAGS) -o $@ $< $(LIB)
 
 .c.o:
        $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $<
 
+.test.o.test:
+       $(CC) $(LDFLAGS) -o $@ $< test-common.o
+
 $(LIBUTF): $(LIBUTFOBJ)
        $(AR) rc $@ $?
        $(RANLIB) $@
@@ -212,6 +237,17 @@ getconf.o: getconf.h
 getconf.h: getconf.sh
        ./getconf.sh > $@
 
+check: $(TEST) $(BIN)
+       @set -e;\
+       echo './sleep.test &' ; ./sleep.test & sleep_pid=$$!;\
+       for f in $(TEST); do\
+               if test "$$f" != sleep.test; then\
+                       echo ./$$f; ./$$f;\
+               fi;\
+       done;\
+       echo 'wait';\
+       wait $$sleep_pid
+
 install: all
        mkdir -p $(DESTDIR)$(PREFIX)/bin
        cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
@@ -271,7 +307,8 @@ sbase-box-uninstall: uninstall
        cd $(DESTDIR)$(PREFIX)/bin && rm -f sbase-box
 
 clean:
-       rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
+       rm -f $(BIN) $(TEST) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
        rm -f getconf.h
+       rm -rf testdir-*/
 
 .PHONY: all install uninstall dist sbase-box sbase-box-install 
sbase-box-uninstall clean
diff --git a/basename.test.c b/basename.test.c
new file mode 100644
index 0000000..bb22153
--- /dev/null
+++ b/basename.test.c
@@ -0,0 +1,68 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+static struct Case {
+       const char *path;
+       const char *suffix;
+       const char *basename;
+} cases[] = {
+       {"/",      NULL,      "/\n"},
+       {"///",    NULL,      "/\n"},
+       {"x/",     NULL,      "x\n"},
+       {"x//",    NULL,      "x\n"},
+       {"a/b/c",  NULL,      "c\n"},
+       {"/a",     NULL,      "a\n"},
+       {"a/b/c/", NULL,      "c\n"},
+       {"/a/",    NULL,      "a\n"},
+       {"a.b",    "b",       "a.\n"},
+       {"a.b/",   "b",       "a.\n"},
+       {"a.b/",   ".b",      "a\n"},
+       {"a.b",    "a.b",     "a.b\n"},
+       {"a.b",    "c",       "a.b\n"},
+       {"a.b",    "longer!", "a.b\n"},
+       {NULL, NULL, NULL}
+};
+
+int
+main(void)
+{
+       size_t i;
+
+       alarm(timeout);
+
+       proc = CMD("./basename");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./basename", "a", "b", "c");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./basename", "");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "\n") || check_stdout(EQUALS, ".\n")));
+
+       proc = CMD("./basename", "--", "");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "\n") || check_stdout(EQUALS, ".\n")));
+
+       proc = CMD("./basename", "//");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       proc = CMD("./basename", "--", "//");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       for (COUNTER(i, 0, cases[i].path)) {
+               proc = CMD("./basename", cases[i].path, cases[i].suffix);
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, cases[i].basename));
+
+               proc = CMD("./basename", "--", cases[i].path, cases[i].suffix);
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, cases[i].basename));
+
+               if (!cases[i].suffix) {
+                       proc = CMD("./basename", cases[i].path, "");
+                       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, cases[i].basename));
+
+                       proc = CMD("./basename", "--", cases[i].path, "");
+                       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, cases[i].basename));
+               }
+       }
+
+       return main_ret;
+}
diff --git a/dirname.test.c b/dirname.test.c
new file mode 100644
index 0000000..e2e08d3
--- /dev/null
+++ b/dirname.test.c
@@ -0,0 +1,55 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+static struct Case {
+       const char *path;
+       const char *dirname;
+} cases[] = {
+       {"",        ".\n"},
+       {"/",       "/\n"},
+       {"///",     "/\n"},
+       {"a/b",     "a\n"},
+       {"a/b/",    "a\n"},
+       {"a/b//",   "a\n"},
+       {"a",       ".\n"},
+       {"a/",      ".\n"},
+       {"/a/b/c",  "/a/b\n"},
+       {"//a/b/c", "//a/b\n"},
+       {NULL, NULL}
+};
+
+int
+main(void)
+{
+       size_t i;
+
+       alarm(timeout);
+
+       proc = CMD("./dirname");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./dirname", "a", "b", "c");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./dirname", "//");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       proc = CMD("./dirname", "--", "//");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       proc = CMD("./dirname", "//a");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       proc = CMD("./dirname", "--", "//a");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
(check_stdout(EQUALS, "/\n") || check_stdout(EQUALS, "//\n")));
+
+       for (COUNTER(i, 0, cases[i].path)) {
+               proc = CMD("./dirname", cases[i].path);
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, cases[i].dirname));
+
+               proc = CMD("./dirname", "--", cases[i].path);
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, cases[i].dirname));
+       }
+
+       return main_ret;
+}
diff --git a/echo.test.c b/echo.test.c
new file mode 100644
index 0000000..168130c
--- /dev/null
+++ b/echo.test.c
@@ -0,0 +1,51 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+static struct Case {
+       const char *argv[4];
+       const char *output;
+} cases[] = {
+       {{NULL}, "\n"},
+       {{"a"}, "a\n"},
+       {{"a b"}, "a b\n"},
+       {{"a  b"}, "a  b\n"},
+       {{"a", "b"}, "a b\n"},
+       {{"--"}, "--\n"},
+       {{"a", "--", "c"}, "a -- c\n"},
+       {{"a", "-n", "b"}, "a -n b\n"},
+       {{"-qwertyiopasdfghjklzxcvbnm"}, "-qwertyiopasdfghjklzxcvbnm\n"},
+       {{"--help"}, "--help\n"},
+       {{"--version"}, "--version\n"},
+       {{"-e", "1"}, "-e 1\n"},
+
+       {{"-n", "a"}, "-n a\n"},
+       {{"-n", "a b"}, "-n a b\n"},
+       {{"-n", "a  b"}, "-n a  b\n"},
+       {{"-n", "a", "b"}, "-n a b\n"},
+       {{"-n", "--"}, "-n --\n"},
+       {{"-n", "a", "--", "c"}, "-n a -- c\n"},
+       {{"-n", "a", "-n", "b"}, "-n a -n b\n"},
+       {{"-n", "-n -qwertyiopasdfghjklzxcvbnm\n"}, "-n 
-qwertyiopasdfghjklzxcvbnm\n"},
+       {{"-n", "--help"}, "-n --help\n"},
+       {{"-n", "--version"}, "-n --version\n"},
+       {{"-n", "-e", "1"}, "-n -e 1\n"},
+       {{"-n", "-n"}, "-n -n\n"},
+       {{"-nn"}, "-nn\n"},
+
+       {{NULL}, NULL},
+};
+
+int
+main(void)
+{
+       size_t i;
+
+       alarm(timeout);
+
+       for (COUNTER(i, 0, cases[i].argv[0])) {
+               proc = CMD("./echo", cases[i].argv[0], cases[i].argv[1], 
cases[i].argv[2], cases[i].argv[3]);
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, cases[i].output));
+       }
+
+       return main_ret;
+}
diff --git a/expand.test.c b/expand.test.c
new file mode 100644
index 0000000..07f9978
--- /dev/null
+++ b/expand.test.c
@@ -0,0 +1,92 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+struct Case {
+       const char *input;
+       size_t input_len;
+       const char *argv[2];
+       const char *output;
+       size_t output_len;
+};
+
+static struct Case failure_cases[] = {
+       {"",   0, {"---"},    "",   0},
+       {"",   0, {"-t2,1"},  "",   0},
+       {"",   0, {"-t0"},    "",   0},
+       {"",   0, {"-t0,1"},  "",   0},
+       {"",   0, {"-t"},     "",   0},
+       {"",   0, {"-t"},     "",   0},
+       {"",   0, {"-t", ""}, "",   0},
+       {NULL, 0, {NULL},     NULL, 0}
+};
+
+static struct Case success_cases[] = {
+       {BIN("x"),               {NULL},        BIN("x")},
+       {BIN("\tx"),             {NULL},        BIN("        x")},
+       {BIN("        x"),       {NULL},        BIN("        x")},
+       {BIN("1\t2"),            {NULL},        BIN("1       2")},
+       {BIN("1\t2"),            {"-t8"},       BIN("1       2")},
+       {BIN("1\t2"),            {"-t", "8"},   BIN("1       2")},
+       {BIN("1\t2"),            {NULL},        BIN("1       2")},
+       {BIN("1\t2"),            {"-t8"},       BIN("1       2")},
+       {BIN("1\t2"),            {"-t", "8"},   BIN("1       2")},
+       {BIN("1\t\t2\t\t3"),     {"-t4"},       BIN("1       2       3")},
+       {BIN("1\t\t\b\t2\t\t3"), {"-t4"},       BIN("1       \b 2       3")},
+       {BIN("1\t\t\b2\t\t3"),   {"-t4"},       BIN("1       \b2        3")},
+       {BIN("1\t\t 2\t\t3"),    {"-t4"},       BIN("1        2      3")},
+       {BIN("\t\n\t\n"),        {NULL},        BIN("        \n        \n")},
+       {BIN("åäö\tx\n"),        {NULL},        BIN("åäö     x\n")},
+       {BIN("åäö\b\tx\n"),      {NULL},        BIN("åäö\b      x\n")},
+#ifdef TODO
+       {BIN("〇\tx\n"),         {NULL},        BIN("〇      x\n")},
+       {BIN("〇\b\tx\n"),       {NULL},        BIN("〇\b       x\n")},
+#endif
+       {BIN("\tx\ty"),          {"-t2,8"},     BIN("  x     y")},
+       {BIN("\tx\t\ty\n"),      {"-t2,7"},     BIN("  x     y\n")},
+       {BIN("\tx    y\n"),      {"-t2,8"},     BIN("  x    y\n")},
+       {BIN("\tx\ty\n"),        {"-t2 8"},     BIN("  x     y\n")},
+       {BIN("\tx\t\ty\n"),      {"-t2 7"},     BIN("  x     y\n")},
+       {BIN("\tx    y\n"),      {"-t2 8"},     BIN("  x    y\n")},
+       {BIN(" \tx\n"),          {NULL},        BIN("        x\n")},
+       {BIN("1\t2"),            {"--"},        BIN("1       2")},
+       {BIN("1\t2"),            {"--", "-"},   BIN("1       2")},
+       {BIN("1\t2"),            {"-t8", "--"}, BIN("1       2")},
+       {BIN("\0\t\0"),          {NULL},        BIN("\0       \0")},
+       {NULL, 0,                {NULL},        NULL, 0}
+};
+
+int
+main(void)
+{
+       size_t i;
+       char f1[200], dir[100];
+
+       alarm(timeout);
+
+       sprintf(dir, "testdir-%ju", (uintmax_t)getpid());
+       if (mkdir(dir, 0700))
+               eperror(dir);
+       sprintf(f1, "%s/1", dir);
+       write_file(f1, "1\t2", 0);
+
+       proc = CMD("./expand", f1, f1);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "1       21       2"));
+
+       for (COUNTER(i, 0, failure_cases[i].input)) {
+               proc = CMD("./expand", failure_cases[i].argv[0], 
failure_cases[i].argv[1]);
+               set_input(IN_NBIN(STDIN_FILENO, failure_cases[i].input, 
failure_cases[i].input_len));
+               CHECK(check_exit(EXIT, REGULAR_ERROR) && !check_stderr(EQUALS, 
"") && check_stdout(EQUALS, ""));
+       }
+
+       for (COUNTER(i, 0, success_cases[i].input)) {
+               proc = CMD("./expand", success_cases[i].argv[0], 
success_cases[i].argv[1]);
+               set_input(IN_NBIN(STDIN_FILENO, success_cases[i].input, 
success_cases[i].input_len));
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") &&
+                     check_stdout(EQUALS | BINARY, success_cases[i].output, 
success_cases[i].output_len));
+       }
+
+       unlink(f1);
+       rmdir(dir);
+
+       return main_ret;
+}
diff --git a/false.test.c b/false.test.c
new file mode 100644
index 0000000..bdfec2b
--- /dev/null
+++ b/false.test.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+#define TEST(...)\
+       proc = CMD(__VA_ARGS__);\
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""))
+/* POSIX only specified !check_exit(EXIT, SUCCESS) */
+
+int
+main(void)
+{
+       alarm(timeout);
+
+       TEST("./false");
+       TEST("./false", "1");
+       TEST("./false", "1", "2");
+       TEST("./false", "1", "2", "3");
+       TEST("./false", "-");
+       TEST("./false", "-h");
+       TEST("./false", "-H");
+       TEST("./false", "-v");
+       TEST("./false", "-V");
+       TEST("./false", "-vVhH");
+       TEST("./false", "--");
+       TEST("./false", "--", "1");
+       TEST("./false", "--hello");
+       TEST("./false", "--help");
+       TEST("./false", "--version");
+       TEST("./false", "---");
+
+       return main_ret;
+}
diff --git a/link.test.c b/link.test.c
new file mode 100644
index 0000000..8307d3c
--- /dev/null
+++ b/link.test.c
@@ -0,0 +1,58 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+int
+main(void)
+{
+       char path1[200], path2[200], path3[200], dir[100];
+
+       alarm(timeout);
+
+       sprintf(dir, "testdir-%ju", (uintmax_t)getpid());
+       if (mkdir(dir, 0700))
+               eperror(dir);
+       sprintf(path1, "%s/a", dir);
+       sprintf(path2, "%s/b", dir);
+       sprintf(path3, "%s/c", dir);
+       if (mkdir(path3, 0700))
+               eperror(path3);
+       close(open(path1, O_CREAT | O_WRONLY | O_EXCL, 0600));
+
+       proc = CMD("./link");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./link", "---");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./link", path1);
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./link", path2, path1);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(ENOENT)));
+
+       proc = CMD("./link", path1, path2);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));
+
+       proc = CMD("./link", path1, path2);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(EEXIST)));
+
+       unlink(path2);
+
+#ifdef TODO
+       proc = CMD("./link", "--", path1, path2);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));
+
+       proc = CMD("./link", "--", path1, path2);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(EEXIST)));
+#endif
+
+       proc = CMD("./link", path1, path3);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(EEXIST)));
+
+       unlink(path1);
+       unlink(path2);
+       rmdir(path3);
+       rmdir(dir);
+
+       return main_ret;
+}
diff --git a/printenv.test.c b/printenv.test.c
new file mode 100644
index 0000000..c1168e6
--- /dev/null
+++ b/printenv.test.c
@@ -0,0 +1,79 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+extern char **environ;
+
+int
+main(void)
+{
+       alarm(timeout);
+
+#ifdef TODO
+       proc = CMD("./printenv", "-0-");
+       CHECK(check_usage_error(BOOLEAN_ERROR));
+
+       proc = CMD("./printenv", "---");
+       CHECK(check_usage_error(BOOLEAN_ERROR));
+#endif
+
+       environ = (char *[]){NULL};
+
+       proc = CMD("./printenv");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+#ifdef TODO
+       proc = CMD("./printenv", "--");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+#endif
+
+       environ = (char *[]){"X=Y", "1=2", NULL};
+
+       proc = CMD("./printenv");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "X=Y\n1=2\n"));
+
+#ifdef TODO
+       proc = CMD("./printenv", "--");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "X=Y\n1=2\n"));
+#endif
+
+       proc = CMD("./printenv", "X", "1");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "Y\n2\n"));
+
+       proc = CMD("./printenv", "1", "X");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "2\nY\n"));
+
+       proc = CMD("./printenv", "X");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "Y\n"));
+
+       proc = CMD("./printenv", "1");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "2\n"));
+
+#ifdef TODO
+       proc = CMD("./printenv", "--", "X", "1");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "Y\n2\n"));
+
+       proc = CMD("./printenv", "--", "1", "X");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "2\nY\n"));
+
+       proc = CMD("./printenv", "--", "X");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "Y\n"));
+
+       proc = CMD("./printenv", "--", "1");
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "2\n"));
+#endif
+
+       proc = CMD("./printenv", " non-existent ");
+       CHECK(check_exit(EXIT, SUCCESS_FALSE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./printenv", "X", " non-existent ", "1");
+       CHECK(check_exit(EXIT, SUCCESS_FALSE) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "Y\n2\n"));
+
+       /*
+        * Do not test e.g. `env A=B=C printenv A=B`, failure of such test
+        * would most likely indicate that getenv(3) does not verify that
+        * the variable name does not contain a '='. Calling printenv with
+        * a '=' would also be user error.
+        */
+
+       return main_ret;
+}
diff --git a/sleep.test.c b/sleep.test.c
new file mode 100644
index 0000000..1f4de12
--- /dev/null
+++ b/sleep.test.c
@@ -0,0 +1,53 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+int
+main(void)
+{
+       alarm(timeout);
+
+       ASYNC_BEGIN {
+               proc = CMD("./sleep", "2");
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(1.99, 2.03));
+       } ASYNC_END;
+
+#ifdef TODO
+       ASYNC_BEGIN {
+               proc = CMD("./sleep", "--", "2");
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(1.99, 2.03));
+       } ASYNC_END;
+
+       ASYNC_BEGIN {
+               proc = CMD("./sleep", "--", "1");
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(0.99, 1.03));
+       } ASYNC_END;
+#endif
+
+       ASYNC_BEGIN {
+               proc = CMD("./sleep", "1");
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(0.99, 1.03));
+       } ASYNC_END;
+
+       proc = CMD("./sleep");
+       CHECK(check_usage_error(REGULAR_ERROR) && check_runtime(0.00, 0.03));
+
+       proc = CMD("./sleep", "-");
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(0.00, 0.03));
+
+#ifdef TODO
+       proc = CMD("./sleep", "--");
+       CHECK(check_usage_error(REGULAR_ERROR) && check_runtime(0.00, 0.03));
+
+       proc = CMD("./sleep", "---");
+       CHECK(check_usage_error(REGULAR_ERROR) && check_runtime(0.00, 0.03));
+#endif
+
+       proc = CMD("./sleep", "--", "-1");
+       CHECK(check_usage_error(REGULAR_ERROR) && check_runtime(0.00, 0.03));
+
+       proc = CMD("./sleep", "0");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "") && check_runtime(0.00, 0.03));
+
+       async_join();
+       return main_ret;
+}
diff --git a/test-common.c b/test-common.c
new file mode 100644
index 0000000..8989755
--- /dev/null
+++ b/test-common.c
@@ -0,0 +1,560 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+struct Counter {
+       const char *name;
+       size_t value;
+};
+
+struct Process *proc = NULL;
+const char *test_file = NULL;
+int test_line = 0;
+int timeout = 10;
+int pdeath_sig = SIGINT;
+int main_ret = 0;
+void (*atfork)(void) = NULL;
+
+static struct Counter counters[16];
+static size_t ncounters = 0;
+static pid_t async_pids[1024];
+static size_t async_npids = 0;
+
+void
+eperror(const char *prefix)
+{
+       perror(prefix);
+       fflush(stderr);
+       exit(99);
+}
+
+void
+push_counter(const char *name)
+{
+       counters[ncounters++].name = name;
+}
+
+void
+set_counter(size_t value)
+{
+       counters[ncounters - 1].value = value;
+}
+
+void
+pop_counter(void)
+{
+       ncounters--;
+}
+
+pid_t
+async_fork(void)
+{
+       pid_t pid;
+
+       if (async_npids == ELEMSOF(async_pids))
+               async_join();
+
+       switch ((pid = fork())) {
+       case -1:
+               eperror("fork");
+       case 0:
+               if (atfork)
+                       atfork();
+               async_npids = 0;
+#ifdef PR_SET_PDEATHSIG
+               prctl(PR_SET_PDEATHSIG, pdeath_sig);
+#endif
+               alarm(timeout);
+               break;
+       default:
+               async_pids[async_npids++] = pid;
+               break;
+       }
+
+       return pid;
+}
+
+void
+async_join(void)
+{
+       int status;
+
+       while (async_npids--) {
+               if (waitpid(async_pids[async_npids], &status, 0) != 
async_pids[async_npids])
+                       eperror("waitpid");
+               if (status)
+                       exit(1);
+       }
+
+       async_npids = 0;
+}
+
+int
+safe_fd(int fd, int cloexec)
+{
+       int new_fd = fcntl(fd, cloexec ? F_DUPFD_CLOEXEC : F_DUPFD, 
FD_SAFE_FROM);
+       if (new_fd < 0)
+               eperror(cloexec ? "fcntl F_DUPFD_CLOEXEC" : "fcntl F_DUPFD");
+       close(fd);
+       return new_fd;
+}
+
+void
+xpipe(int fds[2], int cloexec)
+{
+       if (pipe(fds))
+               eperror("pipe");
+       fds[0] = safe_fd(fds[0], cloexec);
+       fds[1] = safe_fd(fds[1], cloexec);
+}
+
+const char *
+openpt(int ctty, int master_slave[2])
+{
+       const char *slave;
+       master_slave[0] = posix_openpt(O_RDWR | O_NOCTTY);
+       if (master_slave[0] < 0)
+               eperror("posix_openpt");
+       if (grantpt(master_slave[0]) < 0)
+               eperror("grantpt");
+       if (unlockpt(master_slave[0]) < 0)
+               eperror("unlockpt");
+       slave = ptsname(master_slave[0]);
+       if (!slave)
+               eperror("ptsname");
+       master_slave[1] = open(slave, O_RDWR | (ctty ? 0 : O_NOCTTY));
+       if (master_slave[1] < 0)
+               eperror("open");
+       master_slave[0] = safe_fd(master_slave[0], 0);
+       master_slave[1] = safe_fd(master_slave[1], 0);
+       return slave;
+}
+
+void
+write_file(const char *path, const char *data, size_t n)
+{
+       int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
+       size_t p = 0;
+       ssize_t r;
+       n = n ? n : strlen(data);
+       if (fd < 0)
+               eperror("open");
+       for (; p < n; p += (size_t)r)
+               if ((r = write(fd, &data[p], n - p)) < 0)
+                       eperror("write");
+       close(fd);
+}
+
+void
+set_input(struct InputStream *in)
+{
+       size_t i;
+       for (i = 0; i < proc->ninput && in->fd != proc->input[i].fd; i++);
+       proc->ninput += (i == proc->ninput);
+       proc->input[i] = *in;
+}
+
+void
+set_output(struct OutputStream *out)
+{
+       size_t i;
+       for (i = 0; i < proc->noutput && out->fd != proc->output[i].fd; i++);
+       proc->noutput += (i == proc->noutput);
+       proc->output[i] = *out;
+}
+
+
+void
+start_process(void)
+{
+       struct InputStream *in;
+       struct OutputStream *out;
+       int exec_sig_pipe[2];
+       size_t i;
+       ssize_t r;
+       int fd, fds[2];
+
+       proc->started = 1;
+
+       if (!proc->file)
+               proc->file = proc->argv[0];
+
+       for (i = 0; i < proc->ninput; i++) {
+               in = &proc->input[i];
+               in->pid = -1;
+
+               if ((in->flags & CREATE_MASK) == PIPE) {
+                       xpipe(fds, 0);
+                       in->input_fd = fds[0];
+                       in->output_fd = fds[1];
+               }
+
+               if (in->flags & DATA) {
+                       switch ((in->pid = fork())) {
+                       case -1:
+                               eperror("fork");
+                       case 0:
+                               break;
+                       default:
+                               close(in->output_fd);
+                               continue;
+                       }
+                       if (atfork)
+                               atfork();
+                       for (fd = 0; fd < FD_MAX; fd++)
+                               if (fd != STDERR_FILENO && fd != in->output_fd)
+                                       close(fd);
+                       while (in->len) {
+                               r = write(in->output_fd, in->data, in->len);
+                               if (r < 0)
+                                       eperror("write");
+                               in->data += r;
+                               in->len -= (size_t)r;
+                       }
+                       if (close(in->output_fd))
+                               eperror("close");
+                       exit(0);
+               }
+       }
+
+       for (i = 0; i < proc->noutput; i++) {
+               out = &proc->output[i];
+               out->error = 0;
+               out->data = NULL;
+               out->len = 0;
+               out->size = 0;
+
+               if ((out->flags & CREATE_MASK) == PIPE) {
+                       xpipe(fds, 0);
+                       out->input_fd = fds[0];
+                       out->output_fd = fds[1];
+               }
+       }
+
+       xpipe(exec_sig_pipe, 1);
+
+       switch ((proc->pid = fork())) {
+       case -1:
+               eperror("fork");
+       case 0:
+               break;
+       default:
+               if (atfork)
+                       atfork();
+               for (i = 0; i < proc->ninput; i++)
+                       close(proc->input[i].input_fd);
+               for (i = 0; i < proc->noutput; i++)
+                       close(proc->output[i].output_fd);
+               close(exec_sig_pipe[1]);
+               read(exec_sig_pipe[0], &(char){0}, 1);
+               if (clock_gettime(CLOCK_MONOTONIC, &proc->start_time))
+                       eperror("clock_gettime CLOCK_MONOTONIC");
+               close(exec_sig_pipe[0]);
+               return;
+       }
+
+#ifdef PR_SET_PDEATHSIG
+       prctl(PR_SET_PDEATHSIG, proc->pdeath_sig);
+#endif
+
+       for (i = 0; i < proc->ninput; i++) {
+               if (dup2(proc->input[i].input_fd, proc->input[i].fd) != 
proc->input[i].fd)
+                       eperror("dup2");
+               close(proc->input[i].input_fd);
+       }
+
+       for (i = 0; i < proc->noutput; i++) {
+               if (dup2(proc->output[i].output_fd, proc->output[i].fd) != 
proc->output[i].fd)
+                       eperror("dup2");
+               close(proc->output[i].output_fd);
+       }
+
+       alarm(proc->timeout);
+
+       execvp(proc->file, (void *)proc->argv);
+       fprintf(stderr, "exec %s: %s\n", proc->file, strerror(errno));
+       fflush(stderr);
+       exit(99);
+}
+
+void
+wait_process(void)
+{
+       struct pollfd *pfds = NULL;
+       struct OutputStream *out;
+       size_t i, j, n;
+       ssize_t r;
+       int status;
+
+       proc->waited = 1;
+
+       n = proc->noutput;
+       pfds = calloc(n + 1, sizeof(*pfds));
+       if (!pfds)
+               eperror("calloc");
+
+       for (i = j = 0; i < n; j++) {
+               if (proc->output[j].flags & DATA) {
+                       pfds[i].fd = proc->output[j].input_fd;
+                       pfds[i].events = POLLIN;
+                       i++;
+               } else {
+                       n--;
+               }
+       }
+
+       while (n) {
+               r = (ssize_t)poll(pfds, n, -1);
+               if (r < 0)
+                       eperror("poll");
+               for (j = 0; j < n;) {
+                       for (i = 0; proc->output[i].input_fd != pfds[j].fd; 
i++);
+                       out = &proc->output[i];
+                       if (out->len + 512 >= out->size) {
+                               out->data = realloc(out->data, out->size += 
8096);
+                               if (!out->data)
+                                       eperror("realloc");
+                       }
+                       errno = 0;
+                       r = read(out->input_fd, &out->data[out->len], out->size 
- out->len);
+                       if (r > 0) {
+                               out->len += (size_t)r;
+                               j++;
+                       } else {
+                               out->error = errno;
+                               close(out->input_fd);
+                               out->size = out->len + 1;
+                               out->data = realloc(out->data, out->size);
+                               if (!out->data)
+                                       eperror("realloc");
+                               out->data[out->len] = '\0';
+                               memmove(&pfds[j], &pfds[j + 1], (--n - j) * 
sizeof(*pfds));
+                       }
+               }
+       }
+
+       if (waitpid(proc->pid, &proc->status, 0) != proc->pid)
+               eperror("waitpid");
+
+       if (clock_gettime(CLOCK_MONOTONIC, &proc->exit_time))
+               eperror("clock_gettime CLOCK_MONOTONIC");
+       proc->runtime.tv_sec = proc->exit_time.tv_sec - proc->start_time.tv_sec;
+       proc->runtime.tv_nsec = proc->exit_time.tv_nsec - 
proc->start_time.tv_nsec;
+       if (proc->runtime.tv_nsec < 0) {
+               proc->runtime.tv_sec -= 1;
+               proc->runtime.tv_nsec += 1000000000L;
+       }
+
+       for (i = 0; i < proc->ninput; i++) {
+               if (proc->input[i].flags & DATA) {
+                       if (waitpid(proc->input[i].pid, &status, 0) != 
proc->input[i].pid)
+                               eperror("waitpid");
+                       if (status)
+                               exit(99);
+               }
+       }
+
+       free(pfds);
+}
+
+void
+dealloc_process(void)
+{
+       size_t i;
+       for (i = 0; i < proc->noutput; i++)
+               if (proc->output[i].flags & DATA)
+                       free(proc->output[i].data);
+}
+
+int
+check_runtime(double min, double max)
+{
+       double dur;
+       dur = proc->runtime.tv_nsec;
+       dur /= 1000000000.;
+       dur += proc->runtime.tv_sec;
+       return min <= dur && dur <= max;
+}
+
+int
+check_exit(enum ExitStatus type, int min, ...)
+{
+       int max = min, status = proc->status;
+       va_list ap;
+
+       if (type & RANGE) {
+               va_start(ap, min);
+               max = va_arg(ap, int);
+               va_end(ap);
+       }
+
+       if ((type & 0x0F) == EXIT)
+               return WIFEXITED(status) && min <= WEXITSTATUS(status) && 
WEXITSTATUS(status) <= max;
+       else
+               return WIFSTOPPED(status) && min <= WTERMSIG(status) && 
WTERMSIG(status) <= max;
+}
+
+static int
+check_output_test(const char *data, size_t len, struct OutputStream *s, enum 
OutputTest test)
+{
+       int beginning = (test & BEGINNING) == BEGINNING;
+       int contains = (test & CONTAINS) == CONTAINS;
+       int anycase = (test & ANYCASE) == ANYCASE;
+       int at_beginning = 1;
+       size_t i, off, off_end;
+
+       if (len > s->len)
+               return 0;
+
+       off_end = contains ? s->len - len + 1 : 1;
+       for (off = 0; off < off_end; at_beginning = (s->data[off] == '\n'), 
off++) {
+               if (beginning && !at_beginning)
+                       continue;
+               if (!anycase) {
+                       for (i = 0; i < len; i++)
+                               if (data[i] != s->data[i + off])
+                                       break;
+               } else {
+                       for (i = 0; i < len; i++)
+                               if (tolower(data[i]) != tolower(s->data[i + 
off]))
+                                       break;
+               }
+               if (i != len)
+                       continue;
+               if (beginning || contains || i + off == s->len)
+                       return 1;
+       }
+
+       return 0;
+}
+
+int
+check_output(int fd, enum OutputTest test, ...)
+{
+       struct OutputStream *s;
+       const char *data;
+       size_t len;
+       va_list ap;
+
+       for (s = proc->output; s->fd != fd; s++);
+
+       va_start(ap, test);
+
+       if (test == ERROR) {
+               return s->error == va_arg(ap, int);
+       } else if (test & PARTIAL) {
+               if (s->error && s->error != va_arg(ap, int))
+                       return 0;
+       } else {
+               if (s->error)
+                       return 0;
+       }
+
+       data = va_arg(ap, const char *);
+       if (test & BINARY)
+               len = va_arg(ap, size_t);
+       else
+               len = strlen(data);
+
+       if (test & TEXT_ONLY)
+               if (memchr(s->data, '\0', s->len))
+                        return 0;
+
+       va_end(ap);
+
+       return check_output_test(data, len, s, test);
+}
+
+void
+print_failure(void)
+{
+       /* Write to memory first to avoid lines from different tests getting 
mixed
+        * when multiple tests tested concurrently. */
+
+       struct OutputStream *out;
+       FILE *fp;
+       char *buf = NULL;
+       size_t size = 0;
+       size_t i;
+       char *p, *q;
+
+       fp = open_memstream(&buf, &size);
+       if (!fp)
+               eperror("open_memstream");
+
+       fprintf(fp, "test at %s:%i", test_file, test_line);
+       switch (ncounters) {
+       case 0:
+               break;
+       case 1:
+               fprintf(fp, ", with %s=%zu,", counters[0].name, 
counters[0].value);
+               break;
+       case 2:
+               fprintf(fp, ", with %s=%zu and %s=%zu,",
+                       counters[0].name, counters[0].value, counters[1].name, 
counters[1].value);
+               break;
+       default:
+               fprintf(fp, ", with");
+               for (i = 0; i < ncounters; i++) {
+                       if (i == ncounters - 1)
+                               fprintf(fp, " and");
+                       fprintf(fp, " %s=%zu,", counters[i].name, 
counters[i].value);
+               }
+               break;
+       }
+       fprintf(fp, " failed:\n");
+
+       if (WIFEXITED(proc->status))
+               fprintf(fp, "\tnormal exit with value %i\n", 
WEXITSTATUS(proc->status));
+       else if (WIFSTOPPED(proc->status))
+               fprintf(fp, "\tkilled by signal %i (%s)\n", 
WTERMSIG(proc->status), strsignal(WTERMSIG(proc->status)));
+       else
+               fprintf(fp, "\tunrecognised exit status %i\n", proc->status);
+
+       fprintf(fp, "\tran for %lli.%09li seconds\n", (unsigned long long 
int)proc->runtime.tv_sec, proc->runtime.tv_nsec);
+
+       for (i = 0; i < proc->noutput; i++) {
+               out = &proc->output[i];
+               if (!(out->flags & DATA))
+                       continue;
+               fprintf(fp, "\toutput to ");
+               if (out->fd == 0)
+                       fprintf(fp, "stdin");
+               else if (out->fd == 1)
+                       fprintf(fp, "stdout");
+               else if (out->fd == 2)
+                       fprintf(fp, "stderr");
+               else
+                       fprintf(fp, "fd %i", out->fd);
+               fprintf(fp, " was %zu bytes long and in %s, ", out->len,
+                       memchr(out->data, '\0', out->len) ? "binary" :
+                       (out->len && out->data[out->len - 1] == '\n') ? "text" :
+                       "text without newline at the end");
+               if (out->error)
+                       fprintf(fp, "error %i (%s) encountered", out->error, 
strerror(out->error));
+               else
+                       fprintf(fp, "no error encountered");
+               if (!out->len || memchr(out->data, '\0', out->len)) {
+                       fprintf(fp, "\n");
+               } else {
+                       fprintf(fp, ":\n");
+                       for (p = out->data; p && *p; p = q) {
+                               q = strchr(p, '\n');
+                               if (q) {
+                                       q += 1;
+                                       fprintf(fp, "\t\t%.*s", (int)(q - p), 
p);
+                               } else {
+                                       fprintf(fp, "\t\t%s\n", p);
+                               }
+                       }
+               }
+       }
+
+       fprintf(fp, "\n");
+
+       if (fflush(fp))
+               eperror("fflush");
+       if (fclose(fp))
+               eperror("fclose");
+       fwrite(buf, 1, size, stderr);
+       free(buf);
+}
diff --git a/test-common.h b/test-common.h
new file mode 100644
index 0000000..abd4796
--- /dev/null
+++ b/test-common.h
@@ -0,0 +1,219 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef __linux__
+# include <sys/prctl.h>
+#endif
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#define FD_SAFE_FROM 128
+#define FD_MAX       256
+
+enum {
+       SUCCESS       =   0,
+       SUCCESS_TRUE  =   0, /* e.g. true(1), diff(1) with identical files */
+       SUCCESS_FALSE =   1, /* e.g. false(1), diff(1) with differing files */
+       REGULAR_ERROR =   1,
+       BOOLEAN_ERROR =   2, /* e.g. error in diff(1) */
+       PARTIAL_ERROR =  64,
+       SETUP_ERROR   = 125, /* e.g. error before exec in env(1)  */
+       EXEC_ERROR    = 126, /* e.g. error in exec in env(1), but not ENOENT */
+       NOENT_ERROR   = 127  /* e.g. error in exec in env(1), but only ENOENT */
+#define SIGNAL_EXIT(SIGNUM) (128 + (SIGNUM))
+};
+
+enum StreamSpec {
+       PIPE        = 0x01,
+       CREATE_MASK = 0x0F,
+       DATA        = 0x10
+};
+
+enum ExitStatus {
+       EXIT   = 0,
+       SIGNAL = 1,
+       RANGE  = 0x10
+};
+
+enum OutputTest {
+       ERROR     = 0xFF,
+       PARTIAL   = 0x01,
+       EQUALS    = 0,
+       ANYCASE   = 0x02,
+       BEGINNING = 0x04,
+       CONTAINS  = 0x08,
+       BINARY    = 0x10,
+       TEXT_ONLY = 0x20,
+#define LINE (BEGINNING | CONTAINS)
+};
+
+struct InputStream {
+       int fd;
+       int input_fd;
+       int output_fd;
+       enum StreamSpec flags;
+       const char *data;
+       size_t len;
+       pid_t pid;
+};
+
+struct OutputStream {
+       int fd;
+       int input_fd;
+       int output_fd;
+       enum StreamSpec flags;
+       int error;
+       char *data;
+       size_t len;
+       size_t size;
+};
+
+struct Process {
+       const char *file;
+       const char **argv;
+       pid_t pid;
+       int status;
+       int timeout;
+       int pdeath_sig;
+       int started;
+       int waited;
+       struct InputStream input[8];
+       size_t ninput;
+       struct OutputStream output[8];
+       size_t noutput;
+       struct timespec start_time;
+       struct timespec exit_time;
+       struct timespec runtime;
+};
+
+extern struct Process *proc;
+extern const char *test_file;
+extern int test_line;
+extern int timeout;
+extern int pdeath_sig;
+extern int main_ret;
+extern void (*atfork)(void);
+
+#define IN_TEXT(FD, TEXT) IN_NBIN(FD, TEXT, strlen(TEXT))
+
+#define IN_BIN(FD, TEXT) IN_NBIN(FD, TEXT, sizeof(TEXT) - 1)
+
+#define IN_NBIN(FD, TEXT, N)\
+       (&(struct InputStream){\
+               .fd = (FD),\
+               .flags = PIPE | DATA,\
+               .data = (TEXT),\
+               .len = (N)\
+       })
+
+#define IN_FDS(FD, IN, OUT)\
+       (&(struct InputStream){\
+               .fd = (FD),\
+               .input_fd = (IN),\
+               .output_fd = (OUT),\
+               .flags = 0\
+       })
+
+#define OUT_FDS(FD, IN, OUT)\
+       (&(struct OutputStream){\
+               .fd = (FD),\
+               .input_fd = (IN),\
+               .output_fd = (OUT),\
+               .flags = 0\
+       })
+
+#define OUT_PIPE(FD)\
+       (&(struct OutputStream){\
+               .fd = (FD),\
+               .flags = PIPE | DATA\
+       })
+
+#define CMD(...)\
+       (&(struct Process){\
+               .file = NULL,\
+               .argv = (const char *[]){__VA_ARGS__, NULL},\
+               .timeout = timeout,\
+               .pdeath_sig = pdeath_sig,\
+               .started = 0,\
+               .input = {{\
+                       .fd = STDIN_FILENO,\
+                       .flags = PIPE | DATA,\
+                       .data = NULL,\
+                       .len = 0\
+               }},\
+               .ninput = 1,\
+               .output = {{\
+                       .fd = STDOUT_FILENO,\
+                       .flags = PIPE | DATA\
+               }, {\
+                       .fd = STDERR_FILENO,\
+                       .flags = PIPE | DATA\
+               }},\
+               .noutput = 2\
+       })
+
+#define CHECK(OK_COND)\
+       do {\
+               test_line = __LINE__;\
+               test_file = __FILE__;\
+               if (!proc->started)\
+                       start_process();\
+               if (!proc->waited)\
+                       wait_process();\
+               if (!(OK_COND)) {\
+                       print_failure();\
+                       main_ret = 1;\
+               }\
+               dealloc_process();\
+       } while (0)
+
+#define ELEMSOF(A) (sizeof(A) / sizeof(*(A)))
+
+#define COUNTER(VAR, START, WHILE) VAR = START, push_counter(#VAR); (WHILE) ? 
(set_counter(VAR), 1) : (pop_counter(), 0); (VAR)++
+
+#define BIN(TEXT) TEXT, sizeof(TEXT) - 1
+
+#define ASYNC_BEGIN do { if (!async_fork()) {
+#define ASYNC_END   exit(main_ret); }} while (0)
+
+void eperror(const char *prefix);
+
+void push_counter(const char *name);
+void set_counter(size_t value);
+void pop_counter(void);
+
+pid_t async_fork(void);
+void async_join(void);
+
+int safe_fd(int fd, int cloexec);
+void xpipe(int fds[2], int cloexec);
+const char *openpt(int ctty, int master_slave[2]);
+void write_file(const char *path, const char *data, size_t n);
+
+void set_input(struct InputStream *in);
+void set_output(struct OutputStream *out);
+
+void start_process(void);
+void wait_process(void);
+void dealloc_process(void);
+
+int check_runtime(double min, double max);
+int check_exit(enum ExitStatus type, int min, ...);
+int check_output(int fd, enum OutputTest test, ...);
+#define check_stdout(...) check_output(STDOUT_FILENO, __VA_ARGS__)
+#define check_stderr(...) check_output(STDERR_FILENO, __VA_ARGS__)
+#define check_usage_error(EXIT_VALUE) (check_exit(EXIT, EXIT_VALUE) && 
check_stderr(BEGINNING, "usage: ") && check_stdout(EQUALS, ""))
+
+void print_failure(void);
diff --git a/test.test.c b/test.test.c
new file mode 100644
index 0000000..8e6080f
--- /dev/null
+++ b/test.test.c
@@ -0,0 +1,408 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#define FILES\
+       "/dev/loop0", /* -bea */\
+       "/dev/null",  /* -cea */\
+       "/",          /* -dea */\
+       "h",          /* -dheaL */\
+       SAFE_FILES
+
+#define SAFE_FILES\
+       " does not exist ",\
+       "r",          /* -frea */\
+       "w",          /* -fwea */\
+       "x",          /* -fxea */\
+       "u",          /* -fuea */\
+       "g",          /* -fgea */\
+       "k",          /* -fkea */\
+       "+e",         /* -hL */\
+       "s",          /* -fsea */\
+       "S",          /* -Sea */\
+       "p"           /* -pea */
+
+static struct TestInt {
+       const char *s;
+       int cmpval;
+} testints[] = {
+       {"0", 0},
+       {"00", 0},
+       {"1", 1},
+       {"01", 1},
+       {"10", 2},
+       {"11", 3},
+       {"20", 4},
+       {"21", 5},
+       {"100", 6},
+       {"1000", 7},
+       
{"100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 8},
+       
{"100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
 9},
+       
{"200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 10},
+       {"+0", 0},
+       {"+00", 0},
+       {"+1", 1},
+       {"+01", 1},
+       {"+10", 2},
+       {"+11", 3},
+       {"+20", 4},
+       {"+21", 5},
+       {"+100", 6},
+       {"+1000", 7},
+       
{"+100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 8},
+       
{"+100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
 9},
+       
{"+200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 10},
+       {"-0", 0},
+       {"-00", 0},
+       {"-1", -1},
+       {"-01", -1},
+       {"-10", -2},
+       {"-11", -3},
+       {"-20", -4},
+       {"-21", -5},
+       {"-100", -6},
+       {"-1000", -7},
+       
{"-100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 -8},
+       
{"-100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
 -9},
+       
{"-200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
 -10},
+       {NULL, 0}
+};
+
+#define test_check_usage_error() (check_exit(EXIT, BOOLEAN_ERROR) && 
check_stdout(EQUALS, "") && check_stderr(CONTAINS, ": "))
+
+#define SYNTAX_TEST(...)\
+       do {\
+               push_counter("syntax-test-part");\
+               \
+               set_counter(0);\
+               proc = CMD("../test", __VA_ARGS__);\
+               CHECK(test_check_usage_error());\
+               \
+               set_counter(1);\
+               proc = CMD("[", __VA_ARGS__, "]");\
+               proc->file = "../test";\
+               CHECK(test_check_usage_error());\
+               \
+               set_counter(2);\
+               proc = CMD("[", __VA_ARGS__);\
+               proc->file = "../test";\
+               CHECK(test_check_usage_error());\
+               \
+               pop_counter();\
+       } while (0)
+
+#define SINGLE_TEST(OK, ...)\
+       do {\
+               push_counter("single-test-part");\
+               \
+               set_counter(0);\
+               proc = CMD("../test", __VA_ARGS__);\
+               CHECK(check_exit(EXIT, (OK)) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));\
+               \
+               set_counter(1);\
+               proc = CMD("../test", "!", __VA_ARGS__);\
+               CHECK(check_exit(EXIT, !(OK)) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));\
+               \
+               set_counter(2);\
+               proc = CMD("../[", __VA_ARGS__, "]");\
+               proc->file = "../test";\
+               CHECK(check_exit(EXIT, (OK)) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));\
+               \
+               set_counter(3);\
+               proc = CMD("[", "!", __VA_ARGS__, "]"); \
+               proc->file = "../test";\
+               CHECK(check_exit(EXIT, !(OK)) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));\
+               \
+               set_counter(4);\
+               proc = CMD("[", "!", __VA_ARGS__);\
+               proc->file = "../test";\
+               CHECK(test_check_usage_error());\
+               \
+               pop_counter();\
+       } while (0)
+
+#define CMP_SINGLE_TEST(I1, I2, CMP, FLAG)\
+       do {\
+               proc = CMD("../test", (I1)->s, FLAG, (I2)->s);\
+               CHECK(check_exit(EXIT, !((I1)->cmpval CMP (I2)->cmpval)) && 
check_stdout(EQUALS, "") && check_stderr(EQUALS, ""));\
+       } while (0)
+
+#define MULTI_TEST(FLAG, TEST_FILES, ...)\
+       do {\
+               const char *good_files[] = {__VA_ARGS__, NULL};\
+               for (COUNTER(i, 0, (TEST_FILES)[i])) {\
+                       ok = 1;\
+                       for (j = 0; good_files[j]; j++) {\
+                               if (!strcmp((TEST_FILES)[i], good_files[j])) {\
+                                       ok = 0;\
+                                       break;\
+                               }\
+                       }\
+                       SINGLE_TEST(ok, (FLAG), (TEST_FILES)[i]);\
+               }\
+       } while (0)
+
+int
+main(void)
+{
+       const char *files[] = {FILES, NULL};
+       const char *safe_files[] = {SAFE_FILES, NULL};
+       int fd, pfds[2], tfds[2];
+       char dir[100], pfd[50], tfd[50], pfdn[51], tfdn[51];
+       struct sockaddr_un un;
+       struct timespec times[2] = {{.tv_nsec = 0}, {.tv_nsec = 0}};
+       int is_root = !geteuid(), ok;
+       size_t i, j;
+
+       alarm(timeout = 20);
+
+       sprintf(dir, "testdir-%ju", (uintmax_t)getpid());
+       if (mkdir(dir, 0700) || chdir(dir))
+               eperror(dir);
+       if (symlink(".", "h"))
+               eperror("h");
+       if (symlink(" non existent file ", "+e"))
+               eperror("+e");
+       close(open("r", O_WRONLY | O_CREAT, 0400));
+       close(open("w", O_WRONLY | O_CREAT, 0200));
+       close(open("x", O_WRONLY | O_CREAT, 0100));
+       close(open("u", O_WRONLY | O_CREAT, 04000));
+       close(open("g", O_WRONLY | O_CREAT, 02000));
+       close(open("k", O_WRONLY | O_CREAT, 01000));
+       link("x", "x-hardlink");
+       fd = open("s", O_WRONLY | O_CREAT, 0);
+       if (fd < 0)
+               eperror("s");
+       write(fd, &(char){0}, 1);
+       close(fd);
+       fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+       if (fd < 0)
+               eperror("socket");
+       memset(&un, 0, sizeof(un));
+       un.sun_family = AF_LOCAL;
+       stpcpy(un.sun_path, "S");
+       bind(fd, (void *)&un, (socklen_t)sizeof(un));
+       if (fd < 0)
+               eperror("S");
+       close(fd);
+       if (chmod("S", 0))
+               eperror("S");
+       if (mkfifo("p", 0))
+               eperror("p");
+       xpipe(pfds, 0);
+       openpt(0, tfds);
+       sprintf(pfd, "%i", pfds[1]);
+       sprintf(tfd, "%i", tfds[1]);
+       sprintf(pfdn, "%i\n", pfds[1]);
+       sprintf(tfdn, "%i\n", tfds[1]);
+       close(open("-eq", O_WRONLY | O_CREAT, 0400));
+       close(open("also-old", O_WRONLY | O_CREAT, 06000));
+       close(open("old", O_WRONLY | O_CREAT, 06000));
+       close(open("new", O_WRONLY | O_CREAT, 06000));
+       times[0].tv_sec = 1;
+       times[1].tv_sec = 10000;
+       utimensat(AT_FDCWD, "also-old", times, 0);
+       times[0].tv_sec = 1;
+       times[1].tv_sec = 10000;
+       utimensat(AT_FDCWD, "old", times, 0);
+       times[0].tv_sec = 1;
+       times[1].tv_sec = 40000;
+       utimensat(AT_FDCWD, "new", times, 0);
+
+       ASYNC_BEGIN {
+               proc = CMD("[");
+               proc->file = "../test";
+               CHECK(test_check_usage_error());
+
+               for (COUNTER(i, 0, i < 4)) {
+                       proc = CMD("../test", NULL, NULL, NULL, NULL, NULL, 
NULL);
+                       for (j = 1; j < i + 1; j++)
+                               proc->argv[j] = "!";
+                       proc->argv[j] = NULL;
+                       CHECK(check_exit(EXIT, i % 2 == 0 ? SUCCESS_FALSE : 
SUCCESS_TRUE) &&
+                             check_stderr(EQUALS, "") && check_stdout(EQUALS, 
""));
+
+                       proc = CMD("[", NULL, NULL, NULL, NULL, NULL, NULL, 
NULL);
+                       proc->file = "../test";
+                       for (j = 1; j < i + 1; j++)
+                               proc->argv[j] = "!";
+                       proc->argv[j++] = "]";
+                       proc->argv[j] = NULL;
+                       CHECK(check_exit(EXIT, i % 2 == 0 ? SUCCESS_FALSE : 
SUCCESS_TRUE) &&
+                             check_stderr(EQUALS, "") && check_stdout(EQUALS, 
""));
+               }
+
+               SINGLE_TEST(0, "---");
+               SINGLE_TEST(1, "");
+               SINGLE_TEST(0, "--");
+               SINGLE_TEST(0, "x");
+               SINGLE_TEST(0, "abc");
+               SINGLE_TEST(0, "-");
+               SINGLE_TEST(0, " -");
+               SINGLE_TEST(0, " ");
+               SYNTAX_TEST("--", "x");
+               SYNTAX_TEST("-z", "--", "x");
+               SYNTAX_TEST("-n", "--", "x");
+               SINGLE_TEST(1, "-z", "--");
+               SINGLE_TEST(1, "-z", "x");
+               SINGLE_TEST(0, "-z", "");
+               SINGLE_TEST(0, "-z");
+               SINGLE_TEST(0, "-n", "--");
+               SINGLE_TEST(0, "-n", "x");
+               SINGLE_TEST(1, "-n", "");
+               SINGLE_TEST(0, "-n");
+               SINGLE_TEST(0, "-1");
+               SINGLE_TEST(0, "--help");
+               SINGLE_TEST(0, "--version");
+       } ASYNC_END;
+       ASYNC_BEGIN {
+               MULTI_TEST("-b", files, "/dev/loop0");
+               MULTI_TEST("-c", files, "/dev/null");
+               MULTI_TEST("-d", files, "/", "h");
+               MULTI_TEST("-e", files, "/dev/loop0", "/dev/null", "/", "h", 
"r", "w", "x", "u", "g", "k", "s", "S", "p");
+               MULTI_TEST("-f", files, "r", "w", "x", "u", "g", "k", "s");
+               MULTI_TEST("-g", files, "g");
+               MULTI_TEST("-h", files, "h", "+e");
+               MULTI_TEST("-L", files, "h", "+e");
+               MULTI_TEST("-p", files, "p");
+               if (is_root)
+                       MULTI_TEST("-r", safe_files, "r", "w", "x", "u", "g", 
"k", "s", "S", "p");
+               else
+                       MULTI_TEST("-r", safe_files, "r");
+               MULTI_TEST("-S", files, "S");
+               MULTI_TEST("-s", safe_files, "s");
+               MULTI_TEST("-u", files, "u");
+               if (is_root)
+                       MULTI_TEST("-w", safe_files, "r", "w", "x", "u", "g", 
"k", "s", "S", "p");
+               else
+                       MULTI_TEST("-w", safe_files, "w");
+               MULTI_TEST("-x", safe_files, "x");
+               SINGLE_TEST(1, "-t", pfd);
+               SINGLE_TEST(0, "-t", tfd);
+               SINGLE_TEST(0, "", "=", "");
+               SINGLE_TEST(0, "a", "=", "a");
+               SINGLE_TEST(1, "a", "=", "");
+               SINGLE_TEST(1, "", "=", "a");
+               SINGLE_TEST(1, "a", "=", "b");
+               SINGLE_TEST(1, "1", "=", "01");
+               SINGLE_TEST(0, "a=b");
+               SINGLE_TEST(1, "", "!=", "");
+               SINGLE_TEST(1, "a", "!=", "a");
+               SINGLE_TEST(0, "a", "!=", "");
+               SINGLE_TEST(0, "", "!=", "a");
+               SINGLE_TEST(0, "a", "!=", "b");
+               SINGLE_TEST(0, "1", "!=", "01");
+               SINGLE_TEST(0, "=");
+               SINGLE_TEST(0, "!=");
+               SINGLE_TEST(0, "==");
+               SINGLE_TEST(0, "a!=a");
+               SINGLE_TEST(0, "a==b");
+               SYNTAX_TEST("-", "-eq", "-");
+               SYNTAX_TEST("-", "-eq", "+");
+               SINGLE_TEST(0, "!", "=", "!");
+               SINGLE_TEST(1, "(", "=", ")");
+               SINGLE_TEST(0, "-f", "=", "-f");
+               SINGLE_TEST(0, "-f", "-eq");
+       } ASYNC_END;
+       ASYNC_BEGIN {
+               for (COUNTER(i, 0, testints[i].s)) {
+                       SYNTAX_TEST("-", "-eq", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-eq", "-");
+                       SYNTAX_TEST("-", "-ne", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-ne", "-");
+                       SYNTAX_TEST("-", "-lt", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-lt", "-");
+                       SYNTAX_TEST("-", "-le", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-le", "-");
+                       SYNTAX_TEST("-", "-gt", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-gt", "-");
+                       SYNTAX_TEST("-", "-ge", testints[i].s);
+                       SYNTAX_TEST(testints[i].s, "-ge", "-");
+               }
+       } ASYNC_END;
+       for (COUNTER(i, 0, testints[i].s)) {
+               ASYNC_BEGIN {
+                       for (COUNTER(j, 0, testints[j].s)) {
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], ==, 
"-eq");
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], !=, 
"-ne");
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], <,  
"-lt");
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], <=, 
"-le");
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], >,  
"-gt");
+                               CMP_SINGLE_TEST(&testints[i], &testints[j], >=, 
"-ge");
+                       }
+               } ASYNC_END;
+       }
+
+       ASYNC_BEGIN {
+               /* Check non-standard features */
+               MULTI_TEST("-k", files, "k");
+               SINGLE_TEST(0, "x", "-ef", "x");
+               SINGLE_TEST(0, "x", "-ef", "x-hardlink");
+               SINGLE_TEST(1, "x", "-ef", "z");
+               SINGLE_TEST(1, "x", "-ef", "r");
+               SINGLE_TEST(0, "new", "-nt", "old");
+               SINGLE_TEST(1, "old", "-nt", "new");
+               SINGLE_TEST(1, "old", "-nt", "also-old");
+               SINGLE_TEST(1, "new", "-ot", "old");
+               SINGLE_TEST(0, "old", "-ot", "new");
+               SINGLE_TEST(1, "old", "-ot", "also-old");
+       } ASYNC_END;
+       ASYNC_BEGIN {
+               /* Check non-support of harmful features */
+               SYNTAX_TEST("a", "-ab");
+               SYNTAX_TEST("a", "-ob");
+               SYNTAX_TEST("a", "-a");
+               SYNTAX_TEST("a", "-o");
+               SYNTAX_TEST("1", "-a", "1");
+               SYNTAX_TEST("1", "-o", "1");
+               SYNTAX_TEST("1", "-o", "1", "-a", "1");
+               SYNTAX_TEST("1", "-a", "1", "-o", "1");
+               SYNTAX_TEST("(", "-e", "/", ")");
+               SYNTAX_TEST("!", "1", "-o", "1");
+               SYNTAX_TEST("!", "1", "-a", "1");
+               SYNTAX_TEST("!", "1", "-o", "1", "-a", "1");
+               SYNTAX_TEST("!", "1", "-a", "1", "-o", "1");
+               SYNTAX_TEST("!", "(", "-e", "/", ")");
+       } ASYNC_END;
+       ASYNC_BEGIN {
+               /* Check non-support of harmful features */
+               SYNTAX_TEST("1", "<", "1");
+               SYNTAX_TEST("1", "<=", "1");
+               SYNTAX_TEST("1", ">", "1");
+               SYNTAX_TEST("1", ">=", "1");
+               SYNTAX_TEST("!", "1", "<", "1");
+               SYNTAX_TEST("!", "1", "<=", "1");
+               SYNTAX_TEST("!", "1", ">", "1");
+               SYNTAX_TEST("!", "1", ">=", "1");
+       } ASYNC_END;
+
+       async_join();
+
+       close(tfds[0]);
+       close(tfds[1]);
+       close(pfds[0]);
+       close(pfds[1]);
+       unlink("h");
+       unlink("r");
+       unlink("w");
+       unlink("x");
+       unlink("x-hardlink");
+       unlink("u");
+       unlink("g");
+       unlink("k");
+       unlink("+e");
+       unlink("s");
+       unlink("S");
+       unlink("p");
+       unlink("-N");
+       unlink("+N");
+       unlink("also-old");
+       unlink("old");
+       unlink("new");
+       unlink("-eq");
+       chdir("..");
+       rmdir(dir);
+
+       return main_ret;
+}
diff --git a/time.test.c b/time.test.c
new file mode 100644
index 0000000..37f7414
--- /dev/null
+++ b/time.test.c
@@ -0,0 +1,218 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+#include <sys/time.h>
+
+static int
+time_trunc(int argc, char *argv[])
+{
+       int fds[2];
+       pid_t pid;
+       FILE *fp;
+       char *lines[4] = {NULL, NULL, NULL, NULL}, *tmp_line, *p;
+       size_t sizes[4] = {0, 0, 0, 0}, tmp_size;
+       ssize_t lens[4] = {0, 0, 0, 0}, tmp_len;
+       size_t i;
+
+       if (pipe(fds))
+               return perror("pipe"), -1;
+
+       switch ((pid = fork())) {
+       case -1:
+               return perror("fork"), -1;
+       case 0:
+#ifdef SET_PDEATHSIG
+               if (prctl(PR_SET_PDEATHSIG, SIGINT))
+                       return perror("PR_SET_PDEATHSIG"), -1;
+#endif
+               close(fds[0]);
+               if (fds[1] != 2) {
+                       if (dup2(fds[1], 2) != 2)
+                               return perror("dup2"), -1;
+                       close(fds[1]);
+               }
+               execvp(argv[2], &argv[2]);
+               return perror("exec"), -1;
+       default:
+               close(fds[1]);
+               break;
+       }
+
+       fp = fdopen(fds[0], "rb");
+       if (!fp)
+               return perror("fdopen"), -1;
+
+       while ((lens[3] = getline(&lines[3], &sizes[3], fp)) > 0) {
+               tmp_line = lines[0];
+               tmp_size = sizes[0];
+               tmp_len = lens[0];
+               memmove(lines, &lines[1], 3 * sizeof(*lines));
+               memmove(sizes, &sizes[1], 3 * sizeof(*sizes));
+               memmove(lens, &lens[1], 3 * sizeof(*lens));
+               lines[3] = tmp_line;
+               sizes[3] = tmp_size;
+               lens[3] = tmp_len;
+               if (tmp_line)
+                       fwrite(tmp_line, 1, tmp_len, stderr);
+       }
+
+       if (ferror(fp))
+               return perror("ferror"), -1;
+       if (fclose(fp))
+               return perror("fclose"), -1;
+
+       for (i = 0; i < 3; i++) {
+               if (!lines[i])
+                       continue;
+               if (memchr(lines[i], '\0', lens[i]) || !(p = strchr(lines[i], ' 
'))) {
+                       fwrite(lines[i], 1, lens[i], stderr);
+                       continue;
+               }
+               p = strchr(p, '.');
+               if (!p || strspn(&p[1], "0123456789")[&p[1]] != '\n' || 
strlen(p) < 3) {
+                       fwrite(lines[i], 1, lens[i], stderr);
+                       continue;
+               }
+               p[2] = '\0';
+               fprintf(stderr, "%s\n", lines[i]);
+       }
+
+       for (i = 0; i < 4; i++)
+               free(lines[i]);
+
+       if (fflush(stderr))
+               return perror("fflush"), -1;
+       if (ferror(stderr))
+               return perror("ferror"), -1;
+       if (fclose(stderr))
+               return perror("fclose"), -1;
+
+       (void) argc;
+       return 0;
+}
+
+volatile uintmax_t b = 1;
+volatile uintmax_t x;
+volatile uintmax_t *bs = &b;
+
+int
+main(int argc, char *argv[])
+{
+       struct itimerval timerval = {{0, 120000UL}, {0, 120000UL}};
+
+       if (argc > 1) {
+               if (!strcmp(argv[1], "trunc")) {
+                       if (!time_trunc(argc, argv))
+                               return 0;
+               } else if (!strcmp(argv[1], "sleep")) {
+                       setitimer(ITIMER_REAL, &timerval, NULL);
+                       pause();
+               } else if (!strcmp(argv[1], "user")) {
+                       setitimer(ITIMER_REAL, &timerval, NULL);
+                       for (x = 0; x != UINTMAX_MAX;) x += *bs;
+               } else if (!strcmp(argv[1], "signal")) {
+                       timerval.it_interval.tv_usec = 
timerval.it_value.tv_usec = 100UL;
+                       setitimer(ITIMER_REAL, &timerval, NULL);
+                       pause();
+               }
+               return 200;
+       }
+
+       alarm(timeout);
+
+       ASYNC_BEGIN {
+               proc = CMD(argv[0], "trunc", "./time", "-p", "--", argv[0], 
"sleep");
+               CHECK(check_exit(EXIT, 0) && check_runtime(0.10, 0.15) && 
check_stdout(EQUALS, "") &&
+                     check_stderr(LINE, "real 0.1\nuser 0.0\nsys 0.0\n"));
+       } ASYNC_END;
+
+       ASYNC_BEGIN {
+               proc = CMD(argv[0], "trunc", "./time", "--", argv[0], "sleep"); 
/* non-portable test */
+               CHECK(check_exit(EXIT, 0) && check_runtime(0.10, 0.15) && 
check_stdout(EQUALS, "") &&
+                     check_stderr(LINE, "real 0.1\nuser 0.0\nsys 0.0\n"));
+       } ASYNC_END;
+
+       proc = CMD("./time", "---");
+       CHECK(check_exit(EXIT | RANGE, 1, SETUP_ERROR) && 
check_stderr(BEGINNING, "usage: ") && check_stdout(EQUALS, ""));
+
+       proc = CMD("./time");
+       CHECK(check_exit(EXIT | RANGE, 1, SETUP_ERROR) && 
check_stderr(BEGINNING, "usage: ") && check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "--");
+       CHECK(check_exit(EXIT | RANGE, 1, SETUP_ERROR) && 
check_stderr(BEGINNING, "usage: ") && check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "true");
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "false");
+       CHECK(!check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "--", "true");
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "--", "false");
+       CHECK(!check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "-p", "true");
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "-p", "false");
+       CHECK(!check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "cat");
+       set_input(IN_TEXT(STDIN_FILENO, "hello world\n"));
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "hello world\n"));
+
+       proc = CMD("./time", "printf", "hello world\\n");
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "hello world\n"));
+
+       proc = CMD("./time", "-p", "printf", "hello world\\n");
+       CHECK(check_exit(EXIT, SUCCESS) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "hello world\n"));
+
+       proc = CMD("./time", "sh", "-c", "printf 'hello world\\n' >&2");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") &&
+             check_stderr(BEGINNING, "hello world\n") && !check_stderr(EQUALS, 
"hello world\n"));
+
+       proc = CMD("./time", "-p", "sh", "-c", "printf 'hello world\\n' >&2");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") &&
+             check_stderr(BEGINNING, "hello world\n") && !check_stderr(EQUALS, 
"hello world\n"));
+
+       proc = CMD("./time", "sh", "-c", "exit 4");
+       CHECK(check_exit(EXIT, 4) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "-p", "sh", "-c", "exit 4");
+       CHECK(check_exit(EXIT, 4) && !check_stderr(EQUALS, "") && 
check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "/");
+       CHECK(check_exit(EXIT, EXEC_ERROR) && check_stdout(EQUALS, "") &&
+             (check_stderr(CONTAINS, strerror(EISDIR)) || 
check_stderr(CONTAINS, strerror(EACCES))));
+
+       proc = CMD("./time", "-p", "/");
+       CHECK(check_exit(EXIT, EXEC_ERROR) && check_stdout(EQUALS, "") &&
+             (check_stderr(CONTAINS, strerror(EISDIR)) || 
check_stderr(CONTAINS, strerror(EACCES))));
+
+       proc = CMD("./time", "./ file that does not exist ");
+       CHECK(check_exit(EXIT, NOENT_ERROR) && check_stderr(CONTAINS, 
strerror(ENOENT)) && check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "-p", "./ file that does not exist ");
+       CHECK(check_exit(EXIT, NOENT_ERROR) && check_stderr(CONTAINS, 
strerror(ENOENT)) && check_stdout(EQUALS, ""));
+
+       proc = CMD("./time", "-p", "--", argv[0], "signal");
+       CHECK(check_exit(EXIT | RANGE, 1, SETUP_ERROR) || check_exit(EXIT, 
SIGNAL_EXIT(SIGALRM)));
+
+       proc = CMD(argv[0], "trunc", "./time", "-p", "--", argv[0], "signal");
+       CHECK(check_exit(EXIT, 0) && check_stdout(EQUALS, "") && 
check_runtime(0.00, 0.05) &&
+             check_stderr(LINE, "real 0.0\n"));
+
+       async_join();
+
+
+       proc = CMD(argv[0], "trunc", "./time", "-p", "--", argv[0], "user");
+       CHECK(check_exit(EXIT, 0) && check_runtime(0.10, 0.15) && 
check_stdout(EQUALS, "") &&
+             check_stderr(LINE, "real 0.1\nuser 0.1\nsys 0.0\n"));
+
+       proc = CMD(argv[0], "trunc", "./time", "--", argv[0], "user"); /* 
non-portable test */
+       CHECK(check_exit(EXIT, 0) && check_runtime(0.10, 0.15) && 
check_stdout(EQUALS, "") &&
+             check_stderr(LINE, "real 0.1\nuser 0.1\nsys 0.0\n"));
+
+       return main_ret;
+}
diff --git a/true.test.c b/true.test.c
new file mode 100644
index 0000000..0479f23
--- /dev/null
+++ b/true.test.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+#define TEST(...)\
+       proc = CMD(__VA_ARGS__);\
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""))
+
+int
+main(void)
+{
+       alarm(timeout);
+
+       TEST("./true");
+       TEST("./true", "1");
+       TEST("./true", "1", "2");
+       TEST("./true", "1", "2", "3");
+       TEST("./true", "-");
+       TEST("./true", "-h");
+       TEST("./true", "-H");
+       TEST("./true", "-v");
+       TEST("./true", "-V");
+       TEST("./true", "-vVhH");
+       TEST("./true", "--");
+       TEST("./true", "--", "1");
+       TEST("./true", "--hello");
+       TEST("./true", "--help");
+       TEST("./true", "--version");
+       TEST("./true", "---");
+
+       return main_ret;
+}
diff --git a/tty.test.c b/tty.test.c
new file mode 100644
index 0000000..ff4e511
--- /dev/null
+++ b/tty.test.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+int
+main(void)
+{
+       char buf[1024];
+       int fds[2];
+
+       alarm(timeout);
+
+       proc = CMD("./tty", "-");
+       CHECK(check_usage_error(BOOLEAN_ERROR));
+
+       proc = CMD("./tty", "x");
+       CHECK(check_usage_error(BOOLEAN_ERROR));
+
+       proc = CMD("./tty", "---");
+       CHECK(check_usage_error(BOOLEAN_ERROR));
+
+       proc = CMD("./tty");
+       CHECK(check_exit(EXIT, SUCCESS_FALSE) && check_stdout(EQUALS, "not a 
tty\n") && check_stderr(EQUALS, ""));
+
+       stpcpy(stpcpy(buf, openpt(0, fds)), "\n");
+       proc = CMD("./tty");
+       set_input(IN_FDS(STDIN_FILENO, fds[1], fds[0]));
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") &&
+             check_stdout(EQUALS, buf) && check_stdout(BEGINNING, 
"/dev/pts/"));
+       close(fds[0]);
+
+#ifdef TODO
+       proc = CMD("./tty",  "--");
+       CHECK(check_exit(EXIT, SUCCESS_FALSE) && check_stdout(EQUALS, "not a 
tty\n") && check_stderr(EQUALS, ""));
+
+       stpcpy(stpcpy(buf, openpt(0, fds)), "\n");
+       proc = CMD("./tty", "--");
+       set_input(IN_FDS(STDIN_FILENO, fds[1], fds[0]));
+       CHECK(check_exit(EXIT, SUCCESS_TRUE) && check_stderr(EQUALS, "") &&
+             check_stdout(EQUALS, buf) && check_stdout(BEGINNING, 
"/dev/pts/"));
+       close(fds[0]);
+#endif
+
+       return main_ret;
+}
diff --git a/uname.test.c b/uname.test.c
new file mode 100644
index 0000000..c2a825f
--- /dev/null
+++ b/uname.test.c
@@ -0,0 +1,283 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+#include <sys/utsname.h>
+
+#define TEST(...)\
+       proc = CMD(__VA_ARGS__);\
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, buf))
+
+#define TEST1(OUT, ...)\
+       sprintf(buf, "%s\n", OUT);\
+       TEST(__VA_ARGS__)
+
+static const char *values[8];
+static char chars[8];
+
+static void
+test2(size_t f1, size_t f2)
+{
+       char buf[1000], flag1[10], flag2[10];
+       char c1 = chars[f1];
+       char c2 = chars[f2];
+
+       sprintf(buf, "%s %s\n", values[f1], values[f2]);
+       sprintf(flag1, "-%c%c", c1, c2);
+       TEST("./uname", flag1);
+
+       sprintf(flag1, "-%c%c", c2, c1);
+       TEST("./uname", flag1);
+
+       sprintf(flag1, "-%c", c1);
+       sprintf(flag2, "-%c", c2);
+       TEST("./uname", flag1, flag2);
+       TEST("./uname", flag2, flag1);
+}
+
+static void
+test3(size_t f1, size_t f2, size_t f3)
+{
+       char buf[1000], flag1[10], flag2[10], flag3[10];
+
+       sprintf(buf, "%s %s %s\n", values[f1], values[f2], values[f3]);
+       sprintf(flag1, "-%c", chars[f1]);
+       sprintf(flag2, "-%c", chars[f2]);
+       sprintf(flag3, "-%c", chars[f3]);
+
+       TEST("./uname", flag1, flag2, flag3);
+       TEST("./uname", flag1, flag3, flag2);
+       TEST("./uname", flag2, flag1, flag3);
+       TEST("./uname", flag3, flag1, flag2);
+       TEST("./uname", flag2, flag3, flag1);
+       TEST("./uname", flag3, flag2, flag1);
+}
+
+static void
+test4(size_t f1, size_t f2, size_t f3, size_t f4)
+{
+       char buf[1000], flag1[10], flag2[10], flag3[10], flag4[10];
+
+       sprintf(buf, "%s %s %s %s\n", values[f1], values[f2], values[f3], 
values[f4]);
+       sprintf(flag1, "-%c", chars[f1]);
+       sprintf(flag2, "-%c", chars[f2]);
+       sprintf(flag3, "-%c", chars[f3]);
+       sprintf(flag4, "-%c", chars[f4]);
+
+       TEST("./uname", flag1, flag2, flag3, flag4);
+       TEST("./uname", flag1, flag2, flag4, flag3);
+       TEST("./uname", flag1, flag3, flag2, flag4);
+       TEST("./uname", flag1, flag3, flag4, flag2);
+       TEST("./uname", flag1, flag4, flag2, flag3);
+       TEST("./uname", flag1, flag4, flag3, flag2);
+       TEST("./uname", flag2, flag1, flag3, flag4);
+       TEST("./uname", flag2, flag1, flag4, flag3);
+       TEST("./uname", flag2, flag3, flag1, flag4);
+       TEST("./uname", flag2, flag3, flag4, flag1);
+       TEST("./uname", flag2, flag4, flag1, flag3);
+       TEST("./uname", flag2, flag4, flag3, flag1);
+       TEST("./uname", flag3, flag1, flag2, flag4);
+       TEST("./uname", flag3, flag1, flag4, flag2);
+       TEST("./uname", flag3, flag2, flag1, flag4);
+       TEST("./uname", flag3, flag2, flag4, flag1);
+       TEST("./uname", flag3, flag4, flag1, flag2);
+       TEST("./uname", flag3, flag4, flag2, flag1);
+       TEST("./uname", flag4, flag1, flag2, flag3);
+       TEST("./uname", flag4, flag1, flag3, flag2);
+       TEST("./uname", flag4, flag2, flag1, flag3);
+       TEST("./uname", flag4, flag2, flag3, flag1);
+       TEST("./uname", flag4, flag3, flag1, flag2);
+       TEST("./uname", flag4, flag3, flag2, flag1);
+}
+
+static void
+test5(size_t f1, size_t f2, size_t f3, size_t f4, size_t f5)
+{
+       char buf[1000], flag1[10], flag2[10], flag3[10], flag4[10], flag5[10];
+
+       sprintf(buf, "%s %s %s %s %s\n", values[f1], values[f2], values[f3], 
values[f4], values[f5]);
+       sprintf(flag1, "-%c", chars[f1]);
+       sprintf(flag2, "-%c", chars[f2]);
+       sprintf(flag3, "-%c", chars[f3]);
+       sprintf(flag4, "-%c", chars[f4]);
+       sprintf(flag5, "-%c", chars[f5]);
+
+       TEST("./uname", flag1, flag2, flag3, flag4, flag5);
+       TEST("./uname", flag1, flag2, flag4, flag3, flag5);
+       TEST("./uname", flag1, flag3, flag2, flag4, flag5);
+       TEST("./uname", flag1, flag3, flag4, flag2, flag5);
+       TEST("./uname", flag1, flag4, flag2, flag3, flag5);
+       TEST("./uname", flag1, flag4, flag3, flag2, flag5);
+       TEST("./uname", flag2, flag1, flag3, flag4, flag5);
+       TEST("./uname", flag2, flag1, flag4, flag3, flag5);
+       TEST("./uname", flag2, flag3, flag1, flag4, flag5);
+       TEST("./uname", flag2, flag3, flag4, flag1, flag5);
+       TEST("./uname", flag2, flag4, flag1, flag3, flag5);
+       TEST("./uname", flag2, flag4, flag3, flag1, flag5);
+       TEST("./uname", flag3, flag1, flag2, flag4, flag5);
+       TEST("./uname", flag3, flag1, flag4, flag2, flag5);
+       TEST("./uname", flag3, flag2, flag1, flag4, flag5);
+       TEST("./uname", flag3, flag2, flag4, flag1, flag5);
+       TEST("./uname", flag3, flag4, flag1, flag2, flag5);
+       TEST("./uname", flag3, flag4, flag2, flag1, flag5);
+       TEST("./uname", flag4, flag1, flag2, flag3, flag5);
+       TEST("./uname", flag4, flag1, flag3, flag2, flag5);
+       TEST("./uname", flag4, flag2, flag1, flag3, flag5);
+       TEST("./uname", flag4, flag2, flag3, flag1, flag5);
+       TEST("./uname", flag4, flag3, flag1, flag2, flag5);
+       TEST("./uname", flag4, flag3, flag2, flag1, flag5);
+       TEST("./uname", flag1, flag2, flag3, flag5, flag4);
+       TEST("./uname", flag1, flag2, flag4, flag5, flag3);
+       TEST("./uname", flag1, flag3, flag2, flag5, flag4);
+       TEST("./uname", flag1, flag3, flag4, flag5, flag2);
+       TEST("./uname", flag1, flag4, flag2, flag5, flag3);
+       TEST("./uname", flag1, flag4, flag3, flag5, flag2);
+       TEST("./uname", flag2, flag1, flag3, flag5, flag4);
+       TEST("./uname", flag2, flag1, flag4, flag5, flag3);
+       TEST("./uname", flag2, flag3, flag1, flag5, flag4);
+       TEST("./uname", flag2, flag3, flag4, flag5, flag1);
+       TEST("./uname", flag2, flag4, flag1, flag5, flag3);
+       TEST("./uname", flag2, flag4, flag3, flag5, flag1);
+       TEST("./uname", flag3, flag1, flag2, flag5, flag4);
+       TEST("./uname", flag3, flag1, flag4, flag5, flag2);
+       TEST("./uname", flag3, flag2, flag1, flag5, flag4);
+       TEST("./uname", flag3, flag2, flag4, flag5, flag1);
+       TEST("./uname", flag3, flag4, flag1, flag5, flag2);
+       TEST("./uname", flag3, flag4, flag2, flag5, flag1);
+       TEST("./uname", flag4, flag1, flag2, flag5, flag3);
+       TEST("./uname", flag4, flag1, flag3, flag5, flag2);
+       TEST("./uname", flag4, flag2, flag1, flag5, flag3);
+       TEST("./uname", flag4, flag2, flag3, flag5, flag1);
+       TEST("./uname", flag4, flag3, flag1, flag5, flag2);
+       TEST("./uname", flag4, flag3, flag2, flag5, flag1);
+       TEST("./uname", flag1, flag2, flag5, flag3, flag4);
+       TEST("./uname", flag1, flag2, flag5, flag4, flag3);
+       TEST("./uname", flag1, flag3, flag5, flag2, flag4);
+       TEST("./uname", flag1, flag3, flag5, flag4, flag2);
+       TEST("./uname", flag1, flag4, flag5, flag2, flag3);
+       TEST("./uname", flag1, flag4, flag5, flag3, flag2);
+       TEST("./uname", flag2, flag1, flag5, flag3, flag4);
+       TEST("./uname", flag2, flag1, flag5, flag4, flag3);
+       TEST("./uname", flag2, flag3, flag5, flag1, flag4);
+       TEST("./uname", flag2, flag3, flag5, flag4, flag1);
+       TEST("./uname", flag2, flag4, flag5, flag1, flag3);
+       TEST("./uname", flag2, flag4, flag5, flag3, flag1);
+       TEST("./uname", flag3, flag1, flag5, flag2, flag4);
+       TEST("./uname", flag3, flag1, flag5, flag4, flag2);
+       TEST("./uname", flag3, flag2, flag5, flag1, flag4);
+       TEST("./uname", flag3, flag2, flag5, flag4, flag1);
+       TEST("./uname", flag3, flag4, flag5, flag1, flag2);
+       TEST("./uname", flag3, flag4, flag5, flag2, flag1);
+       TEST("./uname", flag4, flag1, flag5, flag2, flag3);
+       TEST("./uname", flag4, flag1, flag5, flag3, flag2);
+       TEST("./uname", flag4, flag2, flag5, flag1, flag3);
+       TEST("./uname", flag4, flag2, flag5, flag3, flag1);
+       TEST("./uname", flag4, flag3, flag5, flag1, flag2);
+       TEST("./uname", flag4, flag3, flag5, flag2, flag1);
+       TEST("./uname", flag1, flag5, flag2, flag3, flag4);
+       TEST("./uname", flag1, flag5, flag2, flag4, flag3);
+       TEST("./uname", flag1, flag5, flag3, flag2, flag4);
+       TEST("./uname", flag1, flag5, flag3, flag4, flag2);
+       TEST("./uname", flag1, flag5, flag4, flag2, flag3);
+       TEST("./uname", flag1, flag5, flag4, flag3, flag2);
+       TEST("./uname", flag2, flag5, flag1, flag3, flag4);
+       TEST("./uname", flag2, flag5, flag1, flag4, flag3);
+       TEST("./uname", flag2, flag5, flag3, flag1, flag4);
+       TEST("./uname", flag2, flag5, flag3, flag4, flag1);
+       TEST("./uname", flag2, flag5, flag4, flag1, flag3);
+       TEST("./uname", flag2, flag5, flag4, flag3, flag1);
+       TEST("./uname", flag3, flag5, flag1, flag2, flag4);
+       TEST("./uname", flag3, flag5, flag1, flag4, flag2);
+       TEST("./uname", flag3, flag5, flag2, flag1, flag4);
+       TEST("./uname", flag3, flag5, flag2, flag4, flag1);
+       TEST("./uname", flag3, flag5, flag4, flag1, flag2);
+       TEST("./uname", flag3, flag5, flag4, flag2, flag1);
+       TEST("./uname", flag4, flag5, flag1, flag2, flag3);
+       TEST("./uname", flag4, flag5, flag1, flag3, flag2);
+       TEST("./uname", flag4, flag5, flag2, flag1, flag3);
+       TEST("./uname", flag4, flag5, flag2, flag3, flag1);
+       TEST("./uname", flag4, flag5, flag3, flag1, flag2);
+       TEST("./uname", flag4, flag5, flag3, flag2, flag1);
+       TEST("./uname", flag5, flag1, flag2, flag3, flag4);
+       TEST("./uname", flag5, flag1, flag2, flag4, flag3);
+       TEST("./uname", flag5, flag1, flag3, flag2, flag4);
+       TEST("./uname", flag5, flag1, flag3, flag4, flag2);
+       TEST("./uname", flag5, flag1, flag4, flag2, flag3);
+       TEST("./uname", flag5, flag1, flag4, flag3, flag2);
+       TEST("./uname", flag5, flag2, flag1, flag3, flag4);
+       TEST("./uname", flag5, flag2, flag1, flag4, flag3);
+       TEST("./uname", flag5, flag2, flag3, flag1, flag4);
+       TEST("./uname", flag5, flag2, flag3, flag4, flag1);
+       TEST("./uname", flag5, flag2, flag4, flag1, flag3);
+       TEST("./uname", flag5, flag2, flag4, flag3, flag1);
+       TEST("./uname", flag5, flag3, flag1, flag2, flag4);
+       TEST("./uname", flag5, flag3, flag1, flag4, flag2);
+       TEST("./uname", flag5, flag3, flag2, flag1, flag4);
+       TEST("./uname", flag5, flag3, flag2, flag4, flag1);
+       TEST("./uname", flag5, flag3, flag4, flag1, flag2);
+       TEST("./uname", flag5, flag3, flag4, flag2, flag1);
+       TEST("./uname", flag5, flag4, flag1, flag2, flag3);
+       TEST("./uname", flag5, flag4, flag1, flag3, flag2);
+       TEST("./uname", flag5, flag4, flag2, flag1, flag3);
+       TEST("./uname", flag5, flag4, flag2, flag3, flag1);
+       TEST("./uname", flag5, flag4, flag3, flag1, flag2);
+       TEST("./uname", flag5, flag4, flag3, flag2, flag1);
+}
+
+int
+main(void)
+{
+       struct utsname name;
+       const char *sflag, *nflag, *rflag, *vflag, *mflag;
+       char buf[1000];
+       size_t a, b, c, d, e;
+
+       alarm(timeout);
+
+       if (uname(&name) < 0)
+               eperror("uname");
+       sflag = values[0] = name.sysname,  chars[0] = 's';
+       nflag = values[1] = name.nodename, chars[1] = 'n';
+       rflag = values[2] = name.release,  chars[2] = 'r';
+       vflag = values[3] = name.version,  chars[3] = 'v';
+       mflag = values[4] = name.machine,  chars[4] = 'm';
+
+#ifdef TODO
+       proc = CMD("./uname", "-");
+       CHECK(check_usage_error(REGULAR_ERROR));
+#endif
+
+       proc = CMD("./uname", "---");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       TEST1(sflag, "./uname");
+       TEST1(sflag, "./uname", "--");
+       TEST1(sflag, "./uname", "-s");
+       TEST1(sflag, "./uname", "-ss");
+       TEST1(sflag, "./uname", "-s", "-s");
+       TEST1(mflag, "./uname", "-m");
+       TEST1(nflag, "./uname", "-n");
+       TEST1(rflag, "./uname", "-r");
+       TEST1(vflag, "./uname", "-v");
+
+       for (COUNTER(a, 0, a < 5)) {
+               ASYNC_BEGIN {
+                       for (COUNTER(b, a + 1, b < 5)) {
+                               test2(a, b);
+                               for (COUNTER(c, b + 1, c < 5)) {
+                                       test3(a, b, c);
+                                       for (COUNTER(d, c + 1, d < 5)) {
+                                               test4(a, b, c, d);
+                                               for (COUNTER(e, d + 1, e < 5))
+                                                       test5(a, b, c, d, e);
+                                       }
+                               }
+                       }
+               } ASYNC_END;
+       }
+
+       sprintf(buf, "%s %s %s %s %s\n", values[0], values[1], values[2], 
values[3], values[4]);
+       TEST("./uname", "-a");
+       TEST("./uname", "-anv");
+       TEST("./uname", "-mras");
+
+       return main_ret;
+}
diff --git a/unexpand.test.c b/unexpand.test.c
new file mode 100644
index 0000000..2666dbf
--- /dev/null
+++ b/unexpand.test.c
@@ -0,0 +1,97 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+struct Case {
+       const char *input;
+       size_t input_len;
+       const char *argv[2];
+       const char *output;
+       size_t output_len;
+};
+
+static struct Case failure_cases[] = {
+       {"",   0, {"---"},    "",   0},
+       {"",   0, {"-t2,1"},  "",   0},
+       {"",   0, {"-t0"},    "",   0},
+       {"",   0, {"-t0,1"},  "",   0},
+       {"",   0, {"-t"},     "",   0},
+       {"",   0, {"-t"},     "",   0},
+       {"",   0, {"-t", ""}, "",   0},
+       {NULL, 0, {NULL},     NULL, 0}
+};
+
+static struct Case success_cases[] = {
+       {BIN("x"),                    {NULL},      BIN("x")},
+       {BIN("        x"),            {NULL},      BIN("\tx")},
+       {BIN("\tx"),                  {NULL},      BIN("\tx")},
+       {BIN("1\t2"),                 {NULL},      BIN("1\t2")},
+       {BIN("1\t2"),                 {"-a"},      BIN("1\t2")},
+       {BIN("1\t2"),                 {"-t8"},     BIN("1\t2")},
+       {BIN("1\t2"),                 {"-t", "8"}, BIN("1\t2")},
+       {BIN("1       2"),            {"-"},       BIN("1       2")},
+       {BIN("1       2"),            {"--"},      BIN("1       2")},
+       {BIN("1       2"),            {"--", "-"}, BIN("1       2")},
+       {BIN("1       2"),            {NULL},      BIN("1       2")},
+       {BIN("1       2"),            {"-a"},      BIN("1\t2")},
+       {BIN("1       2"),            {"-t8"},     BIN("1\t2")},
+       {BIN("1       2"),            {"-t", "8"}, BIN("1\t2")},
+       {BIN("1       2       3"),    {"-a"},      BIN("1\t2\t3")},
+       {BIN("1       2       3"),    {"-t4"},     BIN("1\t\t2\t\t3")},
+       {BIN("1       \b 2       3"), {"-t4"},     BIN("1\t\t\b\t2\t\t3")},
+       {BIN("1       \b2        3"), {"-t4"},     BIN("1\t\t\b2\t\t3")},
+       {BIN("1        2      3"),    {"-t4"},     BIN("1\t\t 2\t\t3")},
+       {BIN("1    \n  8"),           {"-a"},      BIN("1    \n  8")},
+       {BIN("        \n        \n"), {NULL},      BIN("\t\n\t\n")},
+       {BIN("åäö     x\n"),          {"-a"},      BIN("åäö\tx\n")},
+       {BIN("åäö\b      x\n"),       {"-a"},      BIN("åäö\b\tx\n")},
+#ifdef TODO
+       {BIN("〇      x\n"),          {"-a"},      BIN("〇\tx\n")},
+       {BIN("〇\b       x\n"),       {"-a"},      BIN("〇\b\tx\n")},
+#endif
+       {BIN("  x     y\n"),          {"-t2,8"},   BIN("\tx\ty\n")},
+       {BIN("  x     y\n"),          {"-t2,7"},   BIN("\tx\t y\n")},
+       {BIN("  x    y\n"),           {"-t2,8"},   BIN("\tx    y\n")},
+       {BIN("  x     y\n"),          {"-t2 8"},   BIN("\tx\ty\n")},
+       {BIN("  x     y\n"),          {"-t2 7"},   BIN("\tx\t y\n")},
+       {BIN("  x    y\n"),           {"-t2 8"},   BIN("\tx    y\n")},
+       {BIN(" \tx\n"),               {NULL},      BIN("\tx\n")},
+       {BIN("1       2       3"),    {"-at8"},    BIN("1\t2\t3")},
+       {BIN("\0       \0       \0"), {"-at8"}, BIN("\0\t\0\t\0")},
+       {NULL, 0,                     {NULL},      NULL, 0}
+};
+
+int
+main(void)
+{
+       size_t i;
+       char f1[200], dir[100];
+
+       alarm(timeout);
+
+       sprintf(dir, "testdir-%ju", (uintmax_t)getpid());
+       if (mkdir(dir, 0700))
+               eperror(dir);
+       sprintf(f1, "%s/1", dir);
+       write_file(f1, "1       2", 0);
+
+       proc = CMD("./unexpand", "-a", f1, f1);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, "1\t21\t2"));
+
+       for (COUNTER(i, 0, failure_cases[i].input)) {
+               proc = CMD("./unexpand", failure_cases[i].argv[0], 
failure_cases[i].argv[1]);
+               set_input(IN_NBIN(STDIN_FILENO, failure_cases[i].input, 
failure_cases[i].input_len));
+               CHECK(check_exit(EXIT, REGULAR_ERROR) && !check_stderr(EQUALS, 
"") && check_stdout(EQUALS, ""));
+       }
+
+       for (COUNTER(i, 0, success_cases[i].input)) {
+               proc = CMD("./unexpand", success_cases[i].argv[0], 
success_cases[i].argv[1]);
+               set_input(IN_NBIN(STDIN_FILENO, success_cases[i].input, 
success_cases[i].input_len));
+               CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") &&
+                     check_stdout(EQUALS | BINARY, success_cases[i].output, 
success_cases[i].output_len));
+       }
+
+       unlink(f1);
+       rmdir(dir);
+
+       return main_ret;
+}
diff --git a/unlink.test.c b/unlink.test.c
new file mode 100644
index 0000000..27cc5c6
--- /dev/null
+++ b/unlink.test.c
@@ -0,0 +1,56 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+int
+main(void)
+{
+       char path[200], dir[100];
+
+       alarm(timeout);
+
+       sprintf(dir, "testdir-%ju", (uintmax_t)getpid());
+       if (mkdir(dir, 0700))
+               eperror(dir);
+       sprintf(path, "%s/f", dir);
+       close(open(path, O_CREAT | O_WRONLY | O_EXCL, 0600));
+
+#ifdef TODO
+       proc = CMD("./unlink", "--");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./unlink", "---");
+       CHECK(check_usage_error(REGULAR_ERROR));
+#endif
+
+       proc = CMD("./unlink", dir);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(EISDIR)));
+
+#ifdef TODO
+       proc = CMD("./unlink", "--", dir);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(EISDIR)));
+#endif
+
+       proc = CMD("./unlink", path, path);
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./unlink", path);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));
+
+       proc = CMD("./unlink", path);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(ENOENT)));
+
+#ifdef TODO
+       close(open(path, O_CREAT | O_WRONLY | O_EXCL, 0600));
+
+       proc = CMD("./unlink", "--", path);
+       CHECK(check_exit(EXIT, SUCCESS) && check_stdout(EQUALS, "") && 
check_stderr(EQUALS, ""));
+
+       proc = CMD("./unlink", "--", path);
+       CHECK(check_exit(EXIT, REGULAR_ERROR) && check_stdout(EQUALS, "") && 
check_stderr(CONTAINS, strerror(ENOENT)));
+#endif
+
+       unlink(path);
+       rmdir(dir);
+
+       return main_ret;
+}
diff --git a/whoami.test.c b/whoami.test.c
new file mode 100644
index 0000000..213da22
--- /dev/null
+++ b/whoami.test.c
@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+#include <pwd.h>
+
+int
+main(void)
+{
+       char buf[100];
+
+       alarm(timeout);
+
+       stpcpy(stpcpy(buf, getpwuid(geteuid())->pw_name), "\n");
+
+       proc = CMD("./whoami");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, buf));
+
+#ifdef TODO
+       proc = CMD("./whoami", "--");
+       CHECK(check_exit(EXIT, SUCCESS) && check_stderr(EQUALS, "") && 
check_stdout(EQUALS, buf));
+#endif
+
+       proc = CMD("./whoami", "-");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./whoami", "x");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./whoami", "--", "-");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./whoami", "--", "x");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       proc = CMD("./whoami", "---");
+       CHECK(check_usage_error(REGULAR_ERROR));
+
+       return main_ret;
+}
diff --git a/yes.test.c b/yes.test.c
new file mode 100644
index 0000000..d5c41d4
--- /dev/null
+++ b/yes.test.c
@@ -0,0 +1,131 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+int
+head(size_t lines, char *argv[])
+{
+       int fds[2], status;
+       pid_t pid;
+       char buf[BUFSIZ], *p;
+       size_t n;
+       ssize_t r;
+
+       if (pipe(fds))
+               eperror("pipe");
+
+       switch ((pid = fork())) {
+       case -1:
+               eperror("fork");
+       case 0:
+               close(fds[0]);
+               if (fds[1] != STDOUT_FILENO) {
+                       if (dup2(fds[1], STDOUT_FILENO) != STDOUT_FILENO)
+                               eperror("dup2");
+                       close(fds[1]);
+               }
+               execv(*argv, argv);
+               eperror("execv");
+       default:
+               close(fds[1]);
+               break;
+       }
+
+       while (lines) {
+               r = read(fds[0], buf, sizeof(buf));
+               if (r < 0)
+                       perror("read");
+               if (!r)
+                       break;
+               for (n = 0; n < (size_t)r && lines; n++) {
+                       if (buf[n] == '\n' && !--lines) {
+                               n += 1;
+                               break;
+                       }
+               }
+               for (p = buf; n; p = &p[n], n -= (size_t)r)
+                       if ((r = write(STDOUT_FILENO, p, n)) < 0)
+                               eperror("write");
+       }
+
+       close(fds[0]);
+       if (waitpid(pid, &status, 0) != pid)
+               perror("waitpid");
+
+       if (WIFSIGNALED(status))
+               return 128 + WTERMSIG(status);
+       if (WIFEXITED(status))
+               return WEXITSTATUS(status) % 128;
+       return 128;
+}
+
+int
+main(int argc, char *argv[])
+{
+       char *long1, *long2, *long_out1, *long_out2;
+
+       if (argc > 2)
+               return head((size_t)atoi(argv[1]), &argv[2]);
+
+       alarm(timeout);
+
+       long1 = malloc(10000);
+       if (!long1)
+               eperror("malloc");
+       long2 = malloc(10000);
+       if (!long2)
+               eperror("malloc");
+       long_out1 = malloc(20001UL);
+       if (!long_out1)
+               eperror("malloc");
+       long_out2 = malloc(40001UL);
+       if (!long_out2)
+               eperror("malloc");
+       memset(long1, 'x', 9999), long1[9999] = '\0';
+       memset(long2, 'y', 9999), long2[9999] = '\0';
+       stpcpy(stpcpy(stpcpy(stpcpy(long_out1, long1), "\n"), long1), "\n");
+       stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(
+               long_out2,
+               long1), " "), long2), "\n"),
+               long1), " "), long2), "\n");
+
+       timeout = 1;
+
+#ifdef TODO
+       proc = CMD("./yes", "---");
+       CHECK(check_usage_error(REGULAR_ERROR));
+#endif
+
+       /* check support for 0 operands */
+
+       proc = CMD(argv[0], "5", "./yes");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "y\ny\ny\ny\ny\n"));
+
+#ifdef TODO
+       proc = CMD(argv[0], "5", "./yes", "--");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "y\ny\ny\ny\ny\n"));
+#endif
+
+       /* check support for 1 operand */
+
+       proc = CMD(argv[0], "5", "./yes", "x");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "x\nx\nx\nx\nx\n"));
+
+       proc = CMD(argv[0], "4", "./yes", "åäö𝔘");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "åäö𝔘\nåäö𝔘\nåäö𝔘\nåäö𝔘\n"));
+
+       proc = CMD(argv[0], "2", "./yes", long1);
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, long_out1));
+
+       /* check support for multiple operands */
+
+       proc = CMD(argv[0], "5", "./yes", "a", "b");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "a b\na b\na b\na b\na b\n"));
+
+       proc = CMD(argv[0], "3", "./yes", "a", "b", "c");
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, "a b c\na b c\na b c\n"));
+
+       proc = CMD(argv[0], "2", "./yes", long1, long2);
+       CHECK(check_exit(EXIT, SIGNAL_EXIT(SIGPIPE)) && check_stderr(EQUALS, 
"") && check_stdout(EQUALS, long_out2));
+
+       return main_ret;
+}
-- 
2.11.1


Reply via email to