Skip to content
Snippets Groups Projects
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 ( );
		}
	}
}