1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00

test: Import first draft of a new test runner.

This commit is contained in:
Petr Rockai 2014-06-08 18:03:32 +02:00
parent f73526f58c
commit f476655fee

594
test/lib/runner.cpp Normal file
View File

@ -0,0 +1,594 @@
/*
* Copyright (C) 2010-2014 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License v.2.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <iostream>
#include <vector>
#include <deque>
#include <map>
#include <sstream>
#include <cassert>
#include <system_error>
#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/stat.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>
#include <dirent.h>
#define SYSLOG_ACTION_READ_CLEAR 4
#define SYSLOG_ACTION_CLEAR 5
pid_t kill_pid = 0;
bool fatal_signal = false;
std::system_error syserr( std::string msg, std::string ctx = "" ) {
return std::system_error( errno, std::generic_category(), 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;
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 )
throw syserr( "stat error", s );
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;
}
struct Options {
bool verbose, quiet, interactive;
std::string testdir, outdir;
};
struct Journal {
enum R {
STARTED,
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 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";
}
}
typedef std::map< std::string, R > Status;
Status status;
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: "
<< count( PASSED ) << " passed" << std::endl;
}
void details() {
for ( Status::iterator i = status.begin(); i != status.end(); ++i )
if ( i->second != PASSED )
std::cout << i->second << ": " << i->first << std::endl;
}
};
struct Sink {
int fd;
typedef std::deque< char > Stream;
typedef std::map< std::string, std::string > Subst;
Stream stream;
Subst subst;
bool killed;
virtual void outline( bool force )
{
Stream::iterator nl = std::find( stream.begin(), stream.end(), '\n' );
if ( nl == stream.end() && !force )
return;
std::string line( stream.begin(), nl );
stream.erase( stream.begin(), nl + 1 );
if ( std::string( line, 0, 9 ) == "@TESTDIR=" )
subst[ "@TESTDIR@" ] = std::string( line, 9, std::string::npos );
else if ( std::string( line, 0, 8 ) == "@PREFIX=" )
subst[ "@PREFIX@" ] = std::string( line, 8, std::string::npos );
else {
int off;
for ( Subst::iterator s = subst.begin(); s != subst.end(); ++s )
while ( (off = line.find( s->first )) != std::string::npos )
line.replace( off, s->first.length(), s->second );
write( fd, line.c_str(), line.length() );
}
}
void sync() {
while ( !stream.empty() )
outline( true );
}
void push( std::string x ) {
if ( !killed )
std::copy( x.begin(), x.end(), std::back_inserter( stream ) );
}
Sink( int fd ) : fd( fd ), killed( false ) {}
};
struct Observer : Sink {
Observer() : Sink( -1 ) {}
};
struct IO {
typedef std::vector< Sink* > Sinks;
mutable Sinks sinks;
Observer *_observer;
int fd;
char buf[ 4097 ];
void sink( std::string x ) {
for ( Sinks::iterator i = sinks.begin(); i != sinks.end(); ++i )
(*i)->push( x );
}
void sync() {
ssize_t sz;
while ((sz = read(fd, buf, sizeof(buf) - 1)) > 0)
sink( std::string( buf, sz ) );
if ( sz < 0 && errno != EAGAIN )
throw syserr( "reading pipe" );
/* get the kernel ring buffer too */
sz = klogctl( SYSLOG_ACTION_READ_CLEAR, buf, sizeof(buf) - 1 );
if ( sz > 0 )
sink( std::string( buf, sz ) );
}
void close() { ::close( fd ); }
Observer &observer() { return *_observer; }
IO() : fd( -1 ) {
sinks.push_back( _observer = new Observer );
}
IO( const IO &io ) {
fd = io.fd;
sinks = io.sinks;
io.sinks.clear();
}
IO &operator= ( const IO &io ) {
fd = io.fd;
sinks = io.sinks;
io.sinks.clear();
return *this;
}
~IO() {
for ( Sinks::iterator i = sinks.begin(); i != sinks.end(); ++i )
delete *i;
}
};
struct TestProcess
{
int fd;
bool interactive;
std::string filename;
void exec() {
assert( fd >= 0 );
if ( !interactive ) {
close( STDIN_FILENO );
dup2( fd, STDOUT_FILENO );
dup2( fd, STDERR_FILENO );
}
environment();
setpgid( 0, 0 );
klogctl( SYSLOG_ACTION_CLEAR, 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;
std::string name;
IO io;
struct rusage usage;
int status;
bool timeout;
int silent_ctr;
pid_t pid;
time_t start, end;
Options options;
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 );
if ( wait4(pid, &status, WNOHANG, &usage) != 0 )
return false;
/* kill off tests after a minute of silence */
if ( silent_ctr > 2 * 60 ) {
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;
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 */
if ( !options.verbose && !options.interactive )
progress( Update ) << tag( "running" ) << name << " " << end - start << std::flush;
if ( select( io.fd + 1, &set, NULL, NULL, &wait ) <= 0 )
{
silent_ctr++;
return true;
}
io.sync();
silent_ctr = 0;
return true;
}
std::string tag( std::string n ) {
int pad = (8 - n.length());
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;
}
void parent() {
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;
} else
r = Journal::FAILED;
::close( io.fd );
/*
if ((fd_debuglog = open(testdirdebug, O_RDONLY)) != -1) {
drain(fd_debuglog, unlimited ? INT32_MAX : 4 * 1024 * 1024);
close(fd_debuglog);
} */
progress( Last ) << tag( r ) << name << std::endl;
}
void run() {
pipe();
pid = kill_pid = fork();
if (pid < 0) {
perror("Fork failed.");
exit(201);
} else if (pid == 0) {
io.close();
child.exec();
} else {
progress( First ) << tag( "running" ) << name << std::flush;
start = time( 0 );
parent();
}
}
TestCase( std::string path, std::string name )
: timeout( false ), silent_ctr( 0 ), child( path ), name( name ) {}
};
struct Main {
bool die;
time_t start;
typedef std::vector< TestCase > Cases;
Journal journal;
Options options;
Cases cases;
void setup() {
Listing l = listdir( options.testdir, true );
std::sort( l.begin(), l.end() );
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( options.testdir + *i, *i ) );
cases.back().options = options;
}
}
void run() {
setup();
start = time( 0 );
for ( Cases::iterator i = cases.begin(); i != cases.end(); ++i ) {
i->run();
if ( time(0) - start > 3 * 3600 ) {
printf("3 hours passed, giving up...\n");
die = 1;
}
if ( die || fatal_signal )
break;
}
journal.banner();
if ( die || fatal_signal )
exit( 1 );
}
Main( Options o ) : options( o ) {}
};
static void handler( int sig ) {
signal( sig, SIG_DFL );
if ( kill_pid > 0 )
kill( -kill_pid, sig );
fatal_signal = 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);
}
}
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);
}
};
int main(int argc, char **argv)
{
Args args( argc, argv );
Options opt;
if ( args.has( "--quiet" ) || getenv( "QUIET" ) ) {
opt.verbose = false;
opt.quiet = true;
}
if ( args.has( "--verbose" ) || getenv( "VERBOSE" ) ) {
opt.quiet = false;
opt.verbose = true;
}
if ( args.has( "--interactive" ) || getenv( "INTERACTIVE" ) ) {
opt.verbose = false;
opt.quiet = false;
opt.interactive = true;
}
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();
}