From 8437a9bdd2b394055595565a6af84ff976688314 Mon Sep 17 00:00:00 2001 From: krzys-h Date: Wed, 23 Dec 2015 20:39:56 +0100 Subject: [PATCH] CBot testing framework; fixed a few bugs --- src/CBot/CBotProgram.cpp | 2 +- src/CBot/CBotStack.cpp | 8 +- test/unit/CBot/CBot.cpp | 221 +++++++++++++++++++++++++++++++++++++++ test/unit/CMakeLists.txt | 1 + 4 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 test/unit/CBot/CBot.cpp diff --git a/src/CBot/CBotProgram.cpp b/src/CBot/CBotProgram.cpp index 8fffb671..a15108d1 100644 --- a/src/CBot/CBotProgram.cpp +++ b/src/CBot/CBotProgram.cpp @@ -257,7 +257,7 @@ void CBotProgram::Stop() //////////////////////////////////////////////////////////////////////////////// bool CBotProgram::GetRunPos(std::string& functionName, int& start, int& end) { - functionName = nullptr; + functionName = ""; start = end = 0; if (m_pStack == nullptr) return false; diff --git a/src/CBot/CBotStack.cpp b/src/CBot/CBotStack.cpp index f91b56e5..3387ef41 100644 --- a/src/CBot/CBotStack.cpp +++ b/src/CBot/CBotStack.cpp @@ -747,7 +747,7 @@ bool CBotStack::ExecuteCall(long& nIdent, CBotToken* token, CBotVar** ppVar, CBo res = CBotCall::DoCall(nIdent, nullptr, ppVar, this, rettype ); if (res.GetType() >= 0) return res.GetType(); - res = m_prog->GetFunctions()->DoCall(nIdent, nullptr, ppVar, this, token ); + res = m_prog->GetFunctions()->DoCall(nIdent, "", ppVar, this, token ); if (res.GetType() >= 0) return res.GetType(); // if not found (recompile?) seeks by name @@ -828,7 +828,7 @@ void CBotStack::GetRunPos(std::string& FunctionName, int& start, int& end) CBotVar* CBotStack::GetStackVars(std::string& FunctionName, int level) { CBotProgram* prog = m_prog; // current program - FunctionName = nullptr; + FunctionName = ""; // back the stack in the current module CBotStack* p = this; @@ -844,13 +844,13 @@ CBotVar* CBotStack::GetStackVars(std::string& FunctionName, int level) // descends upon the elements of block - while ( p != nullptr && p->m_bBlock != UnknownEnumBlock::UNKNOWN_FALSE ) p = p->m_prev; + while ( p != nullptr && p->m_bBlock == UnknownEnumBlock::UNKNOWN_FALSE ) p = p->m_prev; // Now p is on the beggining of the top block (with local variables) while ( p != nullptr && level++ < 0 ) { p = p->m_prev; - while ( p != nullptr && p->m_bBlock != UnknownEnumBlock::UNKNOWN_FALSE ) p = p->m_prev; + while ( p != nullptr && p->m_bBlock == UnknownEnumBlock::UNKNOWN_FALSE ) p = p->m_prev; } // Now p is on the block "level" diff --git a/test/unit/CBot/CBot.cpp b/test/unit/CBot/CBot.cpp new file mode 100644 index 00000000..ff5bc46d --- /dev/null +++ b/test/unit/CBot/CBot.cpp @@ -0,0 +1,221 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2015, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see http://gnu.org/licenses + */ + +#include "CBot/CBot.h" + +#include + +class CBotTestFail : public std::runtime_error { +public: + CBotTestFail(const std::string& message) : runtime_error(message) + { + } + + CBotTestFail(std::string message, int cursor1, int cursor2) : CBotTestFail(message) + { + this->cursor1 = cursor1; + this->cursor2 = cursor2; + } + + int cursor1 = -1; + int cursor2 = -1; +}; + +CBotTypResult cFail(CBotVar* &var, void* user) +{ + if (var != nullptr) + { + if (var->GetType() != CBotTypString) return CBotTypResult(CBotErrBadString); + var = var->GetNext(); + } + if (var != nullptr) return CBotTypResult(CBotErrOverParam); + return CBotTypResult(CBotTypVoid); +} + +bool rFail(CBotVar* var, CBotVar* result, int& exception, void* user) +{ + std::string message = "CBot test failed"; + if (var != nullptr) + { + message = var->GetValString(); + } + + throw CBotTestFail(message); +} + +class CBotUT : public testing::Test +{ +public: + void SetUp() + { + CBotProgram::Init(); + CBotProgram::AddFunction("FAIL", rFail, cFail); + } + + void TearDown() + { + CBotProgram::Free(); + } + +protected: + // Modified version of PutList from src/script/script.cpp + // Should be probably moved somewhere into the CBot library + void PrintVars(std::stringstream& ss, CBotVar* var, const std::string& baseName = "", bool bArray = false) + { + if (var == nullptr && !baseName.empty()) + { + ss << " " << baseName << " = null" << std::endl; + return; + } + + int index = 0; + while (var != nullptr) + { + CBotVar* pStatic = var->GetStaticVar(); // finds the static element + + std::string p = pStatic->GetName(); // variable name + + std::stringstream varName; + if (baseName.empty()) + { + varName << p; + } + else + { + if (bArray) + { + varName << baseName << "[" << index << "]"; + } + else + { + varName << baseName << "." << p; + } + } + + CBotType type = pStatic->GetType(); + if ( type < CBotTypBoolean ) + { + ss << " " << varName.str() << " = " << pStatic->GetValString() << std::endl; + } + else if ( type == CBotTypString ) + { + ss << " " << varName.str() << " = " << "\"" << pStatic->GetValString() << "\"" << std::endl; + } + else if ( type == CBotTypArrayPointer ) + { + PrintVars(ss, pStatic->GetItemList(), varName.str(), true); + } + else if ( type == CBotTypClass || + type == CBotTypPointer ) + { + PrintVars(ss, pStatic->GetItemList(), varName.str(), false); + } + else + { + ss << " " << varName.str() << " = ?" << std::endl; + } + + index ++; + var = var->GetNext(); + } + } + + void ExecuteTest(const std::string& code, CBotError expectedError = CBotNoErr) + { + auto program = std::unique_ptr(new CBotProgram()); + std::vector tests; + program->Compile(code, tests); + + CBotError error; + int cursor1, cursor2; + if (program->GetError(error, cursor1, cursor2)) + { + FAIL() << "Compile error - " << error << " (" << cursor1 << "-" << cursor2 << ")"; // TODO: Error messages are on Colobot side + } + + for (const std::string& test : tests) + { + try + { + program->Start(test); + while (!program->Run()); + program->GetError(error, cursor1, cursor2); + if (error != expectedError) + { + std::stringstream ss; + if (error != CBotNoErr) + { + ss << "RUNTIME ERROR - " << error; // TODO: Error messages are on Colobot side + } + else + { + ss << "No runtime error, expected " << expectedError; // TODO: Error messages are on Colobot side + cursor1 = cursor2 = -1; + } + throw CBotTestFail(ss.str(), cursor1, cursor2); + } + } + catch (const CBotTestFail& e) + { + std::stringstream ss; + ss << "*** Failed test " << test << ": " << e.what() << std::endl; + + std::string funcName; + program->GetRunPos(funcName, cursor1, cursor2); + if (!funcName.empty()) + { + ss << " while executing function " << funcName << " (" << cursor1 << "-" << cursor2 << ")" << std::endl; + } + else if(e.cursor1 >= 0 && e.cursor2 >= 0) + { + ss << " at unknown location " << e.cursor1 << "-" << e.cursor2 << std::endl; + } + ss << std::endl; + + ss << "Variables:" << std::endl; + int level = 0; + while (true) + { + CBotVar* var = program->GetStackVars(funcName, level--); + if (var == nullptr) break; + + ss << " Block " << -level << ":" << std::endl; + PrintVars(ss, var); + } + + ADD_FAILURE() << ss.str(); + } + } + } +}; + +TEST_F(CBotUT, Test) +{ + ExecuteTest("extern void EmptyTest() { }"); +} + +TEST_F(CBotUT, DISABLED_TestFail) +{ + ExecuteTest("extern void FailingTest() { FAIL(); } extern void AnotherFailingTest() { FAIL(\"This is a message\"); }"); +} + +TEST_F(CBotUT, DivideByZero) +{ + ExecuteTest("extern void DivideByZero() { float a = 5/0; }", CBotErrZeroDiv); +} diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 37eed553..cbf69564 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -10,6 +10,7 @@ set(UT_SOURCES main.cpp app/app_test.cpp CBot/CBotToken_test.cpp + CBot/CBot.cpp common/config_file_test.cpp graphics/engine/lightman_test.cpp math/func_test.cpp