346 lines
7.8 KiB
C++
346 lines
7.8 KiB
C++
|
#pragma once
|
||
|
/**
|
||
|
@file
|
||
|
@brief unit test class
|
||
|
|
||
|
Copyright (C) 2008 Cybozu Labs, Inc., all rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <string>
|
||
|
#include <list>
|
||
|
#include <iostream>
|
||
|
#include <utility>
|
||
|
#if defined(_MSC_VER) && (MSC_VER <= 1500)
|
||
|
#include <cybozu/inttype.hpp>
|
||
|
#else
|
||
|
#include <stdint.h>
|
||
|
#endif
|
||
|
|
||
|
namespace cybozu { namespace test {
|
||
|
|
||
|
class AutoRun {
|
||
|
typedef void (*Func)();
|
||
|
typedef std::list<std::pair<const char*, Func> > UnitTestList;
|
||
|
public:
|
||
|
AutoRun()
|
||
|
: init_(0)
|
||
|
, term_(0)
|
||
|
, okCount_(0)
|
||
|
, ngCount_(0)
|
||
|
, exceptionCount_(0)
|
||
|
{
|
||
|
}
|
||
|
void setup(Func init, Func term)
|
||
|
{
|
||
|
init_ = init;
|
||
|
term_ = term;
|
||
|
}
|
||
|
void append(const char *name, Func func)
|
||
|
{
|
||
|
list_.push_back(std::make_pair(name, func));
|
||
|
}
|
||
|
void set(bool isOK)
|
||
|
{
|
||
|
if (isOK) {
|
||
|
okCount_++;
|
||
|
} else {
|
||
|
ngCount_++;
|
||
|
}
|
||
|
}
|
||
|
std::string getBaseName(const std::string& name) const
|
||
|
{
|
||
|
#ifdef _WIN32
|
||
|
const char sep = '\\';
|
||
|
#else
|
||
|
const char sep = '/';
|
||
|
#endif
|
||
|
size_t pos = name.find_last_of(sep);
|
||
|
std::string ret = name.substr(pos + 1);
|
||
|
pos = ret.find('.');
|
||
|
return ret.substr(0, pos);
|
||
|
}
|
||
|
int run(int, char *argv[])
|
||
|
{
|
||
|
std::string msg;
|
||
|
try {
|
||
|
if (init_) init_();
|
||
|
for (UnitTestList::const_iterator i = list_.begin(), ie = list_.end(); i != ie; ++i) {
|
||
|
std::cout << "ctest:module=" << i->first << std::endl;
|
||
|
try {
|
||
|
(i->second)();
|
||
|
} catch (std::exception& e) {
|
||
|
exceptionCount_++;
|
||
|
std::cout << "ctest: " << i->first << " is stopped by exception " << e.what() << std::endl;
|
||
|
} catch (...) {
|
||
|
exceptionCount_++;
|
||
|
std::cout << "ctest: " << i->first << " is stopped by unknown exception" << std::endl;
|
||
|
}
|
||
|
}
|
||
|
if (term_) term_();
|
||
|
} catch (std::exception& e) {
|
||
|
msg = std::string("ctest:err:") + e.what();
|
||
|
} catch (...) {
|
||
|
msg = "ctest:err: catch unknown exception";
|
||
|
}
|
||
|
fflush(stdout);
|
||
|
if (msg.empty()) {
|
||
|
std::cout << "ctest:name=" << getBaseName(*argv)
|
||
|
<< ", module=" << list_.size()
|
||
|
<< ", total=" << (okCount_ + ngCount_ + exceptionCount_)
|
||
|
<< ", ok=" << okCount_
|
||
|
<< ", ng=" << ngCount_
|
||
|
<< ", exception=" << exceptionCount_ << std::endl;
|
||
|
return 0;
|
||
|
} else {
|
||
|
std::cout << msg << std::endl;
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
static inline AutoRun& getInstance()
|
||
|
{
|
||
|
static AutoRun instance;
|
||
|
return instance;
|
||
|
}
|
||
|
private:
|
||
|
Func init_;
|
||
|
Func term_;
|
||
|
int okCount_;
|
||
|
int ngCount_;
|
||
|
int exceptionCount_;
|
||
|
UnitTestList list_;
|
||
|
};
|
||
|
|
||
|
static AutoRun& autoRun = AutoRun::getInstance();
|
||
|
|
||
|
inline void test(bool ret, const std::string& msg, const std::string& param, const char *file, int line)
|
||
|
{
|
||
|
autoRun.set(ret);
|
||
|
if (!ret) {
|
||
|
printf("%s(%d):ctest:%s(%s);\n", file, line, msg.c_str(), param.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template<typename T, typename U>
|
||
|
bool isEqual(const T& lhs, const U& rhs)
|
||
|
{
|
||
|
return lhs == rhs;
|
||
|
}
|
||
|
|
||
|
inline bool isEqual(const char *lhs, const char *rhs)
|
||
|
{
|
||
|
return strcmp(lhs, rhs) == 0;
|
||
|
}
|
||
|
inline bool isEqual(char *lhs, const char *rhs)
|
||
|
{
|
||
|
return strcmp(lhs, rhs) == 0;
|
||
|
}
|
||
|
inline bool isEqual(const char *lhs, char *rhs)
|
||
|
{
|
||
|
return strcmp(lhs, rhs) == 0;
|
||
|
}
|
||
|
inline bool isEqual(char *lhs, char *rhs)
|
||
|
{
|
||
|
return strcmp(lhs, rhs) == 0;
|
||
|
}
|
||
|
// avoid to compare float directly
|
||
|
inline bool isEqual(float lhs, float rhs)
|
||
|
{
|
||
|
union fi {
|
||
|
float f;
|
||
|
uint32_t i;
|
||
|
} lfi, rfi;
|
||
|
lfi.f = lhs;
|
||
|
rfi.f = rhs;
|
||
|
return lfi.i == rfi.i;
|
||
|
}
|
||
|
// avoid to compare double directly
|
||
|
inline bool isEqual(double lhs, double rhs)
|
||
|
{
|
||
|
union di {
|
||
|
double d;
|
||
|
uint64_t i;
|
||
|
} ldi, rdi;
|
||
|
ldi.d = lhs;
|
||
|
rdi.d = rhs;
|
||
|
return ldi.i == rdi.i;
|
||
|
}
|
||
|
|
||
|
} } // cybozu::test
|
||
|
|
||
|
#ifndef CYBOZU_TEST_DISABLE_AUTO_RUN
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
return cybozu::test::autoRun.run(argc, argv);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
alert if !x
|
||
|
@param x [in]
|
||
|
*/
|
||
|
#define CYBOZU_TEST_ASSERT(x) cybozu::test::test(!!(x), "CYBOZU_TEST_ASSERT", #x, __FILE__, __LINE__)
|
||
|
|
||
|
/**
|
||
|
alert if x != y
|
||
|
@param x [in]
|
||
|
@param y [in]
|
||
|
*/
|
||
|
#define CYBOZU_TEST_EQUAL(x, y) { \
|
||
|
bool eq = cybozu::test::isEqual(x, y); \
|
||
|
cybozu::test::test(eq, "CYBOZU_TEST_EQUAL", #x ", " #y, __FILE__, __LINE__); \
|
||
|
if (!eq) { \
|
||
|
std::cout << "ctest: lhs=" << (x) << std::endl; \
|
||
|
std::cout << "ctest: rhs=" << (y) << std::endl; \
|
||
|
} \
|
||
|
}
|
||
|
/**
|
||
|
alert if fabs(x, y) >= eps
|
||
|
@param x [in]
|
||
|
@param y [in]
|
||
|
*/
|
||
|
#define CYBOZU_TEST_NEAR(x, y, eps) { \
|
||
|
bool isNear = fabs((x) - (y)) < eps; \
|
||
|
cybozu::test::test(isNear, "CYBOZU_TEST_NEAR", #x ", " #y, __FILE__, __LINE__); \
|
||
|
if (!isNear) { \
|
||
|
std::cout << "ctest: lhs=" << (x) << std::endl; \
|
||
|
std::cout << "ctest: rhs=" << (y) << std::endl; \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
#define CYBOZU_TEST_EQUAL_POINTER(x, y) { \
|
||
|
bool eq = x == y; \
|
||
|
cybozu::test::test(eq, "CYBOZU_TEST_EQUAL_POINTER", #x ", " #y, __FILE__, __LINE__); \
|
||
|
if (!eq) { \
|
||
|
std::cout << "ctest: lhs=" << static_cast<const void*>(x) << std::endl; \
|
||
|
std::cout << "ctest: rhs=" << static_cast<const void*>(y) << std::endl; \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
always alert
|
||
|
@param msg [in]
|
||
|
*/
|
||
|
#define CYBOZU_TEST_FAIL(msg) cybozu::test::test(false, "CYBOZU_TEST_FAIL", msg, __FILE__, __LINE__)
|
||
|
|
||
|
/**
|
||
|
verify message in exception
|
||
|
*/
|
||
|
#define CYBOZU_TEST_EXCEPTION_MESSAGE(statement, Exception, msg) \
|
||
|
{ \
|
||
|
int ret = 0; \
|
||
|
std::string errMsg; \
|
||
|
try { \
|
||
|
statement; \
|
||
|
ret = 1; \
|
||
|
} catch (const Exception& e) { \
|
||
|
errMsg = e.what(); \
|
||
|
if (errMsg.find(msg) == std::string::npos) { \
|
||
|
ret = 2; \
|
||
|
} \
|
||
|
} catch (...) { \
|
||
|
ret = 3; \
|
||
|
} \
|
||
|
if (ret) { \
|
||
|
cybozu::test::test(false, "CYBOZU_TEST_EXCEPTION_MESSAGE", #statement ", " #Exception ", " #msg, __FILE__, __LINE__); \
|
||
|
if (ret == 1) { \
|
||
|
std::cout << "ctest: no exception" << std::endl; \
|
||
|
} else if (ret == 2) { \
|
||
|
std::cout << "ctest: bad exception msg:" << errMsg << std::endl; \
|
||
|
} else { \
|
||
|
std::cout << "ctest: unexpected exception" << std::endl; \
|
||
|
} \
|
||
|
} else { \
|
||
|
cybozu::test::autoRun.set(true); \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
#define CYBOZU_TEST_EXCEPTION(statement, Exception) \
|
||
|
{ \
|
||
|
int ret = 0; \
|
||
|
try { \
|
||
|
statement; \
|
||
|
ret = 1; \
|
||
|
} catch (const Exception&) { \
|
||
|
} catch (...) { \
|
||
|
ret = 2; \
|
||
|
} \
|
||
|
if (ret) { \
|
||
|
cybozu::test::test(false, "CYBOZU_TEST_EXCEPTION", #statement ", " #Exception, __FILE__, __LINE__); \
|
||
|
if (ret == 1) { \
|
||
|
std::cout << "ctest: no exception" << std::endl; \
|
||
|
} else { \
|
||
|
std::cout << "ctest: unexpected exception" << std::endl; \
|
||
|
} \
|
||
|
} else { \
|
||
|
cybozu::test::autoRun.set(true); \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
verify statement does not throw
|
||
|
*/
|
||
|
#define CYBOZU_TEST_NO_EXCEPTION(statement) \
|
||
|
try { \
|
||
|
statement; \
|
||
|
cybozu::test::autoRun.set(true); \
|
||
|
} catch (...) { \
|
||
|
cybozu::test::test(false, "CYBOZU_TEST_NO_EXCEPTION", #statement, __FILE__, __LINE__); \
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
append auto unit test
|
||
|
@param name [in] module name
|
||
|
*/
|
||
|
#define CYBOZU_TEST_AUTO(name) \
|
||
|
void cybozu_test_ ## name(); \
|
||
|
struct cybozu_test_local_ ## name { \
|
||
|
cybozu_test_local_ ## name() \
|
||
|
{ \
|
||
|
cybozu::test::autoRun.append(#name, cybozu_test_ ## name); \
|
||
|
} \
|
||
|
} cybozu_test_local_instance_ ## name; \
|
||
|
void cybozu_test_ ## name()
|
||
|
|
||
|
/**
|
||
|
append auto unit test with fixture
|
||
|
@param name [in] module name
|
||
|
*/
|
||
|
#define CYBOZU_TEST_AUTO_WITH_FIXTURE(name, Fixture) \
|
||
|
void cybozu_test_ ## name(); \
|
||
|
void cybozu_test_real_ ## name() \
|
||
|
{ \
|
||
|
Fixture f; \
|
||
|
cybozu_test_ ## name(); \
|
||
|
} \
|
||
|
struct cybozu_test_local_ ## name { \
|
||
|
cybozu_test_local_ ## name() \
|
||
|
{ \
|
||
|
cybozu::test::autoRun.append(#name, cybozu_test_real_ ## name); \
|
||
|
} \
|
||
|
} cybozu_test_local_instance_ ## name; \
|
||
|
void cybozu_test_ ## name()
|
||
|
|
||
|
/**
|
||
|
setup fixture
|
||
|
@param Fixture [in] class name of fixture
|
||
|
@note cstr of Fixture is called before test and dstr of Fixture is called after test
|
||
|
*/
|
||
|
#define CYBOZU_TEST_SETUP_FIXTURE(Fixture) \
|
||
|
Fixture *cybozu_test_local_fixture; \
|
||
|
void cybozu_test_local_init() \
|
||
|
{ \
|
||
|
cybozu_test_local_fixture = new Fixture(); \
|
||
|
} \
|
||
|
void cybozu_test_local_term() \
|
||
|
{ \
|
||
|
delete cybozu_test_local_fixture; \
|
||
|
} \
|
||
|
struct cybozu_test_local_fixture_setup_ { \
|
||
|
cybozu_test_local_fixture_setup_() \
|
||
|
{ \
|
||
|
cybozu::test::autoRun.setup(cybozu_test_local_init, cybozu_test_local_term); \
|
||
|
} \
|
||
|
} cybozu_test_local_fixture_setup_instance_;
|