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
*/
/*
2015-10-26 13:04:58 +03:00
* ( c ) 2014 Petr Rockai < me @ mornfall . net >
2014-07-04 20:24:56 +04:00
* ( 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 . */
2023-10-02 14:59:50 +03:00
# include "configure.h"
2014-07-04 20:24:56 +04:00
# 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>
2023-10-02 14:59:50 +03:00
# include <iostream>
2014-07-04 20:24:56 +04:00
# include <cassert>
# include <iterator>
# include <algorithm>
# include <stdexcept>
2023-10-02 14:59:50 +03:00
# include <ctime>
2014-07-04 20:24:56 +04:00
# 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>
2016-09-20 18:09:46 +03:00
# include <sys/utsname.h>
2014-07-04 20:24:56 +04:00
# include <time.h>
# include <unistd.h>
# endif
2023-10-02 14:59:50 +03:00
static const long TEST_SUITE_TIMEOUT = 4 ; // Timeout for the whole test suite in hours (4 hours)
static const long TEST_TIMEOUT = 10 * 60 ; // Timeout for a single test in seconds (10 minutes)
2015-10-26 13:04:58 +03:00
2014-07-04 20:24:56 +04:00
# ifndef BRICK_SHELLTEST_H
# define BRICK_SHELLTEST_H
namespace brick {
namespace shelltest {
/* TODO: remove this section in favour of brick-filesystem.h */
2023-10-02 14:59:50 +03:00
std : : runtime_error syserr ( const std : : string & msg , std : : string ctx = " " ) {
2014-07-04 20:24:56 +04:00
return std : : runtime_error ( std : : string ( strerror ( errno ) ) + " " + msg + " " + ctx ) ;
}
2023-10-02 14:59:50 +03:00
class Timespec {
struct timespec ts ;
static const long _NSEC_PER_SEC = 1000000000 ;
public :
2023-10-03 13:59:38 +03:00
Timespec ( time_t _sec = 0 , long _nsec = 0 ) { ts = ( struct timespec ) { _sec , _nsec } ; }
Timespec ( const struct timeval & tv ) { ts = ( struct timespec ) { tv . tv_sec , tv . tv_usec * 1000 } ; }
2023-10-02 14:59:50 +03:00
long sec ( ) const { return ( long ) ts . tv_sec ; }
long nsec ( ) const { return ( long ) ts . tv_nsec ; }
void gettime ( ) {
# ifdef HAVE_REALTIME
if ( ! clock_gettime ( CLOCK_MONOTONIC , & ts ) )
return ;
# endif
ts . tv_sec = time ( 0 ) ;
ts . tv_nsec = 0 ;
}
bool is_zero ( ) const { return ! ts . tv_sec & & ! ts . tv_nsec ; }
Timespec elapsed ( ) const {
Timespec now ;
now . gettime ( ) ;
now = now - * this ;
return now ;
}
Timespec operator + ( const Timespec & t ) const {
Timespec r ( ts . tv_sec + t . ts . tv_sec , ts . tv_nsec + t . ts . tv_nsec ) ;
if ( r . ts . tv_nsec > = _NSEC_PER_SEC ) {
r . ts . tv_nsec - = _NSEC_PER_SEC ;
+ + r . ts . tv_sec ;
}
return r ;
}
Timespec operator - ( const Timespec & t ) const {
Timespec r ( ts . tv_sec - t . ts . tv_sec , ts . tv_nsec - t . ts . tv_nsec ) ;
if ( r . ts . tv_nsec < 0 ) {
r . ts . tv_nsec + = _NSEC_PER_SEC ;
r . ts . tv_sec - - ;
}
return r ;
}
friend bool operator = = ( const Timespec & a , const Timespec & b ) {
return ( a . ts . tv_sec = = b . ts . tv_sec ) & & ( a . ts . tv_nsec = = b . ts . tv_nsec ) ;
}
friend bool operator > = ( const Timespec & a , const Timespec & b ) {
return ( a . ts . tv_sec > b . ts . tv_sec ) | |
( ( a . ts . tv_sec = = b . ts . tv_sec ) & & ( a . ts . tv_nsec > = b . ts . tv_nsec ) ) ;
}
friend std : : ostream & operator < < ( std : : ostream & os , const Timespec & t ) {
os < < std : : right < < std : : setw ( 2 ) < < std : : setfill ( ' ' ) < < t . ts . tv_sec / 60 < < " : "
< < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' ) < < t . ts . tv_sec % 60 < < " . "
< < std : : setw ( 3 ) < < t . ts . tv_nsec / 1000000 ; // use miliseconds ATM
return os ;
}
} ;
2023-10-03 13:41:30 +03:00
static void _fsync_name ( std : : string n )
2023-10-03 02:36:20 +03:00
{
2023-10-03 13:41:30 +03:00
int fd = open ( n . c_str ( ) , O_WRONLY ) ;
if ( fd > = 0 ) {
( void ) fsync ( fd ) ;
( void ) close ( fd ) ;
}
2023-10-03 02:36:20 +03:00
}
2014-07-04 20:24:56 +04:00
struct dir {
DIR * d ;
2023-10-02 14:59:50 +03:00
dir ( const std : : string & p ) {
2014-07-04 20:24:56 +04:00
d = opendir ( p . c_str ( ) ) ;
if ( ! d )
throw syserr ( " error opening directory " , p ) ;
}
2021-09-20 02:45:55 +03:00
~ dir ( ) { ( void ) closedir ( d ) ; }
2014-07-04 20:24:56 +04:00
} ;
typedef std : : vector < std : : string > Listing ;
2023-10-02 14:59:50 +03:00
Listing listdir ( std : : string p , bool recurse = false , std : : string prefix = " " )
2014-07-04 20:24:56 +04:00
{
Listing r ;
2023-10-02 23:42:09 +03:00
if ( ( p . size ( ) > 0 ) & & ( p [ p . size ( ) - 1 ] = = ' / ' ) )
p . resize ( p . size ( ) - 1 ) ; // works with older g++
2014-07-04 20:24:56 +04:00
dir d ( p ) ;
2016-04-21 16:30:14 +03:00
# if !defined(__GLIBC__) || (__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 23))
/* readdir_r is deprecated with newer GLIBC */
2014-07-04 20:24:56 +04:00
struct dirent entry , * iter = 0 ;
2016-04-21 16:30:14 +03:00
while ( ( errno = readdir_r ( d . d , & entry , & iter ) ) = = 0 & & iter ) {
2014-07-04 20:24:56 +04:00
std : : string ename ( entry . d_name ) ;
2016-04-21 16:30:14 +03:00
# else
struct dirent * entry ;
errno = 0 ;
while ( ( entry = readdir ( d . d ) ) ) {
std : : string ename ( entry - > d_name ) ;
# endif
2014-07-04 20:24:56 +04:00
if ( ename = = " . " | | ename = = " .. " )
continue ;
if ( recurse ) {
struct stat64 stat ;
std : : string s = p + " / " + ename ;
2016-04-21 16:30:14 +03:00
if ( : : stat64 ( s . c_str ( ) , & stat ) = = - 1 ) {
errno = 0 ;
2014-07-04 20:24:56 +04:00
continue ;
2016-04-21 16:30:14 +03:00
}
2014-07-04 20:24:56 +04:00
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 ) ;
} ;
2016-04-21 16:30:14 +03:00
if ( errno ! = 0 )
2014-07-04 20:24:56 +04:00
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 ) {
2023-10-02 14:59:50 +03:00
const char * t ;
2014-07-04 20:24:56 +04:00
switch ( r ) {
2023-10-02 14:59:50 +03:00
case STARTED : t = " started " ; break ;
case RETRIED : t = " retried " ; break ;
case FAILED : t = " failed " ; break ;
case INTERRUPTED : t = " interrupted " ; break ;
case PASSED : t = " passed " ; break ;
case SKIPPED : t = " skipped " ; break ;
case TIMEOUT : t = " timeout " ; break ;
case WARNED : t = " warnings " ; break ;
default : t = " unknown " ;
2014-07-04 20:24:56 +04:00
}
2023-10-02 14:59:50 +03:00
return o < < t ;
2014-07-04 20:24:56 +04:00
}
friend std : : istream & operator > > ( std : : istream & i , R & r ) {
std : : string x ;
i > > x ;
if ( x = = " started " ) r = STARTED ;
2023-10-02 14:59:50 +03:00
else if ( x = = " retried " ) r = RETRIED ;
else if ( x = = " failed " ) r = FAILED ;
else if ( x = = " interrupted " ) r = INTERRUPTED ;
else if ( x = = " passed " ) r = PASSED ;
else if ( x = = " skipped " ) r = SKIPPED ;
else if ( x = = " timeout " ) r = TIMEOUT ;
else if ( x = = " warnings " ) r = WARNED ;
else r = UNKNOWN ;
2014-07-04 20:24:56 +04:00
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 ;
2023-10-02 14:59:50 +03:00
typedef std : : map < std : : string , std : : string > RUsage ;
RUsage rusage ;
2014-07-04 20:24:56 +04:00
std : : string location , list ;
int timeouts ;
2023-10-02 14:59:50 +03:00
size_t size_max ;
2014-07-04 20:24:56 +04:00
2023-10-02 14:59:50 +03:00
void append ( const std : : string & path ) {
2014-07-04 20:24:56 +04:00
std : : ofstream of ( path . c_str ( ) , std : : fstream : : app ) ;
Status : : iterator writ ;
2023-10-02 14:59:50 +03:00
for ( Status : : const_iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i ) {
2014-07-04 20:24:56 +04:00
writ = written . find ( i - > first ) ;
2023-10-02 23:42:09 +03:00
if ( writ = = written . end ( ) | | writ - > second ! = i - > second ) {
RUsage : : const_iterator ru = rusage . find ( i - > first ) ;
of < < size_max < < std : : left < < std : : setw ( ( int ) size_max ) < < std : : setfill ( ' ' )
< < i - > first < < " " < < std : : setw ( 12 ) < < i - > second ;
if ( ru ! = rusage . end ( ) )
of < < ru - > second ;
else {
struct tm time_info ;
char buf [ 64 ] ;
time_t t = time ( 0 ) ;
if ( localtime_r ( & t , & time_info ) ) {
strftime ( buf , sizeof ( buf ) , " %F %T " , & time_info ) ;
of < < " --- " < < buf < < " --- " ;
}
}
of < < std : : endl ;
}
2014-07-04 20:24:56 +04:00
}
written = status ;
2023-10-02 14:59:50 +03:00
of . flush ( ) ;
2014-07-04 20:24:56 +04:00
of . close ( ) ;
}
2023-10-02 14:59:50 +03:00
void write ( const std : : string & path ) {
2014-07-04 20:24:56 +04:00
std : : ofstream of ( path . c_str ( ) ) ;
2023-10-02 23:42:09 +03:00
for ( Status : : const_iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
of < < i - > first < < " " < < i - > second < < std : : endl ;
2023-10-03 13:41:30 +03:00
of . flush ( ) ;
2023-10-03 02:36:20 +03:00
of . close ( ) ;
2014-07-04 20:24:56 +04:00
}
void sync ( ) {
append ( location ) ;
2023-10-03 13:41:30 +03:00
_fsync_name ( location ) ;
2014-07-04 20:24:56 +04:00
write ( list ) ;
2023-10-03 13:41:30 +03:00
_fsync_name ( list ) ;
2014-07-04 20:24:56 +04:00
}
2023-10-02 14:59:50 +03:00
void started ( const std : : string & n ) {
2014-07-04 20:24:56 +04:00
if ( status . count ( n ) & & status [ n ] = = STARTED )
status [ n ] = RETRIED ;
else
status [ n ] = STARTED ;
sync ( ) ;
}
2023-10-02 14:59:50 +03:00
void done ( const std : : string & n , R r , const std : : string & ru ) {
2014-07-04 20:24:56 +04:00
status [ n ] = r ;
2023-10-02 14:59:50 +03:00
rusage [ n ] = ru ;
2014-07-04 20:24:56 +04:00
if ( r = = TIMEOUT )
+ + timeouts ;
else
timeouts = 0 ;
sync ( ) ;
}
2023-10-02 14:59:50 +03:00
bool done ( const std : : string & n ) {
2014-07-04 20:24:56 +04:00
if ( ! status . count ( n ) )
return false ;
return status [ n ] ! = STARTED & & status [ n ] ! = INTERRUPTED ;
}
2023-10-02 14:59:50 +03:00
unsigned count ( R r ) const {
unsigned c = 0 ;
for ( Status : : const_iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
2014-07-04 20:24:56 +04:00
if ( i - > second = = r )
+ + c ;
return c ;
}
2023-10-02 14:59:50 +03:00
void banner ( const Timespec & start ) const {
2014-07-04 20:24:56 +04:00
std : : cout < < std : : endl < < " ### " < < status . size ( ) < < " tests: "
2014-09-14 13:15:51 +04:00
< < count ( PASSED ) < < " passed, "
< < count ( SKIPPED ) < < " skipped, "
2015-10-01 14:17:00 +03:00
< < count ( TIMEOUT ) < < " timed out, " < < count ( WARNED ) < < " warned, "
2023-10-02 14:59:50 +03:00
< < count ( FAILED ) < < " failed in "
< < start . elapsed ( ) < < std : : endl ;
2014-07-04 20:24:56 +04:00
}
2023-10-02 14:59:50 +03:00
void details ( ) const {
for ( Status : : const_iterator i = status . begin ( ) ; i ! = status . end ( ) ; + + i )
2014-07-04 20:24:56 +04:00
if ( i - > second ! = PASSED )
std : : cout < < i - > second < < " : " < < i - > first < < std : : endl ;
}
2023-10-02 14:59:50 +03:00
void read ( const std : : string & n ) {
2014-07-04 20:24:56 +04:00
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 ) ; }
2023-10-02 23:42:09 +03:00
void check_name_size ( const std : : string & n ) { if ( n . size ( ) > size_max ) size_max = n . size ( ) ; }
2023-10-02 14:59:50 +03:00
Journal ( const std : : string & dir ) ;
~ Journal ( ) ;
2014-07-04 20:24:56 +04:00
} ;
2023-10-02 14:59:50 +03:00
Journal : : Journal ( const std : : string & dir ) :
location ( dir + " /journal " ) ,
list ( dir + " /list " ) ,
timeouts ( 0 ) , size_max ( 0 )
{
}
Journal : : ~ Journal ( )
{
}
2014-07-06 18:22:23 +04:00
struct TimedBuffer {
2023-10-02 14:59:50 +03:00
typedef std : : pair < Timespec , std : : string > Line ;
2014-07-06 18:22:23 +04:00
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 ;
}
2023-10-02 14:59:50 +03:00
void push ( const std : : string & buf ) {
Timespec now ;
if ( stamp )
now . gettime ( ) ;
std : : string : : const_iterator b = buf . begin ( ) , e = buf . begin ( ) ;
2014-07-06 18:22:23 +04:00
while ( e ! = buf . end ( ) )
{
e = std : : find ( b , buf . end ( ) , ' \n ' ) ;
incomplete . second + = std : : string ( b , e ) ;
2023-10-02 14:59:50 +03:00
if ( incomplete . first . is_zero ( ) )
2014-07-06 18:22:23 +04:00
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 ;
2023-10-02 14:59:50 +03:00
now . gettime ( ) ;
2015-04-15 14:11:26 +03:00
}
}
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 ) { }
2023-10-02 14:59:50 +03:00
virtual void push ( const 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 ) {
return line ;
}
} ;
struct Format {
2023-10-02 14:59:50 +03:00
Timespec start ;
2014-07-06 18:22:23 +04:00
Substitute subst ;
std : : string format ( TimedBuffer : : Line l ) {
std : : stringstream result ;
2015-04-15 14:11:26 +03:00
if ( l . first > = start ) {
2023-10-02 14:59:50 +03:00
Timespec rel = l . first - start ;
result < < " [ " < < rel < < " ] " ;
2014-07-06 18:22:23 +04:00
}
result < < subst . map ( l . second ) ;
return result . str ( ) ;
}
2023-10-02 14:59:50 +03:00
Format ( ) { start . gettime ( ) ; }
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 ;
2023-10-02 14:59:50 +03:00
virtual void push ( const 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 ) ;
2021-09-20 11:31:45 +03:00
if ( write ( fd , out . c_str ( ) , out . length ( ) ) < ( int ) out . length ( ) )
perror ( " short write " ) ;
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
}
2023-10-02 14:59:50 +03:00
virtual void push ( const std : : string & x ) {
2014-07-04 20:24:56 +04:00
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 ) { }
2023-10-02 14:59:50 +03:00
~ FdSink ( ) ;
2014-07-04 20:24:56 +04:00
} ;
2023-10-02 14:59:50 +03:00
FdSink : : ~ FdSink ( )
{ // no inline
}
2014-07-04 20:24:56 +04:00
struct FileSink : FdSink {
std : : string file ;
2023-10-02 14:59:50 +03:00
FileSink ( const std : : string & n ) : FdSink ( - 1 ) , file ( n ) { }
2014-07-04 20:24:56 +04:00
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 ) {
2021-09-20 02:45:55 +03:00
( void ) fsync ( fd ) ;
( void ) close ( fd ) ;
2014-07-04 20:24:56 +04:00
}
}
} ;
2016-09-20 18:09:46 +03:00
# define BRICK_SYSLOG_ACTION_READ 2
# define BRICK_SYSLOG_ACTION_READ_ALL 3
2014-07-04 20:24:56 +04:00
# define BRICK_SYSLOG_ACTION_READ_CLEAR 4
# define BRICK_SYSLOG_ACTION_CLEAR 5
2016-09-20 18:09:46 +03:00
# define BRICK_SYSLOG_ACTION_SIZE_UNREAD 9
# define BRICK_SYSLOG_ACTION_SIZE_BUFFER 10
2014-07-04 20:24:56 +04:00
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 ;
2021-09-19 21:21:55 +03:00
/* coverity[stack_use_local_overflow] */
2014-07-22 20:44:32 +04:00
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 )
2021-09-20 02:45:55 +03:00
( void ) : : close ( fd ) ;
2014-07-22 20:44:32 +04:00
}
} ;
struct FileSource : Source {
std : : string file ;
2023-10-02 14:59:50 +03:00
FileSource ( const std : : string & n ) : Source ( - 1 ) , file ( n ) { }
2014-07-22 20:44:32 +04:00
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 )
2021-09-20 11:26:26 +03:00
try {
Source : : sync ( s ) ;
} catch ( . . . ) {
perror ( " failed to sync " ) ;
}
2014-07-22 20:44:32 +04:00
}
} ;
struct KMsg : Source {
bool can_clear ;
2016-09-20 18:09:46 +03:00
ssize_t buffer_size ;
2014-07-22 20:44:32 +04:00
2016-09-20 18:09:46 +03:00
KMsg ( ) : can_clear ( strcmp ( getenv ( " LVM_TEST_CAN_CLOBBER_DMESG " ) ? : " 0 " , " 0 " ) ) ,
buffer_size ( 128 * 1024 )
{
2014-07-04 20:24:56 +04:00
# ifdef __unix
2016-09-20 18:09:46 +03:00
struct utsname uts ;
unsigned kmaj , kmin , krel ;
2016-09-20 23:36:29 +03:00
const char * read_msg = " /dev/kmsg " ;
// Can't use kmsg on kernels pre 3.5, read /var/log/messages
if ( ( : : uname ( & uts ) = = 0 ) & &
( : : sscanf ( uts . release , " %u.%u.%u " , & kmaj , & kmin , & krel ) = = 3 ) & &
2016-09-20 23:50:43 +03:00
( ( kmaj < 3 ) | | ( ( kmaj = = 3 ) & & ( kmin < 5 ) ) ) )
2016-09-20 23:36:29 +03:00
can_clear = false , read_msg = " /var/log/messages " ;
if ( ( fd = open ( read_msg , O_RDONLY | O_NONBLOCK ) ) < 0 ) {
if ( errno ! = ENOENT ) /* Older kernels (<3.5) do not support /dev/kmsg */
fprintf ( stderr , " open log %s %s \n " , read_msg , strerror ( errno ) ) ;
if ( can_clear & & ( klogctl ( BRICK_SYSLOG_ACTION_CLEAR , 0 , 0 ) < 0 ) )
2014-07-22 20:44:32 +04:00
can_clear = false ;
2016-09-20 23:36:29 +03:00
} else if ( lseek ( fd , 0L , SEEK_END ) = = ( off_t ) - 1 ) {
fprintf ( stderr , " lseek log %s %s \n " , read_msg , strerror ( errno ) ) ;
2021-09-20 02:45:55 +03:00
( void ) close ( fd ) ;
2016-09-20 23:36:29 +03:00
fd = - 1 ;
}
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 ;
2016-09-20 18:09:46 +03:00
char buf [ buffer_size ] ;
2014-07-04 20:24:56 +04:00
2016-09-20 23:36:29 +03:00
if ( dev_kmsg ( ) ) {
2016-09-20 18:09:46 +03:00
while ( ( sz = : : read ( fd , buf , buffer_size ) ) > 0 )
2014-07-04 20:24:56 +04:00
s - > push ( std : : string ( buf , sz ) ) ;
2014-07-22 20:44:32 +04:00
} else if ( can_clear ) {
2016-09-20 18:09:46 +03:00
while ( ( sz = klogctl ( BRICK_SYSLOG_ACTION_READ_CLEAR , buf ,
2016-09-20 23:36:29 +03:00
( int ) buffer_size ) ) > 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 ) { }
2023-10-02 14:59:50 +03:00
void push ( const std : : string & s ) {
2014-10-01 13:28:15 +04:00
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
2023-10-02 14:59:50 +03:00
virtual void push ( const std : : string & x ) {
2014-07-04 20:24:56 +04:00
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 ;
2015-11-19 12:48:17 +03:00
std : : vector < std : : string > flavours , filter , skip , watch ;
2014-07-04 20:24:56 +04:00
std : : string flavour_envvar ;
2014-09-14 13:15:51 +04:00
int timeout ;
2023-10-02 14:59:50 +03:00
Options ( ) ;
Options ( const Options & o ) ; // copy
~ Options ( ) ;
2014-07-04 20:24:56 +04:00
} ;
2023-10-02 14:59:50 +03:00
Options : : Options ( ) :
verbose ( false ) , batch ( false ) , interactive ( false ) ,
cont ( false ) , fatal_timeouts ( false ) , kmsg ( true ) ,
timeout ( TEST_TIMEOUT )
{ // no inline
}
Options : : Options ( const Options & o ) :
verbose ( o . verbose ) , batch ( o . batch ) , interactive ( o . interactive ) ,
cont ( o . cont ) , fatal_timeouts ( o . fatal_timeouts ) , kmsg ( o . kmsg ) ,
testdir ( o . testdir ) , outdir ( o . outdir ) , workdir ( o . workdir ) , heartbeat ( o . heartbeat ) ,
flavours ( o . flavours ) , filter ( o . filter ) , skip ( o . skip ) , watch ( o . watch ) ,
flavour_envvar ( o . flavour_envvar ) ,
timeout ( o . timeout )
{ // no inline
}
Options : : ~ Options ( )
{ // no inline
}
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 */
2021-09-20 02:45:55 +03:00
( void ) dup2 ( devnull , STDIN_FILENO ) ;
( void ) close ( devnull ) ;
2014-07-22 20:44:32 +04:00
} else
2021-09-20 02:45:55 +03:00
( void ) close ( STDIN_FILENO ) ;
( void ) dup2 ( fd , STDOUT_FILENO ) ;
( void ) dup2 ( fd , STDERR_FILENO ) ;
( void ) close ( fd ) ;
2014-07-04 20:24:56 +04:00
}
setpgid ( 0 , 0 ) ;
execlp ( " bash " , " bash " , " -noprofile " , " -norc " , filename . c_str ( ) , NULL ) ;
perror ( " execlp " ) ;
_exit ( 202 ) ;
}
2023-10-02 14:59:50 +03:00
TestProcess ( std : : string file ) :
filename ( file ) , interactive ( false ) , fd ( - 1 )
{
}
2014-07-04 20:24:56 +04:00
} ;
struct TestCase {
TestProcess child ;
std : : string name , flavour ;
IO io ;
BufSink * iobuf ;
struct rusage usage ;
int status ;
bool timeout ;
pid_t pid ;
2023-10-02 14:59:50 +03:00
Timespec start , silent_start , last_update , last_heartbeat ;
2014-07-04 20:24:56 +04:00
Options options ;
Journal * journal ;
std : : string pretty ( ) {
if ( options . batch )
return flavour + " : " + name ;
return " [ " + flavour + " ] " + name ;
}
std : : string id ( ) {
return flavour + " : " + name ;
}
void pipe ( ) {
2021-09-20 11:26:05 +03:00
int fds [ 2 ] = { 0 } ;
2014-07-04 20:24:56 +04:00
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 ( ) {
/* heartbeat */
2023-10-02 14:59:50 +03:00
if ( last_heartbeat . elapsed ( ) . sec ( ) > = 20 & & ! options . heartbeat . empty ( ) ) {
2014-07-04 20:24:56 +04:00
std : : ofstream hb ( options . heartbeat . c_str ( ) , std : : fstream : : app ) ;
hb < < " . " ;
2023-10-02 14:59:50 +03:00
hb . flush ( ) ;
2014-07-04 20:24:56 +04:00
hb . close ( ) ;
2023-10-03 13:41:30 +03:00
_fsync_name ( options . heartbeat ) ;
2023-10-02 14:59:50 +03:00
last_heartbeat . gettime ( ) ;
2014-07-04 20:24:56 +04:00
}
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 )
2023-10-02 14:59:50 +03:00
if ( silent_start . elapsed ( ) . sec ( ) > options . timeout ) {
pid_t p ;
for ( int i = 0 ; i < 5 ; + + i ) {
kill ( pid , ( i > 2 ) ? SIGTERM : SIGINT ) ;
if ( ( p = waitpid ( pid , & status , WNOHANG ) ) ! = 0 )
break ;
sleep ( 1 ) ; /* wait a bit for a reaction */
}
if ( ! p ) {
std : : ofstream tr ( " /proc/sysrq-trigger " ) ;
tr < < " t " ;
tr . close ( ) ;
2014-07-04 20:24:56 +04:00
kill ( - pid , SIGKILL ) ;
2021-09-20 02:45:55 +03:00
( void ) waitpid ( pid , & status , 0 ) ;
2014-07-04 20:24:56 +04:00
}
timeout = true ;
2014-10-01 13:27:44 +04:00
io . sync ( true ) ;
2014-07-04 20:24:56 +04:00
return false ;
}
2023-10-03 13:59:38 +03:00
struct timeval wait = ( struct timeval ) { 0 , 500000 /* timeout 0.5s */ } ;
2014-07-04 20:24:56 +04:00
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
if ( ! options . verbose & & ! options . interactive & & ! options . batch ) {
2023-10-02 14:59:50 +03:00
if ( last_update . elapsed ( ) . sec ( ) > = 1 ) {
progress ( Update ) < < tag ( " running " )
< < pretty ( ) < < " " < < start . elapsed ( ) < < std : : flush ;
last_update . gettime ( ) ;
2014-07-04 20:24:56 +04:00
}
}
2015-03-11 23:13:41 +03:00
if ( select ( nfds , & set , NULL , NULL , & wait ) > 0 ) {
io . sync ( false ) ;
2023-10-02 14:59:50 +03:00
silent_start . gettime ( ) ; /* something happened */
2015-03-11 23:13:41 +03:00
}
2014-07-04 20:24:56 +04:00
return true ;
}
std : : string rusage ( )
{
std : : stringstream ss ;
2023-10-02 14:59:50 +03:00
Timespec wall ( start . elapsed ( ) ) , user ( usage . ru_utime ) , system ( usage . ru_stime ) ;
size_t rss = ( usage . ru_maxrss + 512 ) / 1024 ,
inb = ( usage . ru_inblock + 1024 ) / 2048 , // to MiB
outb = ( usage . ru_oublock + 1024 ) / 2048 ; // to MiB
ss < < wall < < " wall " < < user < < " user " < < system < < " sys "
< < std : : setw ( 4 ) < < std : : setfill ( ' ' ) < < rss < < " M RSS | IOPS: "
< < std : : setw ( 5 ) < < inb < < " M in "
< < std : : setw ( 5 ) < < outb < < " M out " ;
2014-07-04 20:24:56 +04:00
return ss . str ( ) ;
}
2023-10-02 14:59:50 +03:00
std : : string tag ( const std : : string & n ) {
2014-07-04 20:24:56 +04:00
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 ( )
{
2021-09-20 02:45:55 +03:00
( void ) : : close ( child . fd ) ;
2014-07-04 20:24:56 +04:00
setupIO ( ) ;
journal - > started ( id ( ) ) ;
2023-10-02 14:59:50 +03:00
start . gettime ( ) ;
silent_start = start ;
2014-07-04 20:24:56 +04:00
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 ) ;
2023-10-02 14:59:50 +03:00
std : : string ru = rusage ( ) ;
journal - > done ( id ( ) , r , ru ) ;
2014-07-04 20:24:56 +04:00
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 )
2023-10-02 14:59:50 +03:00
progress ( First ) < < " " < < ru ;
2015-03-11 23:13:41 +03:00
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 ( ) ;
2023-10-02 14:59:50 +03:00
if ( chdir ( options . workdir . c_str ( ) ) )
perror ( " chdir failed. " ) ;
2014-07-04 20:24:56 +04:00
if ( ! options . flavour_envvar . empty ( ) )
2021-09-20 02:45:55 +03:00
( void ) setenv ( options . flavour_envvar . c_str ( ) , flavour . c_str ( ) , 1 ) ;
2014-07-04 20:24:56 +04:00
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
}
2023-10-02 14:59:50 +03:00
TestCase ( Journal & j , const Options & opt , const std : : string & path , const std : : string & _name , const std : : string & _flavour ) ;
~ TestCase ( ) ;
2014-07-04 20:24:56 +04:00
} ;
2023-10-02 14:59:50 +03:00
TestCase : : TestCase ( Journal & j , const Options & opt , const std : : string & path , const std : : string & _name , const std : : string & _flavour ) :
child ( path ) , name ( _name ) , flavour ( _flavour ) ,
iobuf ( NULL ) , usage ( ( struct rusage ) { { 0 } } ) , status ( 0 ) , timeout ( false ) ,
pid ( 0 ) , options ( opt ) , journal ( & j )
{ // no inline
}
TestCase : : ~ TestCase ( )
{ // no inline
}
2014-07-04 20:24:56 +04:00
struct Main {
bool die ;
2023-10-02 14:59:50 +03:00
Timespec start ;
2014-07-04 20:24:56 +04:00
typedef std : : vector < TestCase > Cases ;
typedef std : : vector < std : : string > Flavours ;
Journal journal ;
Options options ;
Cases cases ;
void setup ( ) {
2015-11-19 12:48:17 +03:00
bool filter ;
2014-07-04 20:24:56 +04:00
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 ;
2015-11-19 12:48:17 +03:00
if ( ! options . filter . empty ( ) ) {
filter = true ;
for ( std : : vector < std : : string > : : iterator filt = options . filter . begin ( ) ;
filt ! = options . filter . end ( ) ; + + filt ) {
if ( i - > find ( * filt ) ! = std : : string : : npos ) {
filter = false ;
break ;
}
}
if ( filter )
continue ;
2014-07-04 20:24:56 +04:00
}
2015-11-19 12:48:17 +03:00
if ( ! options . skip . empty ( ) ) {
filter = false ;
for ( std : : vector < std : : string > : : iterator filt = options . skip . begin ( ) ;
filt ! = options . skip . end ( ) ; + + filt ) {
if ( i - > find ( * filt ) ! = std : : string : : npos ) {
filter = true ;
break ;
}
}
if ( filter )
continue ;
}
2023-10-02 23:42:09 +03:00
journal . check_name_size ( options . testdir + * i ) ;
2014-07-04 20:24:56 +04:00
cases . push_back ( TestCase ( journal , options , options . testdir + * i , * i , * flav ) ) ;
cases . back ( ) . options = options ;
}
}
if ( options . cont )
journal . read ( ) ;
else
2021-09-20 02:45:55 +03:00
( void ) : : unlink ( journal . location . c_str ( ) ) ;
2014-07-04 20:24:56 +04:00
}
int run ( ) {
setup ( ) ;
2023-10-02 14:59:50 +03:00
2014-07-04 20:24:56 +04:00
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 ;
}
2023-10-02 14:59:50 +03:00
if ( start . elapsed ( ) . sec ( ) > ( TEST_SUITE_TIMEOUT * 3600 ) ) {
2015-10-26 13:04:58 +03:00
std : : cerr < < TEST_SUITE_TIMEOUT < < " hours passed, giving up... " < < std : : endl ;
2014-07-04 20:24:56 +04:00
die = 1 ;
}
if ( die | | fatal_signal )
break ;
}
2023-10-02 14:59:50 +03:00
journal . banner ( start ) ;
2014-07-04 20:24:56 +04:00
if ( die | | fatal_signal )
return 1 ;
2015-10-01 14:17:00 +03:00
return journal . count ( Journal : : FAILED ) | | journal . count ( Journal : : TIMEOUT ) ? 1 : 0 ;
2014-07-04 20:24:56 +04:00
}
2023-10-02 14:59:50 +03:00
Main ( const Options & o ) ;
~ Main ( ) ;
2014-07-04 20:24:56 +04:00
} ;
2023-10-02 14:59:50 +03:00
Main : : Main ( const Options & o ) :
die ( false ) , journal ( o . outdir ) , options ( o )
{
start . gettime ( ) ;
}
Main : : ~ Main ( )
{ // no inline
}
2014-07-04 20:24:56 +04:00
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 ( ) ;
}
2015-07-15 15:20:37 +03:00
// TODO: This does not handle `--option=VALUE`:
2014-07-04 20:24:56 +04:00
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-07-15 15:20:37 +03:00
const char * DEF_FLAVOURS = " ndev-vanilla " ;
std : : string resolve_path ( std : : string a_path , const char * default_path = " . " )
{
char temp [ PATH_MAX ] ;
const char * p ;
p = a_path . empty ( ) ? default_path : a_path . c_str ( ) ;
if ( ! realpath ( p , temp ) )
throw syserr ( " Failed to resolve path " , p ) ;
return temp ;
}
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 ;
2015-07-15 15:20:37 +03:00
if ( args . has ( " --help " ) ) {
std : : cout < <
" lvm2-testsuite - Run a lvm2 testsuite. \n \n "
" lvm2-testsuite "
" \n \t "
" [--flavours FLAVOURS] "
" [--only TESTS] "
" \n \t "
" [--outdir OUTDIR] "
" [--testdir TESTDIR] "
" [--workdir WORKDIR] "
" \n \t "
" [--batch|--verbose|--interactive] "
" \n \t "
" [--fatal-timeouts] "
" [--continue] "
" [--heartbeat] "
" [--watch WATCH] "
" [--timeout TIMEOUT] "
" [--nokmsg] \n \n "
/* TODO: list of flavours:
" lvm2-testsuite "
" \n \t "
" --list-flavours [--testdir TESTDIR] "
*/
" \n \n "
" OPTIONS: \n \n "
// TODO: looks like this could be worth a man page...
" Filters: \n "
" --flavours FLAVOURS \n \t \t - comma separated list of flavours to run. \n \t \t For the list of flavours see `$TESTDIR/lib/flavour-*`. \n \t \t Default: \" " < < DEF_FLAVOURS < < " \" . \n "
" --only TESTS \t - comma separated list of tests to run. Default: All tests. \n "
" \n "
" Directories: \n "
" --testdir TESTDIR \n \t \t - directory where tests reside. Default: \" " TESTSUITE_DATA " \" . \n "
" --workdir WORKDIR \n \t \t - directory to change to when running tests. \n \t \t This is directory containing testing libs. Default: TESTDIR. \n "
" --outdir OUTDIR \n \t \t - directory where all the output files should go. Default: \" . \" . \n "
" \n "
" Formatting: \n "
" --batch \t - Brief format for automated runs. \n "
" --verbose \t - More verbose format for automated runs displaying progress on stdout. \n "
" --interactive \t - Verbose format for interactive runs. \n "
" \n "
" Other: \n "
" --fatal-timeouts \n \t \t - exit after encountering 2 timeouts in a row. \n "
" --continue \t - If set append to journal. Otherwise it will be overwritten. \n "
" --heartbeat HEARTBEAT \n \t \t - Name of file to update periodicaly while running. \n "
" --watch WATCH \t - Comma separated list of files to watch and print. \n "
" --timeout TIMEOUT \n \t \t - Period of silence in seconds considered a timeout. Default: 180. \n "
" --nokmsg \t - Do not try to read kernel messages. \n "
" \n \n "
" ENV.VARIABLES: \n \n "
" T \t \t - see --only \n "
" INTERACTIVE \t - see --interactive \n "
" VERBOSE \t - see --verbose \n "
" BATCH \t \t - see --batch \n "
" LVM_TEST_CAN_CLOBBER_DMESG \n \t \t - when set and non-empty tests are allowed to flush \n \t \t kmsg in an attempt to read it. "
" \n \n "
" FORMATS: \n \n "
" When multiple formats are specified interactive overrides verbose \n "
" which overrides batch. Command line options override environment \n "
" variables. \n \n "
;
return 0 ;
}
2014-07-04 20:24:56 +04:00
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 ) ;
2015-11-19 12:48:17 +03:00
if ( args . has ( " --skip " ) )
split ( args . opt ( " --skip " ) , opt . skip ) ;
else if ( hasenv ( " S " ) )
split ( getenv ( " S " ) , opt . skip ) ;
2014-07-04 20:24:56 +04:00
if ( args . has ( " --fatal-timeouts " ) )
opt . fatal_timeouts = true ;
if ( args . has ( " --heartbeat " ) )
opt . heartbeat = args . opt ( " --heartbeat " ) ;
2015-07-15 15:20:37 +03:00
if ( args . has ( " --batch " ) | | args . has ( " --verbose " ) | | args . has ( " --interactive " ) ) {
if ( args . has ( " --batch " ) ) {
opt . verbose = false ;
opt . batch = true ;
}
2014-07-04 20:24:56 +04:00
2015-07-15 15:20:37 +03:00
if ( args . has ( " --verbose " ) ) {
opt . batch = false ;
opt . verbose = true ;
}
if ( args . has ( " --interactive " ) ) {
opt . verbose = false ;
opt . batch = false ;
opt . interactive = true ;
}
} else {
if ( hasenv ( " BATCH " ) ) {
opt . verbose = false ;
opt . batch = true ;
}
if ( hasenv ( " VERBOSE " ) ) {
opt . batch = false ;
opt . verbose = true ;
}
2014-07-04 20:24:56 +04:00
2015-07-15 15:20:37 +03:00
if ( hasenv ( " INTERACTIVE " ) ) {
opt . verbose = false ;
opt . batch = false ;
opt . interactive = true ;
}
2014-07-04 20:24:56 +04:00
}
if ( args . has ( " --flavours " ) )
split ( args . opt ( " --flavours " ) , opt . flavours ) ;
else
2015-07-15 15:20:37 +03:00
split ( DEF_FLAVOURS , opt . flavours ) ;
2014-07-04 20:24:56 +04:00
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
2015-07-15 15:20:37 +03:00
opt . testdir = resolve_path ( args . opt ( " --testdir " ) , TESTSUITE_DATA ) + " / " ;
opt . workdir = resolve_path ( args . opt ( " --workdir " ) , opt . testdir . c_str ( ) ) ;
opt . outdir = resolve_path ( args . opt ( " --outdir " ) , " . " ) ;
2014-07-04 20:24:56 +04:00
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