/* 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 #include #include #include #include #include #include #include #ifdef _WIN32 #include void sleep( int seconds ) { Sleep( seconds*1000 ); } #else #include #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: "<test_name<<"\n"; for( failures_t::iterator fail_i=res_i->failures.begin(); fail_i!=res_i->failures.end(); ++fail_i ) { o<<" "<assertion<<" [at file "<file<<" line "<line<<"]\n"; } ++n_failed; } } o<<"\nOK: "<>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<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<>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>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; iout 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()<>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()<>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()<>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>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()<>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:"<