From 681fbe42d23bb3418089780160fd4cf724c6e438 Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 10 Feb 2017 21:41:34 +0100 Subject: [PATCH] refactor tests to use the microtest framework --- Makefile | 14 +- test/lib/test.cpp | 59 ------ test/lib/test.h | 51 ------ test/{lib/select.cpp => select_tests.cpp} | 4 +- test/{lib/helper.h => sql_asserts.h} | 0 test/sql_grammar_test.cpp | 27 +-- test/sql_tests.cpp | 6 +- test/test.sh | 27 +-- test/thirdparty/microtest/microtest.h | 213 ++++++++++++++++++++++ test/{lib => }/valid_queries.sql | 3 +- 10 files changed, 251 insertions(+), 153 deletions(-) delete mode 100644 test/lib/test.cpp delete mode 100644 test/lib/test.h rename test/{lib/select.cpp => select_tests.cpp} (95%) rename test/{lib/helper.h => sql_asserts.h} (100%) create mode 100644 test/thirdparty/microtest/microtest.h rename test/{lib => }/valid_queries.sql (95%) diff --git a/Makefile b/Makefile index 0aad8f8..507372d 100644 --- a/Makefile +++ b/Makefile @@ -7,18 +7,18 @@ SRCPARSER = src/parser 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 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)/*") ALLTEST = $(shell find test/lib/ -name '*.cpp') $(shell find test/lib/ -name '*.h') # compile & link flages -CFLAGS = -std=c++11 -Wall -fPIC +CFLAGS = -std=c++11 -Wall -fPIC -g LIBFLAGS = -shared TARGET = libsqlparser.so INSTALL = /usr/local -CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ +CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ -g all: library @@ -58,7 +58,7 @@ format: ### Test ### ############ -test: $(BIN)/sql_tests $(BIN)/sql_grammar_test +test: $(BIN)/sql_tests bash test/test.sh # test whete @@ -68,8 +68,4 @@ test_install: $(BIN)/sql_tests: library @mkdir -p $(BIN)/ - $(CXX) $(CTESTFLAGS) $(TESTCPP) test/sql_tests.cpp -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 + $(CXX) $(CTESTFLAGS) $(TESTCPP) -o $(BIN)/sql_tests -lsqlparser diff --git a/test/lib/test.cpp b/test/lib/test.cpp deleted file mode 100644 index 2c37650..0000000 --- a/test/lib/test.cpp +++ /dev/null @@ -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& testNames() { - static std::vector testNames; - return testNames; - } - - static std::vector& tests() { - static std::vector 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; - } -} \ No newline at end of file diff --git a/test/lib/test.h b/test/lib/test.h deleted file mode 100644 index a9c051f..0000000 --- a/test/lib/test.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef __TEST_H__ -#define __TEST_H__ - -#include -#include -#include - - -#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 \ No newline at end of file diff --git a/test/lib/select.cpp b/test/select_tests.cpp similarity index 95% rename from test/lib/select.cpp rename to test/select_tests.cpp index bf5adab..90474d9 100644 --- a/test/lib/select.cpp +++ b/test/select_tests.cpp @@ -1,7 +1,7 @@ -#include "test.h" -#include "helper.h" +#include "thirdparty/microtest/microtest.h" +#include "sql_asserts.h" #include "SQLParser.h" using namespace hsql; diff --git a/test/lib/helper.h b/test/sql_asserts.h similarity index 100% rename from test/lib/helper.h rename to test/sql_asserts.h diff --git a/test/sql_grammar_test.cpp b/test/sql_grammar_test.cpp index ad707eb..46e5c63 100644 --- a/test/sql_grammar_test.cpp +++ b/test/sql_grammar_test.cpp @@ -5,6 +5,8 @@ #include #include #include + +#include "thirdparty/microtest/microtest.h" #include "SQLParser.h" using namespace hsql; @@ -30,10 +32,11 @@ std::vector readlines(std::string path) { #define STREQ(a, b) (std::string(a).compare(std::string(b)) == 0) -int main(int argc, char *argv[]) { - if (argc <= 1) { +TEST(AutoGrammarTest) { + const std::vector& args = mt::Runtime::args(); + if (args.size() <= 1) { fprintf(stderr, "Usage: grammar_test [--false] [-f path] query, ...\n"); - return -1; + return; } bool globalExpectFalse = false; @@ -41,12 +44,12 @@ int main(int argc, char *argv[]) { std::string filePath = ""; // Parse command line arguments - int i = 1; - for (; i < argc; ++i) { - if (STREQ(argv[i], "--false")) globalExpectFalse = true; - else if (STREQ(argv[i], "-f")) { + uint i = 1; + for (; i < args.size(); ++i) { + if (STREQ(args[i], "--false")) globalExpectFalse = true; + else if (STREQ(args[i], "-f")) { useFile = true; - filePath = argv[++i]; + filePath = args[++i]; } else { break; } @@ -58,7 +61,7 @@ int main(int argc, char *argv[]) { if (useFile) { queries = readlines(filePath); } 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) { printf("\033[0;32m{ ok} \033[0mAll %lu grammar tests completed successfully!\n", queries.size()); - return 0; } else { fprintf(stderr, "\033[0;31m{ failed} \033[0mSome grammar tests failed! %d out of %lu tests failed!\n", numFailed, queries.size()); - return -1; } -} \ No newline at end of file + ASSERT_EQ(numFailed, 0); +} + diff --git a/test/sql_tests.cpp b/test/sql_tests.cpp index f562e42..f0d25a6 100644 --- a/test/sql_tests.cpp +++ b/test/sql_tests.cpp @@ -2,8 +2,8 @@ * sql_tests.cpp */ -#include "lib/test.h" -#include "lib/helper.h" +#include "thirdparty/microtest/microtest.h" +#include "sql_asserts.h" #include "SQLParser.h" #include "sqlhelper.h" @@ -166,3 +166,5 @@ TEST(ExecuteStatementTest) { delete result; } + +TEST_MAIN(); \ No newline at end of file diff --git a/test/test.sh b/test/test.sh index 690465c..e7cae2a 100755 --- a/test/test.sh +++ b/test/test.sh @@ -6,23 +6,16 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./ RET=0 -# Running the tests. -bin/sql_grammar_test -f "test/lib/valid_queries.sql" -RET=$(($RET + $?)) +bin/sql_tests -f "test/valid_queries.sql" +RET=$? -bin/sql_tests -RET=$(($RET + $?)) - -# Running memory leak checks. -echo "" -echo "Running memory leak checks..." - -valgrind --leak-check=full --error-exitcode=1 \ - ./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 + $?)) +if [ $RET -eq 0 ]; then + # Running memory leak checks. + echo "" + echo "Running memory leak checks..." + valgrind --leak-check=full --error-exitcode=1 --log-fd=3 \ + ./bin/sql_tests -f "test/valid_queries.sql" 3>&1 >/dev/null 2>/dev/null + RET=$? +fi exit $RET diff --git a/test/thirdparty/microtest/microtest.h b/test/thirdparty/microtest/microtest.h new file mode 100644 index 0000000..f6cb4b7 --- /dev/null +++ b/test/thirdparty/microtest/microtest.h @@ -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 +#include +#include +#include + +//////////////// +// 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& tests() { + static std::vector 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& args(int argc = -1, char** argv = NULL) { + static std::vector 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__ diff --git a/test/lib/valid_queries.sql b/test/valid_queries.sql similarity index 95% rename from test/lib/valid_queries.sql rename to test/valid_queries.sql index ab5a6c5..c391f52 100644 --- a/test/lib/valid_queries.sql +++ b/test/valid_queries.sql @@ -43,4 +43,5 @@ DEALLOCATE PREPARE prep; !gibberish; !SELECT abc; !CREATE TABLE "table" FROM TBL FILE 'students.tbl';SELECT 1 -!CREATE TABLE "table" FROM TBL FILE 'students.tbl';1 \ No newline at end of file +!CREATE TABLE "table" FROM TBL FILE 'students.tbl';1 +!INSERT INTO test_table VALUESd (1, 2, 'test'); \ No newline at end of file