Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-08 Thread Hiltjo Posthuma
On Sun, Jul 08, 2018 at 10:19:46AM +0100, Dimitris Papastamos wrote:
>> [snip]
> 
> the tests are primarily for the developers, no user cares if you can
> pass all the internal regression tests but fail to build firefox.
> 
> time is better spent writing useful test cases rather than some "test
> framework".
> 

I don't like the current implementation. It uses too many macros and unnecesary
abstractions.

It should be in a separate repository.

The test-cases could be useful to catch "low hanging fruit".

Testsuites can have bugs, will you write tests for the tests? :P

Real-world use is most useful, but of course we need to keep to standards.

-- 
Kind regards,
Hiltjo



Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-08 Thread Silvan Jegen
Hi Mattias

Just some comments below (please ignore the mangled formatting.)

On Sat, Jul 7, 2018 at 11:26 PM, Mattias Andrée  wrote:
> Signed-off-by: Mattias Andrée 
> ---
>  Makefile  |  20 +-
>  test-common.c | 823 
> ++
>  test-common.h | 190 ++
>  tty.test.c|  26 ++
>  4 files changed, 1055 insertions(+), 4 deletions(-)
>  create mode 100644 test-common.c
>  create mode 100644 test-common.h
>  create mode 100644 tty.test.c
>
> diff --git a/Makefile b/Makefile
> index 0e421e7..005cf13 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -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,12 @@ BIN =\
>  xinstall\
>  yes
>
> +TEST =\
> + tty.test
> +
>  LIBUTFOBJ = $(LIBUTFSRC:.c=.o)
>  LIBUTILOBJ = $(LIBUTILSRC:.c=.o)
> -OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ)
> +OBJ = $(BIN:=.o) $(TEST:=.o) test-common.o $(LIBUTFOBJ) $(LIBUTILOBJ)
>  SRC = $(BIN:=.c)
>  MAN = $(BIN:=.1)
>
> @@ -193,12 +197,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 +221,9 @@ getconf.o: getconf.h
>  getconf.h: getconf.sh
>  ./getconf.sh > $@
>
> +check: $(TEST) $(BIN)
> + @set -e; for f in $(TEST); do echo ./$$f; ./$$f; done
> +
>  install: all
>  mkdir -p $(DESTDIR)$(PREFIX)/bin
>  cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
> @@ -271,7 +283,7 @@ sbase-box-uninstall: uninstall
>  cd $(DESTDIR)$(PREFIX)/bin && rm -f sbase-box
>
>  clean:
> - rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
> + rm -f $(BIN) $(TEST) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
>  rm -f getconf.h
>
>  .PHONY: all install uninstall dist sbase-box sbase-box-install 
> sbase-box-uninstall clean
> diff --git a/test-common.c b/test-common.c
> new file mode 100644
> index 000..458b094
> --- /dev/null
> +++ b/test-common.c
> @@ -0,0 +1,823 @@
> +/* See LICENSE file for copyright and license details. */
> +#include "test-common.h"
> +
> +struct Counter {
> + const char *name;
> + size_t value;
> +};
> +
> +const char *test_file = NULL;
> +int test_line = 0;
> +int main_ret = 0;
> +int timeout = 10;
> +int pdeath_sig = SIGINT;
> +void (*atfork)(void) = NULL;
> +
> +static struct Counter counters[16];
> +static size_t ncounters = 0;
> +static pid_t async_pids[1024];
> +static size_t async_npids = 0;
> +
> +static void
> +eperror(const char *prefix)
> +{
> + perror(prefix);
> + fflush(stderr);
> + exit(64);
> +}
> +
> +struct Process *
> +stdin_text(struct Process *proc, char *s)
> +{
> + proc->input[0].data = s;
> + proc->input[0].flags &= ~IO_STREAM_BINARY;
> + return proc;
> +}
> +
> +struct Process *
> +stdin_bin(struct Process *proc, char *s, size_t n)
> +{
> + proc->input[0].data = s;
> + proc->input[0].len = n;
> + proc->input[0].flags |= IO_STREAM_BINARY;
> + return proc;
> +}
> +
> +struct Process *
> +stdin_fds(struct Process *proc, int input_fd, int output_fd)
> +{
> + proc->input[0].flags &= ~IO_STREAM_DATA;
> + proc->input[0].input_fd = input_fd;
> + proc->input[0].output_fd = output_fd;
> + return proc;
> +}
> +
> +struct Process *
> +stdin_type(struct Process *proc, int type)
> +{
> + proc->input[0].flags &= ~IO_STREAM_CREATE_MASK;
> + proc->input[0].flags |= type;
> + return proc;
> +}
> +
> +struct Process *
> +stdout_fds(struct Process *proc, int input_fd, int output_fd)
> +{
> + proc->output[0].flags &= ~IO_STREAM_DATA;
> + proc->output[0].input_fd = input_fd;
> + proc->output[0].output_fd = output_fd;
> + return proc;
> +}
> +
> +struct Process *
> +stdout_type(struct Process *proc, int type)
> +{
> + proc->output[0].flags &= ~IO_STREAM_CREATE_MASK;
> + proc->output[0].flags |= type;
> + return proc;
> +}
> +
> +struct Process *
> +stderr_fds(struct Process *proc, int input_fd, int output_fd)
> +{
> + proc->output[1].flags &= ~IO_STREAM_DATA;
> + proc->output[1].input_fd = input_fd;
> + proc->output[1].output_fd = output_fd;
> + return proc;
> +}
> +
> +struct Process *
> +stderr_type(struct Process *proc, int type)
> +{
> + proc->output[1].flags &= ~IO_STREAM_CREATE_MASK;
> + proc->output[1].flags |= type;
> + return proc;
> +}
> +
> +struct Process *
> +set_preexec(struct Process *proc, void (*preexec)(struct Process *))
> +{
> + proc->preexec = preexec;
> + return proc;
> +}
> +
> +struct Process *
> +set_async(struct Process *proc)
> +{
> + proc->flags |= PROCESS_ASYNC;
> + return proc;
> +}
> +
> +struct Process *
> +set_setsid(struct Process *proc)
> +{
> + proc->flags |= PROCESS_SETSID;
> + return proc;
> +}
> +
> +void
> +push_counter

Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-08 Thread Dimitris Papastamos
On Sun, Jul 08, 2018 at 11:13:52AM +0200, Mattias Andrée wrote:
> On Sun, 8 Jul 2018 10:06:14 +0100
> Dimitris Papastamos  wrote:
> 
> > On Sun, Jul 08, 2018 at 01:12:59AM +0200, Mattias Andrée wrote:
> > > On Sat, 7 Jul 2018 23:29:53 +0100
> > > Dimitris Papastamos  wrote:
> > >   
> > > > On Sun, Jul 08, 2018 at 12:12:08AM +0200, Mattias Andrée wrote:  
> > > > > On Sat, 7 Jul 2018 22:55:28 +0100
> > > > > Dimitris Papastamos  wrote:
> > > > > 
> > > > > > This is too intrusive, what's wrong with using a shell script to 
> > > > > > test
> > > > > > the commands?
> > > > > > 
> > > > > > The test framework is more complicated than most sbase commands.
> > > > > > 
> > > > > > It would have been nice to discuss this in advance before writing a
> > > > > > 1000 line patch that might not get merged.
> > > > > > 
> > > > > 
> > > > > Writing all tests in shell isn't the best idea I think.
> > > > > This frameworks makes it easy to write test and it will
> > > > > tell you everything you need to know to figure out what
> > > > > failed. I believe that in the need this will reduce the
> > > > > amount of test code. There are things that are difficult
> > > > > to do in shell: for example create a terminal which is
> > > > > need to test tty(1). Look for example at the tests in
> > > > > https://github.com/maandree/base-util-tests, they aren't
> > > > > that nice, and they even require bash(1), it would be
> > > > > even worse with portable sh(1).
> > > > 
> > > > so what you are saying is that your shell code sucks so
> > > > it is better done in C?
> > > > 
> > > > sorry im not buying it, this is overkill
> > > >   
> > > 
> > > No, I'm saying that if you want to do it in sh(1) you will
> > > need more test, or make really crappy helper functions, and
> > > you will also need to write special utilities in C just to
> > > test some utilities that cannot be tested entirely in sh(1).
> > > 
> > > The C code contains:
> > > 
> > > * a way to print where in a loop a test fails.
> > >   this will be useful for example when when adding test
> > >   cases for the *sum utilities,
> > > 
> > > * a way to make tests asynchronously, this could be removed
> > >   but will be useful for testing sleep(1), so it does not
> > >   take too long,
> > > 
> > > * a simple way to measure and test how long a test took,
> > > 
> > > * a set of tiny functions to declare how to program shall
> > >   be started,
> > > 
> > > * a way to create sockets and TTYs. The socket part can
> > >   probably be removed but it would be useful for testing
> > >   different file types. Support for regular files should
> > >   probably be added. TTYs are required to testing tty(1),
> > > 
> > > * ways to test the exit status with support for both normal
> > >   exit and kill by signal,
> > > 
> > > * ways to check the output to stdout and stderr. In sh(1),
> > >   test(1) and grep(1) could be used,
> > > 
> > > * some code to make to test cases smaller,
> > > 
> > > * a function to spawn a process with the requested files
> > >   and input, and
> > > 
> > > * a function to read a process's output and wait for it.
> > > 
> > > I wouldn't say that it's complex, its just a few functions, and
> > > 2 or 3 of them is are bit long, but not particularly long. I
> > > think this is worth it for the consistence, short tests and
> > > easily readable tests, a convenient way to locate the failing
> > > test and what's actually wrong, and to not have extra utilities
> > > (also written in C) to do the parts of the tests that cannot be
> > > done in C. I'm not sure how mean that the code is complicated,
> > > it's just long.  
> > 
> > https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/usr.bin/
> > 
> > whatever you do, this test code should be in a different repo
> > like sbase-regress or similar.
> > 
> 
> I will reduce code in the test-common.[ch].
> 
> If the tests are in a separate repository, package maintainers
> need to download both, do you really think this is a good idea?
> What's the advantage with make it a separate repository?

the tests are primarily for the developers, no user cares if you can
pass all the internal regression tests but fail to build firefox.

time is better spent writing useful test cases rather than some "test
framework".



Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-08 Thread Mattias Andrée
On Sun, 8 Jul 2018 10:06:14 +0100
Dimitris Papastamos  wrote:

> On Sun, Jul 08, 2018 at 01:12:59AM +0200, Mattias Andrée wrote:
> > On Sat, 7 Jul 2018 23:29:53 +0100
> > Dimitris Papastamos  wrote:
> >   
> > > On Sun, Jul 08, 2018 at 12:12:08AM +0200, Mattias Andrée wrote:  
> > > > On Sat, 7 Jul 2018 22:55:28 +0100
> > > > Dimitris Papastamos  wrote:
> > > > 
> > > > > This is too intrusive, what's wrong with using a shell script to test
> > > > > the commands?
> > > > > 
> > > > > The test framework is more complicated than most sbase commands.
> > > > > 
> > > > > It would have been nice to discuss this in advance before writing a
> > > > > 1000 line patch that might not get merged.
> > > > > 
> > > > 
> > > > Writing all tests in shell isn't the best idea I think.
> > > > This frameworks makes it easy to write test and it will
> > > > tell you everything you need to know to figure out what
> > > > failed. I believe that in the need this will reduce the
> > > > amount of test code. There are things that are difficult
> > > > to do in shell: for example create a terminal which is
> > > > need to test tty(1). Look for example at the tests in
> > > > https://github.com/maandree/base-util-tests, they aren't
> > > > that nice, and they even require bash(1), it would be
> > > > even worse with portable sh(1).
> > > 
> > > so what you are saying is that your shell code sucks so
> > > it is better done in C?
> > > 
> > > sorry im not buying it, this is overkill
> > >   
> > 
> > No, I'm saying that if you want to do it in sh(1) you will
> > need more test, or make really crappy helper functions, and
> > you will also need to write special utilities in C just to
> > test some utilities that cannot be tested entirely in sh(1).
> > 
> > The C code contains:
> > 
> > *   a way to print where in a loop a test fails.
> > this will be useful for example when when adding test
> > cases for the *sum utilities,
> > 
> > *   a way to make tests asynchronously, this could be removed
> > but will be useful for testing sleep(1), so it does not
> > take too long,
> > 
> > *   a simple way to measure and test how long a test took,
> > 
> > *   a set of tiny functions to declare how to program shall
> > be started,
> > 
> > *   a way to create sockets and TTYs. The socket part can
> > probably be removed but it would be useful for testing
> > different file types. Support for regular files should
> > probably be added. TTYs are required to testing tty(1),
> > 
> > *   ways to test the exit status with support for both normal
> > exit and kill by signal,
> > 
> > *   ways to check the output to stdout and stderr. In sh(1),
> > test(1) and grep(1) could be used,
> > 
> > *   some code to make to test cases smaller,
> > 
> > *   a function to spawn a process with the requested files
> > and input, and
> > 
> > *   a function to read a process's output and wait for it.
> > 
> > I wouldn't say that it's complex, its just a few functions, and
> > 2 or 3 of them is are bit long, but not particularly long. I
> > think this is worth it for the consistence, short tests and
> > easily readable tests, a convenient way to locate the failing
> > test and what's actually wrong, and to not have extra utilities
> > (also written in C) to do the parts of the tests that cannot be
> > done in C. I'm not sure how mean that the code is complicated,
> > it's just long.  
> 
> https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/usr.bin/
> 
> whatever you do, this test code should be in a different repo
> like sbase-regress or similar.
> 

I will reduce code in the test-common.[ch].

If the tests are in a separate repository, package maintainers
need to download both, do you really think this is a good idea?
What's the advantage with make it a separate repository?


pgp83S4qBiPq_.pgp
Description: OpenPGP digital signature


Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-08 Thread Dimitris Papastamos
On Sun, Jul 08, 2018 at 01:12:59AM +0200, Mattias Andrée wrote:
> On Sat, 7 Jul 2018 23:29:53 +0100
> Dimitris Papastamos  wrote:
> 
> > On Sun, Jul 08, 2018 at 12:12:08AM +0200, Mattias Andrée wrote:
> > > On Sat, 7 Jul 2018 22:55:28 +0100
> > > Dimitris Papastamos  wrote:
> > >   
> > > > This is too intrusive, what's wrong with using a shell script to test
> > > > the commands?
> > > > 
> > > > The test framework is more complicated than most sbase commands.
> > > > 
> > > > It would have been nice to discuss this in advance before writing a
> > > > 1000 line patch that might not get merged.
> > > >   
> > > 
> > > Writing all tests in shell isn't the best idea I think.
> > > This frameworks makes it easy to write test and it will
> > > tell you everything you need to know to figure out what
> > > failed. I believe that in the need this will reduce the
> > > amount of test code. There are things that are difficult
> > > to do in shell: for example create a terminal which is
> > > need to test tty(1). Look for example at the tests in
> > > https://github.com/maandree/base-util-tests, they aren't
> > > that nice, and they even require bash(1), it would be
> > > even worse with portable sh(1).  
> > 
> > so what you are saying is that your shell code sucks so
> > it is better done in C?
> > 
> > sorry im not buying it, this is overkill
> > 
> 
> No, I'm saying that if you want to do it in sh(1) you will
> need more test, or make really crappy helper functions, and
> you will also need to write special utilities in C just to
> test some utilities that cannot be tested entirely in sh(1).
> 
> The C code contains:
> 
> * a way to print where in a loop a test fails.
>   this will be useful for example when when adding test
>   cases for the *sum utilities,
> 
> * a way to make tests asynchronously, this could be removed
>   but will be useful for testing sleep(1), so it does not
>   take too long,
> 
> * a simple way to measure and test how long a test took,
> 
> * a set of tiny functions to declare how to program shall
>   be started,
> 
> * a way to create sockets and TTYs. The socket part can
>   probably be removed but it would be useful for testing
>   different file types. Support for regular files should
>   probably be added. TTYs are required to testing tty(1),
> 
> * ways to test the exit status with support for both normal
>   exit and kill by signal,
> 
> * ways to check the output to stdout and stderr. In sh(1),
>   test(1) and grep(1) could be used,
> 
> * some code to make to test cases smaller,
> 
> * a function to spawn a process with the requested files
>   and input, and
> 
> * a function to read a process's output and wait for it.
> 
> I wouldn't say that it's complex, its just a few functions, and
> 2 or 3 of them is are bit long, but not particularly long. I
> think this is worth it for the consistence, short tests and
> easily readable tests, a convenient way to locate the failing
> test and what's actually wrong, and to not have extra utilities
> (also written in C) to do the parts of the tests that cannot be
> done in C. I'm not sure how mean that the code is complicated,
> it's just long.

https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/usr.bin/

whatever you do, this test code should be in a different repo
like sbase-regress or similar.



Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Mattias Andrée
On Sat, 7 Jul 2018 23:29:53 +0100
Dimitris Papastamos  wrote:

> On Sun, Jul 08, 2018 at 12:12:08AM +0200, Mattias Andrée wrote:
> > On Sat, 7 Jul 2018 22:55:28 +0100
> > Dimitris Papastamos  wrote:
> >   
> > > This is too intrusive, what's wrong with using a shell script to test
> > > the commands?
> > > 
> > > The test framework is more complicated than most sbase commands.
> > > 
> > > It would have been nice to discuss this in advance before writing a
> > > 1000 line patch that might not get merged.
> > >   
> > 
> > Writing all tests in shell isn't the best idea I think.
> > This frameworks makes it easy to write test and it will
> > tell you everything you need to know to figure out what
> > failed. I believe that in the need this will reduce the
> > amount of test code. There are things that are difficult
> > to do in shell: for example create a terminal which is
> > need to test tty(1). Look for example at the tests in
> > https://github.com/maandree/base-util-tests, they aren't
> > that nice, and they even require bash(1), it would be
> > even worse with portable sh(1).  
> 
> so what you are saying is that your shell code sucks so
> it is better done in C?
> 
> sorry im not buying it, this is overkill
> 

No, I'm saying that if you want to do it in sh(1) you will
need more test, or make really crappy helper functions, and
you will also need to write special utilities in C just to
test some utilities that cannot be tested entirely in sh(1).

The C code contains:

*   a way to print where in a loop a test fails.
this will be useful for example when when adding test
cases for the *sum utilities,

*   a way to make tests asynchronously, this could be removed
but will be useful for testing sleep(1), so it does not
take too long,

*   a simple way to measure and test how long a test took,

*   a set of tiny functions to declare how to program shall
be started,

*   a way to create sockets and TTYs. The socket part can
probably be removed but it would be useful for testing
different file types. Support for regular files should
probably be added. TTYs are required to testing tty(1),

*   ways to test the exit status with support for both normal
exit and kill by signal,

*   ways to check the output to stdout and stderr. In sh(1),
test(1) and grep(1) could be used,

*   some code to make to test cases smaller,

*   a function to spawn a process with the requested files
and input, and

*   a function to read a process's output and wait for it.

I wouldn't say that it's complex, its just a few functions, and
2 or 3 of them is are bit long, but not particularly long. I
think this is worth it for the consistence, short tests and
easily readable tests, a convenient way to locate the failing
test and what's actually wrong, and to not have extra utilities
(also written in C) to do the parts of the tests that cannot be
done in C. I'm not sure how mean that the code is complicated,
it's just long.


pgpaA3iydJlYn.pgp
Description: OpenPGP digital signature


Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Dimitris Papastamos
On Sun, Jul 08, 2018 at 12:12:08AM +0200, Mattias Andrée wrote:
> On Sat, 7 Jul 2018 22:55:28 +0100
> Dimitris Papastamos  wrote:
> 
> > This is too intrusive, what's wrong with using a shell script to test
> > the commands?
> > 
> > The test framework is more complicated than most sbase commands.
> > 
> > It would have been nice to discuss this in advance before writing a
> > 1000 line patch that might not get merged.
> > 
> 
> Writing all tests in shell isn't the best idea I think.
> This frameworks makes it easy to write test and it will
> tell you everything you need to know to figure out what
> failed. I believe that in the need this will reduce the
> amount of test code. There are things that are difficult
> to do in shell: for example create a terminal which is
> need to test tty(1). Look for example at the tests in
> https://github.com/maandree/base-util-tests, they aren't
> that nice, and they even require bash(1), it would be
> even worse with portable sh(1).

so what you are saying is that your shell code sucks so
it is better done in C?

sorry im not buying it, this is overkill



Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Mattias Andrée
On Sat, 7 Jul 2018 22:55:28 +0100
Dimitris Papastamos  wrote:

> This is too intrusive, what's wrong with using a shell script to test
> the commands?
> 
> The test framework is more complicated than most sbase commands.
> 
> It would have been nice to discuss this in advance before writing a
> 1000 line patch that might not get merged.
> 

Writing all tests in shell isn't the best idea I think.
This frameworks makes it easy to write test and it will
tell you everything you need to know to figure out what
failed. I believe that in the need this will reduce the
amount of test code. There are things that are difficult
to do in shell: for example create a terminal which is
need to test tty(1). Look for example at the tests in
https://github.com/maandree/base-util-tests, they aren't
that nice, and they even require bash(1), it would be
even worse with portable sh(1).


pgptwxsDaxak0.pgp
Description: OpenPGP digital signature


Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Dimitris Papastamos
A more realistic approach is to prepend sbase in your PATH and use it
to build real life software.  This is how most bugs are found.



Re: [hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Dimitris Papastamos
This is too intrusive, what's wrong with using a shell script to test
the commands?

The test framework is more complicated than most sbase commands.

It would have been nice to discuss this in advance before writing a
1000 line patch that might not get merged.



[hackers] [sbase][PATCH] Add test framework with a test for tty(1)

2018-07-07 Thread Mattias Andrée
Signed-off-by: Mattias Andrée 
---
 Makefile  |  20 +-
 test-common.c | 823 ++
 test-common.h | 190 ++
 tty.test.c|  26 ++
 4 files changed, 1055 insertions(+), 4 deletions(-)
 create mode 100644 test-common.c
 create mode 100644 test-common.h
 create mode 100644 tty.test.c

diff --git a/Makefile b/Makefile
index 0e421e7..005cf13 100644
--- a/Makefile
+++ b/Makefile
@@ -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,12 @@ BIN =\
xinstall\
yes
 
+TEST =\
+   tty.test
+
 LIBUTFOBJ = $(LIBUTFSRC:.c=.o)
 LIBUTILOBJ = $(LIBUTILSRC:.c=.o)
-OBJ = $(BIN:=.o) $(LIBUTFOBJ) $(LIBUTILOBJ)
+OBJ = $(BIN:=.o) $(TEST:=.o) test-common.o $(LIBUTFOBJ) $(LIBUTILOBJ)
 SRC = $(BIN:=.c)
 MAN = $(BIN:=.1)
 
@@ -193,12 +197,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 +221,9 @@ getconf.o: getconf.h
 getconf.h: getconf.sh
./getconf.sh > $@
 
+check: $(TEST) $(BIN)
+   @set -e; for f in $(TEST); do echo ./$$f; ./$$f; done
+
 install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
@@ -271,7 +283,7 @@ sbase-box-uninstall: uninstall
cd $(DESTDIR)$(PREFIX)/bin && rm -f sbase-box
 
 clean:
-   rm -f $(BIN) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
+   rm -f $(BIN) $(TEST) $(OBJ) $(LIB) sbase-box sbase-$(VERSION).tar.gz
rm -f getconf.h
 
 .PHONY: all install uninstall dist sbase-box sbase-box-install 
sbase-box-uninstall clean
diff --git a/test-common.c b/test-common.c
new file mode 100644
index 000..458b094
--- /dev/null
+++ b/test-common.c
@@ -0,0 +1,823 @@
+/* See LICENSE file for copyright and license details. */
+#include "test-common.h"
+
+struct Counter {
+   const char *name;
+   size_t value;
+};
+
+const char *test_file = NULL;
+int test_line = 0;
+int main_ret = 0;
+int timeout = 10;
+int pdeath_sig = SIGINT;
+void (*atfork)(void) = NULL;
+
+static struct Counter counters[16];
+static size_t ncounters = 0;
+static pid_t async_pids[1024];
+static size_t async_npids = 0;
+
+static void
+eperror(const char *prefix)
+{
+   perror(prefix);
+   fflush(stderr);
+   exit(64);
+}
+
+struct Process *
+stdin_text(struct Process *proc, char *s)
+{
+   proc->input[0].data = s;
+   proc->input[0].flags &= ~IO_STREAM_BINARY;
+   return proc;
+}
+
+struct Process *
+stdin_bin(struct Process *proc, char *s, size_t n)
+{
+   proc->input[0].data = s;
+   proc->input[0].len = n;
+   proc->input[0].flags |= IO_STREAM_BINARY;
+   return proc;
+}
+
+struct Process *
+stdin_fds(struct Process *proc, int input_fd, int output_fd)
+{
+   proc->input[0].flags &= ~IO_STREAM_DATA;
+   proc->input[0].input_fd = input_fd;
+   proc->input[0].output_fd = output_fd;
+   return proc;
+}
+
+struct Process *
+stdin_type(struct Process *proc, int type)
+{
+   proc->input[0].flags &= ~IO_STREAM_CREATE_MASK;
+   proc->input[0].flags |= type;
+   return proc;
+}
+
+struct Process *
+stdout_fds(struct Process *proc, int input_fd, int output_fd)
+{
+   proc->output[0].flags &= ~IO_STREAM_DATA;
+   proc->output[0].input_fd = input_fd;
+   proc->output[0].output_fd = output_fd;
+   return proc;
+}
+
+struct Process *
+stdout_type(struct Process *proc, int type)
+{
+   proc->output[0].flags &= ~IO_STREAM_CREATE_MASK;
+   proc->output[0].flags |= type;
+   return proc;
+}
+
+struct Process *
+stderr_fds(struct Process *proc, int input_fd, int output_fd)
+{
+   proc->output[1].flags &= ~IO_STREAM_DATA;
+   proc->output[1].input_fd = input_fd;
+   proc->output[1].output_fd = output_fd;
+   return proc;
+}
+
+struct Process *
+stderr_type(struct Process *proc, int type)
+{
+   proc->output[1].flags &= ~IO_STREAM_CREATE_MASK;
+   proc->output[1].flags |= type;
+   return proc;
+}
+
+struct Process *
+set_preexec(struct Process *proc, void (*preexec)(struct Process *))
+{
+   proc->preexec = preexec;
+   return proc;
+}
+
+struct Process *
+set_async(struct Process *proc)
+{
+   proc->flags |= PROCESS_ASYNC;
+   return proc;
+}
+
+struct Process *
+set_setsid(struct Process *proc)
+{
+   proc->flags |= PROCESS_SETSID;
+   return proc;
+}
+
+void
+push_counter(const char *name)
+{
+   if (ncounters == ELEMSOF(counters)) {
+   fprintf(stderr, "too many counters\n");
+   fflush(