2014-06-25 23:34:34 +04:00
/* -*- C++ -*- copyright (c) 2014 Red Hat, Inc.
2014-06-08 20:03:32 +04:00
*
* This file is part of LVM2 .
*
2014-06-25 23:34:34 +04:00
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
2014-06-08 20:03:32 +04:00
*
2014-06-25 23:34:34 +04:00
* 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 .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " 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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 .
2014-06-08 20:03:32 +04:00
*/
2014-06-25 23:34:34 +04:00
# include "io.h"
# include "journal.h"
# include "filesystem.h"
2014-06-08 20:03:32 +04:00
# include <iostream>
# include <vector>
# include <deque>
# include <map>
# include <sstream>
# include <cassert>
# include <algorithm>
# include <fcntl.h>
# include <limits.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/resource.h> /* rusage */
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <sys/klog.h>
# include <time.h>
# include <unistd.h>
# include <stdint.h>
pid_t kill_pid = 0 ;
bool fatal_signal = false ;
2014-06-26 03:02:13 +04:00
bool interrupt = false ;
2014-06-08 20:03:32 +04:00
struct Options {
2014-06-27 23:46:07 +04:00
bool verbose , batch , interactive , cont ;
2014-06-08 20:03:32 +04:00
std : : string testdir , outdir ;
2014-06-27 03:13:14 +04:00
std : : vector < std : : string > flavours ;
2014-06-27 23:46:07 +04:00
Options ( ) : verbose ( false ) , batch ( false ) , interactive ( false ) , cont ( false ) { }
2014-06-08 20:03:32 +04:00
} ;
struct TestProcess
{
std : : string filename ;
2014-06-25 23:34:34 +04:00
bool interactive ;
int fd ;
2014-06-08 20:03:32 +04:00
void exec ( ) {
assert ( fd > = 0 ) ;
if ( ! interactive ) {
close ( STDIN_FILENO ) ;
dup2 ( fd , STDOUT_FILENO ) ;
dup2 ( fd , STDERR_FILENO ) ;
2014-06-26 03:08:57 +04:00
close ( fd ) ;
2014-06-08 20:03:32 +04:00
}
environment ( ) ;
setpgid ( 0 , 0 ) ;
execlp ( " bash " , " bash " , " -noprofile " , " -norc " , filename . c_str ( ) , NULL ) ;
perror ( " execlp " ) ;
_exit ( 202 ) ;
}
void environment ( ) {
/* if (strchr(f, ':')) {
strcpy ( flavour , f ) ;
* strchr ( flavour , ' : ' ) = 0 ;
setenv ( " LVM_TEST_FLAVOUR " , flavour , 1 ) ;
strcpy ( script , strchr ( f , ' : ' ) + 1 ) ;
} else {
strcpy ( script , f ) ;
} */
}
TestProcess ( std : : string file )
: filename ( file ) , interactive ( false ) , fd ( - 1 )
{ }
} ;
struct TestCase {
TestProcess child ;
2014-06-27 03:13:14 +04:00
std : : string name , flavour ;
2014-06-08 20:03:32 +04:00
IO io ;
2014-06-27 03:23:02 +04:00
BufSink * iobuf ;
2014-06-08 20:03:32 +04:00
struct rusage usage ;
int status ;
bool timeout ;
pid_t pid ;
2014-06-27 03:21:20 +04:00
time_t start , end , silent_start ;
2014-06-08 20:03:32 +04:00
Options options ;
2014-06-26 03:02:13 +04:00
Journal * journal ;
2014-06-27 03:13:14 +04:00
std : : string pretty ( ) {
return " [ " + flavour + " ] " + name ;
}
std : : string id ( ) {
return flavour + " : " + name ;
}
2014-06-08 20:03:32 +04:00
void pipe ( ) {
int fds [ 2 ] ;
if ( socketpair ( PF_UNIX , SOCK_STREAM , 0 , fds ) ) {
perror ( " socketpair " ) ;
exit ( 201 ) ;
}
if ( fcntl ( fds [ 0 ] , F_SETFL , O_NONBLOCK ) = = - 1 ) {
perror ( " fcntl on socket " ) ;
exit ( 202 ) ;
}
io . fd = fds [ 0 ] ;
child . fd = fds [ 1 ] ;
child . interactive = options . interactive ;
}
bool monitor ( ) {
end = time ( 0 ) ;
2014-06-27 03:21:20 +04:00
if ( wait4 ( pid , & status , WNOHANG , & usage ) ! = 0 ) {
io . sync ( ) ;
2014-06-08 20:03:32 +04:00
return false ;
2014-06-27 03:21:20 +04:00
}
2014-06-08 20:03:32 +04:00
/* kill off tests after a minute of silence */
2014-06-27 03:21:20 +04:00
if ( end - silent_start > 60 ) {
2014-06-08 20:03:32 +04:00
kill ( pid , SIGINT ) ;
sleep ( 5 ) ; /* wait a bit for a reaction */
if ( waitpid ( pid , & status , WNOHANG ) = = 0 ) {
system ( " echo t > /proc/sysrq-trigger " ) ;
kill ( - pid , SIGKILL ) ;
waitpid ( pid , & status , 0 ) ;
}
timeout = true ;
2014-06-27 03:23:02 +04:00
io . sync ( ) ;
2014-06-08 20:03:32 +04:00
return false ;
}
struct timeval wait ;
fd_set set ;
FD_ZERO ( & set ) ;
FD_SET ( io . fd , & set ) ;
wait . tv_sec = 0 ;
wait . tv_usec = 500000 ; /* timeout 0.5s */
2014-06-27 23:46:07 +04:00
if ( ! options . verbose & & ! options . interactive & & ! options . batch )
2014-06-27 03:13:14 +04:00
progress ( Update ) < < tag ( " running " ) < < pretty ( ) < < " " < < end - start < < std : : flush ;
2014-06-08 20:03:32 +04:00
2014-06-27 03:21:20 +04:00
if ( select ( io . fd + 1 , & set , NULL , NULL , & wait ) > 0 )
silent_start = end ; /* something happened */
2014-06-08 20:03:32 +04:00
2014-06-27 03:23:02 +04:00
usleep ( 500000 ) ;
2014-06-08 20:03:32 +04:00
io . sync ( ) ;
return true ;
}
std : : string tag ( std : : string n ) {
2014-06-26 03:02:13 +04:00
int pad = ( 12 - n . length ( ) ) ;
2014-06-08 20:03:32 +04:00
return " ### " + std : : string ( pad , ' ' ) + n + " : " ;
}
std : : string tag ( Journal : : R r ) {
std : : stringstream s ;
s < < r ;
return tag ( s . str ( ) ) ;
}
enum P { First , Update , Last } ;
std : : ostream & progress ( P p = Last )
{
static struct : std : : streambuf { } buf ;
static std : : ostream null ( & buf ) ;
if ( isatty ( STDOUT_FILENO ) ) {
if ( p ! = First )
return std : : cout < < " \r " ;
return std : : cout ;
}
if ( p = = Last )
return std : : cout ;
return null ;
}
2014-06-27 03:21:20 +04:00
void parent ( )
{
2014-06-27 03:23:02 +04:00
: : close ( child . fd ) ;
setupIO ( ) ;
journal - > started ( id ( ) ) ;
2014-06-27 03:21:20 +04:00
silent_start = start = time ( 0 ) ;
2014-06-27 03:23:02 +04:00
progress ( First ) < < tag ( " running " ) < < pretty ( ) < < std : : flush ;
if ( options . verbose | | options . interactive )
progress ( ) < < std : : endl ;
2014-06-08 20:03:32 +04:00
while ( monitor ( ) ) ;
Journal : : R r = Journal : : UNKNOWN ;
if ( timeout ) {
r = Journal : : TIMEOUT ;
} else if ( WIFEXITED ( status ) ) {
if ( WEXITSTATUS ( status ) = = 0 )
r = Journal : : PASSED ;
else if ( WEXITSTATUS ( status ) = = 200 )
r = Journal : : SKIPPED ;
else
r = Journal : : FAILED ;
2014-06-26 03:02:13 +04:00
} else if ( interrupt & & WIFSIGNALED ( status ) & & WTERMSIG ( status ) = = SIGINT )
r = Journal : : INTERRUPTED ;
else
2014-06-08 20:03:32 +04:00
r = Journal : : FAILED ;
2014-06-26 03:08:57 +04:00
io . close ( ) ;
2014-06-08 20:03:32 +04:00
/*
if ( ( fd_debuglog = open ( testdirdebug , O_RDONLY ) ) ! = - 1 ) {
drain ( fd_debuglog , unlimited ? INT32_MAX : 4 * 1024 * 1024 ) ;
close ( fd_debuglog ) ;
} */
2014-06-27 03:23:02 +04:00
if ( iobuf & & r = = Journal : : FAILED | | r = = Journal : : TIMEOUT )
iobuf - > dump ( std : : cout ) ;
2014-06-27 03:13:14 +04:00
journal - > done ( id ( ) , r ) ;
progress ( Last ) < < tag ( r ) < < pretty ( ) < < std : : endl ;
2014-06-27 03:23:02 +04:00
io . clear ( ) ;
2014-06-08 20:03:32 +04:00
}
void run ( ) {
pipe ( ) ;
pid = kill_pid = fork ( ) ;
if ( pid < 0 ) {
perror ( " Fork failed. " ) ;
exit ( 201 ) ;
} else if ( pid = = 0 ) {
io . close ( ) ;
2014-06-27 03:23:14 +04:00
chdir ( options . testdir . c_str ( ) ) ;
2014-06-27 03:13:14 +04:00
setenv ( " LVM_TEST_FLAVOUR " , flavour . c_str ( ) , 1 ) ;
2014-06-08 20:03:32 +04:00
child . exec ( ) ;
} else {
parent ( ) ;
}
}
2014-06-27 03:23:02 +04:00
void setupIO ( ) {
iobuf = 0 ;
if ( options . verbose )
io . sinks . push_back ( new FdSink ( 1 ) ) ;
else
io . sinks . push_back ( iobuf = new BufSink ( ) ) ;
std : : string n = id ( ) ;
std : : replace ( n . begin ( ) , n . end ( ) , ' / ' , ' _ ' ) ;
std : : string fn = options . outdir + " / " + n + " .txt " ;
io . sinks . push_back ( new FileSink ( fn ) ) ;
}
2014-06-27 03:13:14 +04:00
TestCase ( Journal & j , Options opt , std : : string path , std : : string name , std : : string flavour )
: timeout ( false ) , child ( path ) , name ( name ) , flavour ( flavour ) , options ( opt ) , journal ( & j )
2014-06-25 23:34:34 +04:00
{
}
2014-06-08 20:03:32 +04:00
} ;
struct Main {
bool die ;
time_t start ;
typedef std : : vector < TestCase > Cases ;
2014-06-27 03:13:14 +04:00
typedef std : : vector < std : : string > Flavours ;
2014-06-08 20:03:32 +04:00
Journal journal ;
Options options ;
Cases cases ;
void setup ( ) {
Listing l = listdir ( options . testdir , true ) ;
std : : sort ( l . begin ( ) , l . end ( ) ) ;
2014-06-27 03:13:14 +04:00
for ( Flavours : : iterator flav = options . flavours . begin ( ) ;
flav ! = options . flavours . end ( ) ; + + flav ) {
for ( Listing : : iterator i = l . begin ( ) ; i ! = l . end ( ) ; + + i ) {
if ( i - > substr ( i - > length ( ) - 3 , i - > length ( ) ) ! = " .sh " )
continue ;
if ( i - > substr ( 0 , 4 ) = = " lib/ " )
continue ;
cases . push_back ( TestCase ( journal , options , options . testdir + * i , * i , * flav ) ) ;
cases . back ( ) . options = options ;
}
2014-06-08 20:03:32 +04:00
}
2014-06-26 03:02:13 +04:00
if ( options . cont )
journal . read ( ) ;
2014-06-08 20:03:32 +04:00
}
void run ( ) {
setup ( ) ;
start = time ( 0 ) ;
2014-06-25 23:34:34 +04:00
std : : cerr < < " running " < < cases . size ( ) < < " tests " < < std : : endl ;
2014-06-08 20:03:32 +04:00
for ( Cases : : iterator i = cases . begin ( ) ; i ! = cases . end ( ) ; + + i ) {
2014-06-26 03:02:13 +04:00
2014-06-27 03:13:14 +04:00
if ( options . cont & & journal . done ( i - > id ( ) ) )
2014-06-26 03:02:13 +04:00
continue ;
2014-06-08 20:03:32 +04:00
i - > run ( ) ;
if ( time ( 0 ) - start > 3 * 3600 ) {
2014-06-25 23:34:34 +04:00
std : : cerr < < " 3 hours passed, giving up... " < < std : : endl ;
2014-06-08 20:03:32 +04:00
die = 1 ;
}
if ( die | | fatal_signal )
break ;
}
journal . banner ( ) ;
2014-06-26 03:11:19 +04:00
journal . write ( options . outdir + " /list " ) ;
2014-06-27 03:13:14 +04:00
fsync_name ( options . outdir + " /list " ) ;
2014-06-08 20:03:32 +04:00
if ( die | | fatal_signal )
exit ( 1 ) ;
}
2014-06-26 03:02:13 +04:00
Main ( Options o ) : die ( false ) , options ( o ) , journal ( o . outdir ) { }
2014-06-08 20:03:32 +04:00
} ;
static void handler ( int sig ) {
2014-06-26 03:02:13 +04:00
signal ( sig , SIG_DFL ) ; /* die right away next time */
2014-06-08 20:03:32 +04:00
if ( kill_pid > 0 )
kill ( - kill_pid , sig ) ;
fatal_signal = true ;
2014-06-26 03:02:13 +04:00
if ( sig = = SIGINT )
interrupt = true ;
2014-06-08 20:03:32 +04:00
}
void setup_handlers ( ) {
/* set up signal handlers */
for ( int i = 0 ; i < = 32 ; + + i )
switch ( i ) {
case SIGCHLD : case SIGWINCH : case SIGURG :
case SIGKILL : case SIGSTOP : break ;
default : signal ( i , handler ) ;
}
}
static int64_t get_time_us ( void )
{
struct timeval tv ;
( void ) gettimeofday ( & tv , 0 ) ;
return ( int64_t ) tv . tv_sec * 1000000 + ( int64_t ) tv . tv_usec ;
}
static const char * duration ( time_t start , const struct rusage * usage )
{
static char buf [ 100 ] ;
int t = ( int ) ( time ( NULL ) - start ) ;
int p = sprintf ( buf , " %2d:%02d walltime " , t / 60 , t % 60 ) ;
if ( usage )
sprintf ( buf + p , " %2ld:%02ld.%03ld u, %ld:%02ld.%03ld s, %5ldk rss, %8ld/%ld IO " ,
usage - > ru_utime . tv_sec / 60 , usage - > ru_utime . tv_sec % 60 ,
usage - > ru_utime . tv_usec / 1000 ,
usage - > ru_stime . tv_sec / 60 , usage - > ru_stime . tv_sec % 60 ,
usage - > ru_stime . tv_usec / 1000 ,
usage - > ru_maxrss / 1024 ,
usage - > ru_inblock , usage - > ru_oublock ) ;
return buf ;
}
struct Args {
typedef std : : vector < std : : string > V ;
V args ;
Args ( int argc , char * * argv ) {
for ( int i = 1 ; i < argc ; + + i )
args . push_back ( argv [ i ] ) ;
}
bool has ( std : : string fl ) {
return std : : find ( args . begin ( ) , args . end ( ) , fl ) ! = args . end ( ) ;
}
std : : string opt ( std : : string fl ) {
V : : iterator i = std : : find ( args . begin ( ) , args . end ( ) , fl ) ;
if ( i = = args . end ( ) | | i + 1 = = args . end ( ) )
return " " ;
return * ( i + 1 ) ;
}
} ;
2014-06-27 03:03:08 +04:00
bool hasenv ( const char * name ) {
const char * v = getenv ( name ) ;
if ( ! v )
return false ;
if ( strlen ( v ) = = 0 | | ! strcmp ( v , " 0 " ) )
return false ;
return true ;
}
2014-06-08 20:03:32 +04:00
int main ( int argc , char * * argv )
{
Args args ( argc , argv ) ;
Options opt ;
2014-06-26 03:02:13 +04:00
if ( args . has ( " --continue " ) )
opt . cont = true ;
2014-06-27 23:46:07 +04:00
if ( args . has ( " --batch " ) | | hasenv ( " BATCH " ) ) {
2014-06-08 20:03:32 +04:00
opt . verbose = false ;
2014-06-27 23:46:07 +04:00
opt . batch = true ;
2014-06-08 20:03:32 +04:00
}
2014-06-27 03:03:08 +04:00
if ( args . has ( " --verbose " ) | | hasenv ( " VERBOSE " ) ) {
2014-06-27 23:46:07 +04:00
opt . batch = false ;
2014-06-08 20:03:32 +04:00
opt . verbose = true ;
}
2014-06-27 03:03:08 +04:00
if ( args . has ( " --interactive " ) | | hasenv ( " INTERACTIVE " ) ) {
2014-06-08 20:03:32 +04:00
opt . verbose = false ;
2014-06-27 23:46:07 +04:00
opt . batch = false ;
2014-06-08 20:03:32 +04:00
opt . interactive = true ;
}
2014-06-27 03:13:14 +04:00
if ( args . has ( " --flavours " ) ) {
std : : stringstream ss ( args . opt ( " --flavours " ) ) ;
std : : string item ;
while ( std : : getline ( ss , item , ' , ' ) )
opt . flavours . push_back ( item ) ;
} else
opt . flavours . push_back ( " vanilla " ) ;
2014-06-08 20:03:32 +04:00
opt . outdir = args . opt ( " --outdir " ) ;
opt . testdir = args . opt ( " --testdir " ) ;
if ( opt . testdir . empty ( ) )
opt . testdir = " /usr/share/lvm2-testsuite " ;
opt . testdir + = " / " ;
setup_handlers ( ) ;
Main main ( opt ) ;
main . run ( ) ;
}