#ifndef _RVC_SPTEST_H__ #define _RVC_SPTEST_H__ #pragma once #include "SpBase.h" #include "SpHelper.h" #include "SpComm.hpp" #include "SpUtility.hpp" #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__)) #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) #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 entityFunction); /** Test Case */ //struct TestCaseService_RunTest_Req //{ // CSimpleStringA strParam; // // void Serialize(SpBuffer& Buf) // { // auto& buf = Buf & strParam; // } // //}; // //struct TestCaseService_RunTest_Ans //{ // DWORD dwUserCode; // // void Serialize(SpBuffer& Buf) // { // auto& buf = Buf & dwUserCode; // } //}; // //void SimulateTestCastEndPoint(SpReqAnsContext< TestCaseService_RunTest_Req, TestCaseService_RunTest_Ans>::Pointer ctx) //{ // ctx->Answer(Error_Failed); //} struct IMethodTestCase { virtual ErrorCodeEnum RunTest() = 0; virtual ~IMethodTestCase() {}; //void BindEntity(CEntityBase* const pEntity) { pEntityBase = pEntity; } //CEntityBase* pEntityBase = nullptr; }; template class MethodTestCase : public IMethodTestCase { public: MethodTestCase(ErrorCodeEnum(TClass::* method)()) : m_method(method) {} virtual ErrorCodeEnum RunTest() { TClass obj; return (obj.*m_method)(); //return Error_Succeed; } private: virtual ~MethodTestCase() {} 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(TClass::*)(Pointer ctx); using TestTwoWayFuncVoid = void(TClass::*)(Pointer ctx); TwoWayContextTestCaseT(TClass* testee, TestTwoWayFunc func, TestTwoWayFuncVoid funVoid) :SpReqAnsContext(CreateMockTransactionContext(nullptr)) , m_obj(testee), m_func(func), m_voidFunc(funVoid), m_errorCode(Error_IgnoreAll), m_dwUserCode(0) { } TwoWayContextTestCaseT(TClass* testee, TestTwoWayFunc func) :TwoWayContextTestCaseT(nullptr, func, nullptr) {} TwoWayContextTestCaseT(TClass* testee, 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; TClass obj; bool flag = false; if (m_obj == nullptr) flag = !!(m_obj = &obj); PreTest(); if (m_func != nullptr) { result = (m_obj->*m_func)(GetCtx()); } else if (m_voidFunc != nullptr) { (m_obj->*m_voidFunc)(GetCtx()); result = Error_Succeed; } result = (result == Error_Succeed) ? m_errorCode : result; Dbg("TwoWayContextTestCaseT->errorCode: %s", SpStrError(result)); if (flag) m_obj = nullptr; return (((result == Error_Succeed) ? PostTest() : result)); } ErrorCodeEnum Answer(ErrorCodeEnum Error = Error_Succeed) override { LOG_FUNCTION(); m_errorCode = Error; return Error_Succeed; } ErrorCodeEnum Answer(ErrorCodeEnum eSysError, DWORD dwUserError) override { LOG_FUNCTION(); m_errorCode = eSysError; m_dwUserCode = dwUserError; return Error_Succeed; } private: Pointer GetCtx() { Pointer pt = this; pt.AddRef(); return pt; } TestTwoWayFunc m_func; TestTwoWayFuncVoid m_voidFunc; TClass* m_obj; 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(); } void Reset() { totalTestCaseNum = 0; passedTestCaseNum = 0; failureTestCaseNum = 0; ignoreTestCaseNum = 0; } void Clear() { const std::size_t oldCaseNum = totalTestCaseNum; Reset(); totalTestCaseNum = oldCaseNum; } std::size_t Total() const { return (totalTestCaseNum); } bool AllPassed() const { return (failureTestCaseNum == 0); } }; template class ITestCaseSuite { public: ITestCaseSuite() = default; virtual ~ITestCaseSuite(); typedef T* TestObjectPointer; template struct TwoWayMethodTestCaseT : public TwoWayContextTestCaseT { TwoWayMethodTestCaseT(T* pEntity, TestTwoWayFunc testFunc) :TwoWayContextTestCaseT(pEntity, testFunc) {} }; 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; Dbg("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; Dbg("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 TESTCASE_OVERRIDE_ON_EXAM_AND_IMPLEMENT() \ void OnExam(CSmartPointer pTransactionContext) override \ { \ LOG_FUNCTION(); \ ErrorCodeEnum testResult = RunTestCase(); \ if (testResult == Error_Succeed || testResult == Error_IgnoreAll) { \ testResult = AdditionalTest(); \ pTransactionContext->SendAnswer(testResult); \ } else { \ pTransactionContext->SendAnswer(testResult); \ } \ } ///////////////////////////////experiment below/////////////////////////////////////////// #define TESTCASE_DECLARE_BEGIN(serviceName, testFuncName) \ void Test_##testFuncName(CSmartPointer pTransactionContext) {\ SpReqAnsContext< serviceName##_##testFuncName##_Req, serviceName##_##testFuncName##_Ans>::Pointer ctx = \ new SpReqAnsContext< serviceName##_##testFuncName##_Req, serviceName##_##testFuncName##_Ans>(pTransactionContext) /*user et the member value */ #define TESTCASE_DECLARE_INVOKE(testFuncName) \ do {\ DWORD dwUserCode = 0, dwNoUsed = 0;\ Test_##testFuncName(ctx);\ ErrorCodeEnum result = mock->GetExpireTime(dwUserCode, dwNoUsed);\ if (IS_FAILURED(result)) { return; }\ } while (false) #define TESTCASE_DECLARE_END(testFuncName) \ } ///////////////////////////////experiment end/////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// 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 Class*/ template TestAutoReg( ErrorCodeEnum(TClass::* method)(), char const* lpcszClassName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo) { RegisterTestCase(new MethodTestCase(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) {} 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() const { 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_RVC_TEST_CASE_METHOD2(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_RVC_TEST_CASE_METHOD(ClassName, ...) \ INTERNAL_RVC_TEST_CASE_METHOD2(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 TWO_WAY_CHECK_ANSWER_BEGIN(ansName) \ checkAnsFunc = [](TwoWayResultType const& ansName) -> ErrorCodeEnum { #define TWO_WAY_CHECK_ANSWER_END() }; #define INTERNAL_RVC_TEST_CASE_CONTEXT_TWO_WAY2(TestName, ClassName, ServiceName, ContextName, TestFuncName, ...) \ namespace { \ struct TestName : ClassName { \ CSmartPointer mMockTrans; \ SpReqAnsContext< ServiceName##_##ContextName##_Req, ServiceName##_##ContextName##_Ans>::Pointer mCtx;\ using TwoWayResultType = ServiceName##_##ContextName##_Ans; \ void (ClassName::*testFuncVoid)(SpReqAnsContext< ServiceName##_##ContextName##_Req, ServiceName##_##ContextName##_Ans>::Pointer);\ ErrorCodeEnum (ClassName::*testFunc)(SpReqAnsContext< ServiceName##_##ContextName##_Req, ServiceName##_##ContextName##_Ans>::Pointer);\ ErrorCodeEnum testRes; \ std::function checkAnsFunc; \ TestName():mMockTrans(CreateMockTransactionContext(this->GetFunction())), \ mCtx(new SpReqAnsContext< ServiceName##_##ContextName##_Req, ServiceName##_##ContextName##_Ans>(mMockTrans)) \ , testFuncVoid(nullptr),testFunc(nullptr), testRes(Error_Succeed), checkAnsFunc(nullptr) { \ testFunc = &ClassName::TestFuncName; \ }\ void PreTest_##ContextName(ServiceName##_##ContextName##_Req& Req); \ ErrorCodeEnum PostTest_##ContextName() { \ return checkAnsFunc == nullptr ? Error_Succeed : checkAnsFunc(mCtx->Ans); \ } \ ErrorCodeEnum Test_##ContextName() { \ PreTest_##ContextName(mCtx->Req); \ if(testFuncVoid != nullptr) { \ (this->*testFuncVoid)(mCtx); \ } \ else if(testFunc != nullptr) { \ testRes =(this->*testFunc)(mCtx); \ } \ if(testRes == Error_Succeed) { \ DWORD dwUserCode = 0, dwNoUsed = 0; \ testRes = mMockTrans->GetExpireTime(dwUserCode, dwNoUsed); \ } \ if(testRes == Error_Succeed) { \ testRes = PostTest_##ContextName(); \ } else if(testRes == Error_IgnoreAll) \ testRes = Error_Succeed; \ return testRes; \ } \ struct TwoWayContextReqAnsImpl : \ public TwoWayContextTestCaseT \ { \ }; \ }; \ TestAutoReg RVC_INTERVAL_UNIQUE_NAME(testAutoReg)(&TestName::Test_##ContextName, RVC_INTERVAL_STRINGFY(ClassName), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO); \ } \ void TestName::PreTest_##ContextName(ServiceName##_##ContextName##_Req& Req) #define INTERNAL_RVC_TEST_CASE_CONTEXT_TWO_WAY(EntityClassName, ServiceName, TestFuncName, ContextName, ...) \ INTERNAL_RVC_TEST_CASE_CONTEXT_TWO_WAY2(RVC_INTERVAL_UNIQUE_NAME(CVRytitnEtseTduS), EntityClassName, ServiceName, ContextName, TestFuncName, __VA_ARGS__) ////////////////////////////////////////////////////////////////////////// #define INTERNAL_RVC_TEST_CASE_CONTEXT2(TestName, ClassName, ...) \ namespace { \ struct TestName : ClassName { \ CSmartPointer mMockTrans; \ TestName():mMockTrans(CreateMockTransactionContext(this->GetFunction())){}; \ 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), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO); \ } \ void TestName::TestContext(CSmartPointer pTransactionContext) #define INTERNAL_RVC_TEST_CASE_CONTEXT(EntityClassName, ...) \ INTERNAL_RVC_TEST_CASE_CONTEXT2(RVC_INTERVAL_UNIQUE_NAME(CVRytitnEtseTduS), EntityClassName, __VA_ARGS__) /////////////////////////////////////////////////////////////////////////////// enum class Tolerate { Strict, Ignore }; 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_RVC_REGISTER_TEST_CASE_CONTEXT2(TestName, EntityClassName, ServiceName, ContextName, ...) \ namespace { \ struct TestName : TwoWayContextTestCaseT { \ TestName(TestTwoWayFunc func):TwoWayContextTestCaseT(func){ Initial(); }\ void Initial(); \ using TwoWayResultType = ServiceName##_##ContextName##_Ans; \ using AnsMatcher = Matcher; \ using AnsMachers = std::vector< AnsMatcher >; \ AnsMachers matchers; \ 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(&EntityClassName::ContextName) , RVC_INTERVAL_STRINGFY(EntityClassName), NameAndDesc(__VA_ARGS__), SP_INTERNAL_LINEINFO); \ } \ void TestName::Initial() #define INTERNAL_RVC_REGISTER_TEST_CASE_CONTEXT(EntityClassName, ServiceName, ContextName, ... ) \ INTERNAL_RVC_REGISTER_TEST_CASE_CONTEXT2(RVC_INTERVAL_UNIQUE_NAME(CVRtxetnoCtseTduS), 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////////////////////////////////////////////// #define RVC_TEST_CASE( ... ) INTERNAL_RVC_TESTCASE( __VA_ARGS__ ) #define RVC_TEST_CASE_METHOD(className, ...) INTERNAL_RVC_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define RVC_TEST_CASE_VOID_CONTEXT(entityClassName, ...) INTERNAL_RVC_TEST_CASE_CONTEXT( entityClassName, __VA_ARGS__ ) #define RVC_TEST_CASE_CONTEXT_TWO_WAY(entityClassName, serviceName, contextName, ...) INTERNAL_RVC_TEST_CASE_CONTEXT_TWO_WAY(entityClassName, serviceName, contextName, contextName, __VA_ARGS__) #define ANSWER_CHECK(expr) INNER_ANSWER_CHECK(expr, RVC_INTERNAL_STRINGIFY(expr), Tolerate::Ignore) #define ANSWER_REQUIRE(expr) INNER_ANSWER_CHECK(expr, RVC_INTERNAL_STRINGIFY(expr), Tolerate::Strict) #define RVC_TEST_CASE_VOID_CONTEXT_TWO_WAY(entityClassName, serviceName, contextName, ... ) INTERNAL_RVC_REGISTER_TEST_CASE_CONTEXT(entityClassName, serviceName, contextName, __VA_ARGS__ ) #endif //_RVC_SPTEST_H__