I haven't actually written any tests yet, but now I can at least easily write tests when I want to. Also, just having one .t.cc file for each header means we get to test each header in isolation, to prevent include order issues.opengl-renderer-broken
@@ -19,3 +19,29 @@ set_target_properties(libswan PROPERTIES OUTPUT_NAME swan) | |||
target_link_libraries(libswan ${libraries}) | |||
install(TARGETS libswan DESTINATION swan/libswan) | |||
add_executable(test_libswan | |||
test/lib/test.cc | |||
test/Animation.t.cc | |||
test/Body.t.cc | |||
test/BoundingBox.t.cc | |||
test/common.t.cc | |||
test/Entity.t.cc | |||
test/Game.t.cc | |||
test/Item.t.cc | |||
test/Mod.t.cc | |||
test/OS.t.cc | |||
test/Resource.t.cc | |||
test/SRF.t.cc | |||
test/swan.t.cc | |||
test/Tile.t.cc | |||
test/Timer.t.cc | |||
test/util.t.cc | |||
test/Vector2.t.cc | |||
test/Win.t.cc | |||
test/World.t.cc | |||
test/WorldGen.t.cc | |||
test/WorldPlane.t.cc) | |||
target_link_libraries(test_libswan libswan) | |||
target_include_directories(test_libswan | |||
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include/swan") |
@@ -0,0 +1,3 @@ | |||
#include "Animation.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Body.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "BoundingBox.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Entity.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Game.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Item.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Mod.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "OS.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Resource.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "SRF.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Tile.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Timer.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Vector2.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "Win.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "World.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "WorldGen.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "WorldPlane.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "common.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,114 @@ | |||
#include "test.h" | |||
#include <stdlib.h> | |||
#include <string_view> | |||
#include <vector> | |||
#include <algorithm> | |||
#include <iostream> | |||
#include <sstream> | |||
namespace testlib { | |||
static const std::string color_reset = "\033[0m"; | |||
static const std::string color_highlight = "\033[1m"; | |||
static const std::string color_testing = color_highlight; | |||
static const std::string color_desc = "\033[33m"; | |||
static const std::string color_maybe = "\033[35m"; | |||
static const std::string color_success = "\033[32m"; | |||
static const std::string color_fail = "\033[31m"; | |||
static const std::string color_errormsg = "\033[95m"; | |||
std::string color(const std::string &color, std::string_view str) { | |||
return std::string(color) + std::string(str) + std::string(color_reset); | |||
} | |||
struct TestCase { | |||
TestCase(TestSpec *spec): | |||
func(spec->func), description(spec->description), | |||
filename(spec->filename), linenum(spec->linenum), index(spec->index) {} | |||
void (*func)(); | |||
std::string_view description; | |||
std::string_view filename; | |||
int linenum; | |||
int index; | |||
}; | |||
static std::vector<TestCase> cases; | |||
void addTestCase(TestSpec *testcase) { | |||
cases.emplace_back(testcase); | |||
} | |||
static std::stringstream printFailure(const std::string &msg) { | |||
std::stringstream str; | |||
str | |||
<< "\r" << color(color_highlight + color_fail, "✕ ") | |||
<< color(color_fail, "Failed: ") << "\n" | |||
<< " " << msg << "\n"; | |||
return str; | |||
} | |||
static std::stringstream printFailure(const TestFailure &failure) { | |||
std::stringstream str; | |||
str | |||
<< printFailure(failure.message).str() | |||
<< " at " << failure.filename << ":" << failure.linenum << "\n"; | |||
return str; | |||
} | |||
} | |||
int main() { | |||
using namespace testlib; | |||
std::sort(begin(cases), end(cases), [](TestCase &a, TestCase &b) { | |||
if (a.filename != b.filename) | |||
return a.filename < b.filename; | |||
return a.index < b.index; | |||
}); | |||
std::string_view currfile = ""; | |||
bool failed = false; | |||
for (TestCase &testcase: cases) { | |||
if (currfile != testcase.filename) { | |||
currfile = testcase.filename; | |||
size_t lastslash = currfile.find_last_of('/'); | |||
std::cout << '\n' << color(color_testing, currfile.substr(lastslash + 1)) << ":\n"; | |||
} | |||
std::cout | |||
<< color(color_highlight + color_maybe, "? ") | |||
<< color(color_maybe, "Testing: ") | |||
<< color(color_desc, testcase.description) << " " << std::flush; | |||
try { | |||
testcase.func(); | |||
std::cout | |||
<< "\r" << color(color_highlight + color_success, "✓ ") | |||
<< color(color_success, "Success: ") << "\n"; | |||
} catch (const TestFailure &failure) { | |||
failed = true; | |||
std::cout << printFailure(failure).str(); | |||
} catch (const std::exception &ex) { | |||
failed = true; | |||
std::cout << printFailure(ex.what()).str(); | |||
} catch (const std::string &str) { | |||
failed = true; | |||
std::cout << printFailure(str).str(); | |||
} catch (const char *str) { | |||
failed = true; | |||
std::cout << printFailure(str).str(); | |||
} catch (...) { | |||
failed = true; | |||
std::cout << printFailure("Unknown error.").str(); | |||
} | |||
} | |||
std::cout << '\n'; | |||
if (failed) | |||
return EXIT_FAILURE; | |||
return EXIT_SUCCESS; | |||
} |
@@ -0,0 +1,61 @@ | |||
#pragma once | |||
#include <string> | |||
namespace testlib { | |||
struct TestFailure { | |||
TestFailure(const char *msg, const char *file, int line): | |||
message(msg), filename(file), linenum(line) {} | |||
const char *message; | |||
const char *filename; | |||
int linenum; | |||
}; | |||
struct TestSpec; | |||
void addTestCase(TestSpec *testcase); | |||
struct TestSpec { | |||
TestSpec(void (*f)(), const char *desc, const char *file, int line, int idx): | |||
func(f), description(desc), filename(file), linenum(line), index(idx) { | |||
addTestCase(this); | |||
} | |||
void (*func)(); | |||
const char *description; | |||
const char *filename; | |||
int linenum; | |||
int index; | |||
}; | |||
} | |||
#define test3(name, id) name ## id | |||
#define test2(uniqid, desc) \ | |||
static void test3(_test_func_, uniqid)(); \ | |||
static __attribute__((unused)) testlib::TestSpec test3(_test_register_, uniqid)( \ | |||
&test3(_test_func_, uniqid), desc, __FILE__, __LINE__, uniqid); \ | |||
static void test3(_test_func_, uniqid)() | |||
#define test(desc) test2(__COUNTER__, desc) | |||
#define expect(expr) do { \ | |||
if (!(expr)) { \ | |||
throw testlib::TestFailure( \ | |||
"Expected '" #expr "' to be true.", __FILE__, __LINE__); \ | |||
} \ | |||
} while (0) | |||
#define expecteq(a, b) do { \ | |||
if ((a) != (b)) { \ | |||
throw testlib::TestFailure( \ | |||
"Expected '" #a "' to equal '" #b "'.", __FILE__, __LINE__); \ | |||
} \ | |||
} while (0) | |||
#define expectneq(a, b) do { \ | |||
if ((a) == (b)) { \ | |||
throw testlib::TestFailure( \ | |||
"Expected '" #a "' to not equal '" #b "'.", __FILE__, __LINE__); \ | |||
} \ | |||
} while (0) |
@@ -0,0 +1,3 @@ | |||
#include "swan.h" | |||
#include "lib/test.h" |
@@ -0,0 +1,3 @@ | |||
#include "util.h" | |||
#include "lib/test.h" |