Files
secondo/UserInterfaces/TestRunner.cpp
2026-01-23 17:03:45 +08:00

1583 lines
41 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2004-2009, University in Hagen, Faculty of Mathematics and
Computer Science, Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
August 2005, M. Spiekermann. The semantics of reading input files was changed.
Now commands can end with spaces and also a "blank" or a newline will indicate
the end of a command. Moreover, reporting of the error and success messages has
been changed and was implemented in special functions. Finally, unused
functions and variables were removed!.
December 2005, Victor Almeida deleted the deprecated algebra levels
(~executable~, ~descriptive~, and ~hibrid~). Only the executable
level remains.
February 2006, M. Spiekermann. Reimplementation of the ~GetCommand()~ method.
Parsing the input lines is now done with less code by using some useful
functions operating on ~strings~ implemented in "CharTransform.h". Moreover
some bugs concerning result evaluation have been fixed. Finally, some new
features (Approximate comparison of float values, result specification in
external files and envrionment variable expansion in file names) were
implemented.
Nov. 2006, M. Spiekermann. Results may now be also specified as database
objects. Moreover, a new directive coverage was introduced which reports an
error if not all operators of an specified algebra module were used inside the
tests.
Dec. 2006, M. Spiekermann. A new operationg mode for processing ".example"
files was implemented. These files will guarantee that the examples of the
online help (e.g. list operators) are corrects since they could also be used as
input for the TestRunner.
1 Overview
This is the test enviroment for Secondo. The code is derived from SecondoTTY.
2 Includes and defines
*/
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <utility>
#include <map>
#include <unistd.h>
#include <fcntl.h>
#include "Application.h"
#include "Profiles.h"
#include "FileSystem.h"
#include "SecondoSystem.h"
#include "SecondoInterface.h"
#if !defined(SECONDO_CLIENT_SERVER) && !defined(REPLAY)
#include "SecondoInterfaceTTY.h"
#elif defined(SECONDO_CLIENT_SERVER)
#include "SecondoInterfaceCS.h"
#elif defined(REPLAY)
#include "SecondoInterfaceREPLAY.h"
#else
#include "SecondoInterfaceCS.h"
#endif
#include "SecondoSMI.h"
#include "SecParser.h"
#include "WinUnix.h"
#include "NestedList.h"
#include "DisplayTTY.h"
#include "CharTransform.h"
#include "LogMsg.h"
#include "ExampleReader.h"
#include "TTYParameter.h"
using namespace std;
class TestRunner : public Application
{
public:
TestRunner( const TTYParameter& tp );
virtual ~TestRunner() { };
int Execute();
private:
typedef enum { Success, Error, Result,
Unknown, UndefinedObj,
Coverage, Bug, Unpredictable} YieldState;
map<YieldState, string> yieldStateStr;
void ProcessFile( const string& fileName );
void ProcessCommand();
void ProcessCommands();
void ProcessExamples();
bool RunCmd(const string& dbName, SecErrInfo& err);
ListExpr CallSecondo();
void CallSecondo2();
void RegisterError(bool warning = false);
ListExpr MakeConstant(const string& type, ListExpr value);
void CoverageQuery(const string& algebra);
// read only functions
bool AbortOnSignal( int sig ) const;
bool IsInternalCommand( const string& line ) const;
bool GetCommand();
void ShowErrCodeInfo( const int errorCode,
const string& errorMessage,
const ListExpr outList ) const;
void ShowCommand( const string& cmd) const;
void ShowTestTitle() const;
void ShowTestErrorMsg(bool warning = false) const;
void ShowErrorSummary() const;
void ShowTestSuccessMsg(const string& msg) const;
void VerifyResult( ListExpr outList,
ListExpr expectedResult, bool warning = false );
void DisplayError( const string& cmd, ListExpr expectedResult,
int errorCode, const string& errorMessage,
ListExpr outList );
void DisplayWarning( const string& cmd, ListExpr expectedResult,
int errorCode, const string& errorMessage,
ListExpr outList );
string parmFile;
string home;
string user;
string pswd;
string host;
string port;
string iFileName;
string oFileName;
string resultFile;
string cmd;
string missingOps;
int missingOpsNo;
string rightArrow, leftArrow;
int num;
bool isStdInput;
bool runExamples;
bool quit;
bool verbose;
bool useResultFile;
NestedList* nl;
bool isQuery;
bool coverageTest;
SecondoInterface* si;
typedef list< pair<int,int> > ErrorInfo;
ErrorInfo errorLines;
typedef vector< ExampleInfo > ExampleVec;
ExampleVec crashes;
ExampleVec bugs;
ExampleVec unpredictable;
ExampleVec errors;
typedef vector< string > StringVec;
StringVec errMsg;
map<string, bool> algModules;
/*
the following variables and constants are
needed for maintaining the test state
*/
Tolerance realValTolerance;
ListExpr expectedResult;
string testName;
string testCaseName;
YieldState yieldState;
bool skipToTearDown;
int testCaseNumber;
int testCaseLine;
int numErrors;
int state;
static const int START = 0;
static const int SETUP = 1;
static const int TESTCASE = 2;
static const int TEARDOWN = 3;
};
TestRunner::TestRunner( const TTYParameter& tp )
: Application( tp.numArgs, (const char**)tp.argValues )
{
parmFile = tp.parmFile;
home = tp.home;
user = tp.user;
pswd = tp.pswd;
host = tp.host;
port = tp.port;
iFileName = tp.iFileName;
oFileName = tp.oFileName;
num = parse<int>(tp.num);
runExamples = tp.runExamples;
string cmd = "";
missingOps = "";
missingOpsNo = 0;
isStdInput = true;
quit = false;
nl = 0;
si = 0;
skipToTearDown = false;
useResultFile = false;
coverageTest = tp.coverage;
resultFile = "";
state = START;
yieldState = Unknown;
testCaseNumber = 0;
testCaseLine = 0;
numErrors = 0;
expectedResult = nl->TheEmptyList();
verbose = false;
const int n = 30;
const string ra(n,'>');
const string la(n,'<');
stringstream tmp;
tmp << color(red) << ra << color(normal);
rightArrow = tmp.str();
tmp.str("");
tmp << color(red) << la << color(normal);
leftArrow = tmp.str();
yieldStateStr[Success] = "Success";
yieldStateStr[Error] = "Error";
yieldStateStr[Result] = "Result";
yieldStateStr[Unknown] = "Unknown";
yieldStateStr[UndefinedObj] = "UndefinedObj";
yieldStateStr[Coverage] = "Coverage";
yieldStateStr[Bug] = "Bug";
yieldStateStr[Unpredictable] = "Unpredictable";
}
bool
TestRunner::AbortOnSignal( int sig ) const
{
return (true);
}
void
TestRunner::ProcessFile( const string& fileName )
{
bool saveIsStdInput = isStdInput;
streambuf* oldBuffer;
ifstream fileInput( fileName.c_str() );
if ( fileInput )
{
oldBuffer = cin.rdbuf( fileInput.rdbuf() );
cout << "*** Begin processing file '" << fileName << "'." << endl;
isStdInput = false;
ProcessCommands();
isStdInput = saveIsStdInput;
cout << "*** End processing file '" << fileName << "'." << endl;
cin.rdbuf( oldBuffer );
}
else
{
cerr << "*** Error: Could not access file '" << fileName << "'." << endl;
}
}
void
TestRunner::ShowTestTitle() const
{
if ( state != TESTCASE )
return;
cout
<< endl << endl << color(green)
<< "*** Test " << testCaseNumber
<< " (line " << testCaseLine << "): "
<< testCaseName << " ***"
<< color(normal) << endl;
}
void
TestRunner::ShowTestSuccessMsg(const string& msg) const
{
cout
<< color(green)
<< "==> [OK] " << msg << color(normal) << endl;
}
void
TestRunner::ShowTestErrorMsg(bool warning) const
{
if (warning) {
cout
<< endl << color(blue)
<< "==> [TEST COVERAGE WARNING]" << color(normal) << endl;
}
else
{
cout
<< endl << color(red)
<< "==> [ERROR]" << color(normal) << endl;
}
}
void
TestRunner::ShowErrCodeInfo( const int errorCode,
const string& errorMessage,
const ListExpr outList ) const
{
cout
<< "Error-Code: " << errorCode << endl
<< "Error-Text: \"" << SecondoInterface::GetErrorMessage( errorCode )
<< "\""
<< endl;
if ( errorMessage.length() > 0 ) {
cout
<< "Error-Msgs: \"" << errorMessage << "\"" << endl;
}
if ( !nl->IsEmpty(outList) )
{
cout << " Error-List: " << endl;
si->WriteErrorList(outList);
}
}
void
TestRunner::ShowCommand( const string& cmd) const
{
cout
<< endl
<< "Secondo-Cmd: " << endl
<< " " << cmd << endl;
}
void
TestRunner::ShowErrorSummary() const
{
cout
<< endl
<< "TEST SUMMARY :" << endl
<< "--------------" << endl;
if(numErrors == 0)
{
cout << "There were *** no *** unexpected errors." << endl;
}
else if(numErrors == 1)
{
cout << "There was *** 1 *** unexpected error." << endl;
}
else if (numErrors > 1)
{
cout << "There were *** " << numErrors << " *** unexpected errors." << endl;
}
else
{
cout << "There were Errors outside of the test cases!" << endl;
}
cout << endl;
if ( numErrors > 0 ) {
if (errorLines.size() > 0) {
cout << endl << "<testno>@<lineno> which caused an error: ";
ErrorInfo::const_iterator it = errorLines.begin();
while ( it != errorLines.end() )
{
cout << " " << it->first << "@" << it->second;
it++;
}
cout << endl << endl;
}
}
if (errMsg.size() > 0) {
StringVec::const_iterator it = errMsg.begin();
for(it = errMsg.begin(); it != errMsg.end(); it++) {
cout << "* " << *it << endl << endl;
}
}
if (bugs.size() > 0)
cout << "There were " << bugs.size() << " known bugs." << endl;
if (crashes.size() > 0)
cout << "There were " << crashes.size() << " omitted queries "
<< "which are known to crash" << endl;
if (unpredictable.size() > 0)
cout << "There were " << unpredictable.size() << " queries "
<< "having an unpredictable result" << endl;
if (missingOpsNo > 0)
{
cout << endl
<< "COVERAGE (untested operators):" << endl
<< "------------------------------" << endl
<< missingOps << endl;
}
cout << endl;
}
void
TestRunner::RegisterError(bool warning /*= false */)
{
if (!warning) {
numErrors++;
errorLines.push_back( make_pair(testCaseNumber, testCaseLine) );
}
ShowTestErrorMsg(warning);
}
void
TestRunner::CoverageQuery(const string& algebra)
{
cout << "Computing coverage for algebra " << algebra << endl;
cmd = "query SEC2OPERATORUSAGE feed filter[.Algebra = \""
+ algebra
+ "\"] filter[.Calls = 0] project[Operator] sortby[Operator]"
" groupby[Operator; Cnt: group count] consume";
yieldState = Coverage;
string resText = "((rel(tuple((Operator string)(Cnt int))))())";
bool ok = nl->ReadFromString(resText, expectedResult);
assert(ok);
}
void
TestRunner::ProcessCommand()
{
string cmdWord = "";
istringstream is( cmd );
is >> cmdWord;
string query="";
if ( cmdWord[0] == '@' ) {
string queryFile = parse<string>( cmdWord.substr(1) );
cout << "Reading file " << queryFile << endl;
CFile f(queryFile);
f.open();
while (!f.eof()) {
string line = "";
getline(f.ios(), line);
query += line;
}
cout << "QUERY:" << query << endl;
cmd = query;
ProcessCommand();
return;
}
transform( cmdWord.begin(), cmdWord.end(), cmdWord.begin(),
ToUpperProperFunction );
if ( cmdWord == "Q" || cmdWord == "QUIT" )
{
cout << "*** Thank you for using SECONDO!" << endl;
quit = true;
}
else if ( cmdWord == "DEBUG" )
{
int debugLevel;
is >> debugLevel;
si->SetDebugLevel( debugLevel );
cout << "*** Debug level set to " << debugLevel << "." << endl;
}
else
{
isQuery = ( cmdWord == "QUERY"
|| cmdWord == "(QUERY"
|| cmdWord == "( QUERY" );
CallSecondo2();
}
}
bool
TestRunner::IsInternalCommand( const string& line ) const
{
string cmdWord = "";
istringstream is( line );
is >> cmdWord;
transform( cmdWord.begin(), cmdWord.end(), cmdWord.begin(),
ToUpperProperFunction );
return ( cmdWord == "?" || cmdWord == "HELP" ||
cmdWord == "Q" || cmdWord == "QUIT" ||
cmdWord == "DEBUG" || cmdWord == "SHOW" || cmdWord[0] == '@' );
}
bool
TestRunner::GetCommand()
{
static int lineCtr = 0;
bool complete = false;
bool first = true;
string line = "";
cmd = "";
// clear last expected result
expectedResult = nl->Empty();
while (!complete && !cin.eof() && !cin.fail())
{
line = "";
getline( cin, line );
lineCtr++;
if ( line.length() > 0 )
{
if ( line[0] != '#' ) // # no comment => SECONDO command
{
if ( line[line.length()-1] == ';' ) // check for command end
{
complete = true;
line.erase( line.length()-1 );
}
if ( first ) // check for an internal command
{
if ( !complete ) // check only if no command end was found
{
complete = IsInternalCommand( line );
}
cmd = line + " ";
first = false; // don't check again
}
else // concat command parts
{
// check if line contains only spaces
if ( isSpaceStr(line) ) {
complete = true; // command ends
} else {
cmd = cmd + line + " "; // concat command parts
}
}
}
else
{
// process a test runner directive
if(line.size() < 2)
{
/* assume that this line is part of a real comment */
}
else // check for directives
{
// concat lines with continuation symbol "\"
static const char contSym = '\\';
size_t endPos = line.size() - 1;
while( line[endPos] == contSym )
{
string nextLine;
getline(cin, nextLine);
/*
cout << "Concating lines:" << endl;
cout << "1st: <" << line << ">" << endl
<< "2nd: <" << nextLine << ">" << endl;
*/
line.erase(endPos);
line += "\n" + nextLine;
endPos = line.size() - 1;
}
//cout << "Concat result:" << line << endl;
string command = parse<string>(line.substr(1));
size_t pos = line.find(command) + command.size();
string restOfLine = trim( line.substr(pos) );
//cout << "command: " << command << endl;
//cout << "rest: <" << restOfLine << ">" << endl;
// check for TestRunner directives
if(command.find("setup") == 0)
{
istringstream spec(restOfLine);
spec >> testName;
cout << endl << "### Starting test " << testName << endl << endl;
if(state != START)
{
cout
<< "Setup directive must appear just once in a test file."
<< endl;
quit = true;
return false;
}
state = SETUP;
ListExpr result = nl->Empty();
SecErrInfo err;
bool rc = si->Secondo( "list algebras", result, err);
if (!rc) {
cout << "Internal command list algebras failed!" << endl;
quit = true;
return false;
}
ListExpr algList = nl->Second(nl->Second(result));
//cout << nl->ToString(algList) << endl;
while ( !nl->IsEmpty(algList) ) {
algModules[ nl->SymbolValue(nl->First(algList)) ] = true;
algList = nl->Rest(algList);
}
string alg="";
while ( spec >> alg ) {
if (algModules.find(alg) == algModules.end()) {
cout << "Needed algebra module " << alg << " unknown!"
<< " The test will not be executed." << endl;
quit = true;
return false;
}
}
}
else if(command.find("description") == 0) {
cout << "description: " << endl << restOfLine << endl;
}
else if(command.find("stop") == 0) {
if (state == START) {
cout << endl
<< "Stop directive found! Test will not be executed."
<< endl
<< "Reason: " << restOfLine << endl << endl;
quit = true;
return false;
} else {
cout << "Warning: stop directives after setup are ignored!"
<< endl;
}
}
else if(command.find("testcase") == 0)
{
state = TESTCASE;
testCaseName = restOfLine;
testCaseNumber++;
testCaseLine = lineCtr;
}
else if(command.find("tolerance_real") == 0)
{
assert(state == TESTCASE);
string toleranceStr = parse<string>( restOfLine );
int offset=0;
string suffix("");
if (toleranceStr[0] == '%')
{
realValTolerance.isRelative = true;
cout << "Relative";
offset=1;
suffix="%";
}
else
{
realValTolerance.isRelative = false;
cout << "Absolute";
}
double d = parse<double>( toleranceStr.substr(offset) );
if (realValTolerance.isRelative)
realValTolerance.value = d / 100.0;
else
realValTolerance.value = d;
cout << " tolerance set to "
<< realValTolerance.value << suffix << endl;
}
else if(command.find("yields") == 0)
{
assert(state == TESTCASE);
size_t pos = firstNonSpace(restOfLine);
if (pos == string::npos)
{
yieldState = Unknown;
}
else
{
char first=restOfLine[pos];
if ( (first=='@') || (first=='(') || (first=='*') )
{
// result specified
yieldState = Result;
if (first=='@')
{
// result stored in a separate file
string resultFileStr = parse<string>( restOfLine.substr(1) );
cout << "Query result specified in file '" << resultFileStr
<< "'" << endl;
bool ok = nl->ReadFromFile( expandVar(resultFileStr),
expectedResult );
if (!ok) { RegisterError(); }
}
else if (first=='(')
{
nl->ReadFromString(restOfLine, expectedResult);
}
else
{
string ident = parse<string>( restOfLine.substr(1) );
cout << "Query result specified in object '" << ident
<< "'" << endl;
ListExpr resList = nl->Empty();
SecErrInfo err;
bool ok = si->Secondo( "query " + ident, resList, err );
if (!ok) {
expectedResult = nl->Empty();
yieldState = UndefinedObj;
}
else
{
expectedResult = resList;
}
}
}
else if ( restOfLine.find("error") != string::npos )
{
yieldState = Error;
}
else if ( restOfLine.find("success") != string::npos )
{
yieldState = Success;
}
else
{
yieldState = Unknown;
}
}
}
else if(command.find("coverage") == 0)
{
yieldState = Coverage;
CoverageQuery(restOfLine);
}
else if(command.find("teardown") == 0)
{
if(state == TEARDOWN)
{
cout
<< "Teardown directive must not appear more than "
<< "once in a test file." << endl;
}
cout << "Starting teardown." << endl;
state = TEARDOWN;
}
else
{
/* current line is comment */
}
}
}
}
else // Empty line ends command
{
// check if cmd contains information, otherwise continue
complete = (cmd.length() > 0);
first = true;
}
}
return (complete);
}
void
TestRunner::ProcessCommands()
{
if (num > 0) {
cout << "Running only test case number " << num << "!" << endl;
}
while (!cin.eof() && !quit)
{
if ( GetCommand() )
{
// if a number is specified only run a specific test case
if (num > 0) {
if ( state == TESTCASE ) {
if (testCaseNumber == num)
ProcessCommand();
} else {
ProcessCommand();
}
}
else // run every command
{
ProcessCommand();
}
}
}
}
void
TestRunner::ProcessExamples()
{
cout << "Processing examples for " << iFileName << "!" << endl;
const char* envVar = "SECONDO_BUILD_DIR";
char* buildDir = getenv(envVar);
if (buildDir == 0) {
cerr << "Variable " << envVar << " not defined!" << endl;
quit = true;
return;
}
// parse example file
CFile in(iFileName);
string algebraShort = in.getName();
removeSuffix(".examples", algebraShort);
string algebra = algebraShort + "Algebra";
ExampleReader examples(iFileName);
bool parseOk = examples.parse();
if (!parseOk) {
numErrors++;
errMsg.push_back("Parse error in example file!");
return;
}
bool needsRestore = examples.getRestoreFlag();
bool rc = false;
SecErrInfo err;
string dbFile = examples.getDB();
string dbName = FileSystem::Basename(dbFile);
if (!needsRestore) {
cout << "Opening database " << dbName << "!" << endl;
rc = RunCmd("open database " + dbName, err);
if (!rc)
{
cout << "Command failed with msg: " << err.msg << endl;
cout << "Trying to restore ..." << endl;
needsRestore = true;
}
}
if (needsRestore) {
RunCmd("create database " + dbName, err);
rc = RunCmd("restore database " + dbName + " from '" + dbFile +"'", err);
if (!rc) {
numErrors++;
errMsg.push_back("Restore Failed!");
cout << "Restore failed with msg: " << err.msg << endl;
cout << "Giving up!" << endl;
return;
}
}
state = TESTCASE;
yieldState = Result;
// iterate over all examples
ExampleInfo info;
examples.initScan();
for (; !examples.endOfScan(); examples.nextOfScan() )
{
ExampleReader::ExampleList list = examples.getCurrentList();
ExampleReader::ExampleList::const_iterator it = list.begin();
for(; it != list.end(); it++) {
info = **it;
cmd = info.example;
expectedResult = nl->Empty();
ListExpr tmpList = nl->Empty();
bool resultOk = false;
if(info.tolerance<=0){
realValTolerance.relative();
} else {
realValTolerance.isRelative = info.relativeTolerance;
realValTolerance.value = info.tolerance;
}
if ( info.resultType == ExampleInfo::Atom )
{
resultOk = nl->ReadFromString("(" + info.result + ")", tmpList);
//cout << nl->ToString(tmpList) << endl;
if (resultOk) {
if (nl->ListLength(tmpList) >= 1)
tmpList = (nl->First(tmpList));
switch ( nl->AtomType(tmpList) ) {
case BoolType: {
expectedResult = MakeConstant("bool", tmpList);
break;
}
case IntType: {
expectedResult = MakeConstant("int", tmpList);
break;
}
case RealType:{
expectedResult = MakeConstant("real", tmpList);
break;
}
case TextType:{
expectedResult = MakeConstant("text", tmpList);
break;
}
case StringType:{
expectedResult = MakeConstant("string", tmpList);
break;
}
default: { // result will be interpreted as file name
cerr << "Could not determine symbol atom type of '"
<< info.result << "'" << endl;
}
} // end of switch
} // end of resultOk
}
if ( info.resultType == ExampleInfo::List )
{
ListExpr list = nl->Empty();
resultOk = nl->ReadFromString(info.result, list);
// Anaylize list and extract a platform specific result if necessary!
if ( nl->HasMinLength(list,1) ) {
ListExpr list1 = nl->First(list);
if( nl->IsEqual(list1,"platform") )
{
ListExpr list2 = nl->Rest(list);
string token = WinUnix::getPlatformStr();
while ( !nl->IsEmpty(list2) ) {
//cout << nl->ToString(list2) << endl;
if ( nl->HasMinLength(list2, 1) ) {
ListExpr tmpList = nl->First(list2);
if( nl->HasMinLength(tmpList, 1)
&& nl->IsEqual(nl->First(tmpList), token) ) {
list = nl->Second(tmpList);
cout << "Using result for " << token << endl;
if ( nl->IsEqual(list, "bug") )
info.resultType = ExampleInfo::Bug;
if ( nl->IsEqual(list, "unpredictable") )
info.resultType = ExampleInfo::Unpredictable;
if ( nl->IsEqual(list, "file") )
info.resultType = ExampleInfo::File;
if ( nl->IsEqual(list, "platform") )
info.resultType = ExampleInfo::PlatformFile;
if ( nl->IsEqual(list, "crashes") )
info.resultType = ExampleInfo::Crash;
break;
}
else
{
cout << "No result for "
<< token << " specified!" << endl;
}
}
list2 = nl->Rest(list2);
}
}
expectedResult = list;
}
}
bool simpleFile = info.resultType == ExampleInfo::File;
bool platformFile = info.resultType == ExampleInfo::PlatformFile;
useResultFile = false;
if ( simpleFile || platformFile )
{
useResultFile = true;
string file = info.result;
string opIdent = info.opName;
if (info.aliasName != "")
opIdent = info.aliasName;
stringstream ss;
ss << "result" << info.number
<< "_" << opIdent << "_" << algebraShort;
if (platformFile) {
ss << "_" << WinUnix::getPlatformStr();
}
file = ss.str();
resultFile = string(buildDir) + "/Selftest/" + file;
ListExpr objList = nl->Empty();
//cout << "Reading result from file " << file << endl;
resultOk = nl->ReadFromFile(resultFile, objList);
if (resultOk)
{
expectedResult = objList;
// check if result is formatted in OBJECT representation.
if ( nl->ListLength(objList) == 5
&& nl->IsEqual(nl->First(objList),"OBJECT") )
{
ListExpr fourth = nl->Fourth(objList);
ListExpr fifth = nl->Fifth(objList);
expectedResult = nl->TwoElemList(fourth, fifth);
}
}
else
{
cerr << "File " << resultFile << " contains not a "
<< " correct nested list!"
<< endl;
}
}
if (!resultOk) {
cerr << "Error: Could not parse result!" << endl;
cerr << "Result string: " << info.result << endl;
cerr << "Result type : " << info.resultType << endl;
expectedResult = nl->SymbolAtom("ERROR");
}
testCaseNumber++;
testCaseName = info.opName;
testCaseLine = info.lineNo;
switch (info.resultType) {
case ExampleInfo::Bug: {
yieldState = Bug;
bugs.push_back(info);
CallSecondo2();
break;
}
case ExampleInfo::Unpredictable: {
yieldState = Unpredictable;
unpredictable.push_back(info);
CallSecondo2();
break;
}
case ExampleInfo::Crash: {
crashes.push_back(info);
ShowTestTitle();
cout << "Not executed! Known to crash." << endl;
break;
}
default: {
yieldState = Result;
CallSecondo2();
}
}
} // end of list iteration
} // end of operator iteration
if (coverageTest) {
testCaseNumber++;
testCaseName = "Coverage test for " + algebra;
testCaseLine = 0;
yieldState = Coverage;
CoverageQuery(algebra);
CallSecondo2();
}
}
ListExpr
TestRunner::MakeConstant(const string& type, ListExpr value)
{
return nl->TwoElemList(nl->SymbolAtom(type), value);
}
bool
TestRunner::RunCmd(const string& cmd, SecErrInfo& err)
{
ListExpr result = nl->Empty();
cout << "cmd:" << cmd << endl;
bool rc = si->Secondo( cmd, result, err);
return rc;
}
void
TestRunner::VerifyResult( ListExpr outList,
ListExpr expectedResult, bool warning)
{
/* verify that the expected results were delivered */
bool result = false;
double d = realValTolerance.value;
if ( d != 0.0)
{
result = nl->Equal(expectedResult, outList, realValTolerance);
}
else
{
result = nl->Equal(expectedResult, outList);
}
if(result)
{
ShowTestSuccessMsg("computing result as specified.");
}
else
{
RegisterError(warning);
if (!warning) {
cout
<< color(red)
<< "The test returned unexpected results!" << endl;
if (d != 0.0)
{
cout << "Used real value tolerance: " << d;
if (realValTolerance.isRelative ) {
cout << " (relative) ";
} else {
cout << " (absolute) ";
}
cout << endl;
}
}
cout
<< rightArrow << endl
<< color(normal)
<< "Expected : " << endl;
if (useResultFile)
cout << resultFile;
else
nl->WriteListExpr(expectedResult, cout);
cout << endl << "But got: " << endl;
if (useResultFile) {
string errFile = resultFile + "_err";
cout << errFile;
nl->WriteToFile(errFile, outList);
}
else
nl->WriteListExpr(outList, cout);
if (nl->EqualErr() != "")
cout << endl << endl << nl->EqualErr() << endl;
ShowCommand(cmd);
cout << color(red)
<< leftArrow
<< color(normal) << endl;
}
}
void
TestRunner::DisplayError( const string& cmd, ListExpr expectedResult,
int errorCode, const string& errorMessage,
ListExpr outList)
{
cout
<< color(red)
<< "The test returned an error, but should not do so!" << endl
<< rightArrow << endl
<< "Expected result : " << endl
<< nl->ToString(expectedResult) << endl;
ShowErrCodeInfo(errorCode, errorMessage, outList);
ShowCommand(cmd);
cout << color(red)
<< leftArrow
<< color(normal) << endl;
}
void
TestRunner::DisplayWarning( const string& cmd, ListExpr expectedResult,
int errorCode, const string& errorMessage,
ListExpr outList )
{
cout
<< color(blue)
<< "Warning: unexpected result:" << endl
<< rightArrow << endl
<< "Expected result : " << endl
<< nl->ToString(expectedResult) << endl;
ShowErrCodeInfo(errorCode, errorMessage, outList);
ShowCommand(cmd);
cout << color(blue)
<< leftArrow
<< color(normal) << endl;
}
/*
11 CallSecondo
This function gives a query to secondo and receives the result from secondo.
*/
ListExpr
TestRunner::CallSecondo()
{
int errorCode = 0, errorPos = 0;
ListExpr cmdList = nl->TheEmptyList();
ListExpr outList = nl->TheEmptyList();
string errorMessage = "";
string errorText = "";
if(!skipToTearDown)
{
if (verbose) {
cmsg.info()
<< "Passing command >>"
<< cmd << "<< to the secondo interface!" << endl;
cmsg.send();
}
if ( cmd[0] == '(' )
{
if ( nl->ReadFromString( cmd, cmdList ) )
{
si->Secondo( cmd, cmdList, 0, false, false,
outList, errorCode, errorPos, errorMessage );
}
else
{
cout << endl << "*** Error: list expression expected!" << endl;
}
}
else
{
cout << cmd << endl;
si->Secondo( cmd, cmdList, 1, false, false,
outList, errorCode, errorPos, errorMessage );
}
}
switch(state)
{
case START:
if( (errorCode != 0) || (errorMessage.length() > 0) )
{
/* should we report errors in the intial section? */
cout
<< color(red)
<< "*** Encountered error in initial section. ***" << endl
<< color(normal)
<< rightArrow << endl;
ShowErrCodeInfo(errorCode, errorMessage, outList);
ShowCommand(cmd);
cout
<< leftArrow << endl;
}
break;
case SETUP:
if(errorCode != 0)
{
cout
<< "*** Encountered error during setup, skipping to teardown. ***"
<< endl
<< rightArrow << endl;
ShowErrCodeInfo(errorCode, errorMessage, outList);
ShowCommand(cmd);
skipToTearDown = true;
numErrors = -128;
cout
<< leftArrow << endl;
}
break;
case TESTCASE:
if(!skipToTearDown)
{
if( yieldState == Bug) {
ShowTestSuccessMsg("Known bug! The result is ignored!");
} else if(yieldState == Unpredictable){
ShowTestSuccessMsg("Unpredictable result! The result is ignored!");
} else if( yieldState == Error) {
if (errorCode)
{
ShowTestSuccessMsg("returned an error as expected.");
}
else
{
RegisterError();
cout
<< color(red)
<< "The test suceeded, but an error was expected!" << endl
<< color(normal)
<< rightArrow << endl
<< "Result : " << endl;
nl->WriteListExpr(outList, cout);
ShowCommand(cmd);
cout
<< leftArrow << endl;
}
}
else if( yieldState == Success )
{
if(!errorCode)
{
ShowTestSuccessMsg("succeeded as expected.");
}
else
{
RegisterError();
DisplayError(cmd, expectedResult, errorCode, errorMessage, outList);
}
}
else if( yieldState == Result )
{
if(errorCode)
{
RegisterError();
DisplayError(cmd, expectedResult, errorCode, errorMessage, outList);
}
else
{
VerifyResult(outList, expectedResult);
}
}
else if( yieldState == Coverage )
{
if(errorCode)
{
RegisterError();
DisplayError(cmd, expectedResult, errorCode, errorMessage, outList);
}
else
{
// in case of success compare results, but handle
// differences only as warning
VerifyResult(outList, expectedResult, true);
ListExpr ops = nl->Second(outList);
missingOps += nl->ToString(ops);
missingOpsNo += nl->ListLength(ops);
}
}
else if( yieldState == Unknown )
{
RegisterError();
cout
<< color(red)
<< "The test has an unknown yields value!" << endl
<< color(normal) << endl;
}
else if( yieldState == UndefinedObj )
{
RegisterError();
cout
<< color(red)
<< "The test's yields value is an undefined or "
<< "unpresent database object!" << endl
<< color(normal) << endl;
}
else { // default
assert( false ); // should never happen
}
}
break;
case TEARDOWN:
if(errorCode != 0)
{
cout
<< color(red)
<< "*** Encountered error during teardown. ***" << endl
<< color(normal)
<< rightArrow << endl;
ShowErrCodeInfo(errorCode, errorMessage, outList);
ShowCommand(cmd);
cout
<< leftArrow << endl;
}
break;
}
if ( errorCode != 0 )
{
nl->Destroy( outList );
outList = nl->TheEmptyList();
}
if ( cmdList != nl->TheEmptyList() )
{
nl->Destroy( cmdList );
}
return (outList);
}
void
TestRunner::CallSecondo2()
{
ShowTestTitle();
CallSecondo();
nl->initializeListMemory();
}
/*
1 Execute
This function checks the configuration of the Secondo system. If the configuration
seems to be ok the system is intialized. If the initialization succeeds the user
commands are processed. If the initialization fails or the user finishes work
the system is terminated.
*/
int
TestRunner::Execute()
{
cout << endl
<< "--- Secondo TestRunner ---"
<< endl << endl;
streambuf* oldInputBuffer = 0;
streambuf* oldOutputBuffer = 0;
ifstream fileInput;
ofstream fileOutput;
#if !defined(SECONDO_CLIENT_SERVER) && !defined(REPLAY)
si = new SecondoInterfaceTTY(false);
#elif defined(SECONDO_CLIENT_SERVER)
si = new SecondoInterfaceCS(false,0, true);
#elif defined(REPLAY)
si = new SecondoInterfaceREPLAY(false,0);
#else
si = new SecondoInterfaceCS(false,0,true);
#endif
string errorMsg("");
if ( si->Initialize( user, pswd, host, port, parmFile, home, errorMsg ) )
{
if ( iFileName.length() > 0 )
{
cout << endl << "--- Opening file "
<< iFileName << " ---" << endl << endl;
fileInput.open( iFileName.c_str() );
if ( fileInput.is_open() )
{
oldInputBuffer = cin.rdbuf( fileInput.rdbuf() );
isStdInput = false;
}
else
{
cout << "ERROR: Could not open file " << iFileName << endl;
}
}
if (isStdInput)
cout << "Reading from stdin ..." << endl;
if ( oFileName.length() > 0 )
{
fileOutput.open( oFileName.c_str() );
if ( fileOutput.is_open() )
{
oldOutputBuffer = cout.rdbuf( fileOutput.rdbuf() );
}
}
nl = si->GetNestedList();
// DisplayTTY::Set_SI(si);
DisplayTTY::Set_NL(nl);
DisplayTTY::Initialize();
// check RTFlags
if ( RTFlag::isActive("Test:Verbose") ) {
verbose = true;
}
if (runExamples)
{
ProcessExamples();
}
else {
ProcessCommands();
}
if ( iFileName.length() > 0 )
{
if ( fileInput.is_open() )
{
cin.rdbuf( oldInputBuffer );
}
}
if ( oFileName.length() > 0 )
{
if ( fileOutput.is_open() )
{
cout.rdbuf( oldOutputBuffer );
}
}
DisplayTTY::Finish();
} else {
cout << "Error in initialization of secondo: "
<< errorMsg << endl;
}
ShowErrorSummary();
try {
si->Terminate();
delete si;
}
catch (SecondoException& e)
{
cerr << e.msg() << endl;
numErrors++;
}
cout << "--- Secondo TestRunner terminated ---" << endl;
return (numErrors);
}
int SecondoTestRunner(const TTYParameter& tp)
{
TestRunner* appPointer = new TestRunner( tp );
int rc = appPointer->Execute();
delete appPointer;
return (rc);
}