/*
Copyright (C)  2004 Artem Khodush

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "exec-stream.h"

#include <vector>
#include <list>
#include <map>
#include <string>
#include <iostream>
#include <sstream>

#include <stdlib.h>
#include <ctype.h>

#ifdef _WIN32

#include <windows.h>

void sleep( int seconds )
{
    Sleep( seconds*1000 );
}

#else

#include <unistd.h>

#endif

// class for collecting and printing test results

class test_results_t {
public:
    static void add_test( std::string const & name );
    static bool register_failure( std::string const & assertion, std::string const & file, int line );
    static int print( std::ostream & o );

    class error_t : public std::exception {
    public:
        error_t( std::string const & msg )
        : m_msg( msg )
        {
        }

        ~error_t() throw()
        {
        }

        virtual char const * what() const throw()
        {
            return m_msg.c_str();
        }

    private:
        std::string m_msg;
    };

private:
    struct failure_t {
        std::string assertion;
        std::string file;
        int line;
    };
    typedef std::vector< failure_t > failures_t;
    struct result_t {
        std::string test_name;
        failures_t failures;
    };
    typedef std::vector< result_t > results_t;

    static results_t m_results;
    static bool m_printed;

    struct force_print_t {
        ~force_print_t();
    };
    friend struct force_print_t;

    static force_print_t m_force_print;
};

test_results_t::results_t test_results_t::m_results;
bool test_results_t::m_printed;
test_results_t::force_print_t test_results_t::m_force_print;

void test_results_t::add_test( std::string const & name )
{
    results_t::iterator i=m_results.begin();
    while( i!=m_results.end() && i->test_name!=name ) {
        ++i;
    }
    if( i!=m_results.end() ) {
        throw error_t( "test_results_t::add_test: duplicate test name: "+name );
    }
    result_t & r=*m_results.insert( m_results.end(), result_t() );
    r.test_name=name;
}

bool test_results_t::register_failure( std::string const & assertion, std::string const & file, int line )
{
    if( m_results.empty() ) {
        throw error_t( "test_results_t::register_failure: called without add_test() first" );
    }
    failures_t & failures=m_results.back().failures;
    failure_t & failure=*failures.insert( failures.end(), failure_t() );
    failure.assertion=assertion;
    failure.file=file;
    failure.line=line;
    return true;
}

int test_results_t::print( std::ostream & o )
{
    int n_ok=0;
    int n_failed=0;
    for( results_t::iterator res_i=m_results.begin(); res_i!=m_results.end(); ++res_i ) {
        if( res_i->failures.empty() ) {
            ++n_ok;
        }else {
            o<<"FAILED TEST: "<<res_i->test_name<<"\n";
            for( failures_t::iterator fail_i=res_i->failures.begin(); fail_i!=res_i->failures.end(); ++fail_i ) {
                o<<"    "<<fail_i->assertion<<" [at file "<<fail_i->file<<" line "<<fail_i->line<<"]\n";
            }
            ++n_failed;
        }
    }
    o<<"\nOK: "<<n_ok<<" FAILED: "<<n_failed<<"\n";
    m_printed=true;
    return n_failed;
}

test_results_t::force_print_t::~force_print_t()
{
    if( !test_results_t::m_results.empty() && !test_results_t::m_printed ) {
        test_results_t::print( std::cerr );
        std::cerr<<"UNEXPECTED TESTSUITE TERMINATION\n";
        std::cerr<<"while running test '"<<m_results.back().test_name<<"'\n";
    }
}

#define TEST_NAME(n) test_results_t::add_test(n);
#define TEST(e) ((e) || test_results_t::register_failure( #e, __FILE__, __LINE__ ))

std::string read_all( std::istream & i )
{
    std::string one;
    std::string ret;
    while( std::getline( i, one ).good() ) {
        ret+=one;
        ret+="\n";
    }
    ret+=one;
    return ret;
}

std::string random_string( std::size_t size )
{
    std::string s;
    s.reserve( size );
    srand( 1 );
    while( s.size()<size ) {
        char c=rand()%CHAR_MAX;
        if( isprint( c ) && !isspace( c ) ) {
            s+=c;
        }
    }
    return s;
}

bool check_if_english_error_messages()
{
#ifdef _WIN32
    std::string s;
    LPVOID buf;
    if( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
        0,
        1,
        MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
        (LPTSTR) &buf,
        0,
        0
        )==0 ) {
        return false;
    }else {
        s=(LPTSTR)buf;
        LocalFree( buf );
        return s.find( "Incorrect function" )!=std::string::npos;
    }
#else
    std::string s( strerror( 1 ) );
    return s.find( "Operation not permitted" )!=std::string::npos;
#endif
}

// each function below is run as a child process for some test

int hello()
{
    std::cout<<"hello";
    return 0;
}

int helloworld()
{
    std::cout<<"hello\n";
    std::cout<<"world";
    return 0;
}

int hello_o_world_e()
{
    std::cout<<"hello";
    std::cerr<<"world";
    return 0;
}

int with_space()
{
    std::cout<<"with space ok\n";
    return 0;
}

int write_after_pause()
{
    sleep( 10 );
    std::cout<<"after pause";
    return 0;
}

int read_after_pause()
{
    sleep( 10 );
    std::string s;
    std::cin>>s;
    if( s==random_string( 20000 ) ) {
        std::cerr<<"OK";
    }else {
        std::cerr<<"ERROR";
    }
    return 0;
}

int dont_read()
{
    return 0;
}

int dont_stop()
{
    std::string s=random_string( 100 )+"\n";
    while( true ) {
        std::cout<< s;
    }
    return 0;
}

int echo_size() {
    std::string s;
    while( std::getline( std::cin, s ).good() ) {
        std::cout<<s.size()<<std::endl;
    }
    return 0;
}

int echo()
{
    std::string s;
    while( std::getline( std::cin, s ).good() ) {
        std::cout<<s<<"\n";
    }
    return 0;
}

int echo_with_err()
{
    std::string s;
    std::cerr<<random_string( 500000 );
    while( std::getline( std::cin, s ).good() ) {
        std::cout<<s<<"\n";
    }
    return 0;
}

int echo_picky()
{
    std::string s;
    while( std::getline( std::cin, s ).good() ) {
        if( s=="STOP" ) {
            std::cerr<<"OK\n";
            return 0;
        }
        std::cout<<s<<"\n";
    }
    return 0;
}

int pathologic()
{
    std::string in_s=random_string( 1000 );
    int cnt=2500;
    for( int i=0; i<cnt; ++i ) {
        std::string t=in_s+"\n";
        fputs( t.c_str(), stdout );
    }
    std::string out_s;
    int n=0;

    char buf[1002];

    while( fgets( buf, sizeof( buf ), stdin )!=0 ) {
        int len=strlen( buf );

        if( len>0 && buf[len-1]=='\n' ) {
            --len;
        }
        out_s.assign( buf, len );
        if( out_s!=in_s ) {
            fputs( "ERROR", stderr );
            return 0;
        }
        ++n;
    }
    if( n==1200 ) {
        fputs( "OK", stderr );
    }else {
        fputs( "ERROR", stderr );
    }
    return 0;
}

int long_out_line()
{
    std::cout<<random_string( 2000000 );
    return 0;
}

int exit_code()
{
    std::string s;
    std::stringstream ss;
    std::cin>>s;
    ss.str( s );
    int n;
    ss>>n;
    return n;
}

// selection of child functions

typedef int(*child_func_t)();

child_func_t find_child_func( std::string const & name )
{
    typedef std::map< std::string, child_func_t > child_funcs_t;
    static child_funcs_t funcs;
    if( funcs.size()==0 ) {
        funcs["hello"]=hello;
        funcs["helloworld"]=helloworld;
        funcs["hello-o-world-e"]=hello_o_world_e;
        funcs["with space"]=with_space;
        funcs["write-after-pause"]=write_after_pause;
        funcs["read-after-pause"]=read_after_pause;
        funcs["dont-read"]=dont_read;
        funcs["dont-stop"]=dont_stop;
        funcs["echo-size"]=echo_size;
        funcs["echo"]=echo;
        funcs["echo-with-err"]=echo_with_err;
        funcs["echo-picky"]=echo_picky;
        funcs["pathologic"]=pathologic;
        funcs["long-out-line"]=long_out_line;
        funcs["exit-code"]=exit_code;
    }
    child_funcs_t::iterator i=funcs.find( name );
    return i==funcs.end() ? 0 : i->second;
}

void pathologic_one( exec_stream_t & exec_stream, bool expect_ok=true )
{
    std::string in_s=random_string( 1000 );
    int cnt=1200;
    for( int i=0; i<cnt; ++i ) {
        exec_stream.in()<<in_s<<"\n";
    }
    exec_stream.close_in();
    std::string out_s;
    int n=0;
    while( std::getline( exec_stream.out(), out_s ).good() ) {
        if( out_s!=in_s ) {
            TEST( 0=="out_s!=in_s" );
            break;
        }
        ++n;
    }
    TEST( n==2500 );
    exec_stream.err()>>out_s;
    if( expect_ok ) {
        TEST( out_s=="OK" );
    }else {
        TEST( out_s=="ERROR" );
    }
}

int main( int argc, char ** argv )
{
    if( argc>=3 ) {
        if( std::string( argv[1] )=="child" ) {
            if( child_func_t func=find_child_func( argv[2] ) ) {
                return func();
            }
        }else if( std::string( argv[1] )=="args" ) {
            for( int i=2; i<argc; ++i ) {
                std::cout << argv[i] << "\n";
            }
        }
        return 0;
    }

    bool skip_long_tests=false;
    if( argc==2 && std::string( argv[1] )=="skip-long-tests" ) {
        skip_long_tests=true;
    }


    // do not test for specific error message text if it is not in english
    bool english_error_messages=check_if_english_error_messages();

    std::string program="./exec-stream-test";
    int n_failed=42;

    // the tests begin here

    try {

        {
            TEST_NAME( "hello" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child hello" );
            TEST( read_all( exec_stream.out() )=="hello" );
            TEST( read_all( exec_stream.err() )=="" );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "helloworld" );
            exec_stream_t exec_stream( program, "child helloworld" );
            TEST( read_all( exec_stream.out() )=="hello\nworld" );
            TEST( read_all( exec_stream.err() )=="" );
        }

        {
            TEST_NAME( "helloworld binary read" );
            exec_stream_t exec_stream;
            exec_stream.set_binary_mode( exec_stream_t::s_out );
            exec_stream.start( program, "child helloworld" );
#ifdef _WIN32
            TEST( read_all( exec_stream.out() )=="hello\r\nworld" );
#else
            TEST( read_all( exec_stream.out() )=="hello\nworld" );
#endif
            TEST( read_all( exec_stream.err() )=="" );
        }

        {
            TEST_NAME( "hello->out world->err" );
            exec_stream_t exec_stream( program, "child hello-o-world-e" );
            TEST( read_all( exec_stream.out() )=="hello" );
            TEST( read_all( exec_stream.err() )=="world" );
        }

        {
            TEST_NAME( "hello->out world->err read reversed" );
            exec_stream_t exec_stream( program, "child hello-o-world-e" );
            TEST( read_all( exec_stream.err() )=="world" );
            TEST( read_all( exec_stream.out() )=="hello" );
        }

        {
            TEST_NAME( "hello 5 times" );
            exec_stream_t exec_stream;

            exec_stream.start( program, "child helloworld" );
            TEST( read_all( exec_stream.out() )=="hello\nworld" );
            TEST( read_all( exec_stream.err() )=="" );

            exec_stream.start( program, "child helloworld" );
            // do not read, leave all in buffers

            exec_stream.start( program, "child hello-o-world-e" );

            TEST( read_all( exec_stream.out() )=="hello" );
            TEST( read_all( exec_stream.err() )=="world" );

            exec_stream.start( program, "child helloworld" );
            // do not read all, leave something in buffers
            std::string s;
            std::getline( exec_stream.out(), s );

            TEST( s=="hello" );

            exec_stream.start( program, "child hello-o-world-e" );

            TEST( read_all( exec_stream.out() )=="hello" );
            TEST( read_all( exec_stream.err() )=="world" );

            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "with space" );
            std::vector< std::string > args;
            args.push_back( "child" );
            args.push_back( "with space" );
            exec_stream_t exec_stream( program, args.begin(), args.end() );
            std::string s;
            std::getline( exec_stream.out(), s );
            TEST( s=="with space ok" );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "child write after pause" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child write-after-pause" );
            try {
                std::string s;
                std::getline( exec_stream.out(), s );
                TEST( 0=="unreached" );
            }catch( std::exception const & e ) {
                std::string m=e.what();
                if( english_error_messages ) {
                    TEST( m.find( "timeout" )!=std::string::npos || m.find( "timed out" )!=std::string::npos  );
                }
            }
            TEST( !exec_stream.close() );
            exec_stream.kill();

            exec_stream.set_wait_timeout( exec_stream_t::s_out, 12000 );
            exec_stream.start( program, "child write-after-pause" );
            std::string s;
            std::getline( exec_stream.out(), s );
            TEST( s=="after pause" );

            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "child read after pause" );
            exec_stream_t exec_stream;
            exec_stream.set_wait_timeout( exec_stream_t::s_err, 15000 );
            std::string in_s=random_string( 20000 )+"\n";

            exec_stream.start( program, "child read-after-pause" );
            exec_stream.in()<<in_s;
            std::string s;
            exec_stream.err()>>s;
            TEST( s=="OK" );
            TEST( exec_stream.close() );

            exec_stream.set_buffer_limit( exec_stream_t::s_in, 1000 );
            exec_stream.start( program, "child read-after-pause" );
            try {
                exec_stream.in()<<in_s;
                TEST( 0=="unreachable" );
            }catch( std::exception const & e ) {
                std::string m=e.what();
                if( english_error_messages ) {
                    TEST( m.find( "timeout" )!=std::string::npos || m.find( "timed out" )!=std::string::npos  );
                }
            }
            TEST( !exec_stream.close() );
            exec_stream.kill();

            exec_stream.set_wait_timeout( exec_stream_t::s_in, 15000 );
            exec_stream.start( program, "child read-after-pause" );
            exec_stream.in()<<in_s;
            exec_stream.err()>>s;
            TEST( s=="OK" );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "dont read" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child dont-read" );
            try {
                exec_stream.in()<<random_string( 20000 );
                exec_stream.close();
                TEST( 0=="unreachable" );
            }catch( std::exception const & e ) {
                std::string m=e.what();
                if( english_error_messages ) {
                    TEST( m.find( "Broken pipe" )!=std::string::npos
                        || m.find( "pipe is being closed" )!=std::string::npos
                        || m.find( "pipe has been ended" )!=std::string::npos );
                }
            }
        }

        {
            TEST_NAME( "dont stop" );
            exec_stream_t exec_stream;

            // on slow machines when child hogs CPU, this may take a while
            exec_stream.set_wait_timeout( exec_stream_t::s_child, 5000 );

            exec_stream.start( program, "child dont-stop" );
            exec_stream.in()<<random_string( 20000 );
            TEST( !exec_stream.close() );
            exec_stream.kill();

            exec_stream.start( program, "child hello" );
            TEST( read_all( exec_stream.out() )=="hello" );
            TEST( read_all( exec_stream.err() )=="" );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "echo-size" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child echo-size" );
            exec_stream.in()<<"1234\n";
            std::string out;
            exec_stream.out()>>out;
            TEST( out=="4" );
            exec_stream.in()<<"\n";
            exec_stream.out()>>out;
            TEST( out=="0" );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "echo" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child echo" );
            int sizes[]={ 4096-1, 4096+1, 4096+1, 4096-1, 4096*2, 3 };
            int cnt=sizeof( sizes )/sizeof( sizes[0] );
            for( int i=0; i<cnt; ++i ) {
                std::string in_s=random_string( sizes[i] );
                exec_stream.in()<<in_s<<"\n";
                std::string out_s;
                TEST( std::getline( exec_stream.out(), out_s ).good() );
                TEST( in_s==out_s );
            }
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "echo with err" );
            exec_stream_t exec_stream;
            exec_stream.set_wait_timeout( exec_stream_t::s_out, 15000 );
            exec_stream.start( program, "child echo-with-err" );
            int sizes[]={ 4096-1, 4096+1, 4096+1, 4096-1, 4096*2, 3 };
            int cnt=sizeof( sizes )/sizeof( sizes[0] );
            for( int i=0; i<cnt; ++i ) {
                std::string in_s=random_string( sizes[i] );
                exec_stream.in()<<in_s<<"\n";
                std::string out_s;
                TEST( std::getline( exec_stream.out(), out_s ).good() );
                TEST( in_s==out_s );
            }
            exec_stream.close_in();
            std::string err;
            err.reserve( 500000 );
            exec_stream.err()>>err;
            TEST( err==random_string( 500000 ) );
            TEST( exec_stream.close() );
        }

        {
            TEST_NAME( "echo-picky" );
            exec_stream_t exec_stream;

            // disable exceptions while reading/writing
            exec_stream.in().exceptions( std::ios_base::goodbit );
            exec_stream.out().exceptions( std::ios_base::goodbit );
            exec_stream.err().exceptions( std::ios_base::goodbit );

            exec_stream.start( program, "child echo-picky" );
            std::string in_s;
            std::string out_s;

            in_s=random_string( 4096 );
            exec_stream.in()<<in_s<<"\n";
            TEST( std::getline( exec_stream.out(), out_s ).good() );
            TEST( in_s==out_s );

            in_s="ZZZZZ";
            exec_stream.in()<<in_s<<"\n";
            TEST( std::getline( exec_stream.out(), out_s ).good() );
            TEST( in_s==out_s );

            in_s="STOP";
            exec_stream.in()<<in_s<<"\n";
            TEST( !std::getline( exec_stream.out(), out_s ).good() );
            TEST( out_s=="" );

            TEST( std::getline( exec_stream.err(), out_s ).good() );
            TEST( out_s=="OK" );
        }

        {
            TEST_NAME( "echo-picky with exceptions" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child echo-picky" );
            std::string in_s;
            std::string out_s;

            in_s=random_string( 4096 );
            exec_stream.in()<<in_s<<"\n";
            TEST( std::getline( exec_stream.out(), out_s ).good() );
            TEST( in_s==out_s );

            in_s="ZZZZZ";
            exec_stream.in()<<in_s<<"\n";
            TEST( std::getline( exec_stream.out(), out_s ).good() );
            TEST( in_s==out_s );

            in_s="STOP";
            exec_stream.in()<<in_s<<"\n";
            TEST( !std::getline( exec_stream.out(), out_s ).good() );
            TEST( out_s=="" );

            TEST( std::getline( exec_stream.err(), out_s ).good() );
            TEST( out_s=="OK" );
        }

        {
            TEST_NAME( "echo-picky short" );
            exec_stream_t exec_stream;
            exec_stream.start( program, "child echo-picky" );
            std::string in_s;
            std::string out_s;

            in_s="STOP";
            exec_stream.in()<<in_s<<"\n";

            TEST( std::getline( exec_stream.err(), out_s ).good() );
            TEST( out_s=="OK" );
        }

        {
            TEST_NAME( "pathologic" );
            exec_stream_t exec_stream;
            // disable exceptions while reading/writing
            exec_stream.in().exceptions( std::ios_base::goodbit );
            exec_stream.out().exceptions( std::ios_base::goodbit );
            exec_stream.err().exceptions( std::ios_base::goodbit );
            // on slow machines this test may take quite a while
            exec_stream.set_wait_timeout( exec_stream_t::s_in|exec_stream_t::s_out|exec_stream_t::s_err, 60000 );

            exec_stream.start( program, "child pathologic" );
            pathologic_one( exec_stream );
            TEST( exec_stream.close() );

            exec_stream.set_buffer_limit( exec_stream_t::s_in, 500 );
            exec_stream.start( program, "child pathologic" );
            pathologic_one( exec_stream );
            TEST( exec_stream.close() );
        }

        if( !skip_long_tests ) {

            {
                TEST_NAME( "pathologic with exceptions" );
                exec_stream_t exec_stream;

                exec_stream.start( program, "child pathologic" );
                pathologic_one( exec_stream );
                TEST( exec_stream.close() );

                exec_stream.set_buffer_limit( exec_stream_t::s_in, 5000 );
                exec_stream.start( program, "child pathologic" );
                pathologic_one( exec_stream );
                TEST( exec_stream.close() );
            }

            {
                TEST_NAME( "pathologic two" );
                exec_stream_t exec_stream;
                // disable exceptions while reading/writing
                exec_stream.in().exceptions( std::ios_base::goodbit );
                exec_stream.out().exceptions( std::ios_base::goodbit );
                exec_stream.err().exceptions( std::ios_base::goodbit );

                exec_stream.set_buffer_limit( exec_stream_t::s_out, 5000 );
                exec_stream.start( program, "child pathologic" );
                pathologic_one( exec_stream );
                TEST( exec_stream.close() );
                exec_stream.set_buffer_limit( exec_stream_t::s_in|exec_stream_t::s_out, 5000 );
                exec_stream.start( program, "child pathologic" );
                pathologic_one( exec_stream, false );
                TEST( exec_stream.close() );
            }

            {
                TEST_NAME( "pathologic two with exceptions" );
                exec_stream_t exec_stream;

                exec_stream.set_buffer_limit( exec_stream_t::s_out, 5000 );
                exec_stream.start( program, "child pathologic" );
                pathologic_one( exec_stream );
                TEST( exec_stream.close() );
                exec_stream.set_buffer_limit( exec_stream_t::s_in|exec_stream_t::s_out, 5000 );
                exec_stream.start( program, "child pathologic" );
                try {
                    pathologic_one( exec_stream );
                    TEST( 0=="unreachable" );
                }catch( std::exception const & e ) {
                    std::string m=e.what();
                    if( english_error_messages ) {
                        TEST( m.find( "timeout" )!=std::string::npos || m.find( "timed out" )!=std::string::npos  );
                    }
                }
                TEST( exec_stream.close() );
            }

            {
                TEST_NAME( "long out line" );
                exec_stream_t exec_stream;
                exec_stream.set_wait_timeout( exec_stream_t::s_out, 100000 );
                exec_stream.start( program, "child long-out-line" );
                std::string s;
                s.reserve( 2000000 );
                exec_stream.out()>>s;
                TEST( s==random_string( 2000000 ) );
                TEST( exec_stream.close() );
            }

        }

        {
            TEST_NAME( "exit code" );
            exec_stream_t exec_stream;

            exec_stream.start( program, "child exit-code" );
            exec_stream.in()<<"2\n";
            TEST( exec_stream.close() );
            TEST( exec_stream.exit_code()==2 );

            exec_stream.start( program, "child exit-code" );
            exec_stream.in()<<"42\n";
            TEST( exec_stream.close() );
            TEST( exec_stream.exit_code()==42 );
        }

        {
            TEST_NAME( "args" );
            exec_stream_t exec_stream;
            std::string s;

            exec_stream.start( program, "args \"one two\" three" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="one two" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="three" );
            TEST( !std::getline( exec_stream.out(), s ).good() );

            exec_stream.start( program, "args -e \"print \\\"hello world\\\";\"" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="-e" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="print \"hello world\";" );
            TEST( !std::getline( exec_stream.out(), s ).good() );

            char const * args[]={ "args", "one two", "three" };
            exec_stream.start( program, &args[0], &args[3] );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="one two" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="three" );
            TEST( !std::getline( exec_stream.out(), s ).good() );

            std::vector< std::string > args2;
            args2.push_back( "args" );
            args2.push_back( "-e" );
            args2.push_back( "print \"hello world\";" );
            exec_stream.start( program, args2.begin(), args2.end() );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="-e" );
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="print \"hello world\";" );
            TEST( !std::getline( exec_stream.out(), s ).good() );
        }

        {
            TEST_NAME( "kinky args" );
            exec_stream_t exec_stream( program, "args", "zzzz" );
            std::string s;
            TEST( std::getline( exec_stream.out(), s ).good() );
            TEST( s=="zzzz" );
            TEST( !std::getline( exec_stream.out(), s ).good() );
        }

        {
            TEST_NAME( "run unexistent program" );
            std::string prog="don't know what";
            try {
                exec_stream_t exec_stream;
                exec_stream.start( prog, "" );
                TEST( 0=="unreachable" );
            }catch( exec_stream_t::error_t & e ) {
                std::string msg=e.what();
                TEST( msg.find( prog )!=std::string::npos );
            }catch(...) {
                TEST( 0=="unexpected" );
            }
        }

        n_failed=test_results_t::print( std::cout );

    }catch( std::exception const & e ) {
        std::cerr<<"exception:"<<e.what()<<"\n";
    }catch( ... ) {
        std::cerr<<"unknown exception\n";
    }

    return n_failed;
}
