Skip to content

Instantly share code, notes, and snippets.

@cjameshuff
Created December 23, 2013 00:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cjameshuff/8090309 to your computer and use it in GitHub Desktop.
Save cjameshuff/8090309 to your computer and use it in GitHub Desktop.
// *****************************************************************************
// The MIT License (MIT)
//
// Copyright (c) 2013 Christopher James Huff
// https://github.com/cjameshuff/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// *****************************************************************************
//! \file simpletest.h
//! \brief A basic unit testing framework for C++11.
//!
#include <string>
#include <cstdio>
#include <list>
#include <stack>
#include <boost/format.hpp>
#define SIMPLETEST_DECL TestManager testManager;
// ***************************************************************************
// Display attributes
// 0 Reset all attributes
// 1 Bright
// 2 Dim
// 4 Underscore
// 5 Blink
// 7 Reverse
// 8 Hidden
// Foreground colors:
// 30 Black
// 31 Red
// 32 Green
// 33 Yellow
// 34 Blue
// 35 Magenta
// 36 Cyan
// 37 White
// Background Colours
// 40 Black
// 41 Red
// 42 Green
// 43 Yellow
// 44 Blue
// 45 Magenta
// 46 Cyan
// 47 White (gray)
#define FAIL_TEST_ATTR "\033[1;31;43m"
#define PASS_TEST_ATTR "\033[1;32m"
#define FAIL_SUMM_ATTR "\033[1;31m"
#define PASS_SUMM_ATTR "\033[1;32m"
#define RESET_ATTR "\033[0m"
struct TestContext {
std::string groupName;
int testCounter;
bool stop;
};
struct TestLog {
std::string groupName;
int testNumber;
std::string description;
bool passed;
};
struct TestManager {
int testsPassed = 0;
int testsFailed = 0;
bool previousTestFailed = false;
std::stack<TestContext> testContext;
std::list<TestLog> testLog;
auto SummarizeTests() -> void;
~TestManager() {SummarizeTests();}
};
extern TestManager testManager;
inline TestContext & CurrentTestGroup() {return testManager.testContext.top();}
//! Start a test group. At least one test group must exist.
inline auto StartTestGroup(const std::string & groupName) -> void
{
if(testManager.testContext.empty())
{
testManager.testContext.push({
groupName,
0,
false
});
}
else
{
testManager.testContext.push({
CurrentTestGroup().groupName + ":" + groupName,
0
});
}
}
//! End a test group.
inline auto EndTestGroup(const std::string & groupName) -> void
{
testManager.testContext.pop();
}
//! \brief Report result of a test, with string giving a brief description.
inline auto ReportTestResult(const std::string & desc, bool passed) -> void
{
testManager.testLog.push_back({
CurrentTestGroup().groupName,
CurrentTestGroup().testCounter,
desc,
passed
});
if(passed)
{
std::clog << PASS_TEST_ATTR;
std::clog << "PASS: ";
++testManager.testsPassed;
}
else
{
std::clog << FAIL_TEST_ATTR;
std::clog << "FAIL: ";
++testManager.testsFailed;
}
std::clog << CurrentTestGroup().groupName << "[" << CurrentTestGroup().testCounter << "] " << desc;
std::clog << RESET_ATTR << std::endl;
++(CurrentTestGroup().testCounter);
testManager.previousTestFailed = !passed;
}
//! \brief Report summary of test results, with pass/fail counts.
inline auto TestManager::SummarizeTests() -> void
{
std::clog << "\n----------------------------------------" << std::endl;
std::clog << ((testManager.testsFailed)? FAIL_SUMM_ATTR : PASS_SUMM_ATTR);
std::clog << testManager.testsPassed << " tests passed, " << testManager.testsFailed << " tests failed" << std::endl;
std::clog << RESET_ATTR;
}
// ***************************************************************************
template<typename Test>
inline auto Should(const std::string & desc, const Test & test) -> void {
if(CurrentTestGroup().stop)
return;
bool passed = false;
try {passed = test();}
catch(std::exception err) {
std::clog << "Unexpected exception caught" << std::endl;
std::clog << err.what() << std::endl;
passed = false;
}
catch(...) {
std::clog << "Unexpected exception caught" << std::endl;
passed = false;
}
ReportTestResult(desc, passed);
}
// ***************************************************************************
template<typename T, typename Formatter>
inline auto Should(std::string desc, T * value, T * expected, size_t count, Formatter fmt) -> void
{
Should(desc, [&]{
bool pass = true;
for(size_t j = 0; j < count && pass; ++j)
pass = value[j] == expected[j];
if(!pass)
{
for(size_t j = 0; j < count; ++j)
{
bool mismatch = value[j] != expected[j];
std::clog << (mismatch? FAIL_TEST_ATTR: PASS_TEST_ATTR);
std::clog << boost::format("%08d ")%j;
fmt(value[j]);
std::clog << (mismatch? " != ": " == ");
fmt(expected[j]);
std::clog << RESET_ATTR;
std::clog << std::endl;
}
}
return pass;
});
}
template<typename T, typename Formatter>
inline auto Should(std::string desc, T * value, T * expected, size_t count) -> void
{
Should(desc, value, expected, count, [](T & x){std::clog << x;});
}
// ***************************************************************************
template<typename T, typename Formatter>
inline auto Should(std::string desc, T value, T expected, Formatter fmt) -> void
{
Should(desc, [&]{
bool pass = value == expected;
if(!pass)
{
std::clog << FAIL_TEST_ATTR;
fmt(value);
std::clog << " != ";
fmt(expected);
std::clog << RESET_ATTR;
std::clog << std::endl;
}
return pass;
});
}
template<typename T, typename Formatter>
inline auto Should(std::string desc, T value, T expected) -> void
{
Should(desc, value, expected, [](T & x){std::clog << x;});
}
inline auto Should(std::string desc, void * value, void * expected) -> void
{
Should(desc, value, expected, [](const void * x){std::clog << boost::format("%16X")%x;});
}
// ***************************************************************************
template<typename Test>
auto ShouldNotThrow(std::string desc, const Test & test) -> void {
if(CurrentTestGroup().stop)
return;
bool passed = true;
try {test();}
catch(...) {
std::clog << "Unexpected exception caught" << std::endl;
passed = false;
}
ReportTestResult(desc, passed);
}
template<typename Test>
auto ShouldThrowAny(const std::string & desc, const Test & test) -> void {
if(CurrentTestGroup().stop)
return;
bool passed = false;
try {test();}
catch(...) {
std::clog << "Caught expected exception" << std::endl;
passed = true;
}
ReportTestResult(desc, passed);
}
template<typename Excep, typename Test>
auto ShouldThrow(const std::string & desc, const Test & test) -> void {
if(CurrentTestGroup().stop)
return;
bool passed = false;
try {test();}
catch(Excep & excep) {
std::clog << "Caught expected exception" << std::endl;
passed = true;
}
catch(...) {
std::clog << "Unexpected exception caught" << std::endl;
passed = false;
}
ReportTestResult(desc, passed);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment