-
Jan Trávníček authoredJan Trávníček authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TimeoutAqlTest.cpp 5.01 KiB
#include "TimeoutAqlTest.hpp"
#include <cstring>
#include <exception>
#include <parser/Parser.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <global/GlobalData.h>
#include <alib/exception>
#include <alib/random>
#define PIPE_RD 0
#define PIPE_WR 1
/* Communication between signal handler and the rest of the program */
int g_Wakeup [ 2 ]; // pipe for wakeup
int g_RecvSignal; // signalled flag
std::string formatQueries ( const ext::vector < std::string > & queries ) {
std::string formated;
for ( const std::string & query : queries )
formated += " " + query + "\n";
return formated;
}
int waitSignalTimeout ( int timeout ) {
struct timeval tv;
fd_set rd;
tv.tv_sec = 0;
tv.tv_usec = timeout;
FD_ZERO ( &rd );
FD_SET ( g_Wakeup [ PIPE_RD ], &rd );
select ( g_Wakeup [ PIPE_RD ] + 1, &rd, nullptr, nullptr, &tv );
return g_RecvSignal;
}
void newSigChild ( int ) {
char dummy = 0;
g_RecvSignal = 1;
// write into the pipe so select can read something, this effectively means that SIGCHILD was raised
if ( write ( g_Wakeup [ PIPE_WR ], &dummy, 1 ) != 1 )
throw std::runtime_error ( "TimeoutAqlTest: write() failure (wakeup signalling)" );
}
int aqlTest ( int fd, const ext::vector < std::string > & queries, unsigned seed ) {
try {
cli::Environment environment;
std::stringstream ss;
common::Streams::out = ss;
common::Streams::err = ss;
cli::Parser parser = cli::Parser ( cli::Lexer ( "set seed " + ext::to_string ( seed ) ) );
parser.parse ( ) -> run ( environment );
for ( const std::string & q : queries ) {
parser = cli::Parser ( cli::Lexer ( q ) );
parser.parse ( ) -> run ( environment );
if ( write ( fd, ss.str ( ).c_str ( ), ss.str ( ).length ( ) ) != ( ssize_t ) ss.str ( ).length ( ) )
throw std::runtime_error ( "TimeoutAqlTest: child output write() failure (child to parent communication)" );
}
return environment.getResult ( ); /* 0 = OK */
} catch ( const std::exception & ) {
std::ostringstream oss;
alib::ExceptionHandler::handle ( oss );
if ( write ( fd, oss.str ( ).c_str ( ), oss.str ( ).length ( ) ) != ( ssize_t ) oss.str ( ).length ( ) )
throw std::runtime_error ( "TimeoutAqlTest: write() failure (child to parent communication)" );
return -1;
}
}
std::string readChildOutput ( int fd ) {
static const size_t BUFSIZE = 64;
std::string res;
int rd;
char buf [ BUFSIZE ];
while ( ( rd = read ( fd, &buf, BUFSIZE - 1 ) ) > 0 ) {
res.append ( buf, rd );
}
return res;
}
void _TimeoutAqlTest ( const std::chrono::microseconds & timeout, const ext::vector < std::string > & queries ) {
/* Register SIGCHLD handler */
struct sigaction act;
memset ( &act, 0, sizeof ( act ) );
act . sa_handler = newSigChild;
sigaction ( SIGCHLD, &act, nullptr );
/* parent-child communication ( for exceptions ) */
int pipefd [ 2 ];
if ( pipe ( pipefd ) != 0 )
throw std::runtime_error ( "TimeoutAqlTest: Failed to initialize pipe (child to parent communication)" );
/* SIGCHLD was not yet raised, initialize communication pipe */
g_RecvSignal = 0;
if ( pipe ( g_Wakeup ) )
throw std::runtime_error ( "TimeoutAqlTest: Failed to initialize pipe (wakeup signalling)" );
unsigned seed = ext::random_devices::random ( );
/* do the forking */
pid_t x = fork ();
if ( x < 0 ) {
FAIL ( "Fork error" );
} else if ( x == 0 ) {
/* child, run the test here */
act . sa_handler = SIG_DFL;
sigaction ( SIGCHLD, &act, nullptr );
close ( g_Wakeup [ PIPE_RD ] );
close ( g_Wakeup [ PIPE_WR ] );
close ( pipefd [ PIPE_RD ] );
/* just in case ... */
close ( 1 );
close ( 2 );
exit ( aqlTest ( pipefd [ PIPE_WR ], queries, seed ) );
}
close ( pipefd [ PIPE_WR ] );
/* lets wait the specified time of microseconds, maybe the child will terminate on its own */
if ( ! waitSignalTimeout ( timeout.count ( ) ) ) {
/* ... and in case it did not ... */
kill ( x, SIGTERM );
while ( ! waitSignalTimeout ( 250000 ) ) /* 1/4 second */
kill ( x, SIGKILL );
}
/* child termination confirmed */
int status;
waitpid ( x, &status, 0 );
close ( g_Wakeup [ PIPE_RD ] );
close ( g_Wakeup [ PIPE_WR ] );
std::string childOutput = readChildOutput ( pipefd [ PIPE_RD ] );
close ( pipefd [ PIPE_RD ] );
close ( pipefd [ PIPE_WR ] );
if ( WIFEXITED ( status ) ) {
INFO ( "AqlTest failure. Trying to execute: " << "\n" << formatQueries ( queries ) );
INFO ( "Seed was: " << seed );
INFO ( "Child output was: >" << childOutput << "<" );
REQUIRE ( WEXITSTATUS ( status ) == 0 );
} else if ( WIFSIGNALED ( status ) ) {
INFO ( "AqlTest failure. Trying to execute: " << formatQueries ( queries ) );
INFO ( "Seed was: " << seed );
INFO ( "Child output was: >" << childOutput << "<" );
if ( WTERMSIG ( status ) == SIGTERM || WTERMSIG ( status ) == SIGKILL ) { /* killed by timeout control */
WARN ( "Timeout (" << timeout.count ( ) << " us) reached in test (" << queries << ")" );
} else {
INFO ( "Child process signaled, signal " << WTERMSIG ( status ) << " (" << strsignal ( WTERMSIG ( status ) ) << ")" );
FAIL ( );
}
}
}