/// read from the process' output stream
        /// data is stored in the given buffer, at most max_len bytes
+       /// timeout is the number of milliseconds to wait for the process
+       /// to produce output, -1 for indefinite
        /// @return the number of bytes read
-       std::size_t ReadOut(void *buffer, std::size_t max_len);
+       std::size_t ReadOut(void *buffer, std::size_t max_len, int timeout);
        /// close program's output stream
        void CloseOut();
 
        /// read from the process' error stream
        /// data is stored in the given buffer, at most max_len bytes
+       /// timeout is the number of milliseconds to wait for the process
+       /// to produce output, -1 for indefinite
        /// @return the number of bytes read
-       std::size_t ReadErr(void *buffer, std::size_t max_len);
+       std::size_t ReadErr(void *buffer, std::size_t max_len, int timeout);
        /// close program's output stream
        void CloseErr();
 
 
 #  include <fcntl.h>
 #  include <signal.h>
 #  include <unistd.h>
+#  include <sys/select.h>
 #  include <sys/wait.h>
 #endif
 
        size_t WriteIn(const void *buffer, size_t max_len);
        void CloseIn();
 
-       size_t ReadOut(void *buffer, size_t max_len);
+       size_t ReadOut(void *buffer, size_t max_len, int timeout);
        void CloseOut();
 
-       size_t ReadErr(void *buffer, size_t max_len);
+       size_t ReadErr(void *buffer, size_t max_len, int timeout);
        void CloseErr();
 
        void Terminate();
        impl->CloseIn();
 }
 
-size_t Process::ReadOut(void *buffer, size_t max_len) {
-       return impl->ReadOut(buffer, max_len);
+size_t Process::ReadOut(void *buffer, size_t max_len, int timeout) {
+       return impl->ReadOut(buffer, max_len, timeout);
 }
 
 void Process::CloseOut() {
        impl->CloseOut();
 }
 
-size_t Process::ReadErr(void *buffer, size_t max_len) {
-       return impl->ReadErr(buffer, max_len);
+size_t Process::ReadErr(void *buffer, size_t max_len, int timeout) {
+       return impl->ReadErr(buffer, max_len, timeout);
 }
 
 void Process::CloseErr() {
        in_closed = true;
 }
 
-size_t Process::Impl::ReadOut(void *buffer, size_t max_len) {
+size_t Process::Impl::ReadOut(void *buffer, size_t max_len, int timeout) {
 #ifdef _WIN32
+       // TODO: timeout implementation for windows child process I/O
        DWORD ret;
        if (!ReadFile(fd_out[0], buffer, max_len, &ret, nullptr)) {
                throw runtime_error("failed to read from child process' output stream");
        }
        return ret;
 #else
+       if (timeout >= 0) {
+               fd_set read_set;
+               fd_set error_set;
+               FD_ZERO(&read_set);
+               FD_ZERO(&error_set);
+               FD_SET(fd_out[0], &read_set);
+               FD_SET(fd_out[0], &error_set);
+               timeval timer;
+               timer.tv_sec = timeout / 1000;
+               timer.tv_usec = (timeout % 1000) * 1000;
+               if (select(fd_out[0] + 1, &read_set, nullptr, &error_set, &timer) == -1) {
+                       throw SysError("error waiting on child process' output stream");
+               }
+               if (FD_ISSET(fd_out[0], &error_set)) {
+                       throw runtime_error("error condition on child process' output stream");
+               }
+               if (!FD_ISSET(fd_out[0], &read_set)) {
+                       throw runtime_error("timeout while waiting on child process' output stream");
+               }
+       }
        int ret = read(fd_out[0], buffer, max_len);
        if (ret < 0) {
                if (errno == EAGAIN) {
        out_closed = true;
 }
 
-size_t Process::Impl::ReadErr(void *buffer, size_t max_len) {
+size_t Process::Impl::ReadErr(void *buffer, size_t max_len, int timeout) {
 #ifdef _WIN32
+       // TODO: timeout implementation for windows child process I/O
        DWORD ret;
        if (!ReadFile(fd_err[0], buffer, max_len, &ret, nullptr)) {
                throw runtime_error("failed to read from child process' error stream");
        }
        return ret;
 #else
+       if (timeout >= 0) {
+               fd_set read_set;
+               fd_set error_set;
+               FD_ZERO(&read_set);
+               FD_ZERO(&error_set);
+               FD_SET(fd_err[0], &read_set);
+               FD_SET(fd_err[0], &error_set);
+               timeval timer;
+               timer.tv_sec = timeout / 1000;
+               timer.tv_usec = (timeout % 1000) * 1000;
+               if (select(fd_err[0] + 1, &read_set, nullptr, &error_set, &timer) == -1) {
+                       throw SysError("error waiting on child process' error stream");
+               }
+               if (FD_ISSET(fd_err[0], &error_set)) {
+                       throw runtime_error("error condition on child process' error stream");
+               }
+               if (!FD_ISSET(fd_err[0], &read_set)) {
+                       throw runtime_error("timeout while waiting on child process' error stream");
+               }
+       }
        int ret = read(fd_err[0], buffer, max_len);
        if (ret < 0) {
                if (errno == EAGAIN) {
 
                const string expected_output("hello, world\n");
                Process proc("/usr/bin/env", { "env", "echo", test_input.c_str() });
                char buffer[expected_output.length() + 1];
-               size_t len = proc.ReadOut(buffer, sizeof(buffer));
+               size_t len = proc.ReadOut(buffer, sizeof(buffer), 1000);
                const string output(buffer, len);
                int status = proc.Join();
                CPPUNIT_ASSERT_EQUAL_MESSAGE(
                const string expected_output("hello, error\n");
                Process proc("/usr/bin/env", { "env", "sh", "-c", "echo $1 >&2", "echo", test_input.c_str() }, { });
                char buffer[expected_output.length() + 1];
-               size_t len = proc.ReadErr(buffer, sizeof(buffer));
+               size_t len = proc.ReadErr(buffer, sizeof(buffer), 1000);
                const string output(buffer, len);
                int status = proc.Join();
                CPPUNIT_ASSERT_EQUAL_MESSAGE(
                proc.CloseIn();
 
                char buffer[expected_output.length() + 1];
-               len = proc.ReadOut(buffer, sizeof(buffer));
+               len = proc.ReadOut(buffer, sizeof(buffer), 1000);
                const string output(buffer, len);
                int status = proc.Join();
                CPPUNIT_ASSERT_EQUAL_MESSAGE(
                const string expected_output("Hello, environment\n");
                Process proc("/usr/bin/env", { "env", "sh", "-c", "echo $BLANK_ENV_TEST" }, { "BLANK_ENV_TEST=" + test_input });
                char buffer[expected_output.length() + 1];
-               size_t len = proc.ReadOut(buffer, sizeof(buffer));
+               size_t len = proc.ReadOut(buffer, sizeof(buffer), 1000);
                const string output(buffer, len);
                int status = proc.Join();
                CPPUNIT_ASSERT_EQUAL_MESSAGE(
 #endif
 }
 
+void ProcessTest::testTimeout() {
+#ifdef __WIN32
+#  error "TODO: implement Process tests for windows"
+#else
+       Process proc("/usr/bin/env", { "env", "cat" });
+       char buffer;
+       CPPUNIT_ASSERT_THROW_MESSAGE(
+               "read timeout on child process' stdout should throw",
+               proc.ReadOut(&buffer, 1, 1), std::runtime_error);
+       CPPUNIT_ASSERT_THROW_MESSAGE(
+               "read timeout on child process' stderr should throw",
+               proc.ReadErr(&buffer, 1, 1), std::runtime_error);
+       proc.Terminate();
+#endif
+}
+
 }
 }
 
 CPPUNIT_TEST(testExit);
 CPPUNIT_TEST(testStream);
 CPPUNIT_TEST(testEnv);
+CPPUNIT_TEST(testTimeout);
 
 CPPUNIT_TEST_SUITE_END();
 
        void testExit();
        void testStream();
        void testEnv();
+       void testTimeout();
 
 };
 
 
 namespace test {
 
 void ServerTest::setUp() {
-
+       instance.reset(new TestInstance({ "--server" }, true));
+       instance->AssertRunning();
 }
 
 void ServerTest::tearDown() {
-
+       std::unique_ptr<TestInstance> inst(std::move(instance));
+       inst->Terminate();
+       inst->AssertExitStatus(0);
+       inst->AssertNoError();
 }
 
 
 void ServerTest::testStartup() {
-       TestInstance server({ "--server" }, true);
-       server.AssertRunning();
-       server.Terminate();
-       server.AssertExitStatus(0);
-       server.AssertNoError();
+       // setUp and testDown do all the tests
 }
 
 }
 
 #ifndef BLANK_TEST_INTEGRATION_SERVERTEST_HPP_
 #define BLANK_TEST_INTEGRATION_SERVERTEST_HPP_
 
+#include <memory>
 #include <cppunit/extensions/HelperMacros.h>
 
 
 namespace blank {
 namespace test {
 
+class TestInstance;
+
 class ServerTest
 : public CppUnit::TestFixture {
 
 
        void testStartup();
 
+private:
+       std::unique_ptr<TestInstance> instance;
+
 };
 
 }
 
 
 #include "TestInstance.hpp"
 
+
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(blank::test::StandaloneTest, "integration");
 
 
 namespace test {
 
 void StandaloneTest::setUp() {
-
+       instance.reset(new TestInstance({ "--no-vsync" }));
+       instance->AssertRunning();
 }
 
 void StandaloneTest::tearDown() {
-
+       std::unique_ptr<TestInstance> inst(std::move(instance));
+       inst->Terminate();
+       inst->AssertExitStatus(0);
+       inst->AssertNoError();
 }
 
 
 void StandaloneTest::testStartup() {
-       TestInstance standalone({ "--no-vsync" });
-       standalone.AssertRunning();
-       try {
-               standalone.AssertOutputLine("chunk preloading complete");
-               standalone.Terminate();
-       } catch (...) {
-               try {
-                       standalone.Terminate();
-               } catch (...) { }
-               std::string output;
-               standalone.ExhaustError(output);
-               CPPUNIT_ASSERT_EQUAL_MESSAGE(
-                       "process stderr",
-                       std::string(""), output);
-               CPPUNIT_FAIL("exception in runtime");
-       }
-       standalone.AssertExitStatus(0);
-       standalone.AssertNoError();
+       instance->AssertOutputLine("chunk preloading complete");
 }
 
 }
 
 #ifndef BLANK_TEST_INTEGRATION_STANDALONETEST_HPP_
 #define BLANK_TEST_INTEGRATION_STANDALONETEST_HPP_
 
+#include <memory>
 #include <cppunit/extensions/HelperMacros.h>
 
 
 namespace blank {
 namespace test {
 
+class TestInstance;
+
 class StandaloneTest
 : public CppUnit::TestFixture {
 
 
        void testStartup();
 
+private:
+       std::unique_ptr<TestInstance> instance;
+
 };
 
 }
 
 , cmd_buf() {
        if (cmd) {
                // wait for command service startup
-               // TODO: timeouts for reading from process
                WaitOutputLine("listening on TCP port 12354");
                // connect to command service
                conn = tcp::Socket("localhost", 12354);
 void TestInstance::ReadOutputLine(string &line) {
        while (!out_buf.Extract(line)) {
                // buffer exhausted, fetch more data
-               int len = proc.ReadOut(out_buf.WriteHead(), out_buf.Remain());
+               int len = proc.ReadOut(out_buf.WriteHead(), out_buf.Remain(), 5000);
                if (len == 0) {
                        throw runtime_error("failed read from child process' stdout");
                }
 void TestInstance::ExhaustOutput(string &output) {
        while (!out_buf.Extract(output)) {
                // buffer exhausted, fetch more data
-               int len = proc.ReadOut(out_buf.WriteHead(), out_buf.Remain());
+               int len = proc.ReadOut(out_buf.WriteHead(), out_buf.Remain(), 5000);
                if (len == 0) {
                        return;
                }
 void TestInstance::ReadErrorLine(string &line) {
        while (!err_buf.Extract(line)) {
                // buffer exhausted, fetch more data
-               int len = proc.ReadErr(err_buf.WriteHead(), err_buf.Remain());
+               int len = proc.ReadErr(err_buf.WriteHead(), err_buf.Remain(), 5000);
                if (len == 0) {
                        throw runtime_error("failed read from child process' stderr");
                }
 void TestInstance::ExhaustError(string &error) {
        while (!err_buf.Extract(error)) {
                // buffer exhausted, fetch more data
-               int len = proc.ReadErr(err_buf.WriteHead(), err_buf.Remain());
+               int len = proc.ReadErr(err_buf.WriteHead(), err_buf.Remain(), 5000);
                if (len == 0) {
                        return;
                }