#ifndef _RVC_SPTEST_H__ #define _RVC_SPTEST_H__ #pragma once #include "SpBase.h" #include "SpHelper.h" #include "SpComm.hpp" #include "SpUtility.h" #include //helper macro for test temporary. #define TEST_MODE_SWITCH_ON 1 #if (defined(TEST_MODE_SWITCH_ON) && TEST_MODE_SWITCH_ON == 1) #define IFFAILRET(func) \ do { \ ErrorCodeEnum errorCode = func; \ if (errorCode != Error_Succeed) { \ LogError(Severity_High, Error_Failed, 0,\ CSimpleStringA::Format("Invoke \"" #func "\" failed ec=%s! at file: <%s>, line: %d",\ SpStrError(errorCode), _GetFileName(__FILE__), __LINE__));\ return Error_Failed; \ } \ } while (false) #define IFFAILBREAK(func) \ do { \ ErrorCodeEnum errorCode = func; \ if (errorCode != Error_Succeed) { \ LogError(Severity_High, Error_Failed, 0,\ CSimpleStringA::Format("Invoke \"" #func "\" failed ec=%s! at file: <%s>, line: %d",\ SpStrError(errorCode), _GetFileName(__FILE__), __LINE__));\ return; \ } \ } while (false) #define REQUIRE(expr) \ do { \ if (!(expr)) { \ LogError(Severity_High, Error_Failed, 0,\ CSimpleStringA::Format("Expr (\"" #expr "\") requires failed! at file: <%s>, line: %d", \ _GetFileName(__FILE__), __LINE__));\ return Error_Failed; \ } \ } while (false) #define CHECK(expr) \ do { \ if (!(expr)) { \ LogError(Severity_High, Error_Failed, 0,\ CSimpleStringA::Format("Expr (\"" #expr "\") requires failed! at file: <%s>, line: %d", \ _GetFileName(__FILE__), __LINE__));\ return; \ } \ } while (false) #define THROW_FATAL(fmt, ...) \ LogError(Severity_High, Error_Failed, 0, \ CSimpleStringA::Format("%s at file: <%s>, line: %d", \ (LPCTSTR)CSimpleStringA::Format(fmt, ##__VA_ARGS__), _GetFileName(__FILE__), __LINE__)) #define THROW_ERROR(fmt, ...) \ LogError(Severity_Middle, Error_Failed, 0, \ CSimpleStringA::Format("%s at file: <%s>, line: %d", \ (LPCTSTR)CSimpleStringA::Format(fmt, ##__VA_ARGS__), _GetFileName(__FILE__), __LINE__)) #define THROW_FAIL(fmt, ...) \ LogError(Severity_Low, Error_Failed, 0, \ CSimpleStringA::Format("%s at file: <%s>, line: %d", \ (LPCTSTR)CSimpleStringA::Format(fmt, ##__VA_ARGS__), _GetFileName(__FILE__), __LINE__)) #define FALTAL(fmt, ...) LogError(Severity_High, Error_Failed, 0, CSimpleStringA::Format(fmt, ##__VA_ARGS__)) #define ERR(fmt, ...) LogError(Severity_Middle, Error_Failed, 0, CSimpleStringA::Format(fmt, ##__VA_ARGS__)) #define FAIL(fmt, ...) LogError(Severity_Low, Error_Failed, 0, CSimpleStringA::Format(fmt, ##__VA_ARGS__)) #define WARN(fmt, ...) LogWarn(Severity_Middle, Error_Unexpect, 0, CSimpleStringA::Format(fmt, ##__VA_ARGS__)) #define INFO(fmt, ...) LogEvent(Severity_Middle, 0, CSimpleStringA::Format(fmt, ##__VA_ARGS__)) #define RVC_FUNC_TAKEUP_START(func) \ do { SP::Test::Timer consumTimer; \ consumTimer.Start() #define RVC_FUNC_TAKEUP_DONE(func) \ const double duration = consumTimer.GetElapsedSeconds(); \ LogEvent(Severity_Middle, 0, CSimpleStringA::Format("Invoking \"" #func "\" takes %s secs at file: <%s>, line: %d",\ std::to_string(duration).c_str(), _GetFileName(__FILE__), __LINE__ - 1)); \ } while (false) #else #define IFFAILRET(func) ((void)0) #define IFFAILBREAK(func) ((void)0) #define REQUIRE(expr) ((void)0) #define CHECK(expr) ((void)0) #define THROW_FATAL(fmt, ...) ((void)0) #define THROW_ERROR(fmt, ...) ((void)0) #define THROW_FAIL(fmt, ...) ((void)0) #define FALTAL(fmt, ...) ((void)0) #define ERR(fmt, ...) ((void)0) #define FAIL(fmt, ...) ((void)0) #define WARN(fmt, ...) ((void)0) #define INFO(fmt, ...) ((void)0) #define RVC_FUNC_TAKEUP_START(noused) ((void)0) #define RVC_FUNC_TAKEUP_DONE(noused) ((void)0) #endif /*defined(TEST_MODE_SWITCH_ON) && TEST_MODE_SWITCH_ON == 1*/ #define REQUIRE_FALSE(expr) \ REQUIRE(!(expr)) #define CHECK_FALSE(expr) \ CHECK(!(expr)) /** Entity's MOCK Context*/ SPBASE_API CSmartPointer CreateMockTransactionContext(CSmartPointer const& entityFunction); enum class SPClassType { Entity, FSM }; enum class Tolerate { Strict, Ignore }; #ifdef RVC_OS_WIN namespace SP { typedef unsigned long long UInt64; } #else #include #include namespace SP { typedef uint64_t UInt64; } #endif // RVC_OS_WIN namespace SP { namespace Test { namespace { #ifdef RVC_OS_WIN UInt64 GetCurrentTicks() { static UInt64 hz = 0, hzo = 0; if (!hz) { QueryPerformanceFrequency(reinterpret_cast(&hz)); QueryPerformanceCounter(reinterpret_cast(&hzo)); } UInt64 t; QueryPerformanceCounter(reinterpret_cast(&t)); return ((t - hzo) * 1000000) / hz; } #else UInt64 GetCurrentTicks() { timeval t; gettimeofday(&t, nullptr); return static_cast(t.tv_sec) * 1000000ull + static_cast(t.tv_usec); } #endif class Timer { public: Timer() : m_ticks(0) {} void Start() { m_ticks = GetCurrentTicks(); } unsigned int GetElapsedMicroseconds() const { return static_cast(GetCurrentTicks() - m_ticks); } unsigned int GetElapsedMilliseconds() const { return static_cast(GetElapsedMicroseconds() / 1000); } double GetElapsedSeconds() const { return GetElapsedMicroseconds() / 1000000.0; } private: UInt64 m_ticks; }; } } } struct IMethodTestCase { virtual ErrorCodeEnum RunTest() = 0; virtual ~IMethodTestCase() {}; void BindEntity(CEntityBase* const pEntityT) { pEntityBase = pEntityT; } CEntityBase* pEntityBase = nullptr; }; template class MethodTestCase : public IMethodTestCase { public: MethodTestCase(ErrorCodeEnum(TClass::* method)()) : m_method(method) {} virtual ErrorCodeEnum RunTest() { TClass obj; TClass* ptr = &obj; return (ptr->*m_method)(); //return Error_Succeed; } private: virtual ~MethodTestCase() {} ErrorCodeEnum(TClass::* m_method)(); }; template class EntityMethodTestCase : public IMethodTestCase { public: EntityMethodTestCase(ErrorCodeEnum(TClass::* method)()) : m_method(method) {} virtual ErrorCodeEnum RunTest() { TClass obj; TClass* ptr = &obj; if (pEntityBase == nullptr) return Error_NotInit; ptr->MockEntityFunction(pEntityBase->GetFunction()); return (ptr->*m_method)(); } private: virtual ~EntityMethodTestCase() {} ErrorCodeEnum(TClass::* m_method)(); }; template class FSMMethodTestCase : public IMethodTestCase { public: FSMMethodTestCase(ErrorCodeEnum(TClass::* method)()) : m_method(method) {} virtual ErrorCodeEnum RunTest() { TClass obj; TClass* ptr = &obj; if (pEntityBase == nullptr) return Error_NotInit; ErrorCodeEnum result = Error_Succeed; ptr->SetInitState(ptr->GetInitState()); ptr->SetEntityBase(pEntityBase); result = (ptr->*m_method)(); return result; } private: virtual ~FSMMethodTestCase() {} ErrorCodeEnum(TClass::* m_method)(); }; using TestFunction = ErrorCodeEnum(*)(); class DefaultFuncTestCase : public IMethodTestCase { public: DefaultFuncTestCase(TestFunction func) :m_func(func) {} virtual ErrorCodeEnum RunTest() { return m_func(); } private: virtual ~DefaultFuncTestCase() {}; TestFunction m_func; }; template struct TwoWayContextTestCaseT : public SpReqAnsContext, public IMethodTestCase { using Pointer = CSmartPointer >; using TestTwoWayFunc = ErrorCodeEnum(TEntity::*)(Pointer ctx); using TestTwoWayFuncVoid = void(TEntity::*)(Pointer ctx); TwoWayContextTestCaseT(TEntity* pEntityT, TestTwoWayFunc func, TestTwoWayFuncVoid funVoid) :SpReqAnsContext(CreateMockTransactionContext(nullptr)) , m_pEntity(pEntityT), m_func(func), m_voidFunc(funVoid) , m_errorCode(Error_IgnoreAll), m_dwUserCode(0) { } TwoWayContextTestCaseT(TEntity* pEntityT, TestTwoWayFunc func) :TwoWayContextTestCaseT(nullptr, func, nullptr) {} TwoWayContextTestCaseT(TEntity* pEntityT, TestTwoWayFuncVoid func) :TwoWayContextTestCaseT(nullptr, nullptr, func) {} TwoWayContextTestCaseT(TestTwoWayFunc func) :TwoWayContextTestCaseT(nullptr, func) { } TwoWayContextTestCaseT(TestTwoWayFuncVoid func) : TwoWayContextTestCaseT(nullptr, func) { } virtual void PreTest() {/* user should set context request content at Req structure.*/ } virtual ErrorCodeEnum PostTest() { LOG_FUNCTION(); /*User should check response content Ans's validity *if detect the value conveied by 'Ans' is not the dream one, return ErrorCode except Error_Succeed *Or*/return Error_Succeed; /*Tips: Only if the 'RunTest()' returned Error_Succeed, then this function would be invoked.*/ } virtual ErrorCodeEnum RunTest() { ErrorCodeEnum result = Error_IgnoreAll; TEntity entityInst; bool flag = false; if (m_pEntity == nullptr) flag = !!(m_pEntity = &entityInst); if (flag && pEntityBase != nullptr) { m_pEntity->MockEntityFunction(pEntityBase->GetFunction()); } PreTest(); if (m_func != nullptr) { result = (m_pEntity->*m_func)(GetCtx()); } else if (m_voidFunc != nullptr) { (m_pEntity->*m_voidFunc)(GetCtx()); result = Error_Succeed; } result = (result == Error_Succeed) ? m_errorCode : result; DbgWithLink(LOG_LEVEL_INFO, LOG_TYPE_SYSTEM)("TwoWayContextTestCaseT->errorCode: %s", SpStrError(result)); if (flag) m_pEntity = nullptr; return (((result == Error_Succeed) ? PostTest() : result)); } ErrorCodeEnum Answer(ErrorCodeEnum Error = Error_Succeed) override { m_errorCode = Error; return Error_Succeed; } ErrorCodeEnum Answer(ErrorCodeEnum eSysError, DWORD dwUserError) override { m_errorCode = eSysError; m_dwUserCode = dwUserError; return Error_Succeed; } protected: Pointer GetCtx() { Pointer pt = this; pt.AddRef(); return pt; } TestTwoWayFunc m_func; TestTwoWayFuncVoid m_voidFunc; TEntity* m_pEntity; ErrorCodeEnum m_errorCode; DWORD m_dwUserCode; }; struct TestCaseStatistics { std::size_t totalTestCaseNum; std::size_t passedTestCaseNum; std::size_t failureTestCaseNum; std::size_t ignoreTestCaseNum; TestCaseStatistics() { Reset(); } TestCaseStatistics(std::size_t totalNum):totalTestCaseNum(totalNum){ Clear(); } void Reset() { totalTestCaseNum = 0; passedTestCaseNum = 0; failureTestCaseNum = 0; ignoreTestCaseNum = 0; } void ConvertFormUserCode(const DWORD& dwUserCode) { Reset(); totalTestCaseNum = ((dwUserCode >> 16) & 0x0000FFFF); failureTestCaseNum = (dwUserCode & 0x0000FFFF); passedTestCaseNum = totalTestCaseNum - failureTestCaseNum; } double FailureRate() const { double failed = 1.0 * failureTestCaseNum; double rate = failed / (double)(totalTestCaseNum); return rate * 100; } void Clear() { const std::size_t oldCaseNum = totalTestCaseNum; Reset(); totalTestCaseNum = oldCaseNum; } std::size_t Total() const { return (totalTestCaseNum); } std::size_t Failures() const { return (failureTestCaseNum); } bool AllPassed() const { return (failureTestCaseNum == 0); } }; template class ITestCaseSuite { public: ITestCaseSuite() = default; virtual ~ITestCaseSuite(); typedef T* TestObjectPointer; template struct TwoWayMethodTestCaseT : public TwoWayContextTestCaseT { using Pointer = CSmartPointer >; using TestTwoWayFunc = ErrorCodeEnum(T::*)(Pointer ctx); using TestTwoWayFuncVoid = void(T::*)(Pointer ctx); /** constructor*/ TwoWayMethodTestCaseT(T* pEntityT, TestTwoWayFunc testFunc) :TwoWayContextTestCaseT(pEntityT, testFunc) {/*empty*/} }; typedef void(T::* TransMethodProto)(CSmartPointer pTransactionContext); typedef std::vector TestCaseSet; void AddTransMethod(TransMethodProto entry); typedef std::vector MethodTestCaseSet; void AddMethodTestCase(IMethodTestCase* methodCastPtr); protected: virtual ErrorCodeEnum RunTestCase(); /** user can override this function to add any other test, but do not invoke 'AddTransMethod' and 'AddMethodTestCase' method at this scope!!!!*/ virtual ErrorCodeEnum AdditionalTest() { return Error_Succeed; } private: TestCaseSet m_testCases; MethodTestCaseSet m_testMethods; TestCaseStatistics m_testStatistcs; static CSmartPointer m_spMockTransactionContext; }; /*! * [Gifur@2020519] * declare Transaction context as static type for some reason: * 1. when creating SpReqAnsContext class object, we must convey a CSmartPointer type param to initialize, * if we create it at the same time, we cannot get it anymore because it has been declared as private at SpReqAnsContext, but we must * hook it to mock real test result without changing any functional code which I really unwill to see it! * 2. subclass TwoWayMethodTestCaseT inherited from SpReqAnsContext cannot initialize earlier than SpReqAnsContext as children class, so we * cannot declare a 'CSmartPointer' type member and initialze it first then convey it to SpReqAnsContext. * 3. multi-thead unsafe !!! */ template CSmartPointer ITestCaseSuite::m_spMockTransactionContext \ = CreateMockTransactionContext(nullptr); template ITestCaseSuite::~ITestCaseSuite() { for (auto& r : m_testMethods) { if (r) { delete r; r = NULL; } } } /* 'TransMethodProto' Prototype: void Entity::Function(CSmartPointer pTransactionContext) * User should declare and implement it. */ template void ITestCaseSuite::AddTransMethod(TransMethodProto entry) { m_testCases.push_back(entry); m_testStatistcs.totalTestCaseNum++; } template void ITestCaseSuite::AddMethodTestCase(IMethodTestCase* methodCastPtr) { m_testMethods.push_back(methodCastPtr); m_testStatistcs.totalTestCaseNum++; } template ErrorCodeEnum ITestCaseSuite::RunTestCase() { LOG_FUNCTION(); m_testStatistcs.Clear(); std::size_t testCaseNum = 0; TestObjectPointer that = static_cast(this); if (!m_testCases.empty()) { auto it = m_testCases.begin(); while (it != m_testCases.end()) { ++testCaseNum; DbgWithLink(LOG_LEVEL_INFO, LOG_TYPE_SYSTEM)("Run TestCase#%d...", testCaseNum); CSmartPointer mock = CreateMockTransactionContext(that->GetFunction()); (that->*(*it))(mock); DWORD dwUserCode = 0, dwNoUsed = 0; ErrorCodeEnum result = mock->GetExpireTime(dwUserCode, dwNoUsed); if (IS_FAILURED(result) && result != Error_IgnoreAll) { THROW_ERROR("TestCase#%d test failed return %s(userCode: 0x%X)", testCaseNum, SpStrError(result), dwUserCode); m_testStatistcs.failureTestCaseNum++; } else if (result == Error_IgnoreAll) { m_testStatistcs.ignoreTestCaseNum++; } else { m_testStatistcs.passedTestCaseNum++; } it++; } } std::size_t testMethodNum = 0; if (!m_testMethods.empty()) { auto it = m_testMethods.begin(); while (it != m_testMethods.end()) { ++testMethodNum; DbgWithLink(LOG_LEVEL_INFO, LOG_TYPE_SYSTEM)("Run TestMethod#%d...", testMethodNum); ErrorCodeEnum result = (*it)->RunTest(); if (IS_FAILURED(result) && result != Error_IgnoreAll) { THROW_ERROR("TestMethod#%d test failed return %s", testMethodNum, SpStrError(result)); m_testStatistcs.failureTestCaseNum++; } else if (result == Error_IgnoreAll) { m_testStatistcs.ignoreTestCaseNum++; } else { m_testStatistcs.passedTestCaseNum++; } it++; } } return m_testStatistcs.AllPassed() ? Error_Failed : Error_Succeed; } #define INTERNAL_TEST_CAST_ON_EXAM_FUNCTION1() \ { \ LOG_FUNCTION(); \ ErrorCodeEnum testResult = RunTestCase(); \ if (testResult == Error_Succeed || testResult == Error_IgnoreAll) { \ testResult = AdditionalTest(); \ pTransactionContext->SendAnswer(testResult); \ } else { \ pTransactionContext->SendAnswer(testResult); \ } \ } #define INTERNAL_TEST_CAST_ON_EXAM_FUNCTION2() \ { \ LOG_FUNCTION(); \ TestConfig defaultConfig{ this->GetEntityName() }; \ std::vector const& allTestCases = GetRegistryHub().GetTestCaseRegistry().getAllTests(defaultConfig); \ TestCaseStatistics testStatistcs(allTestCases.size() ); \ ErrorCodeEnum testResult = Error_Succeed; \ SP::Test::Timer testCaseTimer; \ double duration = 0; \ for (std::vector::const_iterator itStart = allTestCases.begin(), itEnd = allTestCases.end(); itStart != itEnd; ++itStart) { \ TestCaseInfo const& testCaseInfo = itStart->GetTestInfo(); \ try { \ testCaseTimer.Start(); \ testResult = itStart->RunTest(this); \ duration = testCaseTimer.GetElapsedSeconds(); \ } catch(const std::exception& ex) { \ DbgWithLink(LOG_LEVEL_INFO, LOG_TYPE_SYSTEM)("exception occurs: %s", ex.what()); \ testResult = Error_Exception; \ } \ if (testResult != Error_Succeed) { \ ERR("TestCase<%s %s> %s failed the test, return %s.", \ testCaseInfo.strName.c_str(), \ testCaseInfo.strDescription.empty() ? "(No description)" : testCaseInfo.strDescription.c_str(), \ testCaseInfo.lineInfo.ToString().c_str(), SpStrError(testResult)); \ testStatistcs.failureTestCaseNum++; \ } else { \ INFO("TestCase<%s %s> passed test elapsed: %s secs", \ testCaseInfo.strName.c_str(), \ testCaseInfo.strDescription.empty() ? "(No description)" : testCaseInfo.strDescription.c_str(), \ std::to_string(duration).c_str()); \ testStatistcs.passedTestCaseNum++; \ }\ } \ const DWORD dwStatistics = ((((DWORD)(WORD)testStatistcs.Total()) << 16) | (((DWORD)testStatistcs.Failures()) & 0x0000FFFF)); \ if(testStatistcs.Total() == 0) \ pTransactionContext->SendAnswer(Error_IgnoreAll, dwStatistics); \ else if(testStatistcs.AllPassed()) \ pTransactionContext->SendAnswer(Error_Accept, dwStatistics); \ else { \ pTransactionContext->SendAnswer(Error_Failed, dwStatistics); \ } \ } #define INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_IMPLEMENT1(EntityClassName) \ void EntityClassName::OnExam(CSmartPointer pTransactionContext) override \ INTERNAL_TEST_CAST_ON_EXAM_FUNCTION1() #define INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_DECLARE1() \ void OnExam(CSmartPointer pTransactionContext) \ INTERNAL_TEST_CAST_ON_EXAM_FUNCTION1() #define INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_IMPLEMENT2(EntityClassName) \ void EntityClassName::OnExam(CSmartPointer pTransactionContext) \ INTERNAL_TEST_CAST_ON_EXAM_FUNCTION2() #define INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_DECLARE2() \ void OnExam(CSmartPointer pTransactionContext) \ INTERNAL_TEST_CAST_ON_EXAM_FUNCTION2() #ifdef WITH_BUILD_MODULE_TEST #define TEST_CASE_OVERRIDE_ON_EXAM_DECLAER() \ INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_DECLARE2() #define TEST_CASE_OVERRIDE_ON_EXAM_IMPLEMENT(entityClassName) \ INTERNAL_TEST_CASE_OVERRIDE_ON_EXAM_IMPLEMENT2(entityClassName) #else //WITH_BUILD_MODULE_TEST #define TEST_CASE_OVERRIDE_ON_EXAM_DECLAER() #define TEST_CASE_OVERRIDE_ON_EXAM_IMPLEMENT(entityClassName) #endif // WITH_BUILD_MODULE_TEST ////////////////////////////////////////////////////////////////////////// using namespace SP::Detail; struct NameAndDesc { NameAndDesc(const char* _name = "", const char* _description = "") : name(_name), description(_description) {/*empty*/ } const char* name; const char* description; }; class TestCase; struct TestConfig { std::string filterTag; }; struct ITestCaseRegistry { virtual ~ITestCaseRegistry() {} /** TODO: relate with Entity Name*/ virtual std::vector const& getAllTests(TestConfig const& config) const = 0; }; struct ITestRegistryHub { virtual ~ITestRegistryHub() {} virtual void RegisterTest(TestCase const& testInfo) = 0; virtual ITestCaseRegistry const& GetTestCaseRegistry() const = 0; }; SPBASE_API ITestRegistryHub& GetRegistryHub(); /** TODO: hide*/ SPBASE_API TestCase MakeTestCase(IMethodTestCase* testCase, std::string const& className, std::string const& name, std::string const& desc, SourceLineInfo const& lineInfo); SPBASE_API void RegisterTestCase(IMethodTestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo); SPBASE_API void RegisterTestCaseFunction(TestFunction function, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); struct TestAutoReg { TestAutoReg() {} ~TestAutoReg() {} /** for test simple func*/ TestAutoReg(TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { RegisterTestCaseFunction(function, nameAndDesc, lineInfo); } /** for test normal Class*/ template TestAutoReg( ErrorCodeEnum(TClass::* method)(), char const* lpcszClassName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo) { RegisterTestCase(new MethodTestCase(method), lpcszClassName, nameAndDesc, lineInfo); } /** for test entity Class*/ template TestAutoReg( ErrorCodeEnum(TClass::* method)(), char const* lpcszClassName, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc, SPClassType classType /*for special class from framework, */ ) { RegisterTestCase(new EntityMethodTestCase(method), lpcszClassName, nameAndDesc, lineInfo); } /** for test FSM Class*/ template TestAutoReg( ErrorCodeEnum(TClass::* method)(), char const* lpcszClassName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo, SPClassType classType /*for special class from framework, */ ) { RegisterTestCase(new FSMMethodTestCase(method), lpcszClassName, nameAndDesc, lineInfo); } TestAutoReg( IMethodTestCase* testCaseImpl, char const* lpcszClassName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo) { RegisterTestCase(testCaseImpl, lpcszClassName, nameAndDesc, lineInfo); } }; struct TestCaseInfo { TestCaseInfo(std::string const& name, std::string const& className, std::string const& desc, SourceLineInfo const& info) :strName(name.c_str()), strClassName(className.c_str()),strDescription(desc.c_str()), lineInfo(info) {} TestCaseInfo(TestCaseInfo const& other) :TestCaseInfo(other.strName, other.strClassName, other.strDescription, other.lineInfo) {/*delegrate directory*/} std::string strName; std::string strClassName; std::string strDescription; SourceLineInfo lineInfo; }; class TestCase : public TestCaseInfo { public: TestCase(IMethodTestCase* testCase, TestCaseInfo const& info): TestCaseInfo(info), m_testCase(testCase) {} TestCase(TestCase const& other) :TestCaseInfo(other), m_testCase(other.m_testCase) {} ~TestCase() { m_testCase = nullptr; } TestCaseInfo const& GetTestInfo() const { return *this; } /** for anonymous case regist*/ TestCase CloneExceptName(std::string const& newName) const { TestCase newCase(*this); newCase.strName = newName; return newCase; } ErrorCodeEnum RunTest(CEntityBase* pInvoker) const { m_testCase->BindEntity(pInvoker); return m_testCase->RunTest(); } private: CSmartPointer m_testCase; }; #define RVC_INTERVAL_UNIQUE_NAME_LINE2( name, line ) name##line #define RVC_INTERVAL_UNIQUE_NAME_LINE( name, line ) RVC_INTERVAL_UNIQUE_NAME_LINE2( name, line ) #define RVC_INTERVAL_UNIQUE_NAME(name) RVC_INTERVAL_UNIQUE_NAME_LINE(name, __COUNTER__) #define RVC_INTERVAL_STRINGFY(name) #name #define INTERNAL_TEST_CASE_NORMAL_CLASS2(TestName, ClassName, ...) \ namespace { \ struct TestName : ClassName { \ ErrorCodeEnum Test(); \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName::Test, RVC_INTERVAL_STRINGFY(ClassName), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO); \ } \ ErrorCodeEnum TestName::Test() #define INTERNAL_TEST_CASE_NORMAL_CLASS(ClassName, ...) \ INTERNAL_TEST_CASE_NORMAL_CLASS2(RVC_INTERVAL_UNIQUE_NAME(CVRssalCtseTduS), ClassName, __VA_ARGS__) #define INTERNAL_TEST_CASE_ENTITY_CLASS2(TestName, ClassName, ... ) \ namespace { \ struct TestName : public ClassName { \ TestName():m_pEntityFunctionDelegrate(nullptr){}\ CSmartPointer GetFunction() { return m_pEntityFunctionDelegrate; } \ void MockEntityFunction(CSmartPointer const& func) { m_pEntityFunctionDelegrate = func.GetRawPointer(); } \ ErrorCodeEnum Test(); \ private: \ IEntityFunction* m_pEntityFunctionDelegrate; \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName::Test, RVC_INTERVAL_STRINGFY(ClassName), SP_INTERNAL_LINEINFO, NameAndDesc(__VA_ARGS__), SPClassType::Entity); \ } \ ErrorCodeEnum TestName::Test() #define INTERNAL_TEST_CASE_ENTITY_CLASS(ClassName, ...) \ INTERNAL_TEST_CASE_ENTITY_CLASS2(RVC_INTERVAL_UNIQUE_NAME(CVRssalCtseTduS), ClassName, __VA_ARGS__) #define INTERNAL_TEST_CASE_FSM_CLASS2(TestName, ClassName, ... ) \ namespace { \ struct TestName : public ClassName { \ ErrorCodeEnum Test(); \ void SetInitState(int state) { \ m_iState = state; \ } \ void SetEntityBase(CEntityBase * const pEntityBase) { m_pEntity = pEntityBase; } \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName::Test, RVC_INTERVAL_STRINGFY(ClassName), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO, SPClassType::FSM); \ } \ ErrorCodeEnum TestName::Test() #define INTERNAL_TEST_CASE_FSM_CLASS(ClassName, ...) \ INTERNAL_TEST_CASE_FSM_CLASS2(RVC_INTERVAL_UNIQUE_NAME(CVRssalCtseTduS), ClassName, __VA_ARGS__) ////////////////////////////////////////////////////////////////////////// #define INTERNAL_RVC_TESTCASE2(TestName, ...) \ static ErrorCodeEnum TestName(); \ namespace { \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName, SP_INTERNAL_LINEINFO, NameAndDesc(__VA_ARGS__)); \ } \ static ErrorCodeEnum TestName() #define INTERNAL_RVC_TESTCASE(...) INTERNAL_RVC_TESTCASE2(RVC_INTERVAL_UNIQUE_NAME(CVRcnuFtseTduS), __VA_ARGS__) #include ////////////////////////////////////////////////////////////////////////// #define INTERNAL_RVC_TEST_CASE_VOID_CONTEXT2(TestName, SubClassName, ClassName, ...) \ namespace { \ struct SubClassName : public ClassName { \ SubClassName():m_pEntityFunctionDelegrate(nullptr){}\ CSmartPointer GetFunction() { return m_pEntityFunctionDelegrate; } \ void MockEntityFunction(CSmartPointer const& func) { m_pEntityFunctionDelegrate = func.GetRawPointer(); } \ private: \ IEntityFunction* m_pEntityFunctionDelegrate; \ }; \ struct TestName : SubClassName { \ CSmartPointer mMockTrans; \ TestName():mMockTrans(CreateMockTransactionContext(nullptr)){}; \ ErrorCodeEnum Test() { \ TestContext(mMockTrans); \ DWORD dwUserCode = 0, dwNoUsed = 0; \ ErrorCodeEnum testRes = mMockTrans->GetExpireTime(dwUserCode, dwNoUsed); \ return (testRes == Error_IgnoreAll) ? Error_Succeed : testRes; \ } \ void TestContext(CSmartPointer pTransactionContext); \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName::Test, RVC_INTERVAL_STRINGFY(ClassName), SP_INTERNAL_LINEINFO, NameAndDesc(__VA_ARGS__), SPClassType::Entity); \ } \ void TestName::TestContext(CSmartPointer pTransactionContext) #define INTERNAL_RVC_TEST_CASE_VOID_CONTEXT(EntityClassName, ...) \ INTERNAL_RVC_TEST_CASE_VOID_CONTEXT2(RVC_INTERVAL_UNIQUE_NAME(CVRytitnEtseTduS), RVC_INTERVAL_UNIQUE_NAME(CVRssalCbuSytitnE), EntityClassName, __VA_ARGS__) /////////////////////////////////////////////////////////////////////////////// template struct Matcher { Tolerate level = Tolerate::Ignore; std::string strExpr; std::function exprFunc; SourceLineInfo lineInfo; Matcher(Tolerate const& choice, std::string const& desc, std::function&& expr, SourceLineInfo const& info) :level(choice), strExpr(desc), exprFunc(expr),lineInfo(info) {} }; # define RVC_INTERNAL_STRINGIFY(expr) #expr #define INTERNAL_TEST_CASE_ENTITY_CONTEXT2(TestName, SubEntityClassName, EntityClassName, ServiceName, ContextName, ...) \ namespace { \ struct SubEntityClassName : public EntityClassName { \ SubEntityClassName():m_pEntityFunctionDelegrate(nullptr){}\ CSmartPointer GetFunction() { return m_pEntityFunctionDelegrate; } \ void MockEntityFunction(CSmartPointer const& func) { m_pEntityFunctionDelegrate = func.GetRawPointer(); } \ private: \ IEntityFunction* m_pEntityFunctionDelegrate; \ }; \ struct TestName : public TwoWayContextTestCaseT { \ TestName(TestTwoWayFunc func):TwoWayContextTestCaseT(func){ }\ TestName(TestTwoWayFuncVoid func):TwoWayContextTestCaseT(func){ }\ using TwoWayResultType = ServiceName##_##ContextName##_Ans; \ using AnsMatcher = Matcher; \ using AnsMachers = std::vector< AnsMatcher >; \ AnsMachers matchers; \ void PreTest(); \ ErrorCodeEnum PostTest() { \ ErrorCodeEnum result = Error_Succeed; \ for( AnsMachers::const_iterator it = matchers.begin(), itEnd = matchers.end(); it != itEnd;++it) { \ if (!((it->exprFunc)(Ans))) { \ if(it->level == Tolerate::Strict) { \ ERR("Expr (\" %s \") tests failed !! %s", it->strExpr.c_str(), it->lineInfo.ToString().c_str()); \ result = Error_MisMatched; \ break; \ } else { \ FAIL("Expr (\" %s \") expects failed ! %s", it->strExpr.c_str(), it->lineInfo.ToString().c_str()); \ } \ } \ } \ return result; \ } \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)( new TestName(&SubEntityClassName::ContextName) , RVC_INTERVAL_STRINGFY(EntityClassName), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO); \ } \ void TestName::PreTest() #define INTERNAL_TEST_CASE_ENTITY_CONTEXT(EntityClassName, ServiceName, ContextName, ... ) \ INTERNAL_TEST_CASE_ENTITY_CONTEXT2(RVC_INTERVAL_UNIQUE_NAME(CVRtxetnoCtseTduS), RVC_INTERVAL_UNIQUE_NAME(CVRssalCbuSytitnE), EntityClassName, ServiceName, ContextName, __VA_ARGS__) #define INNER_ANSWER_CHECK(Expression, Description, TolerateLevel) \ do { \ auto f = [](TwoWayResultType const& Ans)->bool { return ( Expression ); }; \ AnsMatcher matchInst(TolerateLevel, std::string(Description), f, SP_INTERNAL_LINEINFO); \ matchers.push_back(matchInst); \ } while (false) ////////////////////////////Export for user////////////////////////////////////////////// /** declare it at entity class scope*/ #define ON_ENTITYT_TEST() TEST_CASE_OVERRIDE_ON_EXAM_DECLAER() /** for test static normal menthod */ #define TEST_CASE_METHOD( ... ) INTERNAL_RVC_TESTCASE( __VA_ARGS__ ) /** for test normal class except for entity and fsm*/ #define TEST_CASE_NORMAL_CLASS(className, ...) INTERNAL_TEST_CASE_NORMAL_CLASS( className, __VA_ARGS__ ) /** for test Entity class, which enable GetFunction() validity*/ #define TEST_CASE_ENTITY_CLASS(className, ...) INTERNAL_TEST_CASE_ENTITY_CLASS( className, __VA_ARGS__ ) /** for test FSM class, which enable GetEntityBase() validity*/ #define TEST_CASE_FSM_CLASS(className, ...) INTERNAL_TEST_CASE_FSM_CLASS( className, __VA_ARGS__ ) #define RVC_TEST_CASE_VOID_CONTEXT(entityClassName, ...) INTERNAL_RVC_TEST_CASE_VOID_CONTEXT( entityClassName, __VA_ARGS__ ) /**deprecate!! please replace with TEST_CASE_ENTITY_CONTEXT*/ #define RVC_TEST_CASE_CONTEXT_TWO_WAY(entityClassName, serviceName, contextName, ...) INTERNAL_RVC_TEST_CASE_CONTEXT_TWO_WAY(entityClassName, serviceName, contextName, contextName, __VA_ARGS__) /** for test Entity's transaction context with two way*/ #define TEST_CASE_ENTITY_CONTEXT(entityClassName, serviceName, contextName, ... ) INTERNAL_TEST_CASE_ENTITY_CONTEXT(entityClassName, serviceName, contextName, __VA_ARGS__ ) /** for check Ans's member validity lightly, only be used at {TEST_CASE_ENTITY_CONTEXT} scope*/ #define ANSWER_CHECK(expr) INNER_ANSWER_CHECK(expr, RVC_INTERNAL_STRINGIFY(expr), Tolerate::Ignore) /** for check Ans's member validity heavilier, only be used at {TEST_CASE_ENTITY_CONTEXT} scope*/ #define ANSWER_REQUIRE(expr) INNER_ANSWER_CHECK(expr, RVC_INTERNAL_STRINGIFY(expr), Tolerate::Strict) #endif //_RVC_SPTEST_H__