2014-07-04 20:24:56 +04:00
// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*-
/*
* This brick allows you to build a test runner for shell - based functional
* tests . It comes with fairly elaborate features ( although most are only
* available on posix systems ) , geared toward difficult - to - test software .
*
* It provides a full - featured " main " function ( brick : : shelltest : : run ) that you
* can use as a drop - in shell test runner .
*
* Features include :
* - interactive and batch - mode execution
* - collects test results and test logs in a simple text - based format
* - measures resource use of individual tests
* - rugged : suited for running in monitored virtual machines
* - supports test flavouring
*/
/*
* ( c ) 2014 Petr Ročkai < me @ mornfall . net >
* ( c ) 2014 Red Hat , Inc .
*/
/* 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 .
*
* 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 . */
# include <fcntl.h>
# include <limits.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <stdint.h>
# include <errno.h>
# include <vector>
# include <map>
# include <deque>
# include <string>
# include <iostream>
# include <iomanip>
# include <fstream>
# include <sstream>
# include <cassert>
# include <iterator>
# include <algorithm>
# include <stdexcept>
# include <dirent.h>
2014-09-14 13:15:51 +04:00
# ifdef __unix
2014-07-04 20:24:56 +04:00
# include <sys/stat.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>
# endif
# ifndef BRICK_SHELLTEST_H
# define BRICK_SHELLTEST_H
namespace brick {
namespace shelltest {
/* TODO: remove this section in favour of brick-filesystem.h */
inline std : : runtime_error syserr ( std : : string msg , std : : string ctx = " " ) {
return std : : runtime_error ( std : : string ( strerror ( errno ) ) + " " + msg + " " + ctx ) ;
}
struct dir {
DIR * d ;
dir ( std : : string p ) {
d = opendir ( p . c_str ( ) ) ;
if ( ! d )
throw syserr ( " error opening directory " , p ) ;
}
~ dir ( ) { closedir ( d ) ; }
} ;
typedef std : : vector < std : : string > Listing ;
inline void fsync_name ( std : : string n )
{
int fd = open ( n . c_str ( ) , O_WRONLY ) ;
if ( fd > = 0 ) {
fsync ( fd ) ;
close ( fd ) ;
}
}
inline Listing listdir ( std : : string p , bool recurse = false , std : : string prefix = " " )
{
Listing r ;
dir d ( p ) ;
struct dirent entry , * iter = 0 ;
int readerr ;
while ( ( readerr = readdir_r ( d . d , & entry , & iter ) ) = = 0 & & iter ) {
std : : string ename ( entry . d_name ) ;
if ( ename = = " . " | | ename = = " .. " )
continue ;
if ( recurse ) {
struct stat64 stat ;
std : : string s = p + " / " + ename ;
if ( : : stat64 ( s . c_str ( ) , & stat ) = = - 1 )
continue ;
if ( S_ISDIR ( stat . st_mode ) ) {
Listing sl = listdir ( s , true , prefix + ename + " / " ) ;
for ( Listing : : iterator i = sl . begin ( ) ; i ! = sl . end ( ) ; + + i )
r . push_back ( prefix + * i ) ;
} else
r . push_back ( prefix + ename ) ;
} else
r . push_back ( ename ) ;
} ;
if ( readerr ! = 0 )
throw syserr ( " error reading directory " , p ) ;
return r ;
}
/* END remove this section */
struct Journal {
enum R {
STARTED ,
RETRIED ,
UNKNOWN ,
FAILED ,
INTERRUPTED ,
KNOWNFAIL ,
PASSED ,
SKIPPED ,
TIMEOUT ,
WARNED ,
} ;
friend std : : ostream & operator < < ( std : : ostream & o , R r ) {
switch ( r ) {
case STARTED : return o < < " started " ;
case RETRIED : return o < < " retried " ;
case FAILED : return o < < " failed " ;
case INTERRUPTED : return o < < " interrupted " ;
case PASSED : return o < < " passed " ;
case SKIPPED : return o < < " skipped " ;
case TIMEOUT : return o < < " timeout " ;
case WARNED : return o < < " warnings " ;
default : return o < < " unknown " ;
}
}
friend std : : istream & operator > > ( std : : istream & i , R & r ) {
std : : string x ;
i > > x ;
r = UNKNOWN ;
if ( x = = " started " ) r = STARTED ;
if ( x = = " retried " ) r = RETRIED ;
if ( x = = " failed " ) r = FAILED ;
if ( x = = " interrupted " ) r = INTERRUPTED ;
if ( x = = " passed " ) r = PASSED ;
if ( x = = " skipped " ) r = SKIPPED ;
if ( x = = " timeout " ) r = TIMEOUT ;
if ( x = = " warnings " ) r = WARNED ;
return i ;
}
template < typename S , typename T >
friend std : : istream & operator > > ( std : : istream & i , std : : pair < S , T > & r ) {
return i > > r . first > > r . second ;
}
typedef std : : map < std : : string , R > Status ;
Status status , written ;
std : : string location , list ;
int timeouts ;
void append ( std : : string path ) {
std : : ofstream of ( path . c_str ( ) , std : : fstream : : app ) ;
Status : : iterator writ ;
for ( Status : : iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i ) {
writ = written . find ( i - > first ) ;
if ( writ = = written . end ( ) | | writ - > second ! = i - > second )
of < < i - > first < < " " < < i - > second < < std : : endl ;
}
written = status ;
of . close ( ) ;
}
void write ( std : : string path ) {
std : : ofstream of ( path . c_str ( ) ) ;
for ( Status : : iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
of < < i - > first < < " " < < i - > second < < std : : endl ;
of . close ( ) ;
}
void sync ( ) {
append ( location ) ;
fsync_name ( location ) ;
write ( list ) ;
fsync_name ( list ) ;
}
void started ( std : : string n ) {
if ( status . count ( n ) & & status [ n ] = = STARTED )
status [ n ] = RETRIED ;
else
status [ n ] = STARTED ;
sync ( ) ;
}
void done ( std : : string n , R r ) {
status [ n ] = r ;
if ( r = = TIMEOUT )
+ + timeouts ;
else
timeouts = 0 ;
sync ( ) ;
}
bool done ( std : : string n ) {
if ( ! status . count ( n ) )
return false ;
return status [ n ] ! = STARTED & & status [ n ] ! = INTERRUPTED ;
}
int count ( R r ) {
int c = 0 ;
for ( Status : : iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
if ( i - > second = = r )
+ + c ;
return c ;
}
void banner ( ) {
std : : cout < < std : : endl < < " ### " < < status . size ( ) < < " tests: "
2014-09-14 13:15:51 +04:00
< < count ( PASSED ) < < " passed, "
< < count ( SKIPPED ) < < " skipped, "
< < count ( TIMEOUT ) + count ( WARNED ) < < " broken, "
< < count ( FAILED ) < < " failed " < < std : : endl ;
2014-07-04 20:24:56 +04:00
}
void details ( ) {
for ( Status : : iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
if ( i - > second ! = PASSED )
std : : cout < < i - > second < < " : " < < i - > first < < std : : endl ;
}
void read ( std : : string n ) {
std : : ifstream ifs ( n . c_str ( ) ) ;
typedef std : : istream_iterator < std : : pair < std : : string , R > > It ;
for ( It i ( ifs ) ; i ! = It ( ) ; + + i )
status [ i - > first ] = i - > second ;
}
void read ( ) { read ( location ) ; }
Journal ( std : : string dir )
: location ( dir + " /journal " ) ,
list ( dir + " /list " ) ,
timeouts ( 0 )
{ }
} ;
2014-07-06 18:22:23 +04:00
struct TimedBuffer {
typedef std : : pair < time_t , std : : string > Line ;
std : : deque < Line > data ;
Line incomplete ;
2015-04-15 14:11:26 +03:00
bool stamp ;
2014-07-06 18:22:23 +04:00
Line shift ( bool force = false ) {
Line result = std : : make_pair ( 0 , " " ) ;
if ( force & & data . empty ( ) )
std : : swap ( result , incomplete ) ;
else {
result = data . front ( ) ;
data . pop_front ( ) ;
}
return result ;
}
void push ( std : : string buf ) {
2015-04-15 14:11:26 +03:00
time_t now = stamp ? time ( 0 ) : 0 ;
2014-07-06 18:22:23 +04:00
std : : string : : iterator b = buf . begin ( ) , e = buf . begin ( ) ;
while ( e ! = buf . end ( ) )
{
e = std : : find ( b , buf . end ( ) , ' \n ' ) ;
incomplete . second + = std : : string ( b , e ) ;
if ( ! incomplete . first )
incomplete . first = now ;
if ( e ! = buf . end ( ) ) {
incomplete . second + = " \n " ;
data . push_back ( incomplete ) ;
2015-04-15 14:11:26 +03:00
if ( incomplete . second [ 0 ] = = ' # ' ) {
/* Disable timing between '## 0 STACKTRACE' & '## teardown' keywords */
2015-04-20 20:09:02 +03:00
if ( incomplete . second . find ( " # 0 STACKTRACE " , 1 ) ! = std : : string : : npos | |
incomplete . second . find ( " # timing off " , 1 ) ! = std : : string : : npos ) {
2015-04-15 14:11:26 +03:00
stamp = false ;
now = 0 ;
2015-04-20 20:09:02 +03:00
} else if ( incomplete . second . find ( " # teardown " , 1 ) ! = std : : string : : npos | |
incomplete . second . find ( " # timing on " , 1 ) ! = std : : string : : npos ) {
2015-04-15 14:11:26 +03:00
stamp = true ;
now = time ( 0 ) ;
}
}
2014-07-06 18:22:23 +04:00
incomplete = std : : make_pair ( now , " " ) ;
}
b = ( e = = buf . end ( ) ? e : e + 1 ) ;
}
}
bool empty ( bool force = false ) {
if ( force & & ! incomplete . second . empty ( ) )
return false ;
return data . empty ( ) ;
}
2015-04-15 14:11:26 +03:00
TimedBuffer ( ) : stamp ( true ) { }
2014-07-06 18:22:23 +04:00
} ;
2014-07-04 20:24:56 +04:00
struct Sink {
virtual void outline ( bool ) { }
virtual void push ( std : : string x ) = 0 ;
2014-10-01 13:27:44 +04:00
virtual void sync ( bool ) { }
2014-07-04 20:24:56 +04:00
virtual ~ Sink ( ) { }
} ;
2014-07-06 18:22:23 +04:00
struct Substitute {
typedef std : : map < std : : string , std : : string > Map ;
2015-04-13 17:31:02 +03:00
std : : string testdir ; // replace testdir first
std : : string prefix ;
2014-07-06 18:22:23 +04:00
std : : string map ( std : : string line ) {
if ( std : : string ( line , 0 , 9 ) = = " @TESTDIR= " )
2015-04-13 17:31:02 +03:00
testdir = std : : string ( line , 9 , line . length ( ) - 10 ) ; // skip \n
2014-07-06 18:22:23 +04:00
else if ( std : : string ( line , 0 , 8 ) = = " @PREFIX= " )
2015-04-13 17:31:02 +03:00
prefix = std : : string ( line , 8 , line . length ( ) - 9 ) ; // skip \n
2014-07-06 18:22:23 +04:00
else {
2015-03-11 13:00:55 +03:00
size_t off ;
2015-04-13 17:31:02 +03:00
if ( ! testdir . empty ( ) )
while ( ( off = line . find ( testdir ) ) ! = std : : string : : npos )
line . replace ( off , testdir . length ( ) , " @TESTDIR@ " ) ;
if ( ! prefix . empty ( ) )
while ( ( off = line . find ( prefix ) ) ! = std : : string : : npos )
line . replace ( off , prefix . length ( ) , " @PREFIX@ " ) ;
2014-07-06 18:22:23 +04:00
}
return line ;
}
} ;
struct Format {
time_t start ;
Substitute subst ;
std : : string format ( TimedBuffer : : Line l ) {
std : : stringstream result ;
2015-04-15 14:11:26 +03:00
if ( l . first > = start ) {
2015-03-11 13:00:55 +03:00
time_t rel = l . first - start ;
2014-07-06 18:22:23 +04:00
result < < " [ " < < std : : setw ( 2 ) < < std : : setfill ( ' ' ) < < rel / 60
< < " : " < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < rel % 60 < < " ] " ;
}
result < < subst . map ( l . second ) ;
return result . str ( ) ;
}
2015-04-15 14:11:26 +03:00
Format ( ) : start ( time ( 0 ) ) { }
2014-07-06 18:22:23 +04:00
} ;
2014-07-04 20:24:56 +04:00
struct BufSink : Sink {
2014-07-06 18:22:23 +04:00
TimedBuffer data ;
Format fmt ;
2014-07-04 20:24:56 +04:00
virtual void push ( std : : string x ) {
2014-07-06 18:22:23 +04:00
data . push ( x ) ;
2014-07-04 20:24:56 +04:00
}
void dump ( std : : ostream & o ) {
2014-07-22 20:44:32 +04:00
o < < std : : endl ;
2014-07-06 18:22:23 +04:00
while ( ! data . empty ( true ) )
2014-07-22 20:44:32 +04:00
o < < " | " < < fmt . format ( data . shift ( true ) ) ;
2014-07-04 20:24:56 +04:00
}
} ;
struct FdSink : Sink {
int fd ;
2014-07-06 18:22:23 +04:00
TimedBuffer stream ;
Format fmt ;
2014-07-04 20:24:56 +04:00
bool killed ;
virtual void outline ( bool force )
{
2014-07-06 18:22:23 +04:00
TimedBuffer : : Line line = stream . shift ( force ) ;
std : : string out = fmt . format ( line ) ;
write ( fd , out . c_str ( ) , out . length ( ) ) ;
2014-07-04 20:24:56 +04:00
}
2014-10-01 13:27:44 +04:00
virtual void sync ( bool force ) {
2014-07-04 20:24:56 +04:00
if ( killed )
return ;
2014-10-01 13:27:44 +04:00
while ( ! stream . empty ( force ) )
outline ( force ) ;
2014-07-04 20:24:56 +04:00
}
virtual void push ( std : : string x ) {
if ( ! killed )
2014-07-06 18:22:23 +04:00
stream . push ( x ) ;
2014-07-04 20:24:56 +04:00
}
FdSink ( int _fd ) : fd ( _fd ) , killed ( false ) { }
} ;
struct FileSink : FdSink {
std : : string file ;
FileSink ( std : : string n ) : FdSink ( - 1 ) , file ( n ) { }
2014-10-01 13:27:44 +04:00
void sync ( bool force ) {
2014-07-04 20:24:56 +04:00
if ( fd < 0 & & ! killed ) {
2015-02-10 16:51:45 +03:00
# ifdef O_CLOEXEC
2014-07-04 20:24:56 +04:00
fd = open ( file . c_str ( ) , O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC , 0644 ) ;
2015-02-10 16:51:45 +03:00
# else
fd = open ( file . c_str ( ) , O_WRONLY | O_CREAT | O_TRUNC , 0644 ) ;
if ( fcntl ( fd , F_SETFD , FD_CLOEXEC ) < 0 )
perror ( " failed to set FD_CLOEXEC on file " ) ;
# endif
2014-07-04 20:24:56 +04:00
if ( fd < 0 )
killed = true ;
}
2014-10-01 13:27:44 +04:00
FdSink : : sync ( force ) ;
2014-07-04 20:24:56 +04:00
}
2014-07-22 20:44:32 +04:00
2014-07-04 20:24:56 +04:00
~ FileSink ( ) {
if ( fd > = 0 ) {
fsync ( fd ) ;
close ( fd ) ;
}
}
} ;
# define BRICK_SYSLOG_ACTION_READ_CLEAR 4
# define BRICK_SYSLOG_ACTION_CLEAR 5
2014-07-22 20:44:32 +04:00
struct Source {
2014-07-04 20:24:56 +04:00
int fd ;
2014-07-22 20:44:32 +04:00
virtual void sync ( Sink * sink ) {
ssize_t sz ;
char buf [ 128 * 1024 ] ;
2015-03-11 23:13:41 +03:00
if ( ( sz = read ( fd , buf , sizeof ( buf ) - 1 ) ) > 0 )
2014-07-22 20:44:32 +04:00
sink - > push ( std : : string ( buf , sz ) ) ;
2015-03-12 02:07:45 +03:00
/*
* On RHEL5 box this code busy - loops here , while
* parent process no longer writes anything .
*
* Unclear why ' select ( ) ' is anouncing available
* data , while we read 0 bytes with errno = = 0.
*
* Temporarily resolved with usleep ( ) instead of loop .
*/
if ( ! sz & & ( ! errno | | errno = = EINTR ) )
usleep ( 50000 ) ;
2014-07-22 20:44:32 +04:00
if ( sz < 0 & & errno ! = EAGAIN )
throw syserr ( " reading pipe " ) ;
}
virtual void reset ( ) { }
2015-02-10 16:51:45 +03:00
virtual int fd_set_ ( fd_set * set ) {
2014-07-22 20:44:32 +04:00
if ( fd > = 0 ) {
FD_SET ( fd , set ) ;
return fd ;
} else
return - 1 ;
}
2015-03-11 13:00:55 +03:00
Source ( int _fd = - 1 ) : fd ( _fd ) { }
2014-07-22 20:44:32 +04:00
virtual ~ Source ( ) {
if ( fd > = 0 )
: : close ( fd ) ;
}
} ;
struct FileSource : Source {
std : : string file ;
FileSource ( std : : string n ) : Source ( - 1 ) , file ( n ) { }
2015-02-10 16:51:45 +03:00
int fd_set_ ( : : fd_set * ) { return - 1 ; } /* reading a file is always non-blocking */
2014-07-22 20:44:32 +04:00
void sync ( Sink * s ) {
if ( fd < 0 ) {
2015-02-10 16:51:45 +03:00
# ifdef O_CLOEXEC
2014-07-22 20:44:32 +04:00
fd = open ( file . c_str ( ) , O_RDONLY | O_CLOEXEC | O_NONBLOCK ) ;
2015-02-10 16:51:45 +03:00
# else
fd = open ( file . c_str ( ) , O_RDONLY | O_NONBLOCK ) ;
if ( fcntl ( fd , F_SETFD , FD_CLOEXEC ) < 0 )
perror ( " failed to set FD_CLOEXEC on file " ) ;
# endif
2014-07-22 20:44:32 +04:00
if ( fd > = 0 )
lseek ( fd , 0 , SEEK_END ) ;
}
if ( fd > = 0 )
Source : : sync ( s ) ;
}
} ;
struct KMsg : Source {
bool can_clear ;
2015-05-08 23:43:06 +03:00
KMsg ( ) : can_clear ( strcmp ( getenv ( " LVM_TEST_CAN_CLOBBER_DMESG " ) ? : " 0 " , " 0 " ) ) {
2014-07-04 20:24:56 +04:00
# ifdef __unix
2015-05-08 23:43:06 +03:00
if ( ( fd = open ( " /dev/kmsg " , O_RDONLY | O_NONBLOCK ) ) < 0 ) {
if ( errno ! = ENOENT ) /* Older kernels (<3.5) do not support /dev/kmsg */
perror ( " opening /dev/kmsg " ) ;
2014-07-22 20:44:32 +04:00
if ( klogctl ( BRICK_SYSLOG_ACTION_CLEAR , 0 , 0 ) < 0 )
can_clear = false ;
2015-05-08 23:43:06 +03:00
} else if ( lseek ( fd , 0L , SEEK_END ) = = ( off_t ) - 1 )
perror ( " lseek /dev/kmsg " ) ;
2014-07-04 20:24:56 +04:00
# endif
}
2015-05-08 23:43:06 +03:00
bool dev_kmsg ( ) {
return fd > = 0 ;
}
2014-07-22 20:44:32 +04:00
void sync ( Sink * s ) {
2014-07-04 20:24:56 +04:00
# ifdef __unix
2015-03-11 13:00:55 +03:00
ssize_t sz ;
2014-07-04 20:24:56 +04:00
char buf [ 128 * 1024 ] ;
if ( dev_kmsg ( ) ) {
while ( ( sz = : : read ( fd , buf , sizeof ( buf ) - 1 ) ) > 0 )
s - > push ( std : : string ( buf , sz ) ) ;
2014-07-22 20:44:32 +04:00
} else if ( can_clear ) {
while ( ( sz = klogctl ( BRICK_SYSLOG_ACTION_READ_CLEAR , buf ,
sizeof ( buf ) - 1 ) ) > 0 )
2014-07-04 20:24:56 +04:00
s - > push ( std : : string ( buf , sz ) ) ;
2014-07-22 20:44:32 +04:00
if ( sz < 0 & & errno = = EPERM )
can_clear = false ;
2014-07-04 20:24:56 +04:00
}
# endif
}
} ;
struct Observer : Sink {
2014-10-01 13:28:15 +04:00
TimedBuffer stream ;
bool warnings ;
Observer ( ) : warnings ( false ) { }
void push ( std : : string s ) {
stream . push ( s ) ;
}
void sync ( bool force ) {
while ( ! stream . empty ( force ) ) {
TimedBuffer : : Line line = stream . shift ( force ) ;
if ( line . second . find ( " TEST WARNING " ) ! = std : : string : : npos )
warnings = true ;
}
}
2014-07-04 20:24:56 +04:00
} ;
struct IO : Sink {
typedef std : : vector < Sink * > Sinks ;
2014-07-22 20:44:32 +04:00
typedef std : : vector < Source * > Sources ;
2014-07-04 20:24:56 +04:00
mutable Sinks sinks ;
2014-07-22 20:44:32 +04:00
mutable Sources sources ;
2014-07-04 20:24:56 +04:00
2014-07-22 20:44:32 +04:00
Observer * _observer ;
2014-07-04 20:24:56 +04:00
virtual void push ( std : : string x ) {
for ( Sinks : : iterator i = sinks . begin ( ) ; i ! = sinks . end ( ) ; + + i )
( * i ) - > push ( x ) ;
}
2014-10-01 13:27:44 +04:00
void sync ( bool force ) {
2014-07-22 20:44:32 +04:00
for ( Sources : : iterator i = sources . begin ( ) ; i ! = sources . end ( ) ; + + i )
( * i ) - > sync ( this ) ;
2014-07-04 20:24:56 +04:00
2014-07-22 20:44:32 +04:00
for ( Sinks : : iterator i = sinks . begin ( ) ; i ! = sinks . end ( ) ; + + i )
2014-10-01 13:27:44 +04:00
( * i ) - > sync ( force ) ;
2014-07-22 20:44:32 +04:00
}
2014-07-04 20:24:56 +04:00
2014-07-22 20:44:32 +04:00
void close ( ) {
for ( Sources : : iterator i = sources . begin ( ) ; i ! = sources . end ( ) ; + + i )
delete * i ;
sources . clear ( ) ;
}
2014-07-04 20:24:56 +04:00
2015-02-10 16:51:45 +03:00
int fd_set_ ( fd_set * set ) {
2014-07-22 20:44:32 +04:00
int max = - 1 ;
2014-07-04 20:24:56 +04:00
2014-07-22 20:44:32 +04:00
for ( Sources : : iterator i = sources . begin ( ) ; i ! = sources . end ( ) ; + + i )
2015-02-10 16:51:45 +03:00
max = std : : max ( ( * i ) - > fd_set_ ( set ) , max ) ;
2014-07-22 20:44:32 +04:00
return max + 1 ;
2014-07-04 20:24:56 +04:00
}
Observer & observer ( ) { return * _observer ; }
2014-07-22 20:44:32 +04:00
IO ( ) {
2014-10-01 18:04:21 +04:00
clear ( ) ;
2014-07-04 20:24:56 +04:00
}
2014-07-22 20:44:32 +04:00
/* a stealing copy constructor */
2014-10-01 18:04:21 +04:00
IO ( const IO & io ) : sinks ( io . sinks ) , sources ( io . sources ) , _observer ( io . _observer )
2014-07-22 20:44:32 +04:00
{
2014-07-04 20:24:56 +04:00
io . sinks . clear ( ) ;
2014-07-22 20:44:32 +04:00
io . sources . clear ( ) ;
2014-07-04 20:24:56 +04:00
}
IO & operator = ( const IO & io ) {
2014-07-22 20:44:32 +04:00
this - > ~ IO ( ) ;
return * new ( this ) IO ( io ) ;
2014-07-04 20:24:56 +04:00
}
2015-03-12 02:07:45 +03:00
void clear ( int to_push = 1 ) {
2014-07-04 20:24:56 +04:00
for ( Sinks : : iterator i = sinks . begin ( ) ; i ! = sinks . end ( ) ; + + i )
delete * i ;
sinks . clear ( ) ;
2015-03-12 02:07:45 +03:00
if ( to_push )
2015-03-11 17:41:53 +03:00
sinks . push_back ( _observer = new Observer ) ;
2014-07-04 20:24:56 +04:00
}
2015-03-11 17:41:53 +03:00
~ IO ( ) { close ( ) ; clear ( 0 ) ; }
2014-07-04 20:24:56 +04:00
} ;
namespace {
pid_t kill_pid = 0 ;
bool fatal_signal = false ;
bool interrupt = false ;
}
struct Options {
2014-07-22 20:44:32 +04:00
bool verbose , batch , interactive , cont , fatal_timeouts , kmsg ;
2014-07-04 20:24:56 +04:00
std : : string testdir , outdir , workdir , heartbeat ;
2014-07-22 20:44:32 +04:00
std : : vector < std : : string > flavours , filter , watch ;
2014-07-04 20:24:56 +04:00
std : : string flavour_envvar ;
2014-09-14 13:15:51 +04:00
int timeout ;
2014-07-04 20:24:56 +04:00
Options ( ) : verbose ( false ) , batch ( false ) , interactive ( false ) ,
2015-03-11 12:43:28 +03:00
cont ( false ) , fatal_timeouts ( false ) , kmsg ( true ) ,
2015-03-11 12:46:51 +03:00
timeout ( 180 ) { }
2014-07-04 20:24:56 +04:00
} ;
struct TestProcess
{
std : : string filename ;
bool interactive ;
int fd ;
2015-03-11 17:41:53 +03:00
void exec ( ) __attribute__ ( ( noreturn ) ) {
2014-07-04 20:24:56 +04:00
assert ( fd > = 0 ) ;
if ( ! interactive ) {
2014-07-22 20:44:32 +04:00
int devnull = : : open ( " /dev/null " , O_RDONLY ) ;
if ( devnull > = 0 ) { /* gcc really doesn't like to not have stdin */
dup2 ( devnull , STDIN_FILENO ) ;
close ( devnull ) ;
} else
close ( STDIN_FILENO ) ;
2014-07-04 20:24:56 +04:00
dup2 ( fd , STDOUT_FILENO ) ;
dup2 ( fd , STDERR_FILENO ) ;
close ( fd ) ;
}
setpgid ( 0 , 0 ) ;
execlp ( " bash " , " bash " , " -noprofile " , " -norc " , filename . c_str ( ) , NULL ) ;
perror ( " execlp " ) ;
_exit ( 202 ) ;
}
TestProcess ( std : : string file )
: filename ( file ) , interactive ( false ) , fd ( - 1 )
{ }
} ;
struct TestCase {
TestProcess child ;
std : : string name , flavour ;
IO io ;
BufSink * iobuf ;
struct rusage usage ;
int status ;
bool timeout ;
pid_t pid ;
time_t start , end , silent_start , last_update , last_heartbeat ;
Options options ;
Journal * journal ;
std : : string pretty ( ) {
if ( options . batch )
return flavour + " : " + name ;
return " [ " + flavour + " ] " + name ;
}
std : : string id ( ) {
return flavour + " : " + name ;
}
void pipe ( ) {
int fds [ 2 ] ;
if ( socketpair ( PF_UNIX , SOCK_STREAM , 0 , fds ) ) {
perror ( " socketpair " ) ;
exit ( 201 ) ;
}
2015-03-11 23:13:41 +03:00
#if 0
2014-07-04 20:24:56 +04:00
if ( fcntl ( fds [ 0 ] , F_SETFL , O_NONBLOCK ) = = - 1 ) {
perror ( " fcntl on socket " ) ;
exit ( 202 ) ;
}
2015-03-11 23:13:41 +03:00
# endif
2014-07-04 20:24:56 +04:00
2014-07-22 20:44:32 +04:00
io . sources . push_back ( new Source ( fds [ 0 ] ) ) ;
2014-07-04 20:24:56 +04:00
child . fd = fds [ 1 ] ;
child . interactive = options . interactive ;
}
bool monitor ( ) {
end = time ( 0 ) ;
/* heartbeat */
if ( end - last_heartbeat > = 20 & & ! options . heartbeat . empty ( ) ) {
std : : ofstream hb ( options . heartbeat . c_str ( ) , std : : fstream : : app ) ;
hb < < " . " ;
hb . close ( ) ;
fsync_name ( options . heartbeat ) ;
last_heartbeat = end ;
}
if ( wait4 ( pid , & status , WNOHANG , & usage ) ! = 0 ) {
2014-10-01 13:27:44 +04:00
io . sync ( true ) ;
2014-07-04 20:24:56 +04:00
return false ;
}
2015-03-11 23:13:41 +03:00
/* kill off tests after a timeout silence */
2014-07-04 20:24:56 +04:00
if ( ! options . interactive )
2014-09-14 13:15:51 +04:00
if ( end - silent_start > options . timeout ) {
2014-07-04 20:24:56 +04:00
kill ( pid , SIGINT ) ;
sleep ( 5 ) ; /* wait a bit for a reaction */
if ( waitpid ( pid , & status , WNOHANG ) = = 0 ) {
2014-07-22 20:44:32 +04:00
system ( " echo t > /proc/sysrq-trigger 2> /dev/null " ) ;
2014-07-04 20:24:56 +04:00
kill ( - pid , SIGKILL ) ;
waitpid ( pid , & status , 0 ) ;
}
timeout = true ;
2014-10-01 13:27:44 +04:00
io . sync ( true ) ;
2014-07-04 20:24:56 +04:00
return false ;
}
struct timeval wait ;
fd_set set ;
FD_ZERO ( & set ) ;
2015-02-10 16:51:45 +03:00
int nfds = io . fd_set_ ( & set ) ;
2014-07-04 20:24:56 +04:00
wait . tv_sec = 0 ;
wait . tv_usec = 500000 ; /* timeout 0.5s */
if ( ! options . verbose & & ! options . interactive & & ! options . batch ) {
if ( end - last_update > = 1 ) {
progress ( Update ) < < tag ( " running " ) < < pretty ( ) < < " "
< < end - start < < std : : flush ;
last_update = end ;
}
}
2015-03-11 23:13:41 +03:00
if ( select ( nfds , & set , NULL , NULL , & wait ) > 0 ) {
2014-07-04 20:24:56 +04:00
silent_start = end ; /* something happened */
2015-03-11 23:13:41 +03:00
io . sync ( false ) ;
}
2014-07-04 20:24:56 +04:00
return true ;
}
std : : string timefmt ( time_t t ) {
std : : stringstream ss ;
ss < < t / 60 < < " : " < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < t % 60 ;
return ss . str ( ) ;
}
std : : string rusage ( )
{
std : : stringstream ss ;
time_t wall = end - start , user = usage . ru_utime . tv_sec ,
system = usage . ru_stime . tv_sec ;
size_t rss = usage . ru_maxrss / 1024 ,
inb = usage . ru_inblock / 100 ,
outb = usage . ru_oublock / 100 ;
size_t inb_10 = inb % 10 , outb_10 = outb % 10 ;
inb / = 10 ; outb / = 10 ;
ss < < timefmt ( wall ) < < " wall " < < timefmt ( user ) < < " user "
< < timefmt ( system ) < < " sys " < < std : : setw ( 3 ) < < rss < < " M RSS | "
< < " IOPS: " < < std : : setw ( 5 ) < < inb < < " . " < < inb_10 < < " K in "
< < std : : setw ( 5 ) < < outb < < " . " < < outb_10 < < " K out " ;
return ss . str ( ) ;
}
std : : string tag ( std : : string n ) {
if ( options . batch )
return " ## " ;
2015-03-11 13:00:55 +03:00
size_t pad = n . length ( ) ;
pad = ( pad < 12 ) ? 12 - pad : 0 ;
2014-07-04 20:24:56 +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 ( options . batch & & p = = First )
return std : : cout ;
if ( isatty ( STDOUT_FILENO ) & & ! options . batch ) {
if ( p ! = First )
return std : : cout < < " \r " ;
return std : : cout ;
}
if ( p = = Last )
return std : : cout ;
return null ;
}
void parent ( )
{
: : close ( child . fd ) ;
setupIO ( ) ;
journal - > started ( id ( ) ) ;
silent_start = start = time ( 0 ) ;
progress ( First ) < < tag ( " running " ) < < pretty ( ) < < std : : flush ;
if ( options . verbose | | options . interactive )
progress ( ) < < std : : endl ;
2015-03-11 17:41:53 +03:00
while ( monitor ( ) )
/* empty */ ;
2014-07-04 20:24:56 +04:00
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 ;
} else if ( interrupt & & WIFSIGNALED ( status ) & & WTERMSIG ( status ) = = SIGINT )
r = Journal : : INTERRUPTED ;
else
r = Journal : : FAILED ;
2014-10-01 13:28:15 +04:00
if ( r = = Journal : : PASSED & & io . observer ( ) . warnings )
r = Journal : : WARNED ;
2014-07-04 20:24:56 +04:00
io . close ( ) ;
if ( iobuf & & ( r = = Journal : : FAILED | | r = = Journal : : TIMEOUT ) )
iobuf - > dump ( std : : cout ) ;
journal - > done ( id ( ) , r ) ;
if ( options . batch ) {
int spaces = std : : max ( 64 - int ( pretty ( ) . length ( ) ) , 0 ) ;
2015-04-15 10:49:19 +03:00
progress ( Last ) < < " " < < std : : string ( spaces , ' . ' ) < < " "
< < std : : left < < std : : setw ( 9 ) < < std : : setfill ( ' ' ) < < r ;
2015-04-14 12:15:42 +03:00
if ( r ! = Journal : : SKIPPED )
2015-03-11 23:13:41 +03:00
progress ( First ) < < " " < < rusage ( ) ;
progress ( Last ) < < std : : endl ;
2014-07-04 20:24:56 +04:00
} else
progress ( Last ) < < tag ( r ) < < pretty ( ) < < std : : endl ;
2014-07-22 20:44:32 +04:00
2014-07-04 20:24:56 +04:00
io . clear ( ) ;
}
void run ( ) {
pipe ( ) ;
pid = kill_pid = fork ( ) ;
if ( pid < 0 ) {
perror ( " Fork failed. " ) ;
exit ( 201 ) ;
} else if ( pid = = 0 ) {
io . close ( ) ;
chdir ( options . workdir . c_str ( ) ) ;
if ( ! options . flavour_envvar . empty ( ) )
setenv ( options . flavour_envvar . c_str ( ) , flavour . c_str ( ) , 1 ) ;
child . exec ( ) ;
} else {
parent ( ) ;
}
}
void setupIO ( ) {
iobuf = 0 ;
if ( options . verbose | | options . interactive )
io . sinks . push_back ( new FdSink ( 1 ) ) ;
else if ( ! options . batch )
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-07-22 20:44:32 +04:00
for ( std : : vector < std : : string > : : iterator i = options . watch . begin ( ) ;
i ! = options . watch . end ( ) ; + + i )
io . sources . push_back ( new FileSource ( * i ) ) ;
if ( options . kmsg )
io . sources . push_back ( new KMsg ) ;
2014-07-04 20:24:56 +04:00
}
2015-03-11 13:00:55 +03:00
TestCase ( Journal & j , Options opt , std : : string path , std : : string _name , std : : string _flavour )
: child ( path ) , name ( _name ) , flavour ( _flavour ) , timeout ( false ) ,
2014-07-04 20:24:56 +04:00
last_update ( 0 ) , last_heartbeat ( 0 ) , options ( opt ) , journal ( & j )
{
}
} ;
struct Main {
bool die ;
time_t start ;
typedef std : : vector < TestCase > Cases ;
typedef std : : vector < std : : string > Flavours ;
Journal journal ;
Options options ;
Cases cases ;
void setup ( ) {
Listing l = listdir ( options . testdir , true ) ;
std : : sort ( l . begin ( ) , l . end ( ) ) ;
for ( Flavours : : iterator flav = options . flavours . begin ( ) ;
flav ! = options . flavours . end ( ) ; + + flav ) {
for ( Listing : : iterator i = l . begin ( ) ; i ! = l . end ( ) ; + + i ) {
2015-02-12 12:39:37 +03:00
if ( ( i - > length ( ) < 3 ) | | ( i - > substr ( i - > length ( ) - 3 , i - > length ( ) ) ! = " .sh " ) )
2014-07-04 20:24:56 +04:00
continue ;
if ( i - > substr ( 0 , 4 ) = = " lib/ " )
continue ;
bool filter = ! options . filter . empty ( ) ;
for ( std : : vector < std : : string > : : iterator filt = options . filter . begin ( ) ;
filt ! = options . filter . end ( ) ; + + filt ) {
if ( i - > find ( * filt ) ! = std : : string : : npos )
filter = false ;
}
if ( filter )
continue ;
cases . push_back ( TestCase ( journal , options , options . testdir + * i , * i , * flav ) ) ;
cases . back ( ) . options = options ;
}
}
if ( options . cont )
journal . read ( ) ;
else
: : unlink ( journal . location . c_str ( ) ) ;
}
int run ( ) {
setup ( ) ;
start = time ( 0 ) ;
std : : cerr < < " running " < < cases . size ( ) < < " tests " < < std : : endl ;
for ( Cases : : iterator i = cases . begin ( ) ; i ! = cases . end ( ) ; + + i ) {
if ( options . cont & & journal . done ( i - > id ( ) ) )
continue ;
i - > run ( ) ;
if ( options . fatal_timeouts & & journal . timeouts > = 2 ) {
journal . started ( i - > id ( ) ) ; // retry the test on --continue
std : : cerr < < " E: Hit 2 timeouts in a row with --fatal-timeouts " < < std : : endl ;
std : : cerr < < " Suspending (please restart the VM). " < < std : : endl ;
sleep ( 3600 ) ;
die = 1 ;
}
if ( time ( 0 ) - start > 3 * 3600 ) {
std : : cerr < < " 3 hours passed, giving up... " < < std : : endl ;
die = 1 ;
}
if ( die | | fatal_signal )
break ;
}
journal . banner ( ) ;
if ( die | | fatal_signal )
return 1 ;
2014-09-14 13:15:51 +04:00
return journal . count ( Journal : : FAILED ) ? 1 : 0 ;
2014-07-04 20:24:56 +04:00
}
Main ( Options o ) : die ( false ) , journal ( o . outdir ) , options ( o ) { }
} ;
namespace {
void handler ( int sig ) {
signal ( sig , SIG_DFL ) ; /* die right away next time */
if ( kill_pid > 0 )
kill ( - kill_pid , sig ) ;
fatal_signal = true ;
if ( sig = = SIGINT )
interrupt = true ;
}
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 ) ;
}
}
}
/* TODO remove in favour of brick-commandline.h */
struct Args {
typedef std : : vector < std : : string > V ;
V args ;
Args ( int argc , const 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 ) ;
}
} ;
namespace {
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 ;
}
template < typename C >
void split ( std : : string s , C & c ) {
std : : stringstream ss ( s ) ;
std : : string item ;
while ( std : : getline ( ss , item , ' , ' ) )
c . push_back ( item ) ;
}
}
2015-03-11 13:00:55 +03:00
static int run ( int argc , const char * * argv , std : : string fl_envvar = " TEST_FLAVOUR " )
2014-07-04 20:24:56 +04:00
{
Args args ( argc , argv ) ;
Options opt ;
opt . flavour_envvar = fl_envvar ;
if ( args . has ( " --continue " ) )
opt . cont = true ;
if ( args . has ( " --only " ) )
split ( args . opt ( " --only " ) , opt . filter ) ;
else if ( hasenv ( " T " ) )
split ( getenv ( " T " ) , opt . filter ) ;
if ( args . has ( " --fatal-timeouts " ) )
opt . fatal_timeouts = true ;
if ( args . has ( " --heartbeat " ) )
opt . heartbeat = args . opt ( " --heartbeat " ) ;
if ( args . has ( " --batch " ) | | hasenv ( " BATCH " ) ) {
opt . verbose = false ;
opt . batch = true ;
}
if ( args . has ( " --verbose " ) | | hasenv ( " VERBOSE " ) ) {
opt . batch = false ;
opt . verbose = true ;
}
if ( args . has ( " --interactive " ) | | hasenv ( " INTERACTIVE " ) ) {
opt . verbose = false ;
opt . batch = false ;
opt . interactive = true ;
}
if ( args . has ( " --flavours " ) )
split ( args . opt ( " --flavours " ) , opt . flavours ) ;
else
opt . flavours . push_back ( " vanilla " ) ;
2014-07-22 20:44:32 +04:00
if ( args . has ( " --watch " ) )
split ( args . opt ( " --watch " ) , opt . watch ) ;
2014-09-14 13:15:51 +04:00
if ( args . has ( " --timeout " ) )
opt . timeout = atoi ( args . opt ( " --timeout " ) . c_str ( ) ) ;
2015-03-11 12:43:28 +03:00
if ( args . has ( " --nokmsg " ) )
opt . kmsg = false ;
2014-07-22 20:44:32 +04:00
2014-07-04 20:24:56 +04:00
opt . outdir = args . opt ( " --outdir " ) ;
opt . testdir = args . opt ( " --testdir " ) ;
opt . workdir = args . opt ( " --workdir " ) ;
if ( opt . testdir . empty ( ) )
opt . testdir = " /usr/share/lvm2-testsuite " ;
if ( opt . workdir . empty ( ) )
opt . workdir = opt . testdir ;
opt . testdir + = " / " ;
setup_handlers ( ) ;
Main main ( opt ) ;
return main . run ( ) ;
}
}
}
# endif
# ifdef BRICK_DEMO
int main ( int argc , const char * * argv ) {
2014-09-14 13:15:51 +04:00
return brick : : shelltest : : run ( argc , argv ) ;
2014-07-04 20:24:56 +04:00
}
# endif
// vim: syntax=cpp tabstop=4 shiftwidth=4 expandtab