#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_TEST_H_ #define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_TEST_H_ #include #include #include "cotest-crf-core.h" #include "cotest-crf-payloads.h" #include "cotest-util-types.h" #include "gmock/internal/gmock-internal-utils.h" namespace testing { namespace crf { // ------------------ Classes ------------------ using coro_impl::MakePayload; using coro_impl::PeekPayload; using coro_impl::PtrToString; using coro_impl::SpecialisePayload; class MockRoutingSession; class InteriorEventSession; template class InteriorSignatureMockCS; class InteriorLaunchSessionBase; template class InteriorLaunchSession; class TestCoroutine : public CoroutineBase, public std::enable_shared_from_this { public: using OnExitFunction = std::function; using CoroutineBase::CoroutineBase; TestCoroutine(coro_impl::BodyFunction cofn_, std::string name_, OnExitFunction on_exit_function_); ~TestCoroutine(); ReplyPair ReceiveMessage(std::unique_ptr &&to_node) override; ReplyPair IterateServer(std::unique_ptr &&to_coro); void InitialActivity(); void YieldServer(std::unique_ptr &&from_coro); bool IsPendingEvent(); template std::shared_ptr> Launch(internal::LaunchLambdaType &&user_lambda, std::string name); std::shared_ptr NextEvent(const char *file, int line); bool IsPostMockIterationRequested(); void DestructionIterations(); std::string DebugString() const override; private: const OnExitFunction on_exit_function; std::unique_ptr next_payload; bool mock_call_locked = false; bool extra_iteration_requested = false; }; class InteriorLaunchSessionBase : public std::enable_shared_from_this { public: InteriorLaunchSessionBase() = default; InteriorLaunchSessionBase(const InteriorLaunchSessionBase &i) = delete; InteriorLaunchSessionBase(InteriorLaunchSessionBase &&i) = delete; InteriorLaunchSessionBase &operator=(const InteriorLaunchSessionBase &) = delete; InteriorLaunchSessionBase &operator=(InteriorLaunchSessionBase &&) = delete; ~InteriorLaunchSessionBase(); InteriorLaunchSessionBase(TestCoroutine *test_coroutine_, std::string dc_name_); void SetLaunchCompleted(); TestCoroutine *GetParentTestCoroutine() const; std::string GetLaunchText() const; private: TestCoroutine *const parent_coroutine; bool launch_completed = false; const std::string launch_text; }; template class InteriorLaunchSession : public InteriorLaunchSessionBase { public: InteriorLaunchSession(TestCoroutine *test_coroutine_, std::string dc_name_); ~InteriorLaunchSession(); void Launch(internal::LaunchLambdaType &&user_lambda); R GetResult(const InteriorEventSession *event) const; }; class InteriorEventSession { public: InteriorEventSession() = delete; InteriorEventSession(const InteriorEventSession &i) = delete; InteriorEventSession(InteriorEventSession &&i) = delete; InteriorEventSession &operator=(const InteriorEventSession &) = delete; InteriorEventSession &operator=(InteriorEventSession &&) = delete; virtual ~InteriorEventSession() = default; InteriorEventSession(TestCoroutine *test_coroutine_, bool via_main_, std::shared_ptr via_launch_); virtual bool IsLaunchResult() const = 0; virtual bool IsLaunchResult(InteriorLaunchSessionBase *launch_session) const = 0; virtual bool IsMockCall() const = 0; virtual void Drop() = 0; virtual void Accept() = 0; virtual void Return() = 0; bool IsFrom(InteriorLaunchSessionBase *source); virtual UntypedReturnValuePointer GetUntypedLaunchResult() const = 0; protected: TestCoroutine *GetTestCoroutine() const; private: TestCoroutine *const test_coroutine; const bool via_main; const std::weak_ptr via_launch; }; class InteriorMockCallSession : public InteriorEventSession, public std::enable_shared_from_this { public: InteriorMockCallSession(TestCoroutine *test_coroutine_, bool via_main_, std::shared_ptr via_launch_, std::unique_ptr &&payload_); ~InteriorMockCallSession(); bool IsLaunchResult() const override; bool IsLaunchResult(InteriorLaunchSessionBase *launch_session) const override; bool IsMockCall() const override; std::string GetName() const; UntypedMockObjectPointer GetMockObject() const; UntypedMockerPointer GetMocker() const; void SeenCall(UntypedArgsPointer args_); template const typename internal::Function::ArgumentTuple *GetArgumentTuple() const; void Drop() override; void Accept() override; void Return() override; UntypedReturnValuePointer GetUntypedLaunchResult() const override; void ReturnImpl(UntypedReturnValuePointer return_val_ptr); bool IsReturned() const; private: std::weak_ptr originator; UntypedMockerPointer mocker; UntypedMockObjectPointer mock_object; std::string name; enum class State { PreMock, Seen, Dropped, Accepted, Returned }; State state = State::PreMock; UntypedArgsPointer args; }; class InteriorLaunchResultSession final : public InteriorEventSession { public: InteriorLaunchResultSession(TestCoroutine *test_coroutine_, std::unique_ptr &&payload_); bool IsLaunchResult() const override; bool IsLaunchResult(InteriorLaunchSessionBase *launch_session) const override; bool IsMockCall() const override; void Drop() override; void Accept() override; void Return() override; UntypedReturnValuePointer GetUntypedLaunchResult() const override; private: const std::weak_ptr originator; UntypedReturnValuePointer const return_value = nullptr; }; template class InteriorSignatureMockCS { using FN = typename internal::Function; using ArgumentTuple = typename FN::ArgumentTuple; public: ~InteriorSignatureMockCS(); InteriorSignatureMockCS(InteriorMockCallSession *mcs_, const ArgumentTuple *args_tuple_); const ArgumentTuple *GetArgumentTuple() const; template void Return(U &&retval); private: InteriorMockCallSession *const mcs; const ArgumentTuple *const args_tuple; }; // ------------------ Templated members ------------------ template std::shared_ptr> TestCoroutine::Launch(internal::LaunchLambdaType &&user_lambda, std::string df_name) { COTEST_ASSERT(!next_payload && "Launch(): must use NextEvent() to collect an event first"); const auto ils = std::make_shared>(this, df_name); ils->Launch(std::move(user_lambda)); return ils; } template InteriorLaunchSession::InteriorLaunchSession(TestCoroutine *test_coroutine_, std::string dc_name_) : InteriorLaunchSessionBase(test_coroutine_, dc_name_) {} template InteriorLaunchSession::~InteriorLaunchSession() {} template void InteriorLaunchSession::Launch(internal::LaunchLambdaType &&user_lambda) { internal::LaunchLambdaWrapperType wrapper_lambda = internal::CotestTypeUtils::WrapLaunchLambda(std::move(user_lambda)); // Note that user_lambda captures all by reference and these captures will not // be safe once launch coroutine yields eg to generate a mock call, so we need // to iterate the launch coroutine immediately. It is assumed that the lambda // is just a function call and this call should normally take arguments by // value in order to be safe. Args passed by reference need to be checked by // the user. auto call_payload = MakePayload(shared_from_this(), wrapper_lambda, GetLaunchText()); GetParentTestCoroutine()->YieldServer(std::move(call_payload)); } template R InteriorLaunchSession::GetResult(const InteriorEventSession *event) const { return internal::CotestTypeUtils::Specialise(event->GetUntypedLaunchResult()); } template const typename internal::Function::ArgumentTuple *InteriorMockCallSession::GetArgumentTuple() const { COTEST_ASSERT(state != State::Returned); COTEST_ASSERT(IsMockCall()); using FN = typename internal::Function; return static_cast(args); } template InteriorSignatureMockCS::InteriorSignatureMockCS(InteriorMockCallSession *const mcs_, const ArgumentTuple *args_tuple_) : mcs(mcs_), args_tuple(args_tuple_) {} template InteriorSignatureMockCS::~InteriorSignatureMockCS() {} template const typename InteriorSignatureMockCS::ArgumentTuple * InteriorSignatureMockCS::GetArgumentTuple() const { COTEST_ASSERT(!mcs->IsReturned()); // Cannot rely on args after return - take a copy! return args_tuple; } template template void InteriorSignatureMockCS::Return(U &&retval) { const UntypedReturnValuePointer p = internal::CotestTypeUtils::Generalise(std::forward(retval)); mcs->ReturnImpl(p); } } // namespace crf } // namespace testing #endif