refactor tests to use the microtest framework

This commit is contained in:
Pedro 2017-02-10 21:41:34 +01:00
parent 2890cd368e
commit 681fbe42d2
10 changed files with 251 additions and 153 deletions

View File

@ -7,18 +7,18 @@ SRCPARSER = src/parser
PARSERFILES = $(SRCPARSER)/bison_parser.cpp $(SRCPARSER)/flex_lexer.cpp PARSERFILES = $(SRCPARSER)/bison_parser.cpp $(SRCPARSER)/flex_lexer.cpp
LIBCPP = $(shell find $(SRC) -name '*.cpp' -not -path "$(SRCPARSER)/*") $(SRCPARSER)/bison_parser.cpp $(SRCPARSER)/flex_lexer.cpp LIBCPP = $(shell find $(SRC) -name '*.cpp' -not -path "$(SRCPARSER)/*") $(SRCPARSER)/bison_parser.cpp $(SRCPARSER)/flex_lexer.cpp
LIBOBJ = $(LIBCPP:%.cpp=%.o) LIBOBJ = $(LIBCPP:%.cpp=%.o)
TESTCPP = $(shell find test/lib/ -name '*.cpp') TESTCPP = $(shell find test/ -name '*.cpp')
ALLLIB = $(shell find $(SRC) -name '*.cpp' -not -path "$(SRCPARSER)/*") $(shell find $(SRC) -name '*.h' -not -path "$(SRCPARSER)/*") ALLLIB = $(shell find $(SRC) -name '*.cpp' -not -path "$(SRCPARSER)/*") $(shell find $(SRC) -name '*.h' -not -path "$(SRCPARSER)/*")
ALLTEST = $(shell find test/lib/ -name '*.cpp') $(shell find test/lib/ -name '*.h') ALLTEST = $(shell find test/lib/ -name '*.cpp') $(shell find test/lib/ -name '*.h')
# compile & link flages # compile & link flages
CFLAGS = -std=c++11 -Wall -fPIC CFLAGS = -std=c++11 -Wall -fPIC -g
LIBFLAGS = -shared LIBFLAGS = -shared
TARGET = libsqlparser.so TARGET = libsqlparser.so
INSTALL = /usr/local INSTALL = /usr/local
CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ -g
all: library all: library
@ -58,7 +58,7 @@ format:
### Test ### ### Test ###
############ ############
test: $(BIN)/sql_tests $(BIN)/sql_grammar_test test: $(BIN)/sql_tests
bash test/test.sh bash test/test.sh
# test whete # test whete
@ -68,8 +68,4 @@ test_install:
$(BIN)/sql_tests: library $(BIN)/sql_tests: library
@mkdir -p $(BIN)/ @mkdir -p $(BIN)/
$(CXX) $(CTESTFLAGS) $(TESTCPP) test/sql_tests.cpp -o $(BIN)/sql_tests -lsqlparser $(CXX) $(CTESTFLAGS) $(TESTCPP) -o $(BIN)/sql_tests -lsqlparser
$(BIN)/sql_grammar_test: library
@mkdir -p $(BIN)/
$(CXX) $(CTESTFLAGS) test/sql_grammar_test.cpp -o $(BIN)/sql_grammar_test -lsqlparser

View File

@ -1,59 +0,0 @@
#include "test.h"
class TestsManager {
// Note: static initialization fiasco
// http://www.parashift.com/c++-faq-lite/static-init-order.html
// http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html
public:
static std::vector<std::string>& testNames() {
static std::vector<std::string> testNames;
return testNames;
}
static std::vector<void (*)(void)>& tests() {
static std::vector<void (*)(void)> tests;
return tests;
}
};
int AddTest(void (*foo)(void), std::string name) {
TestsManager::tests().push_back(foo);
TestsManager::testNames().push_back(name);
return 0;
}
size_t RunTests() {
size_t numFailed = 0;
for (size_t i = 0; i < TestsManager::tests().size(); ++i) {
printf("\033[0;32m{ running}\033[0m %s\n", TestsManager::testNames()[i].c_str());
try {
// Run test
(*TestsManager::tests()[i])();
printf("\033[0;32m{ ok}\033[0m %s\n", TestsManager::testNames()[i].c_str());
} catch (AssertionFailedException& e) {
printf("\033[1;31m{ failed} %s\n", TestsManager::testNames()[i].c_str());
printf("\tAssertion failed: %s\n\033[0m", e.what());
numFailed++;
}
}
return numFailed;
}
int main() {
size_t numFailed = RunTests();
if (numFailed == 0) {
return 0;
} else {
return -1;
}
}

View File

@ -1,51 +0,0 @@
#ifndef __TEST_H__
#define __TEST_H__
#include <vector>
#include <string>
#include <iostream>
#define TEST(name) \
void name();\
namespace g_dummy {\
int _##name = AddTest(name, #name);\
}\
void name()
#define ASSERT(cond) if (!(cond)) throw AssertionFailedException(#cond);
#define ASSERT_TRUE(cond) ASSERT(cond);
#define ASSERT_FALSE(cond) if (cond) throw AssertionFailedException(#cond);
#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL);
#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL);
#define ASSERT_STREQ(a, b) \
if (std::string(a).compare(std::string(b)) != 0) throw AssertionFailedException(#a " == " #b)
#define ASSERT_EQ(a, b) \
if (a != b) { \
std::cout << "Actual values: " << a << " != " << b << std::endl; \
} \
ASSERT(a == b);
class AssertionFailedException: public std::exception {
public:
AssertionFailedException(std::string msg) :
std::exception(),
_msg(msg) {};
virtual const char* what() const throw() {
return _msg.c_str();
}
protected:
std::string _msg;
};
int AddTest(void (*foo)(void), std::string name);
#endif

View File

@ -1,7 +1,7 @@
#include "test.h" #include "thirdparty/microtest/microtest.h"
#include "helper.h" #include "sql_asserts.h"
#include "SQLParser.h" #include "SQLParser.h"
using namespace hsql; using namespace hsql;

View File

@ -5,6 +5,8 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include "thirdparty/microtest/microtest.h"
#include "SQLParser.h" #include "SQLParser.h"
using namespace hsql; using namespace hsql;
@ -30,10 +32,11 @@ std::vector<std::string> readlines(std::string path) {
#define STREQ(a, b) (std::string(a).compare(std::string(b)) == 0) #define STREQ(a, b) (std::string(a).compare(std::string(b)) == 0)
int main(int argc, char *argv[]) { TEST(AutoGrammarTest) {
if (argc <= 1) { const std::vector<std::string>& args = mt::Runtime::args();
if (args.size() <= 1) {
fprintf(stderr, "Usage: grammar_test [--false] [-f path] query, ...\n"); fprintf(stderr, "Usage: grammar_test [--false] [-f path] query, ...\n");
return -1; return;
} }
bool globalExpectFalse = false; bool globalExpectFalse = false;
@ -41,12 +44,12 @@ int main(int argc, char *argv[]) {
std::string filePath = ""; std::string filePath = "";
// Parse command line arguments // Parse command line arguments
int i = 1; uint i = 1;
for (; i < argc; ++i) { for (; i < args.size(); ++i) {
if (STREQ(argv[i], "--false")) globalExpectFalse = true; if (STREQ(args[i], "--false")) globalExpectFalse = true;
else if (STREQ(argv[i], "-f")) { else if (STREQ(args[i], "-f")) {
useFile = true; useFile = true;
filePath = argv[++i]; filePath = args[++i];
} else { } else {
break; break;
} }
@ -58,7 +61,7 @@ int main(int argc, char *argv[]) {
if (useFile) { if (useFile) {
queries = readlines(filePath); queries = readlines(filePath);
} else { } else {
for (; i < argc; ++i) queries.push_back(argv[i]); for (; i < args.size(); ++i) queries.push_back(args[i]);
} }
@ -97,9 +100,9 @@ int main(int argc, char *argv[]) {
if (numFailed == 0) { if (numFailed == 0) {
printf("\033[0;32m{ ok} \033[0mAll %lu grammar tests completed successfully!\n", queries.size()); printf("\033[0;32m{ ok} \033[0mAll %lu grammar tests completed successfully!\n", queries.size());
return 0;
} else { } else {
fprintf(stderr, "\033[0;31m{ failed} \033[0mSome grammar tests failed! %d out of %lu tests failed!\n", numFailed, queries.size()); fprintf(stderr, "\033[0;31m{ failed} \033[0mSome grammar tests failed! %d out of %lu tests failed!\n", numFailed, queries.size());
return -1;
} }
} ASSERT_EQ(numFailed, 0);
}

View File

@ -2,8 +2,8 @@
* sql_tests.cpp * sql_tests.cpp
*/ */
#include "lib/test.h" #include "thirdparty/microtest/microtest.h"
#include "lib/helper.h" #include "sql_asserts.h"
#include "SQLParser.h" #include "SQLParser.h"
#include "sqlhelper.h" #include "sqlhelper.h"
@ -166,3 +166,5 @@ TEST(ExecuteStatementTest) {
delete result; delete result;
} }
TEST_MAIN();

View File

@ -6,23 +6,16 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
RET=0 RET=0
# Running the tests. bin/sql_tests -f "test/valid_queries.sql"
bin/sql_grammar_test -f "test/lib/valid_queries.sql" RET=$?
RET=$(($RET + $?))
bin/sql_tests if [ $RET -eq 0 ]; then
RET=$(($RET + $?)) # Running memory leak checks.
echo ""
# Running memory leak checks. echo "Running memory leak checks..."
echo "" valgrind --leak-check=full --error-exitcode=1 --log-fd=3 \
echo "Running memory leak checks..." ./bin/sql_tests -f "test/valid_queries.sql" 3>&1 >/dev/null 2>/dev/null
RET=$?
valgrind --leak-check=full --error-exitcode=1 \ fi
./bin/sql_grammar_test -f "test/lib/valid_queries.sql" >> /dev/null
RET=$(($RET + $?))
valgrind --leak-check=full --error-exitcode=1 \
./bin/sql_tests -f "test/lib/valid_queries.sql" >> /dev/null
RET=$(($RET + $?))
exit $RET exit $RET

213
test/thirdparty/microtest/microtest.h vendored Normal file
View File

@ -0,0 +1,213 @@
//
// microtest
//
// URL: https://github.com/torpedro/microtest
// Author: Pedro Flemming (http://torpedro.com/)
// License: MIT License (https://github.com/torpedro/microtest/blob/master/LICENSE)
// Copyright (c) 2017 Pedro Flemming
//
// This is a small header-only C++ unit testing framework.
// It allows to define small unit tests with set of assertions available.
//
#ifndef __MICROTEST_H__
#define __MICROTEST_H__
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
////////////////
// Assertions //
////////////////
#define ASSERT(cond)\
ASSERT_TRUE(cond);
#define ASSERT_TRUE(cond)\
if (!(cond)) throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
#define ASSERT_FALSE(cond)\
if (cond) throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
#define ASSERT_NULL(value)\
ASSERT_TRUE(value == NULL);
#define ASSERT_NOTNULL(value)\
ASSERT_TRUE(value != NULL);
#define ASSERT_STREQ(a, b)\
if (std::string(a).compare(std::string(b)) != 0)\
throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__);
#define ASSERT_EQ(a, b)\
if (a != b) {\
printf("%s{ info} %s", mt::yellow(), mt::def());\
std::cout << "Actual values: " << a << " != " << b << std::endl;\
}\
ASSERT(a == b);
#define ASSERT_NEQ(a, b)\
if (a == b) {\
printf("%s{ info} %s", mt::yellow(), mt::def());\
std::cout << "Actual values: " << a << " == " << b << std::endl;\
}\
ASSERT(a != b);
////////////////
// Unit Tests //
////////////////
#define TEST(name) \
void name();\
namespace {\
bool __##name = mt::TestsManager::AddTest(name, #name);\
}\
void name()
///////////////
// Framework //
///////////////
namespace mt {
inline const char* red() {
return "\033[1;31m";
}
inline const char* green() {
return "\033[0;32m";
}
inline const char* yellow() {
return "\033[0;33m";
}
inline const char* def() {
return "\033[0m";
}
inline void printRunning(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ running}%s %s\n", green(), def(), message);
}
inline void printOk(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ ok}%s %s\n", green(), def(), message);
}
inline void printFailed(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ failed} %s%s\n", red(), message, def());
}
// Exception that is thrown when an assertion fails.
class AssertFailedException : public std::exception {
public:
AssertFailedException(std::string description, std::string filepath, int line) :
std::exception(),
description_(description),
filepath_(filepath),
line_(line) {};
virtual const char* what() const throw() {
return description_.c_str();
}
inline const char* getFilepath() {
return filepath_.c_str();
}
inline int getLine() {
return line_;
}
protected:
std::string description_;
std::string filepath_;
int line_;
};
class TestsManager {
// Note: static initialization fiasco
// http://www.parashift.com/c++-faq-lite/static-init-order.html
// http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html
public:
struct Test {
const char* name;
void (*fn)(void);
};
static std::vector<Test>& tests() {
static std::vector<Test> tests_;
return tests_;
}
// Adds a new test to the current set of tests.
// Returns false if a test with the same name already exists.
inline static bool AddTest(void (*fn)(void), const char* name) {
tests().push_back({ name, fn });
return true;
}
// Run all tests that are registered.
// Returns the number of tests that failed.
inline static size_t RunAllTests(FILE* file = stdout) {
size_t num_failed = 0;
for (const Test& test : tests()) {
// Run the test.
// If an AsserFailedException is thrown, the test has failed.
try {
printRunning(test.name, file);
(*test.fn)();
printOk(test.name, file);
} catch (AssertFailedException& e) {
printFailed(test.name, file);
fprintf(file, " %sAssertion failed: %s%s\n",
red(), e.what(), def());
fprintf(file, " %s%s:%d%s\n",
red(), e.getFilepath(), e.getLine(), def());
++num_failed;
}
}
return num_failed;
}
};
// Class that will capture the arguments passed to the program.
class Runtime {
public:
static const std::vector<std::string>& args(int argc = -1, char** argv = NULL) {
static std::vector<std::string> args_;
if (argc >= 0) {
for (int i = 0; i < argc; ++i) {
args_.push_back(argv[i]);
}
}
return args_;
}
};
}
#define TEST_MAIN() \
int main(int argc, char *argv[]) {\
mt::Runtime::args(argc, argv);\
\
size_t num_failed = mt::TestsManager::RunAllTests(stdout);\
if (num_failed == 0) {\
fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def());\
return 0;\
} else {\
double percentage = 100.0 * num_failed / mt::TestsManager::tests().size();\
fprintf(stderr, "%s{ summary} %lu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def());\
return -1;\
}\
}
#endif // __MICROTEST_H__

View File

@ -43,4 +43,5 @@ DEALLOCATE PREPARE prep;
!gibberish; !gibberish;
!SELECT abc; !SELECT abc;
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';SELECT 1 !CREATE TABLE "table" FROM TBL FILE 'students.tbl';SELECT 1
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';1 !CREATE TABLE "table" FROM TBL FILE 'students.tbl';1
!INSERT INTO test_table VALUESd (1, 2, 'test');