@ -80,16 +80,60 @@ using std::vector;
exit ( 1 ) ; \
exit ( 1 ) ; \
}
}
// Test runner that spawns the process being tested and communicates with it
namespace google {
// over a pipe.
namespace protobuf {
class ForkPipeRunner : public google : : protobuf : : ConformanceTestRunner {
public :
ForkPipeRunner ( const std : : string & executable )
: child_pid_ ( - 1 ) , executable_ ( executable ) { }
virtual ~ ForkPipeRunner ( ) { }
void ParseFailureList ( const char * filename ,
std : : vector < string > * failure_list ) {
std : : ifstream infile ( filename ) ;
if ( ! infile . is_open ( ) ) {
fprintf ( stderr , " Couldn't open failure list file: %s \n " , filename ) ;
exit ( 1 ) ;
}
for ( string line ; getline ( infile , line ) ; ) {
// Remove whitespace.
line . erase ( std : : remove_if ( line . begin ( ) , line . end ( ) , : : isspace ) ,
line . end ( ) ) ;
// Remove comments.
line = line . substr ( 0 , line . find ( " # " ) ) ;
if ( ! line . empty ( ) ) {
failure_list - > push_back ( line ) ;
}
}
}
void UsageError ( ) {
fprintf ( stderr ,
" Usage: conformance-test-runner [options] <test-program> \n " ) ;
fprintf ( stderr , " \n " ) ;
fprintf ( stderr , " Options: \n " ) ;
fprintf ( stderr ,
" --failure_list <filename> Use to specify list of tests \n " ) ;
fprintf ( stderr ,
" that are expected to fail. File \n " ) ;
fprintf ( stderr ,
" should contain one test name per \n " ) ;
fprintf ( stderr ,
" line. Use '#' for comments. \n " ) ;
fprintf ( stderr ,
" --enforce_recommended Enforce that recommended test \n " ) ;
fprintf ( stderr ,
" cases are also passing. Specify \n " ) ;
fprintf ( stderr ,
" this flag if you want to be \n " ) ;
fprintf ( stderr ,
" strictly conforming to protobuf \n " ) ;
fprintf ( stderr ,
" spec. \n " ) ;
exit ( 1 ) ;
}
void RunTest ( const std : : string & test_name ,
void ForkPipeRunner : : RunTest (
const std : : string & test_name ,
const std : : string & request ,
const std : : string & request ,
std : : string * response ) {
std : : string * response ) {
if ( child_pid_ < 0 ) {
if ( child_pid_ < 0 ) {
@ -130,26 +174,45 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
CheckedRead ( read_fd_ , ( void * ) response - > c_str ( ) , len ) ;
CheckedRead ( read_fd_ , ( void * ) response - > c_str ( ) , len ) ;
}
}
private :
int ForkPipeRunner : : Run (
// TODO(haberman): make this work on Windows, instead of using these
int argc , char * argv [ ] , ConformanceTestSuite * suite ) {
// UNIX-specific APIs.
char * program ;
//
string failure_list_filename ;
// There is a platform-agnostic API in
std : : vector < string > failure_list ;
// src/google/protobuf/compiler/subprocess.h
//
for ( int arg = 1 ; arg < argc ; + + arg ) {
// However that API only supports sending a single message to the subprocess.
if ( strcmp ( argv [ arg ] , " --failure_list " ) = = 0 ) {
// We really want to be able to send messages and receive responses one at a
if ( + + arg = = argc ) UsageError ( ) ;
// time:
failure_list_filename = argv [ arg ] ;
//
ParseFailureList ( argv [ arg ] , & failure_list ) ;
// 1. Spawning a new process for each test would take way too long for thousands
} else if ( strcmp ( argv [ arg ] , " --verbose " ) = = 0 ) {
// of tests and subprocesses like java that can take 100ms or more to start
suite - > SetVerbose ( true ) ;
// up.
} else if ( strcmp ( argv [ arg ] , " --enforce_recommended " ) = = 0 ) {
//
suite - > SetEnforceRecommended ( true ) ;
// 2. Sending all the tests in one big message and receiving all results in one
} else if ( argv [ arg ] [ 0 ] = = ' - ' ) {
// big message would take away our visibility about which test(s) caused a
fprintf ( stderr , " Unknown option: %s \n " , argv [ arg ] ) ;
// crash or other fatal error. It would also give us only a single failure
UsageError ( ) ;
// instead of all of them.
} else {
void SpawnTestProgram ( ) {
if ( arg ! = argc - 1 ) {
fprintf ( stderr , " Too many arguments. \n " ) ;
UsageError ( ) ;
}
program = argv [ arg ] ;
}
}
suite - > SetFailureList ( failure_list_filename , failure_list ) ;
ForkPipeRunner runner ( program ) ;
std : : string output ;
bool ok = suite - > RunSuite ( & runner , & output ) ;
fwrite ( output . c_str ( ) , 1 , output . size ( ) , stderr ) ;
return ok ? EXIT_SUCCESS : EXIT_FAILURE ;
}
void ForkPipeRunner : : SpawnTestProgram ( ) {
int toproc_pipe_fd [ 2 ] ;
int toproc_pipe_fd [ 2 ] ;
int fromproc_pipe_fd [ 2 ] ;
int fromproc_pipe_fd [ 2 ] ;
if ( pipe ( toproc_pipe_fd ) < 0 | | pipe ( fromproc_pipe_fd ) < 0 ) {
if ( pipe ( toproc_pipe_fd ) < 0 | | pipe ( fromproc_pipe_fd ) < 0 ) {
@ -191,7 +254,7 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
}
}
}
}
void CheckedWrite ( int fd , const void * buf , size_t len ) {
void ForkPipeRunner : : CheckedWrite ( int fd , const void * buf , size_t len ) {
if ( write ( fd , buf , len ) ! = len ) {
if ( write ( fd , buf , len ) ! = len ) {
GOOGLE_LOG ( FATAL ) < < current_test_name_
GOOGLE_LOG ( FATAL ) < < current_test_name_
< < " : error writing to test program: "
< < " : error writing to test program: "
@ -199,7 +262,7 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
}
}
}
}
bool TryRead ( int fd , void * buf , size_t len ) {
bool ForkPipeRunner : : TryRead ( int fd , void * buf , size_t len ) {
size_t ofs = 0 ;
size_t ofs = 0 ;
while ( len > 0 ) {
while ( len > 0 ) {
ssize_t bytes_read = read ( fd , ( char * ) buf + ofs , len ) ;
ssize_t bytes_read = read ( fd , ( char * ) buf + ofs , len ) ;
@ -222,7 +285,7 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
return true ;
return true ;
}
}
void CheckedRead ( int fd , void * buf , size_t len ) {
void ForkPipeRunner : : CheckedRead ( int fd , void * buf , size_t len ) {
if ( ! TryRead ( fd , buf , len ) ) {
if ( ! TryRead ( fd , buf , len ) ) {
GOOGLE_LOG ( FATAL ) < < current_test_name_
GOOGLE_LOG ( FATAL ) < < current_test_name_
< < " : error reading from test program: "
< < " : error reading from test program: "
@ -230,106 +293,5 @@ class ForkPipeRunner : public google::protobuf::ConformanceTestRunner {
}
}
}
}
int write_fd_ ;
} // namespace protobuf
int read_fd_ ;
} // namespace google
pid_t child_pid_ ;
std : : string executable_ ;
std : : string current_test_name_ ;
} ;
void UsageError ( ) {
fprintf ( stderr ,
" Usage: conformance-test-runner [options] <test-program> \n " ) ;
fprintf ( stderr , " \n " ) ;
fprintf ( stderr , " Options: \n " ) ;
fprintf ( stderr ,
" --failure_list <filename> Use to specify list of tests \n " ) ;
fprintf ( stderr ,
" that are expected to fail. File \n " ) ;
fprintf ( stderr ,
" should contain one test name per \n " ) ;
fprintf ( stderr ,
" line. Use '#' for comments. \n " ) ;
fprintf ( stderr ,
" --enforce_recommended Enforce that recommended test \n " ) ;
fprintf ( stderr ,
" cases are also passing. Specify \n " ) ;
fprintf ( stderr ,
" this flag if you want to be \n " ) ;
fprintf ( stderr ,
" strictly conforming to protobuf \n " ) ;
fprintf ( stderr ,
" spec. \n " ) ;
exit ( 1 ) ;
}
void ParseFailureList ( const char * filename , std : : vector < string > * failure_list ) {
std : : ifstream infile ( filename ) ;
if ( ! infile . is_open ( ) ) {
fprintf ( stderr , " Couldn't open failure list file: %s \n " , filename ) ;
exit ( 1 ) ;
}
for ( string line ; getline ( infile , line ) ; ) {
// Remove whitespace.
line . erase ( std : : remove_if ( line . begin ( ) , line . end ( ) , : : isspace ) ,
line . end ( ) ) ;
// Remove comments.
line = line . substr ( 0 , line . find ( " # " ) ) ;
if ( ! line . empty ( ) ) {
failure_list - > push_back ( line ) ;
}
}
}
int main ( int argc , char * argv [ ] ) {
char * program ;
const std : : set < ConformanceTestSuite * > & test_suite_set =
: : google : : protobuf : : GetTestSuiteSet ( ) ;
string failure_list_filename ;
std : : vector < string > failure_list ;
for ( int arg = 1 ; arg < argc ; + + arg ) {
if ( strcmp ( argv [ arg ] , " --failure_list " ) = = 0 ) {
if ( + + arg = = argc ) UsageError ( ) ;
failure_list_filename = argv [ arg ] ;
ParseFailureList ( argv [ arg ] , & failure_list ) ;
} else if ( strcmp ( argv [ arg ] , " --verbose " ) = = 0 ) {
for ( auto * suite : test_suite_set ) {
suite - > SetVerbose ( true ) ;
}
} else if ( strcmp ( argv [ arg ] , " --enforce_recommended " ) = = 0 ) {
for ( auto suite : test_suite_set ) {
suite - > SetEnforceRecommended ( true ) ;
}
} else if ( argv [ arg ] [ 0 ] = = ' - ' ) {
fprintf ( stderr , " Unknown option: %s \n " , argv [ arg ] ) ;
UsageError ( ) ;
} else {
if ( arg ! = argc - 1 ) {
fprintf ( stderr , " Too many arguments. \n " ) ;
UsageError ( ) ;
}
program = argv [ arg ] ;
}
}
for ( auto suite : test_suite_set ) {
suite - > SetFailureList ( failure_list_filename , failure_list ) ;
}
ForkPipeRunner runner ( program ) ;
std : : string output ;
bool ok = true ;
for ( auto suite : test_suite_set ) {
ok & = suite - > RunSuite ( & runner , & output ) ;
}
fwrite ( output . c_str ( ) , 1 , output . size ( ) , stderr ) ;
return ok ? EXIT_SUCCESS : EXIT_FAILURE ;
}