add coroutines source and tests to repo as a sub-project alongside googletest and googlemock

This commit is contained in:
John 2024-02-14 10:24:53 +00:00
parent 9756ee7cba
commit 615c5bc563
46 changed files with 8831 additions and 0 deletions

168
coroutines/.clang-format Normal file
View File

@ -0,0 +1,168 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
- Regex: '^<.*'
Priority: 2
SortPriority: 0
- Regex: '.*'
Priority: 3
SortPriority: 0
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
...

149
coroutines/CMakeLists.txt Normal file
View File

@ -0,0 +1,149 @@
########################################################################
# Note: cotest is being brought up using cmake initially. MSVC and
# hermetic builds probably won't work, for the time being.
#
# CMake build script for cotest's coroutines support library.
#
# To run the tests for coroutines on Linux, use 'make test' or
# ctest. You can select which tests to run using 'ctest -R regex'.
# For more options, run 'ctest --help'.
option(coro_build_samples "Build coro's sample programs." OFF)
option(coro_build_tests "Build all of coro's own tests." OFF)
option(gtest_disable_pthreads "Disable uses of pthreads in gtest." OFF)
# A directory to find Google Test sources.
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gtest/CMakeLists.txt")
set(gtest_dir gtest)
else()
set(gtest_dir ../googletest)
endif()
# A directory to find Google Mock sources.
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gmock/CMakeLists.txt")
set(gmock_dir gmock)
else()
set(gmock_dir ../googlemock)
endif()
########################################################################
#
# Project-wide settings
# Name of the project.
#
# CMake files in this project can refer to the root source directory
# as ${coro_SOURCE_DIR} and to the root binary directory as
# ${coro_BINARY_DIR}.
# Language "C" is required for find_package(Threads).
# Project version:
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0048 NEW)
project(cotest VERSION ${COROUTINES_VERSION} LANGUAGES CXX C)
if (POLICY CMP0063) # Visibility
cmake_policy(SET CMP0063 NEW)
endif (POLICY CMP0063)
# Instructs CMake to process Google Mock's CMakeLists.txt and add its
# targets to the current scope. We are placing Google Mock's binary
# directory in a subdirectory of our own as VC compilation may break
# if they are the same (the default).
add_subdirectory("${gmock_dir}" "${coro_BINARY_DIR}/${gmock_dir}")
# Define helper functions and macros used by Google Test.
include(${gtest_dir}/cmake/internal_utils.cmake)
config_compiler_and_linker() # Defined in internal_utils.cmake.
# Adds coroutines header directories to the search path.
set(coro_build_include_dirs
"${coro_SOURCE_DIR}/include"
"${coro_SOURCE_DIR}"
"${gmock_SOURCE_DIR}/include"
# This directory is needed to build directly from Google Mock sources.
"${gmock_SOURCE_DIR}")
include_directories(${coro_build_include_dirs})
########################################################################
#
# Defines the coroutines library. This is an internal library.
# coroutines library. We build it using more strict warnings than what
# are used for other targets, to ensure that cotest can be compiled by a user
# aggressive about warnings.
# For combined sources
#cxx_library(cotest "${cxx_strict}" "${gmock_dir}/src/gmock-all.cc" src/cotest-all.cc)
# For cotest development, helps keep tabs on deps
cxx_library(cotest
"${cxx_strict}"
"${gmock_dir}/src/gmock-all.cc"
src/cotest.cc
src/cotest-coro-thread.cc
src/cotest-crf-core.cc
src/cotest-crf-launch.cc
src/cotest-crf-mock.cc
src/cotest-crf-payloads.cc
src/cotest-crf-synch.cc
src/cotest-crf-test.cc
src/cotest-integ-finder.cc
src/cotest-integ-mock.cc)
# Attach header directory information
# to the targets for when we are part of a parent build (ie being pulled
# in via add_subdirectory() rather than being a standalone build).
string(REPLACE ";" "$<SEMICOLON>" dirs "${coro_build_include_dirs}")
target_include_directories(cotest SYSTEM INTERFACE
"$<BUILD_INTERFACE:${dirs}>"
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>")
########################################################################
#
# coroutine library tests.
#
# The tests are not built by default. To build them, set the
# gtest_build_tests option to ON. You can do it by running ccmake
# or specifying the -Dgtest_build_tests=ON flag when running cmake.
if (coro_build_tests)
# Allow use of gtest
include_directories(PRIVATE "${gtest_dir}/include" "${gmock_dir}/include")
link_libraries(gtest gtest_main gmock)
# This must be set in the root directory for the tests to be run by
# 'make test' or ctest.
enable_testing()
############################################################
# Corountines internal tests
cxx_test(coro-test-thread cotest)
cxx_test(exp-finder-test cotest)
############################################################
# Test cases that drive complete cotest - phase 1
cxx_test(cotest-action-macro cotest)
cxx_test(cotest-action-functor cotest)
cxx_test(cotest-action-poly cotest)
cxx_test(cotest-ui cotest)
cxx_test(cotest-cardinality cotest)
cxx_test(cotest-ext-filter cotest)
cxx_test(cotest-int-filter cotest)
cxx_test(cotest-lambda cotest)
cxx_test(cotest-mockfunction cotest)
cxx_test(cotest-wild cotest)
cxx_test(cotest-types cotest)
############################################################
# Test cases that drive complete cotest - phase 2
cxx_test(cotest-launch cotest)
cxx_test(cotest-launch-mock cotest)
cxx_test(cotest-all-in cotest)
cxx_test(cotest-mutex cotest)
cxx_test(cotest-launch-multi-coro cotest)
cxx_test(cotest-launch-lifetime cotest)
cxx_test(cotest-serverised cotest)
endif()

View File

@ -0,0 +1,607 @@
#ifndef COROUTINES_INCLUDE_CORO_COTEST_H_
#define COROUTINES_INCLUDE_CORO_COTEST_H_
#include "cotest/internal/cotest-integ-mock.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace testing {
// We use preprocessor macros when we need stringification or
// other trickery, and also when we expect to want to collect
// __FILE__ and __LINE__ (but not all have been added yet).
// ------------------ utils ------------------
// Credit for this goes to the GMock implementors
#define COTEST_PP_VARIADIC_CALL(_Macro, ...) \
GMOCK_PP_IDENTITY(GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG0(__VA_ARGS__))(__VA_ARGS__))
#define COTEST_STR(S) COTEST_STRW(S)
#define COTEST_STRW(S) #S
// ------------------ mock call session ------------------
#define DROP() Drop()
#define ACCEPT() Accept()
// Covers 0-arg and 1-arg cases including unprotected commas.
#define RETURN(...) Return(__VA_ARGS__)
// See also MOCK_CALL_HANDLE, IS_CALL and WAIT_FOR_CALL below
// ------------------ launch session -------------------------
// Launch the supplied expression and return launch session
#define LAUNCH(...) \
cotest_coro_->Launch<decltype(__VA_ARGS__)>([&]() -> decltype(auto) { return (__VA_ARGS__); }, \
COTEST_STR(__VA_ARGS__))
// See also IS_RESULT and WAIT_FOR_RESULT below.
// Result obtained using call syntax:
// result = event_session( launch_session );
// Note on cotest_coro_
// cotest_coro_ is passed into coroutine body as a function parameter
// and is also a public member of a Coroutine. In both cases it just
// points to the coroutine. So, in the body we can use NEXT_EVENT()
// and outside, when we have a Coroutine, we can do coro->NEXT_EVENT().
// cotest_coro_ is also helpful for reducing pollution of the global
// namespace.
// ------------------ cardinality etc --------------------
// Indicate that the coroutine does not need to run to completion
// in order for the test to pass
#define SATISFY() cotest_coro_->SetSatisfied()
// Indicate that it is not an error for the coroutine to see further
// mock calls after exiting (they will be dropped)
#define RETIRE() cotest_coro_->Retire()
// Return from mthe coroutine body. Please use this in preference to return
// for forward-compatibility with C++20 coroutines (and so we can grab
// __FILE__ and __LINE__).
#define EXIT_COROUTINE() return
// --------------------- utilities -------------------------
// De-allocate unused launch coroutines. This plugs a memory leak but
// could slow things down if used frequently.
#define COTEST_CLEANUP() (crf::LaunchCoroutinePool::GetInstance()->CleanUp())
// ------------------ Declaring coroutines ------------------
// Use one of:
// auto my_coro = COROUTINE(){ ...code... };
// or
// auto my_coro = COROUTINE(MyCoroName){ ...code... };
#define COROUTINE(...) COTEST_PP_VARIADIC_CALL(COROUTINE_ARG_, __VA_ARGS__)
#define COROUTINE_ARG_0() ::testing::internal::LambdaCoroFactory() + [&](::testing::internal::Coroutine * cotest_coro_)
#define COROUTINE_ARG_1(NAME) \
::testing::internal::LambdaCoroFactory(COTEST_STR(NAME)) + [&](::testing::internal::Coroutine * cotest_coro_)
// Allocate a coroutine on the heap
#define NEW_COROUTINE(...) COTEST_PP_VARIADIC_CALL(NEW_COROUTINE_ARG_, __VA_ARGS__)
#define NEW_COROUTINE_ARG_0() \
::testing::internal::LambdaCoroFactory() - [&](::testing::internal::Coroutine * cotest_coro_)
#define NEW_COROUTINE_ARG_1(NAME) \
::testing::internal::LambdaCoroFactory(COTEST_STR(NAME)) - [&](::testing::internal::Coroutine * cotest_coro_)
// ------------------ watching for mock calls ------------------
#define WATCH_CALL(...) COTEST_PP_VARIADIC_CALL(WATCH_CALL_ARG_, __VA_ARGS__)
// WATCH_CALL(obj, call) is similar to EXPECT_CALL including implied priority
// scheme and With().
#define WATCH_CALL_ARG_2(obj, call) \
cotest_coro_->WatchCall(std::move(GMOCK_GET_MOCKSPEC(obj, call, gmock)), __FILE__, __LINE__, #obj, #call)
// Wildcard version: any mocked call on supplied mock object
#define WATCH_CALL_ARG_1(obj) \
cotest_coro_->WatchCall(__FILE__, __LINE__, static_cast<::testing::crf::UntypedMockObjectPointer>(&(obj)))
// Wildcard version: any mocked call
#define WATCH_CALL_ARG_0() cotest_coro_->WatchCall(__FILE__, __LINE__)
// ------------------ variadic MOCK_CALL_HANDLE() ------------------
#define MOCK_CALL_HANDLE(...) COTEST_PP_VARIADIC_CALL(MOCK_CALL_HANDLE_ARG_, __VA_ARGS__)
// NULL session for any mock call
#define MOCK_CALL_HANDLE_ARG_0() (::testing::EventHandle())
// No different than MOCK_CALL_HANDLE() - not for human use, only consistency.
#define MOCK_CALL_HANDLE_ARG_1(OBJ) (::testing::EventHandle())
// NULL call session for any mock call with signature matching the supplied mock
// method
#define MOCK_CALL_HANDLE_ARG_2(OBJ, METHOD) \
(::testing::internal::CreateSignatureHandle(std::move(GMOCK_GET_MOCKSPEC(OBJ, METHOD, gmockq))))
// Can also use SignatureHandle<SIGNATURE> where SIGNATURE is eg int(void *)
// ------------------ variadic IS_CALL() ------------------
#define IS_CALL(...) COTEST_PP_VARIADIC_CALL(IS_CALL_ARG_, __VA_ARGS__)
// Is the event a mock call?
#define IS_CALL_ARG_0() IsMockCall()
// Is the event a mock call on the supplied mock object?
#define IS_CALL_ARG_1(obj) IsObject((::testing::crf::UntypedMockObjectPointer) & (obj))
// Is the mock call a match to the method. Matchers and With() are
// supported as with EXPECT_CALL().
#define IS_CALL_ARG_2(obj, call) CoTestIsCallImpl_(std::move(GMOCK_GET_MOCKSPEC(obj, call, gmockq)))
// ------------------ variadic IS_RESULT ------------------
#define IS_RESULT(...) COTEST_PP_VARIADIC_CALL(IS_RESULT_ARG_, __VA_ARGS__)
// Is this event a completed launch result?
#define IS_RESULT_ARG_0() IsLaunchResult()
// Is this event a completed launch result from the given launch
// session?
#define IS_RESULT_ARG_1(DC) IsLaunchResult(DC)
// Note: these operations also tell cotest that the launch return has
// been detected by the coroutine. If this does not happen, cotest will
// report an error.
// ---------------- wait for call ------------------
#define WAIT_FOR_CALL_NSE(...) COTEST_PP_VARIADIC_CALL(WAIT_FOR_CALL_NSE_ARG_, __VA_ARGS__)
#define WAIT_FOR_CALL(...) COTEST_PP_VARIADIC_CALL(WAIT_FOR_CALL_ARG_, __VA_ARGS__)
// Note: _NSE versions do not use the gcc statement expression extension.
// This extnesion permits function-like macros safely that can yield
// inside C++20 coroutines.
// ------------------ By method ------------------
// "Wait" for a mock call that satisfies a matcher similar to
// EXPECT_CALL(). With() not supported. This will drop non-matching
// calls and then when one matches it will accept it and return the
// session.
#define WAIT_FOR_CALL_NSE_ARG_3(CS, OBJ, METHOD) \
auto CS = MOCK_CALL_HANDLE_ARG_2(OBJ, METHOD); \
do { \
auto cg_ = NEXT_EVENT(); \
CS = cg_.IS_CALL_ARG_2(OBJ, METHOD); \
if (!CS) cg_.DROP(); \
} while (!CS); \
CS.ACCEPT()
#define WAIT_FOR_CALL_ARG_2(OBJ, METHOD) \
({ \
WAIT_FOR_CALL_NSE_ARG_3(cs, OBJ, METHOD); \
cs; \
})
// ------------------ By object ------------------
// "Wait" for a mock call on the given object. This will drop non-matching
// calls and then when one matches it will accept it and return the
// session.
#define WAIT_FOR_CALL_NSE_ARG_2(CG, OBJ) \
auto CG = MOCK_CALL_HANDLE_ARG_0(); \
do { \
auto cg_ = NEXT_EVENT(); \
CG = cg_.IS_CALL_ARG_1(OBJ); \
if (!CG) cg_.DROP(); \
} while (!CG); \
CG.ACCEPT()
#define WAIT_FOR_CALL_ARG_1(OBJ) \
({ \
WAIT_FOR_CALL_NSE_ARG_2(cg, OBJ); \
cg; \
})
// ------------------- Any call -----------------------
// "Wait" for any mock call. This will accept it and return the
// session.
#define WAIT_FOR_CALL_NSE_ARG_1(CG) \
auto CG = MOCK_CALL_HANDLE_ARG_0(); \
do { \
auto cg_ = NEXT_EVENT(); \
CG = cg_.IS_CALL_ARG_0(); \
if (!CG) cg_.DROP(); \
} while (!CG); \
CG.ACCEPT()
#define WAIT_FOR_CALL_ARG_0() \
({ \
WAIT_FOR_CALL_NSE_ARG_1(cg); \
cg; \
})
// -------------- wait for call from ----------------
#define WAIT_FOR_CALL_FROM_NSE(...) COTEST_PP_VARIADIC_CALL(WAIT_FOR_CALL_FROM_NSE_ARG_, __VA_ARGS__)
#define WAIT_FOR_CALL_FROM(...) COTEST_PP_VARIADIC_CALL(WAIT_FOR_CALL_FROM_ARG_, __VA_ARGS__)
// ------------------ By method ------------------
// As above, but from given lauch session only
#define WAIT_FOR_CALL_FROM_NSE_ARG_4(CS, OBJ, METHOD, DS) \
auto CS = MOCK_CALL_HANDLE_ARG_2(OBJ, METHOD); \
do { \
auto cg_ = NEXT_EVENT(); \
CS = cg_.IS_CALL_ARG_2(OBJ, METHOD).From(DS); \
if (!CS) cg_.DROP(); \
} while (!CS); \
CS.ACCEPT()
#define WAIT_FOR_CALL_FROM_ARG_3(OBJ, METHOD, DS) \
({ \
WAIT_FOR_CALL_FROM_NSE_ARG_4(cs, OBJ, METHOD, DS); \
cs; \
})
// ------------------ By object ------------------
// As above, but from given lauch session only
#define WAIT_FOR_CALL_FROM_NSE_ARG_3(CG, OBJ, DS) \
auto CG = MOCK_CALL_HANDLE_ARG_0(); \
do { \
auto cg_ = NEXT_EVENT(); \
CG = cg_.IS_CALL_ARG_1(OBJ).From(DS); \
if (!CG) cg_.DROP(); \
} while (!CG); \
CG.ACCEPT()
#define WAIT_FOR_CALL_FROM_ARG_2(OBJ, DS) \
({ \
WAIT_FOR_CALL_FROM_NSE_ARG_3(cg, OBJ, DS); \
cg; \
})
// ------------------- Any call -----------------------
// As above, but from given lauch session only
#define WAIT_FOR_CALL_FROM_NSE_ARG_2(CG, DS) \
auto CG = MOCK_CALL_HANDLE_ARG_0(); \
do { \
auto cg_ = NEXT_EVENT(); \
CG = cg_.IS_CALL_ARG_0().From(DS); \
if (!CG) cg_.DROP(); \
} while (!CG); \
CG.ACCEPT()
#define WAIT_FOR_CALL_FROM_ARG_1(DS) \
({ \
WAIT_FOR_CALL_FROM_NSE_ARG_2(cg, DS); \
cg; \
})
// ---------------- wait for result ------------------
// Wait for a completed launch, dropping mock calls
#define WAIT_FOR_RESULT_NSE(CG) \
auto CG = ::testing::EventHandle(); \
do { \
auto cg_ = NEXT_EVENT(); \
CG = cg_.IS_RESULT_ARG_0(); \
if (!CG) cg_.DROP(); \
} while (!CG);
#define WAIT_FOR_RESULT() \
({ \
WAIT_FOR_RESULT_NSE(cg); \
cg; \
})
// ---------------------- COTEST ---------------------------
#define COTEST_TEST_CLASS_NAME_(TEST_SUITE_NAME, TEST_NAME) TEST_SUITE_NAME##_##TEST_NAME##_Cotest
// Declare a "pure" cotest test. Usage is similar to TEST(). The given body
// is the body of a coroutine. Testing assets should be declared within
// the coro body.
#define COTEST(TEST_SUITE_NAME, TEST_NAME) \
static void COTEST_TEST_CLASS_NAME_(TEST_SUITE_NAME, TEST_NAME)(::testing::internal::Coroutine * cotest_coro_); \
TEST(TEST_SUITE_NAME, TEST_NAME) { \
auto c = ::testing::internal::Coroutine(COTEST_TEST_CLASS_NAME_(TEST_SUITE_NAME, TEST_NAME), \
COTEST_STR(TEST_NAME)); \
} \
static void COTEST_TEST_CLASS_NAME_(TEST_SUITE_NAME, TEST_NAME)(::testing::internal::Coroutine * cotest_coro_)
// Note: this is not in fact the most flexible way to use cotest. A regular
// TEST() case can declare multiple coroutines, whereas a COTEST() only has one.
// ------------------ serverised API ------------------
// Returns the next valid event session. An event can be a mock call, or a launch return.
#define NEXT_EVENT() cotest_coro_->NextEvent(__FILE__, __LINE__)
// This is a lower-level alternative to the WAIT_FOR_ macros, and if the
// returned event is a mock call, the user is required to call DROP() or
// ACCEPT() on it before doing anything else with the cotest API, or exiting.
// NEXT_EVENT will return a mock call session for every event the coro
// can see: if WATCH_CALL() is used, this will be all mock calls not
// handled by a higher-priority watch or expectations.
//
// It is intended for use in a message loop, for when mock calls and launch
// completions must be handled in whatever order they arrive. This is
// termed serverised style.
// ------------------ Classes ------------------
// Handle for the session created by a LAUNCH(). Templated on the session
// return type, which is simply the decltype() of the supplied expression.
template <class RESULT_TYPE>
class LaunchHandle {
public:
LaunchHandle() = default;
explicit LaunchHandle(std::shared_ptr<crf::InteriorLaunchSession<RESULT_TYPE>> crf_ls_);
operator bool() const;
crf::InteriorLaunchSession<RESULT_TYPE> *GetCRF_();
private:
std::shared_ptr<crf::InteriorLaunchSession<RESULT_TYPE>> crf_ls;
};
// Handle for any event received by NEXT_EVENT() which can be a call
// session or a launch result session.
class EventHandle {
public:
EventHandle() = default;
explicit EventHandle(std::shared_ptr<crf::InteriorEventSession> crf_es_);
EventHandle IsLaunchResult() const;
template <typename RESULT_TYPE>
EventHandle IsLaunchResult(LaunchHandle<RESULT_TYPE> launch_session) const;
template <typename RESULT_TYPE>
RESULT_TYPE operator()(LaunchHandle<RESULT_TYPE> launch_session) const;
template <typename R, typename... Args>
SignatureHandle<R(Args...)> CoTestIsCallImpl_(MockSpec<R(Args...)> &&mock_spec);
EventHandle IsObject(crf::UntypedMockObjectPointer object);
EventHandle IsMockCall();
operator bool() const;
EventHandle Drop();
EventHandle Accept();
EventHandle Return();
template <typename RESULT_TYPE>
EventHandle From(LaunchHandle<RESULT_TYPE> &source);
EventHandle FromMain();
std::string GetName() const;
private:
std::shared_ptr<crf::InteriorEventSession> crf_es;
};
// Handle for a mock call session when the function type is known. Templated
// on the function type eg int(char *)
template <typename R, typename... Args>
class SignatureHandle<R(Args...)> : public EventHandle {
public:
using ArgumentTuple = typename internal::Function<R(Args...)>::ArgumentTuple;
SignatureHandle() = default;
SignatureHandle(std::shared_ptr<crf::InteriorEventSession> crf_es_,
std::shared_ptr<crf::InteriorSignatureMockCS<R(Args...)>> &&crf_sig_);
SignatureHandle IsMockCall();
operator bool() const;
SignatureHandle Drop();
SignatureHandle Accept();
SignatureHandle Return();
template <typename RESULT_TYPE>
SignatureHandle From(LaunchHandle<RESULT_TYPE> &source);
SignatureHandle FromMain();
template <typename U>
SignatureHandle Return(U &&retval);
const ArgumentTuple &GetArgs() const;
template <size_t I>
const typename internal::Function<R(Args...)>::Arg<I>::type &GetArg() const;
SignatureHandle With(const Matcher<const ArgumentTuple &> &m);
template <size_t I>
SignatureHandle WithArg(const Matcher<const typename internal::Function<R(Args...)>::Arg<I>::type &> &m);
private:
std::shared_ptr<crf::InteriorSignatureMockCS<R(Args...)>> crf_sig;
};
// ------------------ Templated members ------------------
template <typename RESULT_TYPE>
LaunchHandle<RESULT_TYPE>::LaunchHandle(std::shared_ptr<crf::InteriorLaunchSession<RESULT_TYPE>> crf_ls_)
: crf_ls(crf_ls_) {}
template <typename RESULT_TYPE>
LaunchHandle<RESULT_TYPE>::operator bool() const {
return !!crf_ls;
}
template <typename RESULT_TYPE>
crf::InteriorLaunchSession<RESULT_TYPE> *LaunchHandle<RESULT_TYPE>::GetCRF_() {
return crf_ls.get();
}
template <typename RESULT_TYPE>
EventHandle EventHandle::IsLaunchResult(LaunchHandle<RESULT_TYPE> launch_session) const {
if (crf_es->IsLaunchResult(launch_session.GetCRF_()))
return *this;
else
return EventHandle();
}
template <typename RESULT_TYPE>
RESULT_TYPE EventHandle::operator()(LaunchHandle<RESULT_TYPE> launch_session) const {
if (crf_es->IsLaunchResult(launch_session.GetCRF_()))
return launch_session.GetCRF_()->GetResult(crf_es.get());
else
COTEST_ASSERT(!"Test failure: event is not a launch result");
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> EventHandle::CoTestIsCallImpl_(MockSpec<R(Args...)> &&mock_spec) {
const FunctionMocker<R(Args...)> *mocker = mock_spec.InternalGetMocker();
crf::UntypedMockObjectPointer mock_object = mocker->MockObjectLocked();
auto utmb = static_cast<const internal::UntypedFunctionMockerBase *>(mocker);
auto untyped_mocker = static_cast<crf::UntypedMockerPointer>(utmb);
COTEST_ASSERT(mock_object && "NULL Mock object used with IS_CALL()");
COTEST_ASSERT(crf_es && "event session is NULL, check for failed test");
if (!crf_es->IsMockCall()) return SignatureHandle<R(Args...)>();
auto crf_mcs = std::static_pointer_cast<crf::InteriorMockCallSession>(crf_es);
// Check the mock object
if (mock_object != crf_mcs->GetMockObject()) return SignatureHandle<R(Args...)>();
// Check whether the same method is used (accurate even when the name
// and signature are the same due eg const overloading)
if (untyped_mocker != crf_mcs->GetMocker()) return SignatureHandle<R(Args...)>();
// This is the correct method, so try to match the values
// We have a mock spec (on a NULL mock object) and can get the matchers tuple
// from it
auto matchers = mock_spec.InternalGetMatchers();
// Get the arguments for matching and signature call session
auto args_tuple = crf_mcs->GetArgumentTuple<R(Args...)>();
// Let Google Test perform the matching
if (!internal::TupleMatches(matchers, *args_tuple)) return SignatureHandle<R(Args...)>();
auto crf_sig = std::make_shared<crf::InteriorSignatureMockCS<R(Args...)>>(crf_mcs.get(), args_tuple);
return SignatureHandle<R(Args...)>(crf_es, std::move(crf_sig));
}
template <typename RESULT_TYPE>
EventHandle EventHandle::From(LaunchHandle<RESULT_TYPE> &source) {
COTEST_ASSERT(crf_es);
bool ok = crf_es->IsFrom(source.GetCRF_());
return ok ? *this : EventHandle();
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)>::SignatureHandle(std::shared_ptr<crf::InteriorEventSession> crf_es_,
std::shared_ptr<crf::InteriorSignatureMockCS<R(Args...)>> &&crf_sig_)
: EventHandle(crf_es_), crf_sig(std::move(crf_sig_)) {}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::IsMockCall() {
return *this;
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)>::operator bool() const {
return !!crf_sig;
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::Drop() {
EventHandle::Drop();
return *this;
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::Accept() {
EventHandle::Accept();
return *this;
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::Return() {
EventHandle::Return();
return *this;
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::FromMain() {
bool ok = EventHandle::FromMain();
return ok ? *this : SignatureHandle<R(Args...)>();
}
template <typename R, typename... Args>
template <typename RESULT_TYPE>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::From(LaunchHandle<RESULT_TYPE> &source) {
bool ok = EventHandle::From(source);
return ok ? *this : SignatureHandle<R(Args...)>();
}
template <typename R, typename... Args>
template <typename U>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::Return(U &&retval) {
COTEST_ASSERT(crf_sig && "call session is NULL, check for failed test");
crf_sig->Return(std::forward<U>(retval));
return *this;
}
template <typename R, typename... Args>
const typename SignatureHandle<R(Args...)>::ArgumentTuple &SignatureHandle<R(Args...)>::GetArgs() const {
COTEST_ASSERT(crf_sig && "call session is NULL, check for failed test");
return *crf_sig->GetArgumentTuple();
}
template <typename R, typename... Args>
template <size_t I>
const typename internal::Function<R(Args...)>::Arg<I>::type &SignatureHandle<R(Args...)>::GetArg() const {
return std::get<I>(GetArgs());
}
template <typename R, typename... Args>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::With(const Matcher<const ArgumentTuple &> &m) {
if (!crf_sig) return SignatureHandle<R(Args...)>(); // may have already mismatched
// Let Google Test perform the matching
if (!m.Matches(GetArgs())) return SignatureHandle<R(Args...)>();
return *this;
}
template <typename R, typename... Args>
template <size_t I>
SignatureHandle<R(Args...)> SignatureHandle<R(Args...)>::WithArg(
const Matcher<const typename internal::Function<R(Args...)>::Arg<I>::type &> &m) {
if (!crf_sig) return SignatureHandle<R(Args...)>(); // may have already mismatched
// Let Google Test perform the matching
if (!m.Matches(GetArg<I>())) return SignatureHandle<R(Args...)>();
return *this;
}
namespace internal {
// I apologise for the operator overloads in this class.
class LambdaCoroFactory {
public:
LambdaCoroFactory(std::string name_ = "COROUTINE()") : name(name_) {}
// Sorry about these
Coroutine operator+(Coroutine::BodyFunctionType lambda) { return Coroutine(lambda, name); }
Coroutine *operator-(Coroutine::BodyFunctionType lambda) { return new Coroutine(lambda, name); }
private:
const std::string name;
};
} // namespace internal
} // namespace testing
#endif

View File

@ -0,0 +1,99 @@
#ifndef COROUTINES_INCLUDE_CORO_COTEST_CORO_COMMON_H_
#define COROUTINES_INCLUDE_CORO_COTEST_CORO_COMMON_H_
#include <exception>
#include <functional>
#include <iostream>
#include "cotest/internal/cotest-util-logging.h"
namespace coro_impl {
class Payload {
public:
Payload() = default;
Payload(const Payload &) = delete;
Payload(Payload &&) = delete;
Payload &operator=(const Payload &) = delete;
Payload &operator=(Payload &&) = delete;
virtual ~Payload() = default;
virtual std::string DebugString() const = 0;
};
template <typename PAY, typename ORIG>
const PAY &PeekPayload(const std::unique_ptr<ORIG> &p) {
return *static_cast<PAY *>(p.get());
}
template <typename PAY, typename ORIG>
std::unique_ptr<PAY> SpecialisePayload(std::unique_ptr<ORIG> &&p) {
return std::unique_ptr<PAY>(static_cast<PAY *>(p.release()));
}
template <typename PAY, typename... Args>
std::unique_ptr<PAY> MakePayload(Args &&... args) {
return std::make_unique<PAY>(std::forward<Args>(args)...);
}
class ExteriorInterface {
public:
ExteriorInterface() = default;
ExteriorInterface(const ExteriorInterface &i) = delete;
ExteriorInterface(ExteriorInterface &&i) = delete;
ExteriorInterface &operator=(const ExteriorInterface &) = delete;
ExteriorInterface &operator=(ExteriorInterface &&) = delete;
virtual ~ExteriorInterface() = default;
virtual std::unique_ptr<Payload> Iterate(std::unique_ptr<Payload> &&to_coro) = 0;
virtual std::unique_ptr<Payload> ThrowIn(std::exception_ptr in_ex) = 0;
virtual void Cancel() = 0;
virtual bool IsCoroutineExited() const = 0;
virtual void SetName(std::string name_) = 0;
virtual std::string GetName() const = 0;
};
class InteriorInterface {
public:
InteriorInterface() = default;
InteriorInterface(const InteriorInterface &i) = delete;
InteriorInterface(InteriorInterface &&i) = delete;
InteriorInterface &operator=(const InteriorInterface &) = delete;
InteriorInterface &operator=(InteriorInterface &&) = delete;
virtual ~InteriorInterface() = default;
virtual std::unique_ptr<Payload> Yield(std::unique_ptr<Payload> &&from_coro) = 0;
// Find out which coro is currently running. Any of the instances of
// the same concrete type as the one called on, or NULL.
virtual InteriorInterface *GetActive() = 0;
};
using BodyFunction = std::function<void()>;
class CancellationException : public std::exception {};
// std::exception_ptr lets us be flexible with exception objects without
// having to worry about slicing. The object is stored in the compiler's
// special place and should be considered const - it *cannot* be accessed
// through the exception_ptr - all you can do is pass it around and
// throw it using std::rethrow_exception(). Note that exception_ptr is
// nullable (use = nullptr) but you shouldn't rethrow a null one. The
// exception object could be copied, so don't use identity semantics.
template <class ETYPE, class... Args>
std::exception_ptr MakeException(Args &&... args) try {
throw ETYPE(args...);
COTEST_ASSERT(false); // wut
} catch (ETYPE &) // Don't catch exceptions thrown in ETYPE's constructor
{
return std::current_exception();
}
inline std::ostream &operator<<(std::ostream &os, const Payload *payload) {
if (payload) os << payload->DebugString();
os << PtrToString(payload);
return os;
}
} // namespace coro_impl
#endif

View File

@ -0,0 +1,69 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CORO_THREAD_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CORO_THREAD_H_
#include <condition_variable>
#include <memory>
#include <mutex>
#include <set>
#include <thread>
#include "cotest-coro-common.h"
namespace coro_impl {
/**
* Implement stacky coroutines on C++ threads
*/
class CoroOnThread final : public ExteriorInterface, public InteriorInterface {
public:
// Rule of 5 but disallow copy and move. Immutable and non-nullable.
CoroOnThread() = delete;
CoroOnThread(const CoroOnThread &i) = delete;
CoroOnThread(CoroOnThread &&i) = delete;
CoroOnThread &operator=(const CoroOnThread &) = delete;
CoroOnThread &operator=(CoroOnThread &&) = delete;
~CoroOnThread();
CoroOnThread(BodyFunction cofn_, std::string name);
// ExteriorInterface
std::unique_ptr<Payload> Iterate(std::unique_ptr<Payload> &&to_coro) final;
std::unique_ptr<Payload> ThrowIn(std::exception_ptr in_ex) final;
void Cancel() final;
bool IsCoroutineExited() const final;
void SetName(std::string name_) final;
std::string GetName() const final;
// InteriorInterface
std::unique_ptr<Payload> Yield(std::unique_ptr<Payload> &&from_coro) final;
InteriorInterface *GetActive() final;
private:
enum class Phase { CoroutineRuns, MainRuns, CoroutineExited };
void ThreadRun();
void TrySetThreadName();
void NotifyPhase(Phase new_phase);
void WaitPhases(std::set<Phase> phases);
BodyFunction coro_run_function;
std::thread local_thread;
Phase phase = Phase::MainRuns;
mutable std::mutex phase_mutex;
std::condition_variable cv;
std::unique_ptr<Payload> payload;
std::exception_ptr payload_ex;
std::string name;
static InteriorInterface *active;
};
} // namespace coro_impl
#endif

View File

@ -0,0 +1,132 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_CORE_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_CORE_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include "cotest-coro-common.h"
#include "cotest-coro-thread.h"
#include "cotest-crf-payloads.h"
#include "cotest-util-logging.h"
#include "cotest-util-types.h"
namespace testing {
namespace crf {
// Select the thread-based coroutine impl for all the CRF coroutines.
using CoroImplType = coro_impl::CoroOnThread;
// ------------------ Classes ------------------
class MockRoutingSession;
class InteriorEventSession;
template <typename T>
class InteriorSignatureMockCS;
class InteriorLaunchSessionBase;
template <typename T>
class InteriorLaunchSession;
class TestCoroutine;
using coro_impl::MakePayload;
using coro_impl::PeekPayload;
using coro_impl::SpecialisePayload;
class MessageNode {
public:
// Reply payload, reply destination
using ReplyPair = std::pair<std::unique_ptr<Payload>, MessageNode *>;
MessageNode() = default;
MessageNode(const MessageNode &i) = delete;
MessageNode(MessageNode &&i) = delete;
MessageNode &operator=(const MessageNode &) = delete;
MessageNode &operator=(MessageNode &&) = delete;
virtual ~MessageNode() = default;
virtual ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) = 0;
virtual std::string DebugString() const = 0;
static std::unique_ptr<Payload> SendMessageFromMain(MessageNode *dest, std::unique_ptr<Payload> &&to_node);
private:
static std::unique_ptr<Payload> MessageLoop(MessageNode *dest, std::unique_ptr<Payload> &&to_node);
};
inline std::ostream &operator<<(std::ostream &os, const MessageNode *payload) {
if (payload) os << payload->DebugString();
os << coro_impl::PtrToString(payload);
return os;
}
class CoroutineBase : public virtual MessageNode {
public:
CoroutineBase(const CoroutineBase &i) = delete;
CoroutineBase(CoroutineBase &&i) = delete;
CoroutineBase &operator=(const CoroutineBase &) = delete;
CoroutineBase &operator=(CoroutineBase &&) = delete;
virtual ~CoroutineBase();
CoroutineBase(coro_impl::BodyFunction cofn_, std::string name_);
std::unique_ptr<Payload> Iterate(std::unique_ptr<Payload> &&to_coro);
void Cancel();
bool IsCoroutineExited() const;
std::unique_ptr<Payload> Yield(std::unique_ptr<Payload> &&from_coro);
coro_impl::InteriorInterface *GetImpl();
void SetName(std::string name_);
std::string GetName() const;
std::string ActiveStr() const;
private:
std::string name;
std::unique_ptr<CoroImplType> impl;
bool initial = true;
};
class MockSource : public virtual MessageNode {
public:
MockSource() = default;
MockSource(const MockSource &i) = delete;
MockSource(MockSource &&i) = delete;
MockSource &operator=(const MockSource &) = delete;
MockSource &operator=(MockSource &&) = delete;
virtual ~MockSource() = default;
virtual std::shared_ptr<MockRoutingSession> CreateMockRoutingSession(UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_obj_,
const char *name_) = 0;
void SetCurrentMockRS(std::shared_ptr<MockRoutingSession> current_mock_call_);
std::shared_ptr<MockRoutingSession> GetCurrentMockRS() const;
virtual LaunchCoroutine *GetAsCoroutine() = 0;
virtual const LaunchCoroutine *GetAsCoroutine() const = 0;
private:
std::shared_ptr<MockRoutingSession> current_mock_rs;
};
class ProxyForMain : public MockSource, public std::enable_shared_from_this<ProxyForMain> {
public:
ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) final;
std::shared_ptr<MockRoutingSession> CreateMockRoutingSession(UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_obj_,
const char *name_) final;
std::string DebugString() const final;
LaunchCoroutine *GetAsCoroutine() final;
const LaunchCoroutine *GetAsCoroutine() const final;
static std::shared_ptr<ProxyForMain> GetInstance();
};
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,76 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_LAUNCH_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_LAUNCH_H_
#include <map>
#include <memory>
#include <queue>
#include <string>
#include "cotest-crf-core.h"
#include "cotest-crf-mock.h"
#include "cotest-crf-payloads.h"
#include "cotest-util-types.h"
namespace testing {
namespace crf {
// ------------------ Classes ------------------
class MockRoutingSession;
class InteriorEventSession;
template <typename T>
class InteriorSignatureMockCS;
class InteriorLaunchSessionBase;
template <typename T>
class InteriorLaunchSession;
class TestCoroutine;
class LaunchCoroutine final : public CoroutineBase,
public MockSource,
public std::enable_shared_from_this<LaunchCoroutine> {
public:
LaunchCoroutine(std::string name_);
~LaunchCoroutine();
void Body();
ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) final;
ReplyPair IterateServer(std::unique_ptr<Payload> &&to_coro);
std::shared_ptr<MockRoutingSession> CreateMockRoutingSession(UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_obj_,
const char *name_) final;
std::shared_ptr<InteriorLaunchSessionBase> TryGetCurrentLaunchSession();
std::shared_ptr<const InteriorLaunchSessionBase> TryGetCurrentLaunchSession() const;
LaunchCoroutine *GetAsCoroutine() final;
const LaunchCoroutine *GetAsCoroutine() const final;
std::string DebugString() const final;
private:
std::weak_ptr<InteriorLaunchSessionBase> current_launch_session;
};
class LaunchCoroutinePool final : public virtual MessageNode {
public:
using PoolType = std::map<coro_impl::InteriorInterface *, std::shared_ptr<LaunchCoroutine>>;
LaunchCoroutine *TryGetUnusedLaunchCoro();
LaunchCoroutine *Allocate(std::string launch_text);
std::shared_ptr<MockSource> FindActiveMockSource();
ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) final;
std::string DebugString() const final;
int CleanUp();
static LaunchCoroutinePool *GetInstance();
private:
PoolType pool;
};
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,97 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_MOCK_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_MOCK_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include "cotest-crf-core.h"
#include "cotest-crf-payloads.h"
#include "cotest-util-types.h"
namespace testing {
namespace crf {
// ------------------ Classes ------------------
class MockRoutingSession;
class InteriorEventSession;
template <typename T>
class InteriorSignatureMockCS;
class InteriorLaunchSessionBase;
template <typename T>
class InteriorLaunchSession;
class TestCoroutine;
class MockRoutingSession : public std::enable_shared_from_this<MockRoutingSession> {
public:
MockRoutingSession(const MockRoutingSession &i) = delete;
MockRoutingSession(MockRoutingSession &&i) = delete;
MockRoutingSession &operator=(const MockRoutingSession &) = delete;
MockRoutingSession &operator=(MockRoutingSession &&) = delete;
~MockRoutingSession() = default;
MockRoutingSession(UntypedMockerPointer mocker_, UntypedMockObjectPointer mock_object_, std::string name_);
void PreMockUnlocked();
void Configure(TestCoroutine *handling_coroutine_);
bool SeenMockCallLocked(UntypedArgsPointer args);
UntypedReturnValuePointer ActionsAndReturnUnlocked();
TestCoroutine *GetHandlingTestCoro() const;
virtual std::shared_ptr<MockSource> GetMockSource() const = 0;
std::string GetName() const;
private:
virtual std::unique_ptr<Payload> SendMessageToSynchroniser(std::unique_ptr<Payload> &&to_tc) const = 0;
virtual std::unique_ptr<Payload> SendMessageToHandlingCoro(std::unique_ptr<Payload> &&to_tc) const = 0;
const UntypedMockerPointer mocker;
const UntypedMockObjectPointer mock_object;
const std::string name;
std::weak_ptr<InteriorMockCallSession> call_session;
std::set<const TestCoroutine *> handlers_that_dropped;
TestCoroutine *handling_coroutine;
};
// Note on the extraction of these subclasses: we could get by using virtuals on
// the MockSource, but it's desirable to name the final classes for the constext
// in which their methods will run: ExteriorMockRS runs in main, and
// InteriorMockRS runs in a launch coroutine's interior.
class ExteriorMockRS final : public MockRoutingSession {
public:
using MockRoutingSession::MockRoutingSession;
private:
std::shared_ptr<MockSource> GetMockSource() const final;
std::unique_ptr<Payload> SendMessageToSynchroniser(std::unique_ptr<Payload> &&to_tc) const final;
std::unique_ptr<Payload> SendMessageToHandlingCoro(std::unique_ptr<Payload> &&to_tc) const final;
};
class InteriorMockRS final : public MockRoutingSession {
public:
InteriorMockRS(std::shared_ptr<LaunchCoroutine> launch_coro_, UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_object_, std::string name_);
private:
std::shared_ptr<MockSource> GetMockSource() const final;
std::unique_ptr<Payload> SendMessageToSynchroniser(std::unique_ptr<Payload> &&to_tc) const final;
std::unique_ptr<Payload> SendMessageToHandlingCoro(std::unique_ptr<Payload> &&to_tc) const final;
const std::weak_ptr<LaunchCoroutine> launch_coro;
};
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,218 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_PAYLOADS_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_PAYLOADS_H_
#include <memory>
#include <string>
#include "cotest-coro-common.h"
#include "cotest-util-logging.h"
#include "cotest-util-types.h"
namespace testing {
namespace crf {
using UntypedMockerPointer = const void *;
using UntypedMockObjectPointer = const void *;
using UntypedArgsPointer = const void *;
using UntypedReturnValuePointer = const void *;
// ------------------ Classes ------------------
class MockSource;
class TestCoroutine;
class LaunchCoroutine;
class MockRoutingSession;
class InteriorEventSession;
class InteriorLaunchSessionBase;
class InteriorMockCallSession;
class InteriorLaunchResultSession;
template <typename T>
class InteriorLaunchSession;
enum class PayloadKind {
PreMock = 1000, // =1000 just to make valid values easy to spot in debugger
PreMockAck,
MockSeen,
DropMock,
AcceptMock,
MockAction,
ReturnMock,
Launch,
LaunchResult,
ResumeMain,
TCExited,
TCDestructing
};
class Payload : public coro_impl::Payload {
public:
virtual PayloadKind GetKind() const = 0;
};
class PreMockPayload final : public Payload {
public:
PreMockPayload(std::weak_ptr<MockRoutingSession> originator_, std::string name_,
UntypedMockObjectPointer mock_object_, UntypedMockerPointer mocker_);
PayloadKind GetKind() const final;
PreMockPayload *Clone() const;
std::weak_ptr<MockRoutingSession> GetOriginator() const;
std::string GetName() const;
UntypedMockObjectPointer GetMockObject() const;
UntypedMockerPointer GetMocker() const;
std::string DebugString() const final;
private:
const std::weak_ptr<MockRoutingSession> originator;
const std::string name;
const UntypedMockObjectPointer mock_object;
const UntypedMockerPointer mocker;
};
class PreMockAckPayload : public Payload {
public:
explicit PreMockAckPayload(std::weak_ptr<MockRoutingSession> originator_);
PayloadKind GetKind() const;
std::weak_ptr<MockRoutingSession> GetOriginator() const;
std::string DebugString() const final;
private:
const std::weak_ptr<MockRoutingSession> originator;
};
class MockSeenPayload final : public Payload {
public:
MockSeenPayload(std::weak_ptr<MockRoutingSession> originator_, UntypedArgsPointer args_, std::string name_,
UntypedMockObjectPointer mock_object_, UntypedMockerPointer mocker_);
PayloadKind GetKind() const final;
std::weak_ptr<MockRoutingSession> GetOriginator() const;
UntypedArgsPointer GetArgsUntyped() const;
std::string GetName() const;
UntypedMockObjectPointer GetMockObject() const;
UntypedMockerPointer GetMocker() const;
std::string DebugString() const final;
private:
const std::weak_ptr<MockRoutingSession> originator;
const UntypedArgsPointer args;
const std::string name;
const UntypedMockObjectPointer mock_object;
const UntypedMockerPointer mocker;
};
class MockResponsePayload : public Payload {
public:
MockResponsePayload(std::weak_ptr<MockRoutingSession> originator_,
std::weak_ptr<InteriorMockCallSession> responder_);
std::weak_ptr<MockRoutingSession> GetOriginator() const;
std::weak_ptr<InteriorMockCallSession> GetResponder() const;
protected:
const std::weak_ptr<MockRoutingSession> originator;
const std::weak_ptr<InteriorMockCallSession> responder;
};
class DropMockPayload final : public MockResponsePayload {
public:
using MockResponsePayload::MockResponsePayload;
PayloadKind GetKind() const final;
std::string DebugString() const final;
};
class AcceptMockPayload final : public MockResponsePayload {
public:
using MockResponsePayload::MockResponsePayload;
PayloadKind GetKind() const final;
std::string DebugString() const final;
};
class MockActionPayload final : public MockResponsePayload {
public:
using MockResponsePayload::MockResponsePayload;
PayloadKind GetKind() const final;
std::string DebugString() const final;
};
class ReturnMockPayload final : public MockResponsePayload {
public:
ReturnMockPayload(std::weak_ptr<MockRoutingSession> originator_, std::weak_ptr<InteriorMockCallSession> responder_,
UntypedReturnValuePointer result_);
PayloadKind GetKind() const final;
UntypedReturnValuePointer GetResult();
std::string DebugString() const final;
private:
const UntypedReturnValuePointer return_val_ptr;
};
class LaunchPayload final : public Payload {
public:
LaunchPayload(std::weak_ptr<InteriorLaunchSessionBase> originator_,
internal::LaunchLambdaWrapperType wrapper_lambda_, std::string name_);
PayloadKind GetKind() const final;
std::weak_ptr<InteriorLaunchSessionBase> GetOriginator() const;
internal::LaunchLambdaWrapperType GetLambdaWrapper() const;
std::string GetName() const;
std::string DebugString() const final;
private:
const std::weak_ptr<InteriorLaunchSessionBase> originator;
const internal::LaunchLambdaWrapperType wrapper_lambda;
const std::string name;
};
class LaunchResultPayload final : public Payload {
public:
LaunchResultPayload(std::weak_ptr<InteriorLaunchSessionBase> originator_, std::weak_ptr<LaunchCoroutine> responder_,
UntypedReturnValuePointer result_);
PayloadKind GetKind() const final;
std::weak_ptr<InteriorLaunchSessionBase> GetOriginator() const;
std::weak_ptr<LaunchCoroutine> GetResponder() const;
UntypedReturnValuePointer GetResult();
std::string DebugString() const final;
private:
const std::weak_ptr<InteriorLaunchSessionBase> originator;
const std::weak_ptr<LaunchCoroutine> responder;
const UntypedReturnValuePointer return_val_ptr;
};
class ResumeMainPayload : public Payload {
public:
explicit ResumeMainPayload(std::weak_ptr<TestCoroutine> originator_);
PayloadKind GetKind() const;
std::weak_ptr<TestCoroutine> GetOriginator() const;
std::string DebugString() const final;
private:
const std::weak_ptr<TestCoroutine> originator;
};
// Test Coroutine Exited
class TCExitedPayload : public Payload {
public:
explicit TCExitedPayload(std::weak_ptr<TestCoroutine> originator_);
PayloadKind GetKind() const;
std::weak_ptr<TestCoroutine> GetOriginator() const;
std::string DebugString() const final;
private:
const std::weak_ptr<TestCoroutine> originator;
};
// Test Coroutine Destructing
class TCDestructingPayload : public Payload {
public:
explicit TCDestructingPayload(std::weak_ptr<TestCoroutine> originator_);
PayloadKind GetKind() const;
std::weak_ptr<TestCoroutine> GetOriginator() const;
std::string DebugString() const final;
private:
const std::weak_ptr<TestCoroutine> originator;
};
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,49 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_SYNCH_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_SYNCH_H_
#include <memory>
#include <queue>
#include "cotest-crf-core.h"
#include "cotest-crf-payloads.h"
#include "cotest-util-types.h"
namespace testing {
namespace crf {
// ------------------ Classes ------------------
class MockRoutingSession;
class InteriorEventSession;
template <typename T>
class InteriorSignatureMockCS;
class InteriorLaunchSessionBase;
template <typename T>
class InteriorLaunchSession;
class TestCoroutine;
class PreMockSynchroniser : public virtual MessageNode {
public:
ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) override;
std::string DebugString() const override;
static PreMockSynchroniser *GetInstance();
private:
enum class State { Idle, PassToMain, Start, Working, Complete, WaitingForAck };
State state = State::Idle;
std::unique_ptr<PreMockPayload> current_pre_mock;
std::queue<std::weak_ptr<TestCoroutine>> send_pm_to;
static PreMockSynchroniser instance;
};
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,273 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_TEST_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_CRF_TEST_H_
#include <memory>
#include <string>
#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 <typename T>
class InteriorSignatureMockCS;
class InteriorLaunchSessionBase;
template <typename T>
class InteriorLaunchSession;
class TestCoroutine : public CoroutineBase, public std::enable_shared_from_this<TestCoroutine> {
public:
using OnExitFunction = std::function<void()>;
using CoroutineBase::CoroutineBase;
TestCoroutine(coro_impl::BodyFunction cofn_, std::string name_, OnExitFunction on_exit_function_);
~TestCoroutine();
ReplyPair ReceiveMessage(std::unique_ptr<Payload> &&to_node) override;
ReplyPair IterateServer(std::unique_ptr<Payload> &&to_coro);
void InitialActivity();
void YieldServer(std::unique_ptr<Payload> &&from_coro);
bool IsPendingEvent();
template <typename R>
std::shared_ptr<InteriorLaunchSession<R>> Launch(internal::LaunchLambdaType<R> &&user_lambda, std::string name);
std::shared_ptr<InteriorEventSession> NextEvent(const char *file, int line);
bool IsPostMockIterationRequested();
void DestructionIterations();
std::string DebugString() const override;
private:
const OnExitFunction on_exit_function;
std::unique_ptr<Payload> next_payload;
bool mock_call_locked = false;
bool extra_iteration_requested = false;
};
class InteriorLaunchSessionBase : public std::enable_shared_from_this<InteriorLaunchSessionBase> {
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 <typename R>
class InteriorLaunchSession : public InteriorLaunchSessionBase {
public:
InteriorLaunchSession(TestCoroutine *test_coroutine_, std::string dc_name_);
~InteriorLaunchSession();
void Launch(internal::LaunchLambdaType<R> &&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<InteriorLaunchSessionBase> 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<InteriorLaunchSessionBase> via_launch;
};
class InteriorMockCallSession : public InteriorEventSession,
public std::enable_shared_from_this<InteriorMockCallSession> {
public:
InteriorMockCallSession(TestCoroutine *test_coroutine_, bool via_main_,
std::shared_ptr<InteriorLaunchSessionBase> via_launch_,
std::unique_ptr<PreMockPayload> &&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 <typename F>
const typename internal::Function<F>::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<MockRoutingSession> 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<LaunchResultPayload> &&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<InteriorLaunchSessionBase> originator;
UntypedReturnValuePointer const return_value = nullptr;
};
template <typename R, typename... Args>
class InteriorSignatureMockCS<R(Args...)> {
using FN = typename internal::Function<R(Args...)>;
using ArgumentTuple = typename FN::ArgumentTuple;
public:
~InteriorSignatureMockCS();
InteriorSignatureMockCS(InteriorMockCallSession *mcs_, const ArgumentTuple *args_tuple_);
const ArgumentTuple *GetArgumentTuple() const;
template <typename U>
void Return(U &&retval);
private:
InteriorMockCallSession *const mcs;
const ArgumentTuple *const args_tuple;
};
// ------------------ Templated members ------------------
template <typename R>
std::shared_ptr<InteriorLaunchSession<R>> TestCoroutine::Launch(internal::LaunchLambdaType<R> &&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<InteriorLaunchSession<R>>(this, df_name);
ils->Launch(std::move(user_lambda));
return ils;
}
template <typename R>
InteriorLaunchSession<R>::InteriorLaunchSession(TestCoroutine *test_coroutine_, std::string dc_name_)
: InteriorLaunchSessionBase(test_coroutine_, dc_name_) {}
template <typename R>
InteriorLaunchSession<R>::~InteriorLaunchSession() {}
template <typename R>
void InteriorLaunchSession<R>::Launch(internal::LaunchLambdaType<R> &&user_lambda) {
internal::LaunchLambdaWrapperType wrapper_lambda =
internal::CotestTypeUtils<R>::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<LaunchPayload>(shared_from_this(), wrapper_lambda, GetLaunchText());
GetParentTestCoroutine()->YieldServer(std::move(call_payload));
}
template <typename R>
R InteriorLaunchSession<R>::GetResult(const InteriorEventSession *event) const {
return internal::CotestTypeUtils<R>::Specialise(event->GetUntypedLaunchResult());
}
template <typename F>
const typename internal::Function<F>::ArgumentTuple *InteriorMockCallSession::GetArgumentTuple() const {
COTEST_ASSERT(state != State::Returned);
COTEST_ASSERT(IsMockCall());
using FN = typename internal::Function<F>;
return static_cast<const typename FN::ArgumentTuple *>(args);
}
template <typename R, typename... Args>
InteriorSignatureMockCS<R(Args...)>::InteriorSignatureMockCS(InteriorMockCallSession *const mcs_,
const ArgumentTuple *args_tuple_)
: mcs(mcs_), args_tuple(args_tuple_) {}
template <typename R, typename... Args>
InteriorSignatureMockCS<R(Args...)>::~InteriorSignatureMockCS() {}
template <typename R, typename... Args>
const typename InteriorSignatureMockCS<R(Args...)>::ArgumentTuple *
InteriorSignatureMockCS<R(Args...)>::GetArgumentTuple() const {
COTEST_ASSERT(!mcs->IsReturned()); // Cannot rely on args after return - take a copy!
return args_tuple;
}
template <typename R, typename... Args>
template <typename U>
void InteriorSignatureMockCS<R(Args...)>::Return(U &&retval) {
const UntypedReturnValuePointer p = internal::CotestTypeUtils<R>::Generalise(std::forward<U>(retval));
mcs->ReturnImpl(p);
}
} // namespace crf
} // namespace testing
#endif

View File

@ -0,0 +1,66 @@
#ifndef COROUTINES_INCLUDE_CORO_COTEST_INTEG_FINDER_H_
#define COROUTINES_INCLUDE_CORO_COTEST_INTEG_FINDER_H_
#include <cstdint>
#include <exception>
#include <iostream>
#include <map>
#include <set>
#include "cotest-crf-core.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace testing {
namespace internal {
using MockHandlerScheme = UntypedFunctionMockerBase::UntypedExpectations;
class MockHandler {
protected:
MockHandler();
MockHandler(const MockHandler &) = default;
MockHandler(MockHandler &&) = default;
MockHandler &operator=(const MockHandler &) = default;
MockHandler &operator=(MockHandler &&) = default;
virtual ~MockHandler();
public:
virtual const MockHandlerScheme *GetMockHandlerScheme() const = 0;
virtual std::string GetName() const = 0;
virtual std::shared_ptr<crf::TestCoroutine> GetCRFTestCoroutine() = 0;
};
class CotestMockHandlerPool : public AlternateMockCallManager {
public:
using ShouldHandleCallPredicate = std::function<bool(ExpectationBase *)>;
using ForTestCoroutineLambda = std::function<void(std::shared_ptr<crf::TestCoroutine> &&crf_sp)>;
static CotestMockHandlerPool *GetOrCreateInstance();
void AddOwnerLocked(MockHandler *owner);
void RemoveOwnerLocked(MockHandler *owner);
void AddExpectation(std::function<void(void)> creator);
void PreMockUnlocked(const UntypedFunctionMockerBase *mocker, const void *mock_obj, const char *name) override;
void ForAll(ForTestCoroutineLambda &&lambda);
// Cannot be a template since we want a virtual interface to keep the
// dependency into gmock weak
bool IsUninteresting(const UntypedFunctionMockerBase *mocker, const void *untyped_args) const override;
ExpectationBase *FindMatchingExpectationLocked(const UntypedFunctionMockerBase *mocker, const void *untyped_args,
bool *is_mocker_exp) const override;
static ExpectationBase *Finder(std::vector<const MockHandlerScheme *> &schemes, ShouldHandleCallPredicate predicate,
unsigned *which);
private:
bool got_untyped_watchers = false;
std::set<MockHandler *> owners;
};
} // namespace internal
} // namespace testing
#endif

View File

@ -0,0 +1,445 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_INTEG_LAYER_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_INTEG_LAYER_H_
#include <list>
#include "cotest-crf-launch.h"
#include "cotest-crf-test.h"
#include "cotest-integ-finder.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace testing {
// ------------------ Classes ------------------
template <typename T>
class LaunchHandle;
class EventHandle;
template <typename T>
class SignatureHandle;
namespace internal {
class UntypedFunctionMockerBase; // A GMock class
template <typename T>
class CotestExpectationFactory;
template <typename T>
class CotestWatcher;
class CotestCardinality;
class RAIISetFlag;
class Coroutine : public MockHandler {
public:
using BodyFunctionType = std::function<void(Coroutine *)>;
Coroutine() = delete;
Coroutine(const Coroutine &i) = delete;
Coroutine(Coroutine &&i);
Coroutine &operator=(const Coroutine &) = delete;
Coroutine &operator=(Coroutine &&) = delete;
~Coroutine();
Coroutine(BodyFunctionType body, std::string name);
template <typename R>
LaunchHandle<R> Launch(LaunchLambdaType<R> &&user_lambda, std::string name);
void WatchCall(const char *file, int line, crf::UntypedMockObjectPointer obj = nullptr);
template <typename R, typename... Args>
TypedExpectation<R(Args...)> &WatchCall(MockSpec<R(Args...)> &&mock_spec, const char *file, int line,
const char *obj, const char *call);
void SetSatisfied();
void Retire();
bool IsRetired() const;
std::string GetName() const override;
EventHandle NextEvent(const char *file, int line);
std::shared_ptr<crf::TestCoroutine> GetCRFTestCoroutine() override;
void OnTestCoroExit();
void OnWatcherDestruct();
void AddWatcher(std::shared_ptr<ExpectationBase> watcher);
static RAIISetFlag RAIIGMockMutexIsLocked();
private:
const MockHandlerScheme *GetMockHandlerScheme() const override;
void DestructionIterations();
const std::shared_ptr<crf::TestCoroutine> crf;
const std::string name;
std::list<std::weak_ptr<ExpectationBase>> my_watchers_all;
const std::shared_ptr<CotestCardinality> my_cardinality;
MockHandlerScheme my_untyped_watchers;
bool retired = false;
bool initial_activity_complete = false;
static bool gmock_mutex_held; // because the mutex is static
public:
// When cotest UI macros are used with an explicit object eg
// my_coro.WATCH_CALL(), suppress the error caused by the macro
// injecting cotest_coro_->
Coroutine *const cotest_coro_;
};
template <typename R, typename... Args>
class CotestExpectationFactory<R(Args...)> : public SpecFactory<R(Args...)> {
private:
using F = R(Args...);
public:
CotestExpectationFactory() = delete;
CotestExpectationFactory(Coroutine *coroutine_, CotestCardinality *cardinality_)
: coroutine(coroutine_), cardinality(cardinality_) {}
using ArgumentMatcherTuple = typename Function<F>::ArgumentMatcherTuple;
OnCallSpec<F> *CreateOnCall(const char *a_file, int a_line, const ArgumentMatcherTuple &m) override;
std::shared_ptr<TypedExpectation<F>> CreateExpectation(FunctionMocker<F> *owning_mocker, const char *a_file,
int a_line, const std::string &a_source_text,
const ArgumentMatcherTuple &m) override;
Coroutine *const coroutine;
CotestCardinality *const cardinality;
};
template <typename R, typename... Args>
class CotestWatcher<R(Args...)> : public TypedExpectation<R(Args...)> {
using Result = typename Function<R(Args...)>::Result;
using ArgumentTuple = typename Function<R(Args...)>::ArgumentTuple;
using ArgumentMatcherTuple = typename Function<R(Args...)>::ArgumentMatcherTuple;
public:
CotestWatcher() = delete;
CotestWatcher(const CotestWatcher &) = delete;
CotestWatcher(CotestWatcher &&) = delete;
CotestWatcher &operator=(const CotestWatcher &) = delete;
CotestWatcher &operator=(CotestWatcher &&) = delete;
~CotestWatcher() override;
CotestWatcher(Coroutine *const coroutine_, crf::UntypedMockObjectPointer watched_mock_object_,
FunctionMocker<R(Args...)> *owning_mocker_, const char *a_file, int a_line,
const std::string &a_source_text, const ArgumentMatcherTuple &m, CotestCardinality *cardinality_);
void DetachCoroutine() override;
private:
// Decide whether the coroutine should accept the call. GMock mutex is
// LOCKED during this call.
bool ShouldHandleCall(const UntypedFunctionMockerBase *mocker, const void *untyped_args) override;
// Call passed exterior matching and must be shown to the coroutine
bool SeenMockCallLocked(const UntypedFunctionMockerBase *mocker, const void *untyped_args);
// Similar side-effects to to GetActionForArguments() and GetCurrentAction()
bool UpdateCardinality(const UntypedFunctionMockerBase *mocker, const void *untyped_args, ::std::ostream *what,
::std::ostream *why) override;
// Implement cotest action here
bool TryPerformAction(const UntypedFunctionMockerBase *mocker, const void *untyped_args,
const void **untyped_return_value) override;
// We need our own describe function because we haven't set up any
// matchers. At minimum, reveal that a coroutine is being used.
void ExplainMatchResultTo(const ArgumentTuple &args, ::std::ostream *os) const override;
private:
const crf::UntypedMockObjectPointer watched_mock_object;
const bool is_typed_watcher;
Coroutine *coroutine;
CotestCardinality *const cardinality;
};
class CotestCardinality : public CardinalityInterface {
public:
CotestCardinality();
bool IsSatisfiedByCallCount(int call_count) const override;
bool IsSaturatedByCallCount(int call_count) const override;
void DescribeTo(::std::ostream *os) const override;
void InteriorSetSatisfied();
void OnTestCoroExit();
bool OnSeenMockCall();
private:
enum class State { Unsatisfied, SatisfiedByUser, SatisfiedByExit, Oversaturated };
State state = State::Unsatisfied;
};
class RAIISetFlag {
public:
RAIISetFlag(bool *flag_) : flag(flag_), old_flag(*flag) {
COTEST_ASSERT(flag);
*flag = true;
}
RAIISetFlag(RAIISetFlag &) = delete;
RAIISetFlag &operator=(RAIISetFlag &) = delete;
RAIISetFlag(RAIISetFlag &&) = default;
RAIISetFlag &operator=(RAIISetFlag &&) = delete;
~RAIISetFlag() {
COTEST_ASSERT(*flag);
*flag = old_flag;
}
private:
bool *const flag;
bool const old_flag;
};
template <typename F>
SignatureHandle<F> CreateSignatureHandle(MockSpec<F> &&mock_spec) {
return SignatureHandle<F>();
}
// ------------------ Templated members ------------------
template <typename R>
LaunchHandle<R> Coroutine::Launch(LaunchLambdaType<R> &&user_lambda, std::string launch_text) {
return LaunchHandle<R>(crf->Launch<R>(std::move(user_lambda), launch_text));
}
template <typename R, typename... Args>
TypedExpectation<R(Args...)> &Coroutine::WatchCall(MockSpec<R(Args...)> &&mock_spec, const char *file, int line,
const char *obj, const char *call) {
CotestExpectationFactory<R(Args...)> factory(this, my_cardinality.get());
TypedExpectation<R(Args...)> &new_exp = mock_spec.InternalExpectedAt(&factory, file, line, obj, call);
// This is only to get the right GMock behaviour
new_exp.set_repeated_action(DoDefault());
// Hooking up backend to cardinality interface so we can
// satisfy and saturate.
new_exp.set_cardinality(Cardinality(my_cardinality));
return new_exp;
}
template <typename R, typename... Args>
OnCallSpec<R(Args...)> *CotestExpectationFactory<R(Args...)>::CreateOnCall(const char *a_file, int a_line,
const ArgumentMatcherTuple &m) {
return new OnCallSpec<F>(a_file, a_line, m);
}
template <typename R, typename... Args>
std::shared_ptr<TypedExpectation<R(Args...)>> CotestExpectationFactory<R(Args...)>::CreateExpectation(
FunctionMocker<F> *owning_mocker, const char *a_file, int a_line, const std::string &a_source_text,
const ArgumentMatcherTuple &m) {
const auto sp = std::make_shared<CotestWatcher<F>>(coroutine, nullptr, owning_mocker, a_file, a_line, a_source_text,
m, cardinality);
coroutine->AddWatcher(sp);
if (coroutine->IsRetired()) {
MutexLock l(&g_gmock_mutex);
sp->Retire();
}
return sp;
}
template <typename R, typename... Args>
CotestWatcher<R(Args...)>::CotestWatcher(Coroutine *const coroutine_,
crf::UntypedMockObjectPointer watched_mock_object_,
FunctionMocker<R(Args...)> *owning_mocker_, const char *a_file, int a_line,
const std::string &a_source_text, const ArgumentMatcherTuple &m,
CotestCardinality *cardinality__)
: CotestWatcher<R(Args...)>::TypedExpectation(owning_mocker_, a_file, a_line, a_source_text, m),
watched_mock_object(watched_mock_object_),
// NOTE: the owning_mocker_ could fall out of scope and be destructed
// while we're still alive, so try to avoid using it and if it's really
// necessary, a DetachMocker() mechanism will be needed.
is_typed_watcher(owning_mocker_ != nullptr),
coroutine(coroutine_),
cardinality(cardinality__) {}
template <typename R, typename... Args>
CotestWatcher<R(Args...)>::~CotestWatcher() {
if (coroutine) coroutine->OnWatcherDestruct();
}
template <typename R, typename... Args>
void CotestWatcher<R(Args...)>::DetachCoroutine() {
std::clog << COTEST_THIS << std::endl;
coroutine = nullptr;
if (!is_typed_watcher) {
// Since untyped watches are not registered with GMock registry, we
// need to directly request a cardinality state report.
MutexLock l(&g_gmock_mutex);
ExpectationBase::VerifyExpectationLocked();
}
}
template <typename R, typename... Args>
bool CotestWatcher<R(Args...)>::ShouldHandleCall(const UntypedFunctionMockerBase *mocker,
crf::UntypedArgsPointer untyped_args) {
RAIISetFlag gmmh(Coroutine::RAIIGMockMutexIsLocked()); // This is called from GMock with
// mutex held
COTEST_ASSERT(coroutine && "coroutine object was destructed before being sent a call");
std::clog << "CotestWatcher::ShouldHandleCall() (typed=" << is_typed_watcher << ")..." << std::endl;
COTEST_ASSERT(mocker);
// Respect retirement state and pre-requisites in gmock exp in case
// they are chained.
if (CotestWatcher<R(Args...)>::TypedExpectation::is_retired() ||
!CotestWatcher<R(Args...)>::TypedExpectation::AllPrerequisitesAreSatisfied())
return false;
// Apply exterior filtering (main and extra) if we've got typed arguments
if (is_typed_watcher) {
// We can use types based on our template arguments
const ArgumentTuple *typed_args = static_cast<const ArgumentTuple *>(untyped_args);
if (this->Matches(*typed_args)) return SeenMockCallLocked(mocker, untyped_args);
} else {
// We cannot use types based on our template arguments
if (!watched_mock_object || watched_mock_object == mocker->MockObjectLocked())
return SeenMockCallLocked(mocker, untyped_args);
}
return false;
}
template <typename R, typename... Args>
bool CotestWatcher<R(Args...)>::SeenMockCallLocked(const UntypedFunctionMockerBase *mocker, const void *untyped_args) {
std::clog << this << " CotestWatcher::SeenMockCallLocked()..." << std::endl;
// If oversaturated, return in order to permit cmock to detect this and talk
// to the user about it.
if (cardinality->OnSeenMockCall()) return true;
const std::shared_ptr<crf::MockSource> mock_source =
crf::LaunchCoroutinePool::GetInstance()->FindActiveMockSource();
const std::shared_ptr<crf::MockRoutingSession> crf_mrs = mock_source->GetCurrentMockRS();
auto crf_tc = coroutine->GetCRFTestCoroutine();
crf_mrs->Configure(crf_tc.get());
// Permit coroutine to perform interior filtering
const std::string name = mocker->NameLocked();
const bool accepted = crf_mrs->SeenMockCallLocked(untyped_args);
COTEST_ASSERT(crf::LaunchCoroutinePool::GetInstance()->FindActiveMockSource() ==
mock_source); // should still be on same source after the yield
if (!accepted || coroutine->GetCRFTestCoroutine()->IsCoroutineExited()) {
crf_mrs->Configure(nullptr);
return false;
}
return true;
}
template <typename R, typename... Args>
bool CotestWatcher<R(Args...)>::UpdateCardinality(const UntypedFunctionMockerBase *mocker, const void *untyped_args,
::std::ostream *what, ::std::ostream *why) {
// Note: code lifted from GetActionForArguments() and GetCurrentAction()
// without any attempt at integration into cotest.
std::clog << "CotestWatcher::UpdateCardinality() (typed=" << is_typed_watcher << ")..." << std::endl;
g_gmock_mutex.AssertHeld();
RAIISetFlag gmmh(Coroutine::RAIIGMockMutexIsLocked()); // This is called from GMock with
// mutex held
using Base = ExpectationBase;
const ::std::string &expectation_description = ExpectationBase::GetDescription();
if (Base::IsSaturated()) {
// We have an excessive call.
Base::IncrementCallCount();
*what << "Mock function ";
if (!expectation_description.empty()) {
*what << "\"" << expectation_description << "\" ";
}
*what << "called more times than expected - ";
// mocker->DescribeDefaultActionTo(args, what);
Base::DescribeCallCountTo(why);
return false;
}
Base::IncrementCallCount();
Base::RetireAllPreRequisites();
if (Base::retires_on_saturation_ && Base::IsSaturated()) {
Base::Retire();
}
// Must be done after IncrementCount()!
*what << "Mock function ";
if (!expectation_description.empty()) {
*what << "\"" << expectation_description << "\" ";
}
*what << "call matches " << Base::source_text() << "...\n";
const int count = Base::call_count();
COTEST_ASSERT(count >= 1);
//"call_count() is <= 0 when UpdateCardinality() is "
//"called - this should never happen.");
const int action_count = static_cast<int>(Base::untyped_actions_.size());
if (action_count > 0 && !Base::repeated_action_specified_ && count > action_count) {
// If there is at least one WillOnce() and no WillRepeatedly(),
// we warn the user when the WillOnce() clauses ran out.
::std::stringstream ss;
Base::DescribeLocationTo(&ss);
ss << "Actions ran out in " << Base::source_text() << "...\n"
<< "Called " << count << " times, but only " << action_count << " WillOnce()"
<< (action_count == 1 ? " is" : "s are") << " specified - ";
// mocker->DescribeDefaultActionTo(args, &ss);
Log(kWarning, ss.str(), 1);
}
return Base::repeated_action_specified_;
}
template <typename R, typename... Args>
bool CotestWatcher<R(Args...)>::TryPerformAction(const UntypedFunctionMockerBase *mocker, const void *untyped_args,
const void **untyped_return_value) {
// Note: this is called from GMock WITHOUT mutex held
const std::shared_ptr<crf::MockSource> mock_source =
crf::LaunchCoroutinePool::GetInstance()->FindActiveMockSource();
const std::shared_ptr<crf::MockRoutingSession> crf_mrs = mock_source->GetCurrentMockRS();
std::clog << "CotestWatcher::TryPerformAction() (typed=" << is_typed_watcher << " ms=" << mock_source << ")..."
<< std::endl;
COTEST_ASSERT(mocker);
COTEST_ASSERT(coroutine); // coroutine object was destructed before being sent a call
// Let the coroutine run and collect the return value
*untyped_return_value = crf_mrs->ActionsAndReturnUnlocked();
if (is_typed_watcher) COTEST_ASSERT(CotestTypeUtils<R>::NullCheck(*untyped_return_value));
crf_mrs->Configure(nullptr);
return true;
}
template <typename R, typename... Args>
void CotestWatcher<R(Args...)>::ExplainMatchResultTo(const CotestWatcher<R(Args...)>::ArgumentTuple &args,
::std::ostream *os) const {
RAIISetFlag gmmh(Coroutine::RAIIGMockMutexIsLocked()); // This is called from GMock with
// mutex held
const std::shared_ptr<crf::MockSource> mock_source =
crf::LaunchCoroutinePool::GetInstance()->FindActiveMockSource();
// TOOD improve in phase 3
if (CotestWatcher<R(Args...)>::TypedExpectation::is_retired()) {
*os << " Expected: the coroutine is active\n"
<< " Actual: it is retired\n";
} else {
*os << " Expected: determined by coroutine\n"
<< " Actual: mock call dropped or not seen\n";
}
}
} // namespace internal
} // namespace testing
#endif

View File

@ -0,0 +1,48 @@
#ifndef COROUTINES_INCLUDE_CORO_COTEST_UTIL_LOGGING_H_
#define COROUTINES_INCLUDE_CORO_COTEST_UTIL_LOGGING_H_
//#define COMPARING_LOGS
#include <iomanip> // setfill etc
#include <sstream> // std::stringstream
namespace coro_impl {
#define COTEST_STR(S) COTEST_STRW(S)
#define COTEST_STRW(S) #S
#define COTEST_ASSERT(COND) \
do { \
if (!(COND)) { \
std::cerr << __FILE__ << ":" << __LINE__ << std::endl \
<< " COTEST_ASSERT failed: " << COTEST_STR(COND) << std::endl; \
std::abort(); \
} \
} while (0)
inline std::string PtrToString(const void *p) {
if (!p) return "@NULL";
#ifdef COMPARING_LOGS
return "@PTR";
#else
const int biggest_prime_below_1000 = 997;
int ptrhash = reinterpret_cast<uintptr_t>(p) % biggest_prime_below_1000;
std::stringstream ss;
ss << "@" << std::setfill('0') << std::setw(3) << ptrhash;
return ss.str();
#endif
}
template <typename P, typename Q>
inline std::string PtrToString(const std::pair<P, Q> &p) {
return "(" + PtrToString(p.first) + ", " + PtrToString(p.second) + ")";
}
// TODO proper logging system
#define COTEST_THIS this << "::" << __func__ << "()"
#define COTEST_THIS_FL COTEST_THIS << " " << file << ":" << line
} // namespace coro_impl
#endif

View File

@ -0,0 +1,96 @@
#ifndef COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_UTIL_TYPES_H_
#define COROUTINES_INCLUDE_CORO_INTERNAL_COTEST_UTIL_TYPES_H_
#include <functional>
#include <type_traits>
namespace testing {
namespace internal {
template <typename R>
using LaunchLambdaType = std::function<R()>;
using LaunchYielder = std::function<void(const void *)>;
using LaunchLambdaWrapperType = std::function<void(LaunchYielder)>;
template <typename R>
static void YieldWithAddressOfReturn(R &&returned_object, LaunchYielder yielder) {
// Get past refusing to take the address of a temporary by turning
// it into a named rvalue. Returned_object lasts until yielder returns,
// at which point we will have yielded the value back to the coro
// via its untyped address. Obviously the temporary only lasts as
// long as a single yield cycle of the launch coro.
yielder(static_cast<const void *>(&returned_object));
}
template <typename R, typename = void>
struct CotestTypeUtils {
using StorableR = typename std::remove_reference<R>::type;
static const void *Generalise(R &&typed_value) { return &typed_value; }
static const void *Generalise(R &typed_value) { return &typed_value; }
static R &&Specialise(const void *untyped_value) {
if (!untyped_value) std::terminate(); // Implementation should use NullCheck() first
auto untyped_return_value_nc = const_cast<void *>(untyped_value);
// Returning std::move() is required because R may be unique_ptr or similar
return std::move(*static_cast<StorableR *>(untyped_return_value_nc));
}
static bool NullCheck(const void *untyped_value) // return true if correct
{
return !!untyped_value; // non-NULL is correct
}
static LaunchLambdaWrapperType WrapLaunchLambda(LaunchLambdaType<R> user_lambda) {
return [user_lambda](LaunchYielder yielder) { YieldWithAddressOfReturn(user_lambda(), yielder); };
}
};
template <typename R>
struct CotestTypeUtils<R, typename std::enable_if<std::is_reference<R>::value>::type> {
using StorableR = typename std::remove_reference<R>::type;
static const void *Generalise(R &typed_value) { return &typed_value; }
static R Specialise(const void *untyped_value) {
if (!untyped_value) std::terminate(); // Implementation should use NullCheck() first
auto untyped_return_value_nc = const_cast<void *>(untyped_value);
return *static_cast<StorableR *>(untyped_return_value_nc);
}
static bool NullCheck(const void *untyped_value) // return true if correct
{
return !!untyped_value; // non-NULL is correct
}
static LaunchLambdaWrapperType WrapLaunchLambda(LaunchLambdaType<R> user_lambda) {
return [user_lambda](LaunchYielder yielder) { YieldWithAddressOfReturn(user_lambda(), yielder); };
}
};
template <>
struct CotestTypeUtils<void> {
inline static void Specialise(const void *untyped_value) {
if (untyped_value) std::terminate(); // Implementation should use NullCheck() first
(void)untyped_value;
return;
}
static bool NullCheck(const void *untyped_value) // return true if correct
{
return !untyped_value; // NULL is correct
}
inline static LaunchLambdaWrapperType WrapLaunchLambda(LaunchLambdaType<void> user_lambda) {
return [user_lambda](LaunchYielder yielder) {
user_lambda();
yielder(nullptr);
};
}
};
} // namespace internal
} // namespace testing
#endif

View File

@ -0,0 +1,10 @@
#include "src/cotest-coro-thread.cc"
#include "src/cotest-crf-core.cc"
#include "src/cotest-crf-launch.cc"
#include "src/cotest-crf-mock.cc"
#include "src/cotest-crf-payloads.cc"
#include "src/cotest-crf-synch.cc"
#include "src/cotest-crf-test.cc"
#include "src/cotest-integ-finder.cc"
#include "src/cotest-integ-mock.cc"
#include "src/cotest.cc"

View File

@ -0,0 +1,123 @@
#include "cotest/internal/cotest-coro-thread.h"
#include <unistd.h>
#include <iostream>
#include "cotest/internal/cotest-util-logging.h"
namespace coro_impl {
CoroOnThread::CoroOnThread(BodyFunction cofn_, std::string name_) : coro_run_function(cofn_), name(name_) {
local_thread = std::thread(&CoroOnThread::ThreadRun, this);
TrySetThreadName();
}
CoroOnThread::~CoroOnThread() {
COTEST_ASSERT(IsCoroutineExited());
local_thread.join();
}
std::unique_ptr<Payload> CoroOnThread::Iterate(std::unique_ptr<Payload> &&to_coro) {
COTEST_ASSERT(phase != Phase::CoroutineExited);
COTEST_ASSERT(!payload);
COTEST_ASSERT(!payload_ex);
payload = std::move(to_coro);
NotifyPhase(Phase::CoroutineRuns);
WaitPhases({Phase::MainRuns, Phase::CoroutineExited});
return std::move(payload);
}
std::unique_ptr<Payload> CoroOnThread::ThrowIn(std::exception_ptr in_ex) {
COTEST_ASSERT(phase != Phase::CoroutineExited);
COTEST_ASSERT(!payload);
COTEST_ASSERT(!payload_ex);
payload_ex = std::move(in_ex);
NotifyPhase(Phase::CoroutineRuns);
WaitPhases({Phase::MainRuns, Phase::CoroutineExited});
return std::move(payload);
}
void CoroOnThread::Cancel() {
// We do not support yields in destructors in the coro
std::unique_ptr<Payload> coro_resp = ThrowIn(MakeException<CancellationException>());
COTEST_ASSERT(!coro_resp); // not expecting response to cancellation
COTEST_ASSERT(phase == Phase::CoroutineExited);
}
bool CoroOnThread::IsCoroutineExited() const {
std::lock_guard<std::mutex> lk(phase_mutex);
return phase == Phase::CoroutineExited;
}
void CoroOnThread::SetName(std::string name_) {
name = name_;
TrySetThreadName();
}
std::string CoroOnThread::GetName() const { return name; }
std::unique_ptr<Payload> CoroOnThread::Yield(std::unique_ptr<Payload> &&from_coro) {
COTEST_ASSERT(!payload);
payload = std::move(from_coro);
NotifyPhase(Phase::MainRuns);
WaitPhases({Phase::CoroutineRuns});
COTEST_ASSERT(!(payload && payload_ex));
if (payload_ex)
std::rethrow_exception(payload_ex);
else
return std::move(payload);
}
void CoroOnThread::ThreadRun() {
WaitPhases({Phase::CoroutineRuns});
COTEST_ASSERT(!payload); // coro starts up without a message
try {
if (payload_ex) std::rethrow_exception(payload_ex);
coro_run_function();
payload.reset();
} catch (const CancellationException &exc) {
payload_ex = nullptr;
}
// At present we don't catch other exceptions and so they cause a terminate.
// We could re-throw into exterior, but should only catch outside the scope
// of the coro, at which point the exception caused a cancel (effectively)
// on both sides.
NotifyPhase(Phase::CoroutineExited);
}
void CoroOnThread::TrySetThreadName() {
std::thread::native_handle_type handle = local_thread.native_handle();
#ifdef __linux__
// Do not fail for OS's that support setting a name
const int max = 15;
// The last n characters are more likely to be distinct
std::string trimmed_name = name.size() <= max ? name : "..." + name.substr(name.size() - (max - 3));
COTEST_ASSERT(pthread_setname_np(handle, trimmed_name.c_str()) == 0);
#endif
// COT-10 to add support for MSVC
}
void CoroOnThread::NotifyPhase(Phase new_phase) {
std::lock_guard<std::mutex> lk(phase_mutex);
phase = new_phase;
if (new_phase == Phase::CoroutineRuns) {
COTEST_ASSERT(!active);
active = this;
} else {
COTEST_ASSERT(active);
active = nullptr;
}
cv.notify_one();
}
void CoroOnThread::WaitPhases(std::set<Phase> phases) {
std::unique_lock<std::mutex> lk(phase_mutex);
cv.wait(lk, [&] { return phases.count(phase) > 0; });
}
InteriorInterface *CoroOnThread::GetActive() { return active; }
InteriorInterface *CoroOnThread::active = nullptr;
} // namespace coro_impl

View File

@ -0,0 +1,147 @@
#include "cotest/internal/cotest-crf-core.h"
#include <memory>
#include "cotest/internal/cotest-crf-mock.h"
#include "cotest/internal/cotest-crf-synch.h"
#include "cotest/internal/cotest-integ-finder.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
std::unique_ptr<Payload> MessageNode::SendMessageFromMain(MessageNode *dest, std::unique_ptr<Payload> &&to_node) {
// Fill in main as sender and then check return is for main
return MessageLoop(dest, std::move(to_node));
}
std::unique_ptr<Payload> MessageNode::MessageLoop(MessageNode *dest, std::unique_ptr<Payload> &&to_node) {
// std::clog << __func__ << "() starts: dispatching " << to_node.get() << " to
// " << dest->DebugString() << std::endl;
while (1) {
// Dispatch a message
COTEST_ASSERT(dest);
std::unique_ptr<Payload> reply;
MessageNode *reply_dest;
std::tie(reply, reply_dest) = dest->ReceiveMessage(std::move(to_node));
// std::clog << __func__ << "() iterates: dispatching " << reply.get() << "
// to " << (reply_dest ? reply_dest->DebugString() : "NULL") << std::endl;
COTEST_ASSERT(reply_dest && "MessageNode did not provide reply destination");
// Messages for main are just returned to caller, which should be main
if (reply_dest == ProxyForMain::GetInstance().get()) return reply;
// For the next iteration...
dest = reply_dest;
to_node = std::move(reply);
}
// std::clog << __func__ << "() returns to main" << std::endl;
}
CoroutineBase::~CoroutineBase() { COTEST_ASSERT(IsCoroutineExited()); }
CoroutineBase::CoroutineBase(coro_impl::BodyFunction cofn, std::string name_)
: name(std::move(name_)), impl(std::make_unique<CoroImplType>(cofn, name)) {}
std::unique_ptr<Payload> CoroutineBase::Iterate(std::unique_ptr<Payload> &&to_coro) {
// std::clog << ActiveStr() << COTEST_THIS << " iterates with " <<
// to_coro.get() << std::endl;
initial = false;
std::unique_ptr<coro_impl::Payload> from_coro_base = impl->Iterate(std::move(to_coro));
// if( IsCoroutineExited() )
// std::clog << ActiveStr() << COTEST_THIS << " exited" << std::endl;
if (from_coro_base)
return SpecialisePayload<Payload>(std::move(from_coro_base));
else
return nullptr;
}
void CoroutineBase::Cancel() {
// std::clog << ActiveStr() << COTEST_THIS << " cancelling" << std::endl;
COTEST_ASSERT(!IsCoroutineExited());
impl->Cancel();
}
bool CoroutineBase::IsCoroutineExited() const { return impl->IsCoroutineExited(); }
std::unique_ptr<Payload> CoroutineBase::Yield(std::unique_ptr<Payload> &&from_coro) {
// std::clog << ActiveStr() << COTEST_THIS << " yields " << from_coro.get()
// << std::endl;
std::unique_ptr<coro_impl::Payload> to_coro_base = impl->Yield(std::move(from_coro));
// We have to assume this is a CRF payload. In practice, they all are.
if (to_coro_base)
return SpecialisePayload<Payload>(std::move(to_coro_base));
else
return nullptr;
}
void CoroutineBase::SetName(std::string name_) {
name = name_;
impl->SetName(name);
}
std::string CoroutineBase::GetName() const { return name; }
std::string CoroutineBase::ActiveStr() const {
auto *active = static_cast<CoroImplType *>(impl->GetActive());
if (!active)
return ">< "; // running in main context
else if (active == impl.get())
return "[] ";
else
return "[" + active->GetName() + "!!] "; // Wrong coro is active - probably an internal error
}
coro_impl::InteriorInterface *CoroutineBase::GetImpl() {
return static_cast<coro_impl::InteriorInterface *>(impl.get());
}
void MockSource::SetCurrentMockRS(std::shared_ptr<MockRoutingSession> current_mock_call_) {
COTEST_ASSERT(current_mock_call_);
current_mock_rs = current_mock_call_;
}
std::shared_ptr<MockRoutingSession> MockSource::GetCurrentMockRS() const {
COTEST_ASSERT(current_mock_rs);
return current_mock_rs;
}
std::shared_ptr<MockRoutingSession> ProxyForMain::CreateMockRoutingSession(UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_obj_,
const char *name_) {
auto mrs_sp = std::make_shared<ExteriorMockRS>(mocker_, mock_obj_, name_);
SetCurrentMockRS(mrs_sp);
return mrs_sp;
}
LaunchCoroutine *ProxyForMain::GetAsCoroutine() {
COTEST_ASSERT(!"Internal error: attempting to get a coroutine for main (as a mock source)");
}
const LaunchCoroutine *ProxyForMain::GetAsCoroutine() const {
COTEST_ASSERT(!"Internal error: attempting to get a coroutine for main (as a mock source)");
}
std::shared_ptr<ProxyForMain> ProxyForMain::GetInstance() {
static auto instance = std::make_shared<ProxyForMain>();
return instance;
}
MessageNode::ReplyPair ProxyForMain::ReceiveMessage(std::unique_ptr<Payload> &&to_node) {
COTEST_ASSERT(!"Messages should be dispatched to main by returning from the message loop (internal error)");
}
std::string ProxyForMain::DebugString() const {
std::stringstream ss;
ss << "ProxyForMain()";
return ss.str();
}
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,218 @@
#include "cotest/internal/cotest-crf-launch.h"
#include <memory>
#include "cotest/internal/cotest-crf-synch.h"
#include "cotest/internal/cotest-crf-test.h"
#include "cotest/internal/cotest-integ-finder.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
LaunchCoroutine::LaunchCoroutine(std::string name_) : CoroutineBase(std::bind(&LaunchCoroutine::Body, this), name_) {
// Permit the coroutine to yield in order to get its first message
std::unique_ptr<Payload> from_coro = Iterate(nullptr);
COTEST_ASSERT(!from_coro);
}
LaunchCoroutine::~LaunchCoroutine() {
if (!IsCoroutineExited()) Cancel();
}
void LaunchCoroutine::Body() {
// Get the first launch
std::unique_ptr<Payload> to_coro = Yield(nullptr);
while (1) {
COTEST_ASSERT(to_coro->GetKind() == PayloadKind::Launch);
auto launch_payload = SpecialisePayload<LaunchPayload>(std::move(to_coro));
internal::LaunchLambdaWrapperType lambda_wrapper = launch_payload->GetLambdaWrapper();
// The idea is that something will invoke this "yielder" while the return
// object is still in scope.
internal::LaunchYielder yielder = [this, &launch_payload, &to_coro](UntypedReturnValuePointer rvp) {
auto launch_result_payload =
MakePayload<LaunchResultPayload>(launch_payload->GetOriginator(), shared_from_this(), rvp);
to_coro = Yield(std::move(launch_result_payload));
};
// Invoking the MUT here
std::clog << ActiveStr() << COTEST_THIS << " launching lambda_wrapper for \"" << GetName() << "\"" << std::endl;
lambda_wrapper(yielder);
std::clog << ActiveStr() << COTEST_THIS << " completed launch" << std::endl;
COTEST_ASSERT(to_coro); // Was set via lambda ref capture
}
}
MessageNode::ReplyPair LaunchCoroutine::ReceiveMessage(std::unique_ptr<Payload> &&to_node) {
// Collect a pointer to the current launch session, so that we can effectively
// garbage-collect the launch lambda context which is holding the return value
// as a temporary. We use a weak pointer, and will expire when the launch
// session is destructed
if (to_node && to_node->GetKind() == PayloadKind::Launch)
current_launch_session = PeekPayload<LaunchPayload>(to_node).GetOriginator();
return IterateServer(std::move(to_node));
}
MessageNode::ReplyPair LaunchCoroutine::IterateServer(std::unique_ptr<Payload> &&to_coro) {
COTEST_ASSERT(!IsCoroutineExited());
// It does not make sense to sent a waiting message into the launch coro, even
// though the message emerged from a test coro. The infrastructure must deal
// with it.
if (to_coro) COTEST_ASSERT(to_coro->GetKind() != PayloadKind::ResumeMain);
std::unique_ptr<Payload> from_coro = Iterate(std::move(to_coro));
COTEST_ASSERT(!IsCoroutineExited());
COTEST_ASSERT(from_coro);
MessageNode *reply_dest;
switch (from_coro->GetKind()) {
case PayloadKind::PreMock: {
std::shared_ptr<MockRoutingSession> orig = PeekPayload<PreMockPayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig && "we have a pre mock but session already expired");
SetCurrentMockRS(orig);
reply_dest = PreMockSynchroniser::GetInstance();
break;
}
case PayloadKind::MockSeen: {
std::shared_ptr<MockRoutingSession> orig = PeekPayload<MockSeenPayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig && "we have a mock call but session already expired");
COTEST_ASSERT(GetCurrentMockRS().get() == orig.get());
// To the test coro identified by GMock. MOCK_SEEN responses
// shall be by return if they are for our originator.
reply_dest = orig->GetHandlingTestCoro();
break;
}
case PayloadKind::MockAction: {
std::shared_ptr<MockRoutingSession> orig = PeekPayload<MockActionPayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig && "we have a mock call but session already expired");
COTEST_ASSERT(GetCurrentMockRS().get() == orig.get());
// To the test coro identified by GMock.
reply_dest = orig->GetHandlingTestCoro();
break;
}
case PayloadKind::LaunchResult: {
auto orig = PeekPayload<LaunchResultPayload>(from_coro).GetOriginator().lock();
reply_dest = orig->GetParentTestCoroutine();
break;
}
case PayloadKind::DropMock:
case PayloadKind::AcceptMock:
case PayloadKind::ReturnMock:
case PayloadKind::TCExited:
case PayloadKind::Launch:
case PayloadKind::PreMockAck:
case PayloadKind::ResumeMain:
case PayloadKind::TCDestructing: {
COTEST_ASSERT("!unhandled message from launch coroutine");
}
}
COTEST_ASSERT(reply_dest);
return std::make_pair(std::move(from_coro), reply_dest);
}
LaunchCoroutine *LaunchCoroutine::GetAsCoroutine() { return this; }
const LaunchCoroutine *LaunchCoroutine::GetAsCoroutine() const { return this; }
std::shared_ptr<MockRoutingSession> LaunchCoroutine::CreateMockRoutingSession(UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_obj_,
const char *name_) {
return std::make_shared<InteriorMockRS>(shared_from_this(), mocker_, mock_obj_, name_);
}
std::shared_ptr<InteriorLaunchSessionBase> LaunchCoroutine::TryGetCurrentLaunchSession() {
return current_launch_session.lock();
}
std::shared_ptr<const InteriorLaunchSessionBase> LaunchCoroutine::TryGetCurrentLaunchSession() const {
return current_launch_session.lock();
}
std::string LaunchCoroutine::DebugString() const {
std::stringstream ss;
ss << "LaunchCoroutine(\"" << GetName() << "\""
<< ", cur_ls=" << PtrToString(current_launch_session.lock().get()) << ")";
return ss.str();
}
LaunchCoroutine *LaunchCoroutinePool::Allocate(std::string launch_text) {
// Leaky algorithm has no reclamation or limit but there is COTEST_CLEANUP()
auto dc = std::make_shared<LaunchCoroutine>(launch_text);
pool.insert(std::make_pair(dc->GetImpl(), dc));
return dc.get();
}
std::shared_ptr<MockSource> LaunchCoroutinePool::FindActiveMockSource() {
if (pool.empty()) return ProxyForMain::GetInstance(); // No launch coros so must be main
// As long as all the coros in the pool have the same implementation type,
// we are free to choose any one of them to call GetActive()
auto active_ii = pool.begin()->first->GetActive();
if (!active_ii) return ProxyForMain::GetInstance();
PoolType::iterator it = pool.find(active_ii);
if (it != pool.end()) return it->second;
COTEST_ASSERT(!"Cannot invoke mock functions from within test coroutines");
}
LaunchCoroutine *LaunchCoroutinePool::TryGetUnusedLaunchCoro() {
for (auto p : pool) {
if (!p.second->TryGetCurrentLaunchSession()) return p.second.get();
}
return nullptr;
}
int LaunchCoroutinePool::CleanUp() {
if (pool.empty()) return 0;
COTEST_ASSERT(!pool.begin()->first->GetActive() && "Not allowed in coroutine interior");
int num = 0;
for (PoolType::const_iterator it = pool.cbegin(); it != pool.cend();) {
if (!it->second->TryGetCurrentLaunchSession()) {
it = pool.erase(it);
num++;
} else {
++it;
}
}
return num;
}
MessageNode::ReplyPair LaunchCoroutinePool::ReceiveMessage(std::unique_ptr<Payload> &&to_node) {
COTEST_ASSERT(to_node);
COTEST_ASSERT(to_node->GetKind() == PayloadKind::Launch);
const std::string launch_text = PeekPayload<LaunchPayload>(to_node).GetName();
LaunchCoroutine *dc = TryGetUnusedLaunchCoro();
if (dc)
dc->SetName(launch_text);
else
dc = Allocate(launch_text);
return make_pair(std::move(to_node), dc);
}
std::string LaunchCoroutinePool::DebugString() const {
std::stringstream ss;
ss << "LaunchCoroutinePool( pool_size=" << pool.size() << ")";
return ss.str();
}
LaunchCoroutinePool *LaunchCoroutinePool::GetInstance() {
static LaunchCoroutinePool instance;
return &instance;
}
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,154 @@
#include "cotest/internal/cotest-crf-mock.h"
#include <memory>
#include "cotest/internal/cotest-crf-core.h"
#include "cotest/internal/cotest-crf-launch.h"
#include "cotest/internal/cotest-crf-synch.h"
#include "cotest/internal/cotest-crf-test.h"
#include "cotest/internal/cotest-integ-finder.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
MockRoutingSession::MockRoutingSession(UntypedMockerPointer mocker_, UntypedMockObjectPointer mock_object_,
std::string name_)
: mocker(mocker_), mock_object(mock_object_), name(name_) {}
void MockRoutingSession::Configure(TestCoroutine *handling_coroutine_) { handling_coroutine = handling_coroutine_; }
void MockRoutingSession::PreMockUnlocked() {
// Note: mock calls are not necessarily handled by the test coroutine that
// owns the mock source. Indeed, when the mock source is main, no test
// coroutine owns it. At pre-mock stage, we don't yet know which test coro
// will handle the call.
auto pre_call_payload = MakePayload<PreMockPayload>(shared_from_this(), name, mock_object, mocker);
std::unique_ptr<Payload> response_payload = SendMessageToSynchroniser(std::move(pre_call_payload));
COTEST_ASSERT(response_payload && response_payload->GetKind() == PayloadKind::PreMockAck);
// We can't get the mock call session from this message, because the
// synchroniser sent it to all the test coroutines - we don't know which will
// handle until one accepts.
}
bool MockRoutingSession::SeenMockCallLocked(UntypedArgsPointer args) {
// This is the mutexed iteration for filtering - in MT applications, we need
// to get a DROP_MOCK or ACCEPT_MOCK while mutex is held.
// We may get called on behalf of multiple candidate test coroutines, before
// we know which will handle the call.
if (handlers_that_dropped.count(handling_coroutine) > 0)
return false; // Multiple overlapping watches: drop again immediately
auto mock_call_payload = MakePayload<MockSeenPayload>(shared_from_this(), args, name, mock_object, mocker);
std::unique_ptr<Payload> response_payload = SendMessageToHandlingCoro(std::move(mock_call_payload));
COTEST_ASSERT(response_payload);
const PayloadKind kind = response_payload->GetKind();
auto mock_response_payload = SpecialisePayload<MockResponsePayload>(std::move(response_payload));
COTEST_ASSERT(mock_response_payload->GetOriginator().lock() = shared_from_this());
switch (kind) {
case PayloadKind::DropMock: {
// TestCoroutine dropped the call: call not handled, there will not be a
// return
auto drop_payload = SpecialisePayload<DropMockPayload>(std::move(mock_response_payload));
COTEST_ASSERT(drop_payload->GetOriginator().lock().get() == this);
handlers_that_dropped.insert(handling_coroutine);
return false;
}
case PayloadKind::AcceptMock: {
// TestCoroutine accepted: can release mutex and let coroutine do actions
auto accept_payload = SpecialisePayload<AcceptMockPayload>(std::move(mock_response_payload));
COTEST_ASSERT(accept_payload->GetOriginator().lock().get() == this);
call_session = accept_payload->GetResponder();
return true;
}
case PayloadKind::ReturnMock:
COTEST_ASSERT(!"Accept required before return");
case PayloadKind::TCExited:
COTEST_ASSERT(false); // Can't exit while handling a mock call
return false;
case PayloadKind::Launch:
case PayloadKind::PreMockAck:
case PayloadKind::ResumeMain:
case PayloadKind::PreMock:
case PayloadKind::MockSeen:
case PayloadKind::MockAction:
case PayloadKind::LaunchResult:
case PayloadKind::TCDestructing:
COTEST_ASSERT(!"Bad response to mock call");
}
return false;
}
UntypedReturnValuePointer MockRoutingSession::ActionsAndReturnUnlocked() {
// This is the un-mutexed iteration for actions - in MT applications, mutex
// is released to avoid deadlocks.
COTEST_ASSERT(!handling_coroutine->IsCoroutineExited());
auto action_payload = MakePayload<MockActionPayload>(shared_from_this(), call_session);
std::unique_ptr<Payload> response_payload = SendMessageToHandlingCoro(std::move(action_payload));
COTEST_ASSERT(!handling_coroutine->IsCoroutineExited());
COTEST_ASSERT(response_payload);
COTEST_ASSERT(response_payload->GetKind() == PayloadKind::ReturnMock &&
"Coroutine must return mock call from main before main can "
"supply more events");
auto return_payload = SpecialisePayload<ReturnMockPayload>(std::move(response_payload));
COTEST_ASSERT(return_payload->GetOriginator().lock().get() == this);
// Check return came from the correct coroutine
COTEST_ASSERT(return_payload->GetResponder().lock() == call_session.lock());
return return_payload->GetResult();
}
TestCoroutine *MockRoutingSession::GetHandlingTestCoro() const { return handling_coroutine; }
std::string MockRoutingSession::GetName() const { return name; }
std::shared_ptr<MockSource> ExteriorMockRS::GetMockSource() const { return ProxyForMain::GetInstance(); }
std::unique_ptr<Payload> ExteriorMockRS::SendMessageToSynchroniser(std::unique_ptr<Payload> &&to_tc) const {
// We're in main, so start the message loop and provide destination
return MessageNode::SendMessageFromMain(PreMockSynchroniser::GetInstance(), std::move(to_tc));
}
std::unique_ptr<Payload> ExteriorMockRS::SendMessageToHandlingCoro(std::unique_ptr<Payload> &&to_tc) const {
// We're in main, so start the message loop and provide destination
return MessageNode::SendMessageFromMain(GetHandlingTestCoro(), std::move(to_tc));
}
std::shared_ptr<MockSource> InteriorMockRS::GetMockSource() const {
auto msl = launch_coro.lock();
COTEST_ASSERT(msl);
return msl;
}
InteriorMockRS::InteriorMockRS(std::shared_ptr<LaunchCoroutine> launch_coro_, UntypedMockerPointer mocker_,
UntypedMockObjectPointer mock_object_, std::string name_)
: MockRoutingSession(mocker_, mock_object_, name_), launch_coro(launch_coro_) {}
std::unique_ptr<Payload> InteriorMockRS::SendMessageToSynchroniser(std::unique_ptr<Payload> &&to_tc) const {
// We're in launch coro interior, so yield
auto dcl = launch_coro.lock();
COTEST_ASSERT(dcl);
return dcl->Yield(std::move(to_tc));
}
std::unique_ptr<Payload> InteriorMockRS::SendMessageToHandlingCoro(std::unique_ptr<Payload> &&to_tc) const {
// We're in launch coro interior, so yield
auto dcl = launch_coro.lock();
COTEST_ASSERT(dcl);
return dcl->Yield(std::move(to_tc));
}
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,184 @@
#include "cotest/internal/cotest-crf-payloads.h"
#include <memory>
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
PreMockPayload::PreMockPayload(std::weak_ptr<MockRoutingSession> originator_, std::string name_,
UntypedMockObjectPointer mock_object_, UntypedMockerPointer mocker_)
: originator(originator_), name(name_), mock_object(mock_object_), mocker(mocker_) {}
PayloadKind PreMockPayload::GetKind() const { return PayloadKind::PreMock; }
PreMockPayload *PreMockPayload::Clone() const { return new PreMockPayload(originator, name, mock_object, mocker); }
std::weak_ptr<MockRoutingSession> PreMockPayload::GetOriginator() const { return originator; }
std::string PreMockPayload::GetName() const {
COTEST_ASSERT(!name.empty());
return name;
}
UntypedMockObjectPointer PreMockPayload::GetMockObject() const {
COTEST_ASSERT(mock_object);
return mock_object;
}
UntypedMockerPointer PreMockPayload::GetMocker() const {
COTEST_ASSERT(mocker);
return mocker;
}
std::string PreMockPayload::DebugString() const {
return "PayloadKind::PreMock(O=" + PtrToString(originator.lock().get()) + ", \"" + name + "\"" +
", mo=" + PtrToString(mock_object) + ", m=" + PtrToString(mocker) + ")";
}
PreMockAckPayload::PreMockAckPayload(std::weak_ptr<MockRoutingSession> originator_) : originator(originator_) {}
std::weak_ptr<MockRoutingSession> PreMockAckPayload::GetOriginator() const { return originator; }
PayloadKind PreMockAckPayload::GetKind() const { return PayloadKind::PreMockAck; }
std::string PreMockAckPayload::DebugString() const {
return "PayloadKind::PreMockAck(O=" + PtrToString(originator.lock().get()) + ")";
}
MockSeenPayload::MockSeenPayload(std::weak_ptr<MockRoutingSession> originator_, UntypedArgsPointer args_,
std::string name_, UntypedMockObjectPointer mock_object_, UntypedMockerPointer mocker_)
: originator(originator_), args(args_), name(name_), mock_object(mock_object_), mocker(mocker_) {}
PayloadKind MockSeenPayload::GetKind() const { return PayloadKind::MockSeen; }
std::weak_ptr<MockRoutingSession> MockSeenPayload::GetOriginator() const { return originator; }
UntypedArgsPointer MockSeenPayload::GetArgsUntyped() const { return args; }
std::string MockSeenPayload::GetName() const {
COTEST_ASSERT(!name.empty());
return name;
}
UntypedMockObjectPointer MockSeenPayload::GetMockObject() const {
COTEST_ASSERT(mock_object);
return mock_object;
}
UntypedMockerPointer MockSeenPayload::GetMocker() const {
COTEST_ASSERT(mocker);
return mocker;
}
std::string MockSeenPayload::DebugString() const {
return "PayloadKind::MockSeen(O=" + PtrToString(originator.lock().get()) + ", a=" + PtrToString(args) + ", \"" +
name + "\"" + ", mo=" + PtrToString(mock_object) + ", m=" + PtrToString(mocker) + ")";
}
MockResponsePayload::MockResponsePayload(std::weak_ptr<MockRoutingSession> originator_,
std::weak_ptr<InteriorMockCallSession> responder_)
: originator(originator_), responder(responder_) {}
std::weak_ptr<MockRoutingSession> MockResponsePayload::GetOriginator() const { return originator; }
std::weak_ptr<InteriorMockCallSession> MockResponsePayload::GetResponder() const { return responder; }
PayloadKind DropMockPayload::GetKind() const { return PayloadKind::DropMock; }
std::string DropMockPayload::DebugString() const {
return "PayloadKind::DropMock(O=" + PtrToString(originator.lock().get()) +
", R=" + PtrToString(responder.lock().get()) + ")";
}
PayloadKind AcceptMockPayload::GetKind() const { return PayloadKind::AcceptMock; }
std::string AcceptMockPayload::DebugString() const {
return "PayloadKind::AcceptMock(O=" + PtrToString(originator.lock().get()) +
", R=" + PtrToString(responder.lock().get()) + ")";
}
PayloadKind MockActionPayload::GetKind() const { return PayloadKind::MockAction; }
std::string MockActionPayload::DebugString() const {
return "PayloadKind::MockAction(O=" + PtrToString(originator.lock().get()) +
", R=" + PtrToString(responder.lock().get()) + ")";
}
ReturnMockPayload::ReturnMockPayload(std::weak_ptr<MockRoutingSession> originator_,
std::weak_ptr<InteriorMockCallSession> responder_,
UntypedReturnValuePointer return_val_ptr_)
: MockResponsePayload(originator_, responder_), return_val_ptr(return_val_ptr_) {}
PayloadKind ReturnMockPayload::GetKind() const { return PayloadKind::ReturnMock; }
UntypedReturnValuePointer ReturnMockPayload::GetResult() { return return_val_ptr; }
std::string ReturnMockPayload::DebugString() const {
return "PayloadKind::ReturnMock(O=" + PtrToString(originator.lock().get()) +
", R=" + PtrToString(responder.lock().get()) + ", rv=" + PtrToString(return_val_ptr) + ")";
}
LaunchPayload::LaunchPayload(std::weak_ptr<InteriorLaunchSessionBase> originator_,
internal::LaunchLambdaWrapperType wrapper_lambda_, std::string name_)
: originator(originator_), wrapper_lambda(wrapper_lambda_), name(name_) {}
PayloadKind LaunchPayload::GetKind() const { return PayloadKind::Launch; }
std::weak_ptr<InteriorLaunchSessionBase> LaunchPayload::GetOriginator() const { return originator; }
internal::LaunchLambdaWrapperType LaunchPayload::GetLambdaWrapper() const { return wrapper_lambda; }
std::string LaunchPayload::GetName() const { return name; }
std::string LaunchPayload::DebugString() const {
return "PayloadKind::Launch(O=" + PtrToString(originator.lock().get()) + ", \"" + name + "\"" + ")";
}
LaunchResultPayload::LaunchResultPayload(std::weak_ptr<InteriorLaunchSessionBase> originator_,
std::weak_ptr<LaunchCoroutine> responder_,
UntypedReturnValuePointer return_val_ptr_)
: originator(originator_), responder(responder_), return_val_ptr(return_val_ptr_) {}
PayloadKind LaunchResultPayload::GetKind() const { return PayloadKind::LaunchResult; }
std::weak_ptr<InteriorLaunchSessionBase> LaunchResultPayload::GetOriginator() const { return originator; }
std::weak_ptr<LaunchCoroutine> LaunchResultPayload::GetResponder() const { return responder; }
UntypedReturnValuePointer LaunchResultPayload::GetResult() { return return_val_ptr; }
std::string LaunchResultPayload::DebugString() const {
return "PayloadKind::LaunchResult(O=" + PtrToString(originator.lock().get()) +
", R=" + PtrToString(responder.lock().get()) + ", rv=" + PtrToString(return_val_ptr) + ")";
}
ResumeMainPayload::ResumeMainPayload(std::weak_ptr<TestCoroutine> originator_) : originator(originator_) {}
PayloadKind ResumeMainPayload::GetKind() const { return PayloadKind::ResumeMain; }
std::weak_ptr<TestCoroutine> ResumeMainPayload::GetOriginator() const { return originator; }
std::string ResumeMainPayload::DebugString() const { return "PayloadKind::ResumeMain()"; }
TCExitedPayload::TCExitedPayload(std::weak_ptr<TestCoroutine> originator_) : originator(originator_) {}
PayloadKind TCExitedPayload::GetKind() const { return PayloadKind::TCExited; }
std::weak_ptr<TestCoroutine> TCExitedPayload::GetOriginator() const { return originator; }
std::string TCExitedPayload::DebugString() const { return "PayloadKind::TCExited()"; }
TCDestructingPayload::TCDestructingPayload(std::weak_ptr<TestCoroutine> originator_) : originator(originator_) {}
PayloadKind TCDestructingPayload::GetKind() const { return PayloadKind::TCDestructing; }
std::weak_ptr<TestCoroutine> TCDestructingPayload::GetOriginator() const { return originator; }
std::string TCDestructingPayload::DebugString() const { return "PayloadKind::TCDestructing()"; }
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,141 @@
#include "cotest/internal/cotest-crf-synch.h"
#include <memory>
#include "cotest/internal/cotest-crf-core.h"
#include "cotest/internal/cotest-crf-mock.h"
#include "cotest/internal/cotest-crf-test.h"
#include "cotest/internal/cotest-integ-finder.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
MessageNode::ReplyPair PreMockSynchroniser::ReceiveMessage(std::unique_ptr<Payload> &&to_node) {
// Fall-through machine with early return on reply.
// Each state action is inside its own if-statement, meaning that multiple
// actions and state transitions can complete in a signle iteration. The order
// of the if-statement is immaterial to the algorithm implmented by the
// machine but does affect the states that can lead to an iteration
// completing. Here we choose an ordering that does not end the iteration
// until a reply message is ready. Then do ensure we do end in this case, we
// use early returns.
if (state == State::Idle) {
// Handle incoming messages when we don't require acknowledgement
switch (to_node->GetKind()) {
case PayloadKind::PreMock:
COTEST_ASSERT(!current_pre_mock);
current_pre_mock = SpecialisePayload<PreMockPayload>(std::move(to_node));
state = State::Start;
break;
case PayloadKind::PreMockAck:
COTEST_ASSERT(!"Not expecting PRE_MOCK_ACK in State::Idle");
case PayloadKind::ResumeMain:
case PayloadKind::TCExited:
state = State::PassToMain;
break;
case PayloadKind::MockSeen:
case PayloadKind::AcceptMock:
case PayloadKind::MockAction:
case PayloadKind::DropMock:
case PayloadKind::ReturnMock:
case PayloadKind::Launch:
case PayloadKind::LaunchResult:
case PayloadKind::TCDestructing:
COTEST_ASSERT(!"Unhanled message in State::Idle");
}
}
if (state == State::WaitingForAck) {
// Handle incoming messages when we require an acknowledgement
switch (to_node->GetKind()) {
case PayloadKind::PreMock:
COTEST_ASSERT(!"Not expecting another PRE_MOCK while State::WaitingForAck. A TC may be delaying acknowlegement of PM");
break;
case PayloadKind::PreMockAck: {
auto pmack = SpecialisePayload<PreMockAckPayload>(std::move(to_node));
auto mock_source = pmack->GetOriginator().lock();
auto expected_mock_source = current_pre_mock->GetOriginator().lock();
COTEST_ASSERT(mock_source && expected_mock_source && "Mock source expired while synchronsing PRE_MOCK");
COTEST_ASSERT(mock_source == expected_mock_source); // should relate to the same mock source
state = send_pm_to.empty() ? State::Complete : State::Working;
break;
}
case PayloadKind::ResumeMain:
// If we're waiting for an ack from a coroutine and instead get that it
// has nothing to do, we may be headed for a deadlock. Pass to main in
// case main can progress things. This path not covered by tests at the
// time of writing.
state = State::PassToMain;
break;
case PayloadKind::TCExited:
// Discard ack requirement for exited coro
state = send_pm_to.empty() ? State::Complete : State::Working;
break;
case PayloadKind::MockSeen:
case PayloadKind::AcceptMock:
case PayloadKind::MockAction:
case PayloadKind::DropMock:
case PayloadKind::ReturnMock:
case PayloadKind::Launch:
case PayloadKind::LaunchResult:
case PayloadKind::TCDestructing:
COTEST_ASSERT(!"Unhanled message in State::WaitingForAck");
}
}
if (state == State::PassToMain) {
// Forward the message to main
state = State::Idle;
return std::make_pair(std::move(to_node), ProxyForMain::GetInstance().get());
}
if (state == State::Start) {
// Start a new synchronisation cycle
auto pool = internal::CotestMockHandlerPool::GetOrCreateInstance();
pool->ForAll([this](std::shared_ptr<TestCoroutine> &&crf_sp) { send_pm_to.push(std::move(crf_sp)); });
state = send_pm_to.empty() ? State::Complete : State::Working;
}
if (state == State::Working) {
// Continue the current synchronisation cycle
COTEST_ASSERT(!send_pm_to.empty());
ReplyPair p;
p.first = std::unique_ptr<PreMockPayload>(current_pre_mock->Clone());
auto pm_to_locked = send_pm_to.front().lock();
COTEST_ASSERT(pm_to_locked);
p.second = pm_to_locked.get();
send_pm_to.pop();
state = State::WaitingForAck;
return p;
}
if (state == State::Complete) {
// Complete the synchronisation cycle
COTEST_ASSERT(send_pm_to.empty());
auto mock_call_session = current_pre_mock->GetOriginator().lock();
current_pre_mock.reset();
state = State::Idle;
COTEST_ASSERT(mock_call_session);
// Reply to current mock source
return std::make_pair(MakePayload<PreMockAckPayload>(mock_call_session),
mock_call_session->GetMockSource().get());
}
COTEST_ASSERT(!"We have to send a reply message on each iteration");
}
std::string PreMockSynchroniser::DebugString() const {
std::stringstream ss;
ss << "PreMockSynchroniser(PM=" << current_pre_mock.get() << " #TCs=" << send_pm_to.size() << ")";
return ss.str();
}
PreMockSynchroniser *PreMockSynchroniser::GetInstance() { return &instance; }
PreMockSynchroniser PreMockSynchroniser::instance;
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,391 @@
#include "cotest/internal/cotest-crf-test.h"
#include <memory>
#include "cotest/internal/cotest-crf-core.h"
#include "cotest/internal/cotest-crf-launch.h"
#include "cotest/internal/cotest-crf-synch.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace crf {
using coro_impl::PtrToString;
TestCoroutine::TestCoroutine(coro_impl::BodyFunction cofn_, std::string name_, OnExitFunction on_exit_function_)
: CoroutineBase(cofn_, name_), on_exit_function(on_exit_function_) {
// Comment this out for logging
std::clog.setstate(std::ios_base::failbit);
std::clog << ActiveStr() << COTEST_THIS << " constructing" << std::endl;
}
TestCoroutine::~TestCoroutine() { std::clog << ActiveStr() << COTEST_THIS << " destructing" << std::endl; }
MessageNode::ReplyPair TestCoroutine::ReceiveMessage(std::unique_ptr<Payload> &&to_node) {
COTEST_ASSERT(!IsCoroutineExited());
return IterateServer(std::move(to_node));
}
MessageNode::ReplyPair TestCoroutine::IterateServer(std::unique_ptr<Payload> &&to_coro) {
COTEST_ASSERT(!IsCoroutineExited());
extra_iteration_requested = false; // requirement satisfied by this iteration
std::unique_ptr<Payload> from_coro = Iterate(std::move(to_coro));
if (IsCoroutineExited()) {
on_exit_function();
from_coro = MakePayload<TCExitedPayload>(shared_from_this());
}
if (!from_coro) return std::make_pair(std::move(from_coro), nullptr);
MessageNode *reply_dest;
switch (from_coro->GetKind()) {
case PayloadKind::Launch:
reply_dest = LaunchCoroutinePool::GetInstance(); // let the pool deal with it
break;
case PayloadKind::PreMockAck:
reply_dest = PreMockSynchroniser::GetInstance();
break;
case PayloadKind::DropMock:
case PayloadKind::AcceptMock:
case PayloadKind::ReturnMock: {
auto orig = PeekPayload<MockResponsePayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig && "Call session expired unexpectedly");
reply_dest = orig->GetMockSource().get();
// Coroutine will need to run beyond the cs.RETURN() before we ask it too
// many questions, in case it needs to SATISFY(), RETIRE() or exit. But we
// can't iterate it now, since it currently owns the return value. CRF
// constraints #3 and #4.
extra_iteration_requested = true;
break;
}
case PayloadKind::ResumeMain:
case PayloadKind::TCExited:
reply_dest = PreMockSynchroniser::GetInstance();
break;
case PayloadKind::PreMock:
case PayloadKind::MockSeen:
case PayloadKind::MockAction:
case PayloadKind::LaunchResult:
case PayloadKind::TCDestructing:
COTEST_ASSERT(!"unhandled message from test coroutine");
}
COTEST_ASSERT(reply_dest);
return std::make_pair(std::move(from_coro), reply_dest);
}
void TestCoroutine::InitialActivity() {
COTEST_ASSERT(!IsCoroutineExited() && "Cannot reuse coroutine instance");
std::unique_ptr<Payload> from_coro = SendMessageFromMain(this, nullptr);
// We should return from this when the coro is blocked, eg waiting for
// a mock call from somewhere other than a local launch
COTEST_ASSERT(from_coro->GetKind() == PayloadKind::TCExited || from_coro->GetKind() == PayloadKind::ResumeMain);
}
static MockSource *GetMockSourceFromMessage(const std::unique_ptr<Payload> &payload) {
COTEST_ASSERT(payload);
switch (payload->GetKind()) {
case PayloadKind::PreMock: {
auto ext_mock_cs = PeekPayload<PreMockPayload>(payload).GetOriginator().lock();
return ext_mock_cs->GetMockSource().get();
}
case PayloadKind::MockSeen: {
auto ext_mock_cs = PeekPayload<MockSeenPayload>(payload).GetOriginator().lock();
return ext_mock_cs->GetMockSource().get();
}
case PayloadKind::MockAction: {
auto ext_mock_cs = PeekPayload<MockActionPayload>(payload).GetOriginator().lock();
return ext_mock_cs->GetMockSource().get();
}
case PayloadKind::LaunchResult: {
return PeekPayload<LaunchResultPayload>(payload).GetResponder().lock().get();
}
case PayloadKind::TCDestructing: {
return ProxyForMain::GetInstance().get();
}
case PayloadKind::DropMock:
case PayloadKind::AcceptMock:
case PayloadKind::ReturnMock:
case PayloadKind::Launch:
case PayloadKind::PreMockAck:
case PayloadKind::ResumeMain:
case PayloadKind::TCExited:
COTEST_ASSERT(!"Unhandled payload type");
break;
}
return nullptr;
}
static std::pair<bool, std::shared_ptr<InteriorLaunchSessionBase>> GetLaunchSessionFromMessage(
const std::unique_ptr<Payload> &payload) {
MockSource *ms = GetMockSourceFromMessage(payload);
if (ms == ProxyForMain::GetInstance().get()) return std::make_pair(true, nullptr);
auto launch_coro = ms->GetAsCoroutine();
return std::make_pair(false, launch_coro->TryGetCurrentLaunchSession());
}
void TestCoroutine::YieldServer(std::unique_ptr<Payload> &&from_coro) {
// These outgoing payloads will release the mock lock
if (from_coro->GetKind() == PayloadKind::DropMock || from_coro->GetKind() == PayloadKind::AcceptMock)
mock_call_locked = false;
std::unique_ptr<Payload> to_coro = Yield(std::move(from_coro));
COTEST_ASSERT(to_coro);
if (to_coro->GetKind() == PayloadKind::TCDestructing || to_coro->GetKind() == PayloadKind::MockAction)
return; // Discard these here - the interior event or call session ensures
// a return will be made
COTEST_ASSERT(!next_payload && "Internal error: already have an event");
next_payload = std::move(to_coro);
}
bool TestCoroutine::IsPendingEvent() { return !!next_payload; }
std::shared_ptr<InteriorEventSession> TestCoroutine::NextEvent(const char *file, int line) {
COTEST_ASSERT(!mock_call_locked &&
"Cannot request a new event until mock call has been dropped, "
"accepted or returned");
// Start loping, because we're going to handle NULL messages and PreMock
// locally
std::unique_ptr<InteriorMockCallSession> mock_call_event;
while (!next_payload || next_payload->GetKind() == PayloadKind::PreMock) {
std::unique_ptr<Payload> response;
if (!next_payload) {
std::clog << ActiveStr() << COTEST_THIS_FL
<< " no event has been sent to this coro, so requesting "
"resumption of main test function"
<< std::endl;
response = MakePayload<ResumeMainPayload>(shared_from_this());
} else if (next_payload->GetKind() == PayloadKind::PreMock) {
// Acknowledge PreMock, and then grab the subsequent message. We can
// get another PreMock for example if GMock doesn't show us the mock call
// for the first one. We can also get somehting other than SeenMock this
// way. PreMock acknowledged here rather than YieldServer() because we
// need to be sure the test coro is really ready to handle a mock call.
auto p = GetLaunchSessionFromMessage(next_payload);
auto pm_payload = SpecialisePayload<PreMockPayload>(std::move(next_payload));
response = MakePayload<PreMockAckPayload>(pm_payload->GetOriginator());
mock_call_event = std::make_unique<InteriorMockCallSession>(this, p.first, p.second, std::move(pm_payload));
}
std::clog << ActiveStr() << COTEST_THIS_FL << " acknowledging PreMock: " << response.get() << std::endl;
YieldServer(std::move(response));
}
if (next_payload->GetKind() == PayloadKind::MockSeen) {
COTEST_ASSERT(mock_call_event && "Got MOCK_SEEN without a State::PreMock");
mock_call_locked = true;
auto call_payload = SpecialisePayload<MockSeenPayload>(std::move(next_payload));
mock_call_event->SeenCall(call_payload->GetArgsUntyped());
// Redundant info in messages just in case we need to match up messages eg
// in future multi-threaded scenario.
COTEST_ASSERT(call_payload->GetMocker() == mock_call_event->GetMocker());
COTEST_ASSERT(call_payload->GetMockObject() == mock_call_event->GetMockObject());
COTEST_ASSERT(call_payload->GetName() == mock_call_event->GetName());
std::clog << ActiveStr() << COTEST_THIS_FL << " -> with InteriorMockCallSession "
<< PtrToString(mock_call_event.get()) << std::endl;
return mock_call_event;
} else if (next_payload->GetKind() == PayloadKind::LaunchResult) {
auto dr_payload = SpecialisePayload<LaunchResultPayload>(std::move(next_payload));
auto launch_result_event = std::make_unique<InteriorLaunchResultSession>(this, std::move(dr_payload));
std::clog << ActiveStr() << COTEST_THIS_FL << " -> InteriorLaunchResultSession "
<< PtrToString(launch_result_event.get()) << std::endl;
return launch_result_event;
} else {
COTEST_ASSERT(!"Unhandled payload type in NextEvent()");
}
}
bool TestCoroutine::IsPostMockIterationRequested() { return extra_iteration_requested; }
void TestCoroutine::DestructionIterations() {
COTEST_ASSERT(!IsCoroutineExited());
// Note:
// extra_iteration_requested is to allow a test coroutine to "get ahead" after
// having returned from a mock call, for example so it can exit or set
// satisfied flag.
if (!extra_iteration_requested) return;
extra_iteration_requested = false;
std::unique_ptr<Payload> from_coro =
SendMessageFromMain(this, MakePayload<TCDestructingPayload>(shared_from_this()));
// This can cause coro to exit
COTEST_ASSERT(from_coro->GetKind() == PayloadKind::TCExited || from_coro->GetKind() == PayloadKind::ResumeMain);
if (from_coro->GetKind() == PayloadKind::TCExited) {
auto orig = PeekPayload<TCExitedPayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig.get() == this);
}
if (from_coro->GetKind() == PayloadKind::ResumeMain) {
auto orig = PeekPayload<ResumeMainPayload>(from_coro).GetOriginator().lock();
COTEST_ASSERT(orig.get() == this);
}
}
std::string TestCoroutine::DebugString() const {
std::stringstream ss;
ss << "TestCoroutine(\"" << GetName() << "\""
<< ", next_payload=" << !next_payload.get() << ", eir=" << (extra_iteration_requested ? "true" : "false") << ")";
return ss.str();
}
InteriorLaunchSessionBase::InteriorLaunchSessionBase(TestCoroutine *parent_coroutine_, std::string dc_name_)
: parent_coroutine(parent_coroutine_), launch_text(dc_name_) {}
InteriorLaunchSessionBase::~InteriorLaunchSessionBase() {
// Test coroutine has to "see" the result before we know for sure that the
// launch actually did run to completion. Successful IsLaunchResult() or
// IsLaunchResult(DC) is enough. We could detect launch completion earlier,
// but the rule is that the test coro has to do something to ensure this.
COTEST_ASSERT(launch_completed && "Launch session destructing before result confirmed");
}
void InteriorLaunchSessionBase::SetLaunchCompleted() { launch_completed = true; }
TestCoroutine *InteriorLaunchSessionBase::GetParentTestCoroutine() const { return parent_coroutine; }
std::string InteriorLaunchSessionBase::GetLaunchText() const { return launch_text; }
InteriorEventSession::InteriorEventSession(TestCoroutine *test_coroutine_, bool via_main_,
std::shared_ptr<InteriorLaunchSessionBase> via_launch_)
: test_coroutine(test_coroutine_), via_main(via_main_), via_launch(via_launch_) {
// Bad times trying to extract via_main_ and via_launch_ from the payload in
// this constructor: If we get the payload as a reference, the type of the
// unique_ptr<> is wrong. If we use rvalue reference, we consume it before the
// subclass can use it.
}
bool InteriorEventSession::IsFrom(InteriorLaunchSessionBase *source) {
// source is NULL for IsFromMain()
if (via_main) return !source;
auto via_launch_locked = via_launch.lock();
if (via_launch_locked)
return source == via_launch_locked.get();
else
return false; // Assuming source has not expired, the launch must be a
// different one
}
TestCoroutine *InteriorEventSession::GetTestCoroutine() const { return test_coroutine; }
InteriorMockCallSession::InteriorMockCallSession(TestCoroutine *test_coroutine_, bool via_main_,
std::shared_ptr<InteriorLaunchSessionBase> via_launch_,
std::unique_ptr<PreMockPayload> &&payload)
: InteriorEventSession(test_coroutine_, via_main_, via_launch_),
originator(payload->GetOriginator()),
mocker(payload->GetMocker()),
mock_object(payload->GetMockObject()),
name(payload->GetName())
{}
InteriorMockCallSession::~InteriorMockCallSession() {
COTEST_ASSERT(state == State::PreMock || state == State::Dropped || state == State::Returned);
}
std::string InteriorMockCallSession::GetName() const { return name; }
UntypedMockObjectPointer InteriorMockCallSession::GetMockObject() const { return mock_object; }
UntypedMockerPointer InteriorMockCallSession::GetMocker() const { return mocker; }
void InteriorMockCallSession::SeenCall(UntypedArgsPointer args_) {
COTEST_ASSERT(state == State::PreMock);
args = args_;
state = State::Seen;
}
bool InteriorMockCallSession::IsLaunchResult() const { return false; }
bool InteriorMockCallSession::IsLaunchResult(InteriorLaunchSessionBase *launch_session) const { return false; }
bool InteriorMockCallSession::IsMockCall() const { return true; }
void InteriorMockCallSession::Drop() {
COTEST_ASSERT(state == State::Seen && "in DROP()");
state = State::Dropped;
COTEST_ASSERT(!GetTestCoroutine()->IsPendingEvent() && "Internal error: event received during mock lock");
GetTestCoroutine()->YieldServer(MakePayload<DropMockPayload>(originator, shared_from_this()));
}
void InteriorMockCallSession::Accept() {
COTEST_ASSERT(state == State::Seen && "in ACCEPT()");
state = State::Accepted;
COTEST_ASSERT(!GetTestCoroutine()->IsPendingEvent() && "Internal error: event received during mock lock");
GetTestCoroutine()->YieldServer(MakePayload<AcceptMockPayload>(originator, shared_from_this()));
}
void InteriorMockCallSession::Return() { ReturnImpl(nullptr); }
UntypedReturnValuePointer InteriorMockCallSession::GetUntypedLaunchResult() const {
COTEST_ASSERT(!"Cannot get return value from a mock call session");
}
void InteriorMockCallSession::ReturnImpl(UntypedReturnValuePointer return_value) {
COTEST_ASSERT((state == State::Seen || state == State::Accepted) && "in RETURN()");
if (state == State::Seen) Accept();
state = State::Returned;
COTEST_ASSERT(!GetTestCoroutine()->IsPendingEvent() && "Return(): must use NextEvent() to collect an event first");
GetTestCoroutine()->YieldServer(MakePayload<ReturnMockPayload>(originator, shared_from_this(), return_value));
}
bool InteriorMockCallSession::IsReturned() const { return state == State::Returned; }
InteriorLaunchResultSession::InteriorLaunchResultSession(TestCoroutine *test_coroutine_,
std::unique_ptr<LaunchResultPayload> &&payload)
: InteriorEventSession(test_coroutine_, false, payload->GetOriginator().lock()),
originator(payload->GetOriginator()),
return_value(payload->GetResult()) {}
bool InteriorLaunchResultSession::IsLaunchResult() const {
auto orig = originator.lock();
if (orig) orig->SetLaunchCompleted();
return true;
}
bool InteriorLaunchResultSession::IsLaunchResult(InteriorLaunchSessionBase *launch_session) const {
COTEST_ASSERT(launch_session);
std::shared_ptr<InteriorLaunchSessionBase> orig = originator.lock();
if (!orig)
return false; // Rationale is: this launch session is still in existance,
// so the dead session must have been some other
if (orig.get() != launch_session) return false;
orig->SetLaunchCompleted();
return true;
}
bool InteriorLaunchResultSession::IsMockCall() const { return false; }
void InteriorLaunchResultSession::Drop() {
// Dropping sessions in error is hard to debug. If the handler comes later in
// the coro, the coro will fall out at that point (dropped event) and CMock
// will report unsatisfied. But we want to be shown where the unwanted drop
// occurred. So fail the test immediately. There's no real case for fall-back
// handling as with mock calls (where you can have a lower priority
// EXPECT_CALL() or WATCH_CALL()) as far as I can see.
COTEST_ASSERT(!"Dropping a launch result is not allowed");
}
void InteriorLaunchResultSession::Accept() { COTEST_ASSERT(!"No need to accept a launch result"); }
void InteriorLaunchResultSession::Return() { COTEST_ASSERT(!"Cannot return from a launch result"); }
UntypedReturnValuePointer InteriorLaunchResultSession::GetUntypedLaunchResult() const { return return_value; }
} // namespace crf
} // namespace testing

View File

@ -0,0 +1,139 @@
#include "cotest/internal/cotest-integ-finder.h"
#include <functional>
#include <iostream>
#include "cotest/internal/cotest-crf-core.h"
#include "cotest/internal/cotest-crf-launch.h"
#include "cotest/internal/cotest-crf-payloads.h"
#include "cotest/internal/cotest-crf-test.h"
#include "cotest/internal/cotest-integ-finder.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace internal {
using coro_impl::PtrToString;
MockHandler::MockHandler() {
MutexLock l(&g_gmock_mutex);
CotestMockHandlerPool::GetOrCreateInstance()->AddOwnerLocked(this);
}
MockHandler::~MockHandler() {
MutexLock l(&g_gmock_mutex);
CotestMockHandlerPool::GetOrCreateInstance()->RemoveOwnerLocked(this);
}
CotestMockHandlerPool *CotestMockHandlerPool::GetOrCreateInstance() {
if (!instance) instance = new CotestMockHandlerPool();
auto cem = static_cast<CotestMockHandlerPool *>(instance);
return cem;
}
void CotestMockHandlerPool::AddOwnerLocked(MockHandler *owner) { owners.insert(owner); }
void CotestMockHandlerPool::RemoveOwnerLocked(MockHandler *owner) { owners.erase(owner); }
void CotestMockHandlerPool::AddExpectation(std::function<void(void)> creator) {
// Rather than increment once for each new expectation, we increment twice
// around untyped watchers and not at all for other exps/watchers. This should
// help to reduce wraps in light of legacy tests with very large numbers of
// mocker exps. There should not be large numbers of untyped (=wildcard)
// watchers because the complexity should be in their coroutines.
next_global_priority++;
creator();
got_untyped_watchers = true;
next_global_priority++;
}
void CotestMockHandlerPool::PreMockUnlocked(const UntypedFunctionMockerBase *mocker, const void *mock_obj,
const char *name) {
std::clog << "CotestMockHandlerPool::PreMockUnlocked() call is " << name << std::endl;
const std::shared_ptr<crf::MockSource> mock_source =
crf::LaunchCoroutinePool::GetInstance()->FindActiveMockSource();
const auto crf_mock_routing_session =
mock_source->CreateMockRoutingSession(static_cast<crf::UntypedMockerPointer>(mocker), mock_obj, name);
crf_mock_routing_session->PreMockUnlocked();
}
void CotestMockHandlerPool::ForAll(ForTestCoroutineLambda &&lambda) {
for (MockHandler *o : owners) {
auto crf_sp = o->GetCRFTestCoroutine();
if (crf_sp && !crf_sp->IsCoroutineExited()) lambda(std::move(crf_sp));
}
}
bool CotestMockHandlerPool::IsUninteresting(const UntypedFunctionMockerBase *mocker, const void *untyped_args) const {
MutexLock l(&g_gmock_mutex);
// For now, assume any untyped watcher is full wild, making all calls
// interesting
return !got_untyped_watchers && mocker->GetMockHandlerScheme()->empty();
}
ExpectationBase *CotestMockHandlerPool::FindMatchingExpectationLocked(const UntypedFunctionMockerBase *mocker,
const void *untyped_args,
bool *is_mocker_exp) const {
std::vector<const MockHandlerScheme *> schemes(1);
schemes[0] = mocker->GetMockHandlerScheme(); // Be at index 0
for (const MockHandler *o : owners) {
schemes.push_back(o->GetMockHandlerScheme());
}
auto predicate = [&](ExpectationBase *exp) { return exp->ShouldHandleCall(mocker, untyped_args); };
unsigned which = 0;
auto exp = Finder(schemes, predicate, &which);
*is_mocker_exp = (which == 0);
return exp;
}
ExpectationBase *CotestMockHandlerPool::Finder(std::vector<const MockHandlerScheme *> &schemes,
ShouldHandleCallPredicate predicate, unsigned *which) {
std::vector<typename MockHandlerScheme::const_reverse_iterator> its;
std::map<Priority, unsigned> state;
for (unsigned i = 0; i < schemes.size(); i++) {
const MockHandlerScheme *scheme = schemes.at(i);
its.push_back(scheme->rbegin()); // begin with highest prio expectation in this scheme
if (!scheme->empty()) {
Priority initial_prio = (*its.at(i))->GetPriority(); // Prio of last element is highest
COTEST_ASSERT(state.count(initial_prio) == 0); // no prio value should be in more than one scheme
state[initial_prio] = i;
}
}
int num_remaining_schemes = state.size();
while (num_remaining_schemes >= 1) {
const auto state_it = std::prev(state.end());
const Priority p = state_it->first;
const unsigned i = state_it->second; // index of scheme with highest next prio
auto &it = its.at(i); // Its iterator
if (predicate(it->get())) {
// Found it
*which = i;
return it->get();
}
state.erase(state_it);
const MockHandlerScheme *scheme = schemes.at(i);
++it;
if (it == scheme->rend()) {
// this scheme has run out of exps
num_remaining_schemes--;
} else {
// this scheme has more, so re-insert
Priority new_p = (*it)->GetPriority();
COTEST_ASSERT(new_p <= p); // prirorities must be ordered with each
// scheme, and we're working downwards in prio
state[new_p] = i;
}
}
return nullptr;
}
} // namespace internal
} // namespace testing

View File

@ -0,0 +1,224 @@
#include "cotest/internal/cotest-integ-mock.h"
#include "cotest/internal/cotest-util-logging.h"
namespace testing {
namespace internal {
using coro_impl::PtrToString;
Coroutine::Coroutine(BodyFunctionType body, std::string name_)
: crf(std::make_shared<crf::TestCoroutine>(std::bind(body, this), name_,
std::bind(&Coroutine::OnTestCoroExit, this))),
name(name_),
my_cardinality(std::make_shared<CotestCardinality>()),
cotest_coro_(this) {
// Strong RAII model: this call to InitialActivity() can complete the entire
// test, leaving an exited coroutine and nothing to do but clean up.
crf->InitialActivity();
initial_activity_complete = true;
}
Coroutine::Coroutine(Coroutine&& i)
: crf(std::move(i.crf)),
name(std::move(i.name)),
my_watchers_all(std::move(i.my_watchers_all)),
my_cardinality(std::move(i.my_cardinality)),
cotest_coro_(this) {
// To move the coroutine after adding Watchers would invalidate the
// coroutine pointers inside them, and we don't need to do it. This
// move constructor is just to help with the UI syntax around creating
// the coroutine.
COTEST_ASSERT(my_watchers_all.empty());
}
Coroutine::~Coroutine() {
// This destructor is doing a lot of stuff, including iterating coroutines.
// Justification is that we want RAII style in the UI following GMock/GTest UI
// design.
DestructionIterations();
if (!crf->IsCoroutineExited()) {
// Generate this message as early as possible - assists with fiding cause
if (my_cardinality->IsSatisfiedByCallCount(0))
std::clog << "Cancelling a satisfied coroutine " << crf->GetName() << " - this is not an error."
<< std::endl;
else
std::clog << "Cancelling an unsatisfied coroutine " << crf->GetName() << " - this will cause test failure."
<< std::endl;
}
for (auto p_exp : my_watchers_all)
if (auto p_exp_locked = p_exp.lock()) p_exp_locked->DetachCoroutine();
if (!crf->IsCoroutineExited()) {
crf->Cancel();
}
}
void Coroutine::WatchCall(const char* file, int line, crf::UntypedMockObjectPointer obj) {
MutexLock l(&g_gmock_mutex);
// It's unfortunate that we have to provide a function type here, but we
// require functionality available in the templated types (TypedExpectation<>
// and CotestWatcher<>) and don't want to duplicate it. At least only one
// instantiation of the templated code arises as a result.
using UntypedWatcher = CotestWatcher<void()>;
std::shared_ptr<UntypedWatcher> sp;
// Add this expectation to our list, making use of the expectation
// finder singleton so it can update the global priority level.
auto cem = CotestMockHandlerPool::GetOrCreateInstance();
cem->AddExpectation([&]() {
sp = std::make_shared<UntypedWatcher>(this, obj, nullptr, file, line, "",
Function<int()>::ArgumentMatcherTuple(), my_cardinality.get());
my_untyped_watchers.push_back(sp);
});
AddWatcher(sp);
// This is only to get the right GMock behaviour
sp->set_repeated_action(DoDefault());
// Hooking up backend to cardinality interface so we can
// satisfy and saturate.
sp->set_cardinality(Cardinality(my_cardinality));
}
void Coroutine::SetSatisfied() { my_cardinality->InteriorSetSatisfied(); }
void Coroutine::Retire() {
// Lock if required since expectation retire flags are mutex-protected, so
// we should not change one while another thread has a lock on it.
std::unique_ptr<testing::internal::MutexLock> plock;
if (!gmock_mutex_held) plock = std::make_unique<testing::internal::MutexLock>(&testing::internal::g_gmock_mutex);
retired = true;
for (auto p_exp : my_watchers_all) {
if (auto p_exp_locked = p_exp.lock()) p_exp_locked->Retire();
}
}
bool Coroutine::IsRetired() const { return retired; }
std::string Coroutine::GetName() const { return name; }
const MockHandlerScheme* Coroutine::GetMockHandlerScheme() const { return &my_untyped_watchers; }
void Coroutine::DestructionIterations() {
if (crf->IsCoroutineExited()) return; // no action required
crf->DestructionIterations();
}
std::shared_ptr<crf::TestCoroutine> Coroutine::GetCRFTestCoroutine() {
COTEST_ASSERT(crf);
return crf;
}
void Coroutine::OnTestCoroExit() { my_cardinality->OnTestCoroExit(); }
void Coroutine::OnWatcherDestruct() {
// It is safe to destruct a mock object:
// - (a) if this coroutine is not watching it, or
// - (b) before waiting for any activity in main, or
// - (c) after coroutine body has exited, or
// - (d) after the coroutine object has been destructed.
//
// This is to prevent GMock from verifying end-of-life cardinality
// on a coroutine that might be about to call eg RETIRE()
// or SATURATE() but has not, because of CRF constraint #3
//
// (b) Is OK because while in intial actions, everything is
// happening in coro body and therefore synchronised.
// (c) An exited coro body is also synchronised.
// (d) An extra isteration is provided in this case to allow
// corotuine to run and update cardinality state.
// if coro destructed, this method is not called.
bool ok = !initial_activity_complete || crf->IsCoroutineExited();
COTEST_ASSERT(ok &&
"Mock object was destructed at an unsafe time: error reports "
"may be inaccurate");
}
void Coroutine::AddWatcher(std::shared_ptr<testing::internal::ExpectationBase> watcher) {
my_watchers_all.push_back(watcher);
}
RAIISetFlag Coroutine::RAIIGMockMutexIsLocked() { return RAIISetFlag(&gmock_mutex_held); }
CotestCardinality::CotestCardinality() {}
bool CotestCardinality::IsSatisfiedByCallCount(int call_count) const {
// These are our satisfied states. Final checks will pass if we destruct in
// these states. State::Unsatisfied fails the test because it is assumed that
// mock calls were expected but didn't arrive; State::Oversaturated is a fail
// for the opposite reson and should cause this method to return false.
return state == State::SatisfiedByUser || state == State::SatisfiedByExit;
}
bool CotestCardinality::IsSaturatedByCallCount(int call_count) const {
// These states correspond to saturation and will cause GMock to
// report that the coroutine was saturated.
return state == State::SatisfiedByExit || state == State::Oversaturated;
}
void CotestCardinality::DescribeTo(::std::ostream* os) const { *os << "called as determined by coroutine"; }
void CotestCardinality::InteriorSetSatisfied() {
// User has invoked SATISFY()
switch (state) {
case State::Unsatisfied:
state = State::SatisfiedByUser;
break;
case State::SatisfiedByUser:
break;
case State::SatisfiedByExit:
COTEST_ASSERT(!"Interior call while exited");
case State::Oversaturated:
COTEST_ASSERT(!"Interior call while exited");
};
}
void CotestCardinality::OnTestCoroExit() {
// Cotest policy: exiting a coroutine satisfies its cardinality
// unless or until a mock call gets through exterior matching.
switch (state) {
case State::Unsatisfied:
case State::SatisfiedByUser:
state = State::SatisfiedByExit;
break;
case State::SatisfiedByExit:
COTEST_ASSERT(!"Multiple OnTestCoroExit()");
case State::Oversaturated:
COTEST_ASSERT(!"Multiple OnTestCoroExit()");
};
}
bool CotestCardinality::OnSeenMockCall() {
// Cotest policy: if the coroutine exited and we've matched on
// exterior criteria, report over-saturation. We will not be
// able to perform interior matching if the coroutine exited,
// so the user's intention must be considered undefined and we
// fail. However, the user can prevent this by invoking
// RETIRE() before exit.
switch (state) {
case State::Unsatisfied:
break;
case State::SatisfiedByUser:
break;
case State::SatisfiedByExit:
state = State::Oversaturated;
break;
case State::Oversaturated:
break;
};
return state == State::Oversaturated; // proceed to interior matching
}
} // namespace internal
} // namespace testing

63
coroutines/src/cotest.cc Normal file
View File

@ -0,0 +1,63 @@
#include "cotest/cotest.h"
namespace testing {
EventHandle internal::Coroutine::NextEvent(const char *file, int line) {
return EventHandle(crf->NextEvent(file, line));
}
bool internal::Coroutine::gmock_mutex_held = false;
EventHandle::EventHandle(std::shared_ptr<crf::InteriorEventSession> crf_es_) : crf_es(crf_es_) {}
EventHandle EventHandle::IsLaunchResult() const { return crf_es->IsLaunchResult() ? *this : EventHandle(); }
EventHandle EventHandle::IsObject(crf::UntypedMockObjectPointer object) {
COTEST_ASSERT(crf_es);
if (!crf_es->IsMockCall()) return EventHandle();
auto crf_mcs = std::static_pointer_cast<crf::InteriorMockCallSession>(crf_es);
// Check the mock object
if (object != crf_mcs->GetMockObject()) return EventHandle();
return *this;
}
EventHandle EventHandle::IsMockCall() {
COTEST_ASSERT(crf_es);
return crf_es->IsMockCall() ? *this : EventHandle();
}
EventHandle::operator bool() const { return !!crf_es; }
EventHandle EventHandle::Drop() {
COTEST_ASSERT(crf_es);
crf_es->Drop();
return *this;
}
EventHandle EventHandle::Accept() {
COTEST_ASSERT(crf_es);
crf_es->Accept();
return *this;
}
EventHandle EventHandle::Return() {
COTEST_ASSERT(crf_es);
crf_es->Return();
return *this;
}
EventHandle EventHandle::FromMain() {
COTEST_ASSERT(crf_es);
bool ok = crf_es->IsFrom(nullptr);
return ok ? *this : EventHandle();
}
std::string EventHandle::GetName() const {
COTEST_ASSERT(crf_es && "call session is NULL, check for failed test");
return static_cast<crf::InteriorMockCallSession *>(crf_es.get())->GetName();
}
} // namespace testing

View File

@ -0,0 +1,160 @@
#include <string>
#include "cotest/internal/cotest-coro-thread.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace std;
using namespace coro_impl;
struct TestPayload : public Payload {
TestPayload(int value_) : value(value_) {}
std::string DebugString() const final { return "TestPayload(" + to_string(value) + ")"; }
int value;
};
struct DestructorCheck {
DestructorCheck() { undestructed_count++; }
~DestructorCheck() { undestructed_count--; }
static int undestructed_count;
};
int DestructorCheck::undestructed_count = 0;
TEST(CoroTestThread, SimpleYieldingSequence) {
string coro_stage = "init";
auto cl = [&](InteriorInterface *ii) {
DestructorCheck dc;
std::unique_ptr<Payload> from_main;
coro_stage = "point 1";
from_main = ii->Yield(MakePayload<TestPayload>(10));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 100);
coro_stage = "point 2";
from_main = ii->Yield(MakePayload<TestPayload>(20));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 200);
coro_stage = "point 3";
};
CoroOnThread coroutine(std::bind(cl, &coroutine), "coroutine");
std::unique_ptr<Payload> from_coro;
EXPECT_FALSE(coroutine.IsCoroutineExited());
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(coroutine.IsCoroutineExited());
EXPECT_TRUE(from_coro);
EXPECT_EQ(PeekPayload<TestPayload>(from_coro).value, 10);
EXPECT_EQ(coro_stage, "point 1");
from_coro = coroutine.Iterate(MakePayload<TestPayload>(100));
EXPECT_TRUE(from_coro);
EXPECT_EQ(PeekPayload<TestPayload>(from_coro).value, 20);
EXPECT_FALSE(coroutine.IsCoroutineExited());
EXPECT_EQ(coro_stage, "point 2");
from_coro = coroutine.Iterate(MakePayload<TestPayload>(200));
EXPECT_FALSE(from_coro);
EXPECT_TRUE(coroutine.IsCoroutineExited());
EXPECT_EQ(coro_stage, "point 3");
EXPECT_EQ(DestructorCheck::undestructed_count, 0);
}
TEST(CoroTestThread, LongYieldingSequence) {
int coro_stage = -1;
const int n = 1000;
auto cl = [&](InteriorInterface *ii) {
DestructorCheck dc;
std::unique_ptr<Payload> from_main;
for (int i = 0; i < n; i++) {
coro_stage = i;
from_main = ii->Yield(MakePayload<TestPayload>(i * 10));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, (i + 1) * 100);
}
coro_stage = n;
};
CoroOnThread coroutine(std::bind(cl, &coroutine), "coroutine");
std::unique_ptr<Payload> from_coro;
EXPECT_FALSE(coroutine.IsCoroutineExited());
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(coroutine.IsCoroutineExited());
EXPECT_TRUE(from_coro);
EXPECT_EQ(PeekPayload<TestPayload>(from_coro).value, 0);
EXPECT_EQ(coro_stage, 0);
for (int i = 1; i < n; i++) {
from_coro = coroutine.Iterate(MakePayload<TestPayload>(i * 100));
EXPECT_FALSE(coroutine.IsCoroutineExited());
EXPECT_TRUE(from_coro);
EXPECT_EQ(PeekPayload<TestPayload>(from_coro).value, i * 10);
EXPECT_EQ(coro_stage, i);
}
from_coro = coroutine.Iterate(MakePayload<TestPayload>(n * 100));
EXPECT_TRUE(coroutine.IsCoroutineExited());
EXPECT_FALSE(from_coro);
EXPECT_EQ(coro_stage, n);
EXPECT_EQ(DestructorCheck::undestructed_count, 0);
}
TEST(CoroTestThread, Cancelling) {
string coro_stage = "init";
auto cl = [&](InteriorInterface *ii) {
DestructorCheck dc;
std::unique_ptr<Payload> from_main;
coro_stage = "point 1";
from_main = ii->Yield(MakePayload<TestPayload>(10));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 100);
coro_stage = "point 2";
from_main = ii->Yield(MakePayload<TestPayload>(20));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 200);
coro_stage = "point 3";
};
CoroOnThread coroutine(std::bind(cl, &coroutine), "coroutine");
std::unique_ptr<Payload> from_coro;
EXPECT_FALSE(coroutine.IsCoroutineExited());
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(coroutine.IsCoroutineExited());
EXPECT_TRUE(from_coro);
EXPECT_EQ(PeekPayload<TestPayload>(from_coro).value, 10);
EXPECT_EQ(coro_stage, "point 1");
coroutine.Cancel();
EXPECT_TRUE(coroutine.IsCoroutineExited());
EXPECT_EQ(DestructorCheck::undestructed_count, 0);
}
TEST(CoroTestThread, ImmediateCancelling) {
string coro_stage = "init";
auto cl = [&](InteriorInterface *ii) {
DestructorCheck dc;
std::unique_ptr<Payload> from_main;
coro_stage = "point 1";
from_main = ii->Yield(MakePayload<TestPayload>(10));
EXPECT_TRUE(from_main);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 100);
coro_stage = "point 2";
};
CoroOnThread coroutine(std::bind(cl, &coroutine), "coroutine");
std::unique_ptr<Payload> from_coro;
EXPECT_FALSE(coroutine.IsCoroutineExited());
coroutine.Cancel();
EXPECT_TRUE(coroutine.IsCoroutineExited());
EXPECT_EQ(DestructorCheck::undestructed_count, 0);
}

View File

@ -0,0 +1,118 @@
#include <string>
#include "cotest/internal/cotest-coro-thread.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
//#define EXPLICIT_FOR_GDB
using namespace std;
using namespace coro_impl;
using ::testing::StrictMock;
using CoroImplType = CoroOnThread;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual void Mock2(int degrees) = 0;
virtual int Mock1() const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(void, Mock2, (int degrees), (override));
MOCK_METHOD(int, Mock1, (), (const, override));
};
struct TestPayload : public Payload {
TestPayload(int value_) : value(value_) {}
std::string DebugString() const final { return "TestPayload(" + to_string(value) + ")"; }
int value;
};
std::unique_ptr<Payload> from_coro;
struct IterateCoro0 {
IterateCoro0(ExteriorInterface *coroutine_) : coroutine(coroutine_) {}
int operator()() {
from_coro = coroutine->Iterate(nullptr);
return PeekPayload<TestPayload>(from_coro).value;
}
ExteriorInterface *const coroutine;
};
//////////////////////////////////////////////
// The actual tests
TEST(ActionFunctorTest, WithReturn) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr); // waiting for the first mock call
from_main = ii->Yield(MakePayload<TestPayload>(100));
from_main = ii->Yield(MakePayload<TestPayload>(200));
from_main = ii->Yield(MakePayload<TestPayload>(300));
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
#ifdef EXPLICIT_FOR_GDB
const IterateCoro0 functor(&coroutine);
const testing::Action<int()> action(functor); // lands on Action(G&& fun) then void Init(G&& g,
// ::true_type)
EXPECT_CALL(mock_object, Mock1()).Times(3).WillRepeatedly(action);
#else
EXPECT_CALL(mock_object, Mock1()).Times(3).WillRepeatedly(IterateCoro0(&coroutine));
#endif
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 100);
EXPECT_EQ(mock_object.Mock1(), 200);
EXPECT_EQ(mock_object.Mock1(), 300);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}
struct IterateCoro1 {
IterateCoro1(ExteriorInterface *coroutine_) : coroutine(coroutine_) {}
void operator()(int arg0) { from_coro = coroutine->Iterate(MakePayload<TestPayload>(arg0)); }
ExteriorInterface *const coroutine;
};
TEST(ActionFunctorTest, WithArg) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 45);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 90);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 180);
from_main = ii->Yield(nullptr); // return from the last mock call
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
EXPECT_CALL(mock_object, Mock2).Times(3).WillRepeatedly(IterateCoro1(&coroutine));
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
mock_object.Mock2(45);
mock_object.Mock2(90);
mock_object.Mock2(180);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}

View File

@ -0,0 +1,102 @@
#include <string>
#include "cotest/internal/cotest-coro-thread.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace std;
using namespace coro_impl;
using ::testing::StrictMock;
using CoroImplType = CoroOnThread;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual void Mock2(int degrees) = 0;
virtual int Mock1() const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(void, Mock2, (int degrees), (override));
MOCK_METHOD(int, Mock1, (), (const, override));
};
struct TestPayload : public Payload {
TestPayload(int value_) : value(value_) {}
std::string DebugString() const final { return "TestPayload(" + to_string(value) + ")"; }
int value;
};
std::unique_ptr<Payload> from_coro;
ACTION_P(IterateCoro0, coroutine) {
from_coro = coroutine->Iterate(nullptr);
return PeekPayload<TestPayload>(from_coro).value;
}
//////////////////////////////////////////////
// The actual tests
TEST(ActionMacroTest, WithReturn) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr); // waiting for the first mock call
from_main = ii->Yield(MakePayload<TestPayload>(100));
from_main = ii->Yield(MakePayload<TestPayload>(200));
from_main = ii->Yield(MakePayload<TestPayload>(300));
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
EXPECT_CALL(mock_object, Mock1()).Times(3).WillRepeatedly(IterateCoro0(&coroutine));
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 100);
EXPECT_EQ(mock_object.Mock1(), 200);
EXPECT_EQ(mock_object.Mock1(), 300);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}
int coro_arg0;
ACTION_P(IterateCoro1, coroutine) { from_coro = coroutine->Iterate(MakePayload<TestPayload>(arg0)); }
TEST(ActionMacroTest, WithArg) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 45);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 90);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 180);
from_main = ii->Yield(nullptr); // return from the las mock call
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
EXPECT_CALL(mock_object, Mock2).Times(3).WillRepeatedly(IterateCoro1(&coroutine));
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
mock_object.Mock2(45);
mock_object.Mock2(90);
mock_object.Mock2(180);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}

View File

@ -0,0 +1,126 @@
#include <string>
#include "cotest/internal/cotest-coro-thread.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using namespace std;
using namespace coro_impl;
using ::testing::StrictMock;
using CoroImplType = CoroOnThread;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual void Mock2(int degrees) = 0;
virtual int Mock1() const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(void, Mock2, (int degrees), (override));
MOCK_METHOD(int, Mock1, (), (const, override));
};
struct TestPayload : public Payload {
TestPayload(int value_) : value(value_) {}
std::string DebugString() const final { return "TestPayload(" + to_string(value) + ")"; }
int value;
};
std::unique_ptr<Payload> from_coro;
class IterateCoro0Action {
public:
IterateCoro0Action(ExteriorInterface *coroutine_) : coroutine(coroutine_) {}
template <typename Result, typename ArgumentTuple>
Result Perform(const ArgumentTuple &args) const {
from_coro = coroutine->Iterate(nullptr);
return PeekPayload<TestPayload>(from_coro).value;
}
ExteriorInterface *const coroutine;
};
inline testing::PolymorphicAction<IterateCoro0Action> IterateCoro0(ExteriorInterface *coroutine) {
return testing::MakePolymorphicAction(IterateCoro0Action(coroutine));
}
//////////////////////////////////////////////
// The actual tests
TEST(ActionPolymorphicTest, WithReturn) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr); // waiting for the first mock call
from_main = ii->Yield(MakePayload<TestPayload>(100));
from_main = ii->Yield(MakePayload<TestPayload>(200));
from_main = ii->Yield(MakePayload<TestPayload>(300));
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
EXPECT_CALL(mock_object, Mock1()).Times(3).WillRepeatedly(IterateCoro0(&coroutine));
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 100);
EXPECT_EQ(mock_object.Mock1(), 200);
EXPECT_EQ(mock_object.Mock1(), 300);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}
int coro_arg0;
class IterateCoro1Action {
public:
IterateCoro1Action(ExteriorInterface *coroutine_) : coroutine(coroutine_) {}
template <typename Result, typename ArgumentTuple>
Result Perform(const ArgumentTuple &args) const {
from_coro = coroutine->Iterate(MakePayload<TestPayload>(get<0>(args)));
}
ExteriorInterface *const coroutine;
};
inline testing::PolymorphicAction<IterateCoro1Action> IterateCoro1(ExteriorInterface *coroutine) {
return testing::MakePolymorphicAction(IterateCoro1Action(coroutine));
}
TEST(ActionPolymorphicTest, WithArg) {
StrictMock<MockClass> mock_object;
auto cl = [](InteriorInterface *ii) {
std::unique_ptr<Payload> from_main;
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 45);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 90);
from_main = ii->Yield(nullptr);
EXPECT_EQ(PeekPayload<TestPayload>(from_main).value, 180);
from_main = ii->Yield(nullptr); // return from the las mock call
};
CoroImplType coroutine(std::bind(cl, &coroutine), "coroutine");
EXPECT_CALL(mock_object, Mock2).Times(3).WillRepeatedly(IterateCoro1(&coroutine));
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
// This is the body of the test case
mock_object.Mock2(45);
mock_object.Mock2(90);
mock_object.Mock2(180);
// Allow coroutine to exit
from_coro = coroutine.Iterate(nullptr);
EXPECT_FALSE(from_coro);
}

View File

@ -0,0 +1,147 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) = 0;
virtual int Mock2(int i, int j) const = 0;
virtual int Mock3(int i) const = 0;
virtual int Mock4(int i) const = 0;
virtual int Mock4(int i) = 0;
};
class ExampleClass {
public:
ExampleClass(ClassToMock *dep_) : dep(dep_) {}
int Example1(int a) { return dep->Mock1(a + 1) * 2; }
int Example2(int a) { return dep->Mock1(a + 1) * dep->Mock1(0); }
private:
ClassToMock *const dep;
};
//////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (override));
MOCK_METHOD(int, Mock2, (int i, int j), (const, override));
MOCK_METHOD(int, Mock3, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (override));
};
//////////////////////////////////////////////
// The actual tests
StrictMock<MockClass> *mock_object_p = nullptr;
/*
* Why this works
*
* Expectations, watches etc are created in the coro but under shared_ptr<>
* and are held alive by the mockers in the mock object. In this test case,
* the mockers outlast the coro itself, so we end up using DetachCoroutine()
* etc.
*/
TEST(AllInTest, WatchInside) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE() {
WATCH_CALL(mock_object, Mock1);
WATCH_CALL(mock_object, Mock2);
// See later tests in which LAUNCH() is used to run code-under-test
// from within coroutine.
mock_object_p = &mock_object;
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock1(201)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(_)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(200)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, 401)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, 400)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, _)).RETURN(30));
};
ASSERT_TRUE(mock_object_p);
EXPECT_EQ(mock_object_p->Mock1(200), 20);
EXPECT_EQ(mock_object_p->Mock2(200, 400), 30);
mock_object_p = nullptr;
}
TEST(AllInTest, LaunchMock) {
auto coro = COROUTINE() {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
WATCH_CALL();
auto d = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_CALL(mock_object, Mock1(5)).RETURN(1000));
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 2000);
};
}
COTEST(AllInTest, CotestMacro) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
WATCH_CALL();
auto d = LAUNCH(example.Example1(4));
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_TRUE(cs.GetArg<0>() == 5);
cs.RETURN(1000);
auto e = WAIT_FOR_RESULT();
EXPECT_EQ(e(d), 2000);
}
COTEST(AllInTest, CotestMacroWithExpect) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
// EXPECT_CALL() is fine inside a COTEST(). Semantics are the
// same.
EXPECT_CALL(mock_object, Mock1(0)).WillRepeatedly(Return(1));
WATCH_CALL();
auto d = LAUNCH(example.Example2(4));
auto cs = WAIT_FOR_CALL(mock_object, Mock1(Gt(0)));
EXPECT_TRUE(cs.GetArg<0>() == 5);
cs.RETURN(1000);
auto e = WAIT_FOR_RESULT();
EXPECT_EQ(e(d), 1000);
}

View File

@ -0,0 +1,516 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1() const = 0;
virtual int Mock2() const = 0;
virtual int Mock4() const = 0;
virtual int Mock5(int x) const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (), (const, override));
MOCK_METHOD(int, Mock2, (), (const, override));
MOCK_METHOD(int, Mock4, (), (const, override));
MOCK_METHOD(int, Mock5, (int x), (const, override));
};
using ::testing::Return;
//////////////////////////////////////////////
// The actual tests
TEST(CardinalityTest, ExitAfterMethod1_OK) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock2(), -1);
EXPECT_EQ(mock_object.Mock1(), 10);
}
TEST(CardinalityTest, ExitAfterMethod1_OK_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL();
// This is the body of the test case
EXPECT_EQ(mock_object.Mock2(), -1);
EXPECT_EQ(mock_object.Mock1(), 10);
}
TEST(CardinalityTest, ExitAfterMethod1_Oversaturate) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object, Mock4);
// This is the body of the test case
mock_object.Mock2();
mock_object.Mock1();
// We expect a TEST FAILURE while running the MUT, with a message about
// oversaturation
EXPECT_NONFATAL_FAILURE(mock_object.Mock4(), "Actual: called once - over-saturated and active");
}
TEST(CardinalityTest, ExitAfterMethod1_Oversaturate_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object);
// This is the body of the test case
mock_object.Mock2();
mock_object.Mock1();
// We expect a TEST FAILURE while running the MUT, with a message about
// oversaturation
EXPECT_NONFATAL_FAILURE(mock_object.Mock4(), "Actual: called twice - over-saturated and active");
// Note different message compared to ExitAfterMethod1_Oversaturate,
// this is because the call count used in the message is kept by the
// individual watchers (=expectations), not the coroutine.
}
TEST(CardinalityTest, ExitAfterMethod1_Simple) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 10);
}
TEST(CardinalityTest, ExitAfterMethod1_Simple_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
coro.WATCH_CALL();
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 10);
}
// Note: this one does not use coroutines at all and only
// serves as an example of unsatisfaction in gmock.
TEST(CardinalityTest, NoCoroutine_Unsatisfy) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
EXPECT_CALL(*mock_object_ptr, Mock1).Times(3);
// This is the body of the test case
mock_object_ptr->Mock1();
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: called once - unsatisfied and active");
}
TEST(CardinalityTest, SatisfyAfterMethod1_Unsatisfy) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
// Mock calls after this are not required for the test to pass
SATISFY();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(20);
};
// absorb mock calls
EXPECT_CALL(*mock_object_ptr, Mock2).WillOnce(Return(-1));
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
mock_object_ptr->Mock2();
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: never called - unsatisfied and active");
}
TEST(CardinalityTest, SatisfyAfterMethod1_Unsatisfy_Wild) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
// Mock calls after this are not required for the test to pass
SATISFY();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(20);
};
// absorb mock calls
EXPECT_CALL(*mock_object_ptr, Mock2).WillOnce(Return(-1));
coro_ptr->WATCH_CALL(*mock_object_ptr);
// This is the body of the test case
mock_object_ptr->Mock2();
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(delete coro_ptr, "Actual: never called - unsatisfied and active");
mock_object_ptr.reset();
}
TEST(CardinalityTest, SatisfyImmediate_OK) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(SatisfyImmediate) {
// Mock calls after this are not required for the test to pass
SATISFY();
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
// This is the body of the test case
mock_object.Mock2();
}
TEST(CardinalityTest, SatisfyImmediate_OK_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(SatisfyImmediate) {
// Mock calls after this are not required for the test to pass
SATISFY();
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
};
// absorb mock calls
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL();
// This is the body of the test case
mock_object.Mock2();
}
TEST(CardinalityTest, SatisfyAfterMethod1_OK) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(mock_object, Mock1).RETURN(10);
// Mock calls after this are not required for the test to pass
SATISFY();
WAIT_FOR_CALL(mock_object, Mock1).RETURN(20);
};
coro.WATCH_CALL(mock_object, Mock1);
// This is the body of the test case
mock_object.Mock1();
}
TEST(CardinalityTest, SatisfyAfterMethod1_OK_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(mock_object, Mock1).RETURN(10);
// Mock calls after this are not required for the test to pass
SATISFY();
WAIT_FOR_CALL(mock_object, Mock1).RETURN(20);
};
coro.WATCH_CALL(mock_object);
// This is the body of the test case
mock_object.Mock1();
}
TEST(CardinalityTest, RetireAfterMethod1_OK) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(RetireAfterMethod1) {
WAIT_FOR_CALL(mock_object, Mock1).RETURN(10);
// Retire will mean the mock will drop all further calls
RETIRE();
};
EXPECT_CALL(mock_object, Mock4).WillOnce(Return(33));
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 10);
EXPECT_EQ(mock_object.Mock1(), -1);
EXPECT_EQ(mock_object.Mock4(), 33);
}
TEST(CardinalityTest, RetireAfterMethod1_OK_Wild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(RetireAfterMethod1) {
WAIT_FOR_CALL(mock_object, Mock1).RETURN(10);
// Retire will mean the mock will drop all further calls
RETIRE();
};
EXPECT_CALL(mock_object, Mock4).WillOnce(Return(33));
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-1));
coro.WATCH_CALL();
// This is the body of the test case
EXPECT_EQ(mock_object.Mock1(), 10);
EXPECT_EQ(mock_object.Mock1(), -1);
EXPECT_EQ(mock_object.Mock4(), 33);
}
TEST(CardinalityTest, RetireAfterMethod1_Unsatisfy) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(RetireAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
// Retire will mean the mock will drop all further calls
RETIRE();
};
EXPECT_CALL(*mock_object_ptr, Mock4).WillOnce(Return(33));
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
EXPECT_EQ(mock_object_ptr->Mock4(), 33);
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: never called - unsatisfied and active");
}
TEST(CardinalityTest, RetireAfterMethod1_Unsatisfy_Wild) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(RetireAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
// Retire will mean the mock will drop all further calls
RETIRE();
};
EXPECT_CALL(*mock_object_ptr, Mock4).WillOnce(Return(33));
coro_ptr->WATCH_CALL();
// This is the body of the test case
EXPECT_EQ(mock_object_ptr->Mock4(), 33);
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(delete coro_ptr;, "Actual: never called - unsatisfied and active");
mock_object_ptr.reset();
}
TEST(CardinalityTest, NoCoroutine_Unexpected) {
StrictMock<MockClass> mock_object;
EXPECT_CALL(mock_object, Mock5(99));
// This is the body of the test case
mock_object.Mock5(99);
EXPECT_NONFATAL_FAILURE(mock_object.Mock5(55), "Unexpected mock function call - returning default value.");
}
TEST(CardinalityTest, Coroutine_Unexpected) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Unexpected) {
SATISFY();
while (1) {
auto e = NEXT_EVENT();
if (auto e2 = e.IS_CALL(mock_object, Mock5(99)))
e2.RETURN(0);
else
e.DROP();
}
};
coro.WATCH_CALL();
// This is the body of the test case
mock_object.Mock5(99);
EXPECT_NONFATAL_FAILURE(mock_object.Mock5(55), "Unexpected mock function call - returning default value.");
}
TEST(CardinalityTest, ExitAfterMethod1_OversaturateOnSatisfy) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ExitAfterMethod1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
cs.RETURN(10);
SATISFY();
};
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object, Mock4);
// This is the body of the test case
mock_object.Mock2();
mock_object.Mock1();
// We expect a TEST FAILURE while running the MUT, with a message about
// oversaturation even though the interior indicated satisfaction.
EXPECT_NONFATAL_FAILURE(mock_object.Mock4(), "Actual: called once - over-saturated and active");
}
TEST(CardinalityTest, UnsatisfyRetired1) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
RETIRE();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
};
// absorb mock calls
EXPECT_CALL(*mock_object_ptr, Mock2).WillOnce(Return(-1));
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
mock_object_ptr->Mock2();
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: never called - unsatisfied and retired");
}
TEST(CardinalityTest, UnsatisfyRetired2) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
RETIRE();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(20); // Ensure coro is unsatisfied
};
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
mock_object_ptr->Mock1();
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: called once - unsatisfied and retired");
}
TEST(CardinalityTest, UnsatisfyRetired3) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
RETIRE();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10); // Ensure RETIRE() runs (see UnsatisfyRetired2)
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(20); // Ensure coro is unsatisfied
};
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
mock_object_ptr->Mock1();
EXPECT_NONFATAL_FAILURE(mock_object_ptr->Mock1(), "Actual: it is retired");
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
EXPECT_NONFATAL_FAILURE(mock_object_ptr.reset(), "Actual: called once - unsatisfied and retired");
}
TEST(CardinalityTest, SatisfyDestruct) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
SATISFY();
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(20); // Ensure coro is unsatisfied
};
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
EXPECT_EQ(mock_object_ptr->Mock1(), 10);
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
mock_object_ptr.reset();
}
TEST(CardinalityTest, SatisfyDestruct2) {
auto mock_object_ptr = make_unique<StrictMock<MockClass>>();
auto coro_ptr = NEW_COROUTINE(SatisfyAfterMethod1) {
WAIT_FOR_CALL(*mock_object_ptr, Mock1).RETURN(10);
SATISFY();
};
coro_ptr->WATCH_CALL(*mock_object_ptr, Mock1);
// This is the body of the test case
EXPECT_EQ(mock_object_ptr->Mock1(), 10);
delete coro_ptr; // Coro may not have exited so we must delete it before the
// mock object
// The mock object should be unsatisfied and therefore fail in its destructor
mock_object_ptr.reset();
}

View File

@ -0,0 +1,235 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) const = 0;
virtual int Mock2(int i, int j) const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (const, override));
MOCK_METHOD(int, Mock2, (int i, int j), (const, override));
};
//////////////////////////////////////////////
// The actual tests
TEST(UserInterfaceTest, Method1_Base) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_EQ(cs.GetArg<0>(), 200);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1);
EXPECT_EQ(mock_object.Mock1(200), 10);
}
TEST(UserInterfaceTest, Method1_Underscore) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_EQ(cs.GetArg<0>(), 200);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1(_));
EXPECT_EQ(mock_object.Mock1(200), 10);
}
TEST(UserInterfaceTest, Method1_Match) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method1) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_EQ(cs.GetArg<0>(), 200);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1(200));
EXPECT_EQ(mock_object.Mock1(200), 10);
}
TEST(UserInterfaceTest, Method1_NoMatch) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(FailIfCall) {
SATISFY(); // If we never get the call, we're fine
WAIT_FOR_CALL();
COTEST_ASSERT(false);
};
EXPECT_CALL(mock_object, Mock1(200)).WillOnce(Return(20));
coro.WATCH_CALL(mock_object, Mock1(100));
EXPECT_EQ(mock_object.Mock1(200), 20);
}
TEST(UserInterfaceTest, Method2_Base) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2);
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_Underscore) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2(_, _));
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_Mix1) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2(_, 300));
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_Mix2) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2(200, _));
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_Match) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2(200, 300));
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_Mix_Mismatch) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(FailIfCall) {
SATISFY(); // If we never get the call, we're fine
WAIT_FOR_CALL();
COTEST_ASSERT(false);
};
EXPECT_CALL(mock_object, Mock2(200, _)).WillOnce(Return(20));
coro.WATCH_CALL(mock_object, Mock2(202, _));
EXPECT_EQ(mock_object.Mock2(200, 300), 20);
}
TEST(UserInterfaceTest, Method2_Mismatch) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(FailIfCall) {
SATISFY(); // If we never get the call, we're fine
WAIT_FOR_CALL();
COTEST_ASSERT(false);
};
EXPECT_CALL(mock_object, Mock2(200, 300)).WillOnce(Return(20));
coro.WATCH_CALL(mock_object, Mock2(200, 303));
EXPECT_EQ(mock_object.Mock2(200, 300), 20);
}
TEST(UserInterfaceTest, Method2_With) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2_With) {
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_EQ(cs.GetArg<0>(), 200);
EXPECT_EQ(cs.GetArg<1>(), 300);
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock2).With(Lt());
EXPECT_EQ(mock_object.Mock2(200, 300), 10);
}
TEST(UserInterfaceTest, Method2_With_Mismatch) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Method2_With_Mismatch) {
SATISFY(); // If we never get the call, we're fine
WAIT_FOR_CALL();
COTEST_ASSERT(false);
};
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(20));
coro.WATCH_CALL(mock_object, Mock2).With(Gt());
EXPECT_EQ(mock_object.Mock2(200, 300), 20);
}
TEST(UserInterfaceTest, OverlappingWatchers) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto e = NEXT_EVENT();
e.DROP();
};
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(20));
// These two watchers overlap in scope: cotest must ensure the
// coro only "sees" the mock call once.
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL();
EXPECT_EQ(mock_object.Mock2(200, 300), 20);
}

View File

@ -0,0 +1,347 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) const = 0;
virtual int Mock2(int i, int j) const = 0;
virtual int Mock3(int i) const = 0;
virtual int Mock4(int i) const = 0;
virtual int Mock4(int i) = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (const, override));
MOCK_METHOD(int, Mock2, (int i, int j), (const, override));
MOCK_METHOD(int, Mock3, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (override));
};
class ClassToMockA {
public:
virtual ~ClassToMockA() {}
virtual int Mock(int i) const = 0;
};
class MockClassA : public ClassToMockA {
public:
MOCK_METHOD(int, Mock, (int i), (const, override));
};
class ClassToMockB {
public:
virtual ~ClassToMockB() {}
virtual int Mock(int i) const = 0;
};
class MockClassB : public ClassToMockB {
public:
MOCK_METHOD(int, Mock, (int i), (const, override));
};
//////////////////////////////////////////////
// The actual tests
TEST(InteriorFilteringTest, MethodBasic) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodBasic) {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.FromMain());
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock1(201)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(10));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, 401)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, 400)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, _)).RETURN(20));
};
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
EXPECT_EQ(mock_object.Mock1(200), 10);
EXPECT_EQ(mock_object.Mock2(200, 400), 20);
}
TEST(InteriorFilteringTest, MethodName) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodName) {
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock3));
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock3(_)));
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock3(200)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1).RETURN(10));
};
coro.WATCH_CALL(mock_object, Mock1);
EXPECT_EQ(mock_object.Mock1(200), 10);
}
TEST(InteriorFilteringTest, MethodConst) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodConst) {
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(Const(mock_object), Mock4(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock4(_)).RETURN(30));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock4(_)));
EXPECT_TRUE(cg.IS_CALL(Const(mock_object), Mock4(_)).RETURN(40));
};
const StrictMock<MockClass> &const_ref_mock_object(mock_object);
coro.WATCH_CALL(mock_object, Mock4(_));
coro.WATCH_CALL(Const(mock_object), Mock4(_));
EXPECT_EQ(mock_object.Mock4(200), 30);
EXPECT_EQ(const_ref_mock_object.Mock4(200), 40);
}
TEST(InteriorFilteringTest, TwoMethod) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethod) {
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock1(201)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(_)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(200)));
EXPECT_TRUE(cg.IS_CALL(mock_object)); // 1-arg IS_CALL() matches by mock object
EXPECT_FALSE(cg.IS_CALL(mock_object2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, 401)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(Ne(201), Ne(399))));
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(Ne(200), _)));
EXPECT_TRUE(cg.IS_CALL(mock_object));
EXPECT_FALSE(cg.IS_CALL(mock_object2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, 400)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, _)).RETURN(30));
};
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(InteriorFilteringTest, MethodByClass) {
StrictMock<MockClassA> mock_object_a;
StrictMock<MockClassB> mock_object_b;
auto coro = COROUTINE(MethodByClass) {
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object_b, Mock));
EXPECT_TRUE(cg.IS_CALL(mock_object_a, Mock).RETURN(10));
};
coro.WATCH_CALL(mock_object_a, Mock);
EXPECT_EQ(mock_object_a.Mock(200), 10);
}
TEST(InteriorFilteringTest, WithClause) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(WithClause) {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)).With(Lt()));
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(0, 0)).With(Lt())); // With() on NULL call session must be safe
auto cs = cg.IS_CALL(mock_object, Mock2);
EXPECT_THAT(cs.GetArgs(), Lt());
EXPECT_THAT(cs.GetArgs(), Not(Ge()));
cs.RETURN(10);
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, _)).With(Lt()));
cg.IS_CALL(mock_object, Mock2).With(Gt()).RETURN(20);
};
coro.WATCH_CALL(mock_object, Mock2);
EXPECT_EQ(mock_object.Mock2(200, 400), 10);
EXPECT_EQ(mock_object.Mock2(200, 100), 20);
}
MATCHER(IsEven, "") { return (arg % 2) == 0; }
TEST(InteriorFilteringTest, Complex) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(Complex) {
while (true) {
auto cs = MOCK_CALL_HANDLE(mock_object, Mock1(_));
auto cs2 = MOCK_CALL_HANDLE(mock_object, Mock1);
cs = WAIT_FOR_CALL().IS_CALL(mock_object, Mock1(IsEven()));
EXPECT_TRUE(cs);
cs.RETURN(10);
cs2 = WAIT_FOR_CALL().IS_CALL(mock_object, Mock1(Not(IsEven())));
EXPECT_TRUE(cs2);
cs2.RETURN(10);
SATISFY();
}
};
coro.WATCH_CALL(mock_object, Mock1);
for (int i = 0; i < 10; i++) EXPECT_EQ(mock_object.Mock1(i), 10);
}
TEST(InteriorFilteringTest, ComplexWaiting) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ComplexWaiting) {
while (true) {
// Be a better neighbour by passing on unmatching calls rather than
// generating an error here - but we may be passing on too many
auto cs = WAIT_FOR_CALL(mock_object, Mock1(IsEven()));
EXPECT_TRUE(cs);
cs.RETURN(10);
cs = WAIT_FOR_CALL(mock_object, Mock1(Not(IsEven())));
EXPECT_TRUE(cs);
cs.RETURN(10);
SATISFY();
}
};
coro.WATCH_CALL(mock_object, Mock1);
for (int i = 0; i < 10; i++) EXPECT_EQ(mock_object.Mock1(i), 10);
}
TEST(InteriorFilteringTest, ComplexWaitingPrio) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ComplexWaitingPrio) {
while (true) {
// Only drop negative args
auto cs = WAIT_FOR_CALL(mock_object, Mock1(Ge(0)));
EXPECT_TRUE(cs.WithArg<0>(IsEven()));
EXPECT_FALSE(cs.WithArg<0>(Not(IsEven())));
cs.RETURN(10);
cs = WAIT_FOR_CALL(mock_object, Mock1(Ge(0)));
EXPECT_TRUE(cs.WithArg<0>(Not(IsEven())));
EXPECT_FALSE(cs.WithArg<0>(IsEven()));
cs.RETURN(10);
SATISFY();
}
};
// Of course, the EXPECT_CALL could come after the WATCH_CALL and
// therefore have a higher priority, and thus do the filtering, but this way
// tests the coro more.
EXPECT_CALL(mock_object, Mock1(-1)).Times(3).WillRepeatedly(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(mock_object.Mock1(i), 10);
if (i % 7 == 0 || i % 5 == 0) {
EXPECT_EQ(mock_object.Mock1(-1), -1);
}
}
}
TEST(InteriorFilteringTest, ComplexWaitingPrioET) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(ComplexWaitingPrioET) {
while (true) {
// Pass on negative args
auto cs = WAIT_FOR_CALL(mock_object, Mock1(Ge(0)));
EXPECT_THAT(cs.GetArg<0>(), IsEven());
cs.RETURN(10);
cs = WAIT_FOR_CALL(mock_object, Mock1(Ge(0)));
EXPECT_THAT(cs.GetArg<0>(), Not(IsEven()));
EXPECT_THAT(cs.GetArg<0>() + 1,
IsEven()); // show that we have the underlying int
cs.RETURN(10);
SATISFY();
}
};
EXPECT_CALL(mock_object, Mock1(-5)).Times(3).WillRepeatedly(Return(-2));
coro.WATCH_CALL(mock_object, Mock1);
for (int i = 0; i < 10; i++) {
EXPECT_EQ(mock_object.Mock1(i), 10);
if (i % 7 == 0 || i % 5 == 0) {
EXPECT_EQ(mock_object.Mock1(-5), -2);
}
}
}
TEST(InteriorFilteringTest, TwoMethodWaiting) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethodWaiting) {
auto cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
};
EXPECT_CALL(mock_object2, Mock1).Times(2).WillRepeatedly(Return(-2));
EXPECT_CALL(mock_object2, Mock2).Times(2).WillRepeatedly(Return(-3));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object2, Mock1);
coro.WATCH_CALL(mock_object2, Mock2);
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
// If we want to make further calls here,
// we will need the coro to RETIRE() to avoid oversaturation,
// see test case TwoMethodWaitingAndRetire
}
TEST(InteriorFilteringTest, TwoMethodWaitingAndRetire) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethodWaitingAndRetire) {
auto cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
// We believe we're done: retire to drop all subsequent calls
RETIRE();
};
EXPECT_CALL(mock_object2, Mock1).Times(3).WillRepeatedly(Return(-2));
EXPECT_CALL(mock_object2, Mock2).Times(3).WillRepeatedly(Return(-3));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object2, Mock1);
coro.WATCH_CALL(mock_object2, Mock2);
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
// These actually reach the retired coroutine
EXPECT_EQ(mock_object2.Mock1(502), -2);
EXPECT_EQ(mock_object2.Mock2(502, 602), -3);
}

View File

@ -0,0 +1,88 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) const = 0;
virtual int Mock2(int i, int j) const = 0;
virtual int Mock3(int i) const = 0;
virtual int Mock4(int i) const = 0;
virtual int Mock4(int i) = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (const, override));
MOCK_METHOD(int, Mock2, (int i, int j), (const, override));
MOCK_METHOD(int, Mock3, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (override));
};
//////////////////////////////////////////////
// The actual tests
TEST(LambdaTest, Lambda) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(Lambda) {
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock1(201)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(_)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(200)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, 401)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, 400)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, _)).RETURN(30));
};
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(LambdaTest, WatchInside) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE() // Test the no name given case
{
WATCH_CALL(mock_object, Mock1);
WATCH_CALL(mock_object, Mock2);
auto cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock1(201)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(_)));
EXPECT_FALSE(cg.IS_CALL(mock_object2, Mock1(200)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(_)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL();
EXPECT_FALSE(cg.IS_CALL(mock_object, Mock2(_, 401)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, _)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(_, 400)));
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, _)).RETURN(30));
};
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}

View File

@ -0,0 +1,111 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
struct ResultObjectDestructCounter {
~ResultObjectDestructCounter() { count++; }
static int count;
};
int ResultObjectDestructCounter::count = 0;
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1() const = 0;
};
class ExampleClass {
public:
ResultObjectDestructCounter Example1() { return ResultObjectDestructCounter(); }
};
////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (), (const, override));
};
//////////////////////////////////////////////
// The actual tests
TEST(ResultLifetimeTest, ReturnOverlapCase1) {
StrictMock<MockClass> mock_object;
ExampleClass example;
COTEST_CLEANUP();
ResultObjectDestructCounter::count = 0;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
auto d2 = LAUNCH(example.Example1());
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_TRUE(e2.IS_RESULT(d2));
// Both launch sessions (d and d2) are still in scope and so the
// returned objects should not have been destructed.
EXPECT_EQ(ResultObjectDestructCounter::count, 0);
};
}
TEST(ResultLifetimeTest, ReturnOverlapCase2) {
StrictMock<MockClass> mock_object;
ExampleClass example;
COTEST_CLEANUP();
ResultObjectDestructCounter::count = 0;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
auto d2 = LAUNCH(example.Example1());
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d2));
// Both launch sessions (d and d2) are still in scope and so the
// returned objects should not have been destructed.
EXPECT_EQ(ResultObjectDestructCounter::count, 0);
};
}
TEST(ResultLifetimeTest, ReturnOverlapCase3) {
StrictMock<MockClass> mock_object;
ExampleClass example;
COTEST_CLEANUP();
ResultObjectDestructCounter::count = 0;
{
auto coro = COROUTINE() {
{
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
}
auto d2 = LAUNCH(example.Example1());
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d2));
// Check that at least one launch session (d2) has not had its
// return object destructed. Note the _LE condition - destruction
// is not required by the implementation.
EXPECT_LE(ResultObjectDestructCounter::count, 1);
};
}
}

View File

@ -0,0 +1,361 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int x) = 0;
virtual int Mock2(int x) = 0;
};
class ExampleClass {
public:
ExampleClass(ClassToMock *dep_) : dep(dep_) {}
int Example1(int a) { return dep->Mock1(a + 1) * 2; }
int Example1a(int a) { return dep->Mock1(a - 1) * 5; }
int ExampleNoMock() { return 33; }
int Example2(int a) { return dep->Mock2(a + 2) * 3; }
int Example1And2(int a) {
int b = dep->Mock1(a);
return dep->Mock2(a + 1) + b * 2;
}
void Example1Void(int a) { (void)dep->Mock1(a + 11); }
private:
ClassToMock *const dep;
};
////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int x), (override));
MOCK_METHOD(int, Mock2, (int x), (override));
};
//////////////////////////////////////////////
// The actual tests
TEST(LaunchWithMockTest, Simple) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL(); // Needs to be inside coro body so it executes before we
// start driving MUT calls
auto d = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT().IS_CALL(mock_object, Mock1(5));
EXPECT_TRUE(e);
EXPECT_TRUE(e.From(d));
EXPECT_FALSE(e.FromMain());
e.RETURN(1000);
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d));
EXPECT_EQ(e2(d), 2000);
};
}
TEST(LaunchWithMockTest, SimpleVoidReturn) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL(); // Needs to be inside coro body so it executes before we
// start driving MUT calls
auto d = LAUNCH(example.Example1Void(4));
auto e = NEXT_EVENT().IS_CALL(mock_object, Mock1);
EXPECT_TRUE(e.IS_CALL(mock_object, Mock1(15)));
EXPECT_TRUE(e.From(d));
e.RETURN(1000);
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d));
};
}
TEST(LaunchWithMockTest, Dual) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL(); // Needs to be inside coro body so it executes before we
// start driving MUT calls
auto d1 = LAUNCH(example.Example1(4));
auto e3 = NEXT_EVENT().IS_CALL(mock_object, Mock1(5));
EXPECT_TRUE(e3); // from d
e3.ACCEPT(); // required to avoid deadlocking on GMock's mutex
EXPECT_TRUE(e3.From(d1));
auto d2 = LAUNCH(example.Example2(3));
auto e2 = NEXT_EVENT().IS_CALL(mock_object, Mock2(5));
EXPECT_TRUE(e2);
EXPECT_TRUE(e2.From(d2));
EXPECT_FALSE(e2.From(d1));
EXPECT_TRUE(e2.IS_CALL());
e2.ACCEPT(); // required to avoid deadlocking on GMock's mutex
e3.RETURN(1000);
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d1));
EXPECT_EQ(e(d1), 2000);
e2.RETURN(2000);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d2));
EXPECT_EQ(e(d2), 6000);
};
}
TEST(LaunchWithMockTest, DualNestMock) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL(); // Needs to be inside coro body so it executes before we
// start driving MUT calls
auto d = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT().IS_CALL(mock_object, Mock1(5));
EXPECT_TRUE(e.From(d));
e.ACCEPT(); // required to avoid deadlocking on GMock's mutex
auto d2 = LAUNCH(example.Example1a(3));
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_CALL().From(d2));
e2.ACCEPT(); // required to avoid deadlocking on GMock's mutex
EXPECT_TRUE(e2);
e2.IS_CALL(mock_object, Mock1(2)).RETURN(2000);
e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e2(d2), 10000);
e.RETURN(1000);
auto e3 = NEXT_EVENT();
EXPECT_TRUE(e3.IS_RESULT(d));
EXPECT_EQ(e3(d), 2000);
};
}
TEST(LaunchWithMockTest, DualWaitFrom) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL(); // Needs to be inside coro body so it executes before we
// start driving MUT calls
auto d = LAUNCH(example.Example1(4));
auto e4 = WAIT_FOR_CALL_FROM(d).IS_CALL(mock_object, Mock1(5));
EXPECT_TRUE(e4);
EXPECT_TRUE(e4.From(d));
auto d2 = LAUNCH(example.Example1a(3));
auto e2 = WAIT_FOR_CALL_FROM(mock_object, Mock1(2), d2);
EXPECT_TRUE(e2.From(d2));
auto d3 = LAUNCH(example.Example1a(9));
auto e3 = WAIT_FOR_CALL_FROM(mock_object, d3).IS_CALL(mock_object, Mock1(8));
EXPECT_TRUE(e3);
EXPECT_TRUE(e3.From(d3));
e4.RETURN(1000);
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 2000);
e2.RETURN(2000);
auto er2 = NEXT_EVENT();
EXPECT_TRUE(er2.IS_RESULT(d2));
EXPECT_EQ(er2(d2), 10000);
e3.RETURN(1001);
auto er3 = NEXT_EVENT();
EXPECT_TRUE(er3.IS_RESULT(d3));
EXPECT_EQ(er3(d3), 5005);
};
}
TEST(LaunchWithMockTest, EffectOfUnseenMockBase) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL();
// This seems like a strange place to add an expectation but
// we need to add it after the WATCH_CALL() because we want it
// to have a higher priority, and yet it must be added before the
// coroutine runs, which is immediately after its declaration.
// See DelayStartExample for another approach.
EXPECT_CALL(mock_object, Mock1).WillRepeatedly(Return(99));
auto d = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 198);
};
}
TEST(LaunchWithMockTest, EffectOfUnseenMock1) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL();
EXPECT_CALL(mock_object, Mock1).WillRepeatedly(Return(99));
auto d1 = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d1));
EXPECT_EQ(e(d1), 198);
auto d2 = LAUNCH(example.ExampleNoMock());
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d2));
EXPECT_EQ(e(d2), 33);
};
}
TEST(LaunchWithMockTest, EffectOfUnseenMock2) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL();
EXPECT_CALL(mock_object, Mock1).WillRepeatedly(Return(99));
auto d1 = LAUNCH(example.Example1And2(4));
auto e3 = NEXT_EVENT().IS_CALL(mock_object, Mock2(5));
EXPECT_TRUE(e3);
EXPECT_TRUE(e3.From(d1));
e3.ACCEPT();
auto d2 = LAUNCH(example.ExampleNoMock());
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e2(d2), 33);
e3.RETURN(1000);
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d1));
EXPECT_EQ(e(d1), 1000 + 99 * 2);
};
}
TEST(LaunchWithMockTest, NextEvent1) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL();
auto d1 = LAUNCH(example.ExampleNoMock()); // no mock call
// d1/EM3() makes no mock call, so we expect it to return immediately,
// and so we expect to collect its return before dealing with d2/EM4()
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d1));
EXPECT_EQ(e(d1), 33);
auto d2 = LAUNCH(example.Example2(6)); // ->Mockmethod2()
auto e2 = NEXT_EVENT().IS_CALL(mock_object, Mock2(8));
EXPECT_TRUE(e2);
EXPECT_TRUE(e2.From(d2));
e2.RETURN(1000);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d2));
EXPECT_EQ(e(d2), 3000);
};
}
TEST(LaunchWithMockTest, NextEvent2) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto coro = COROUTINE() {
WATCH_CALL();
auto d1 = LAUNCH(example.Example1(4)); // ->Mock1()
auto e3 = NEXT_EVENT().IS_CALL(mock_object, Mock1);
EXPECT_TRUE(e3);
EXPECT_TRUE(e3.From(d1));
e3.ACCEPT();
auto d2 = LAUNCH(example.Example2(6)); // ->Mock2()
auto e2 = NEXT_EVENT().IS_CALL(mock_object, Mock2(8));
EXPECT_TRUE(e2);
EXPECT_TRUE(e2.From(d2));
e2.ACCEPT();
e3.RETURN(99);
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d1));
EXPECT_EQ(e(d1), 99 * 2);
e2.RETURN(1000);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d2));
EXPECT_EQ(e(d2), 3000);
};
}
TEST(LaunchWithMockTest, DelayStartExample) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
// This test serves as an example of an alternate way of adding
// expectations/watches at a higher priority than the coroutine.
// Declare a MockFunction to wait for.
MockFunction<void()> delay_start;
auto coro = COROUTINE() {
// Wait for it and immediately return
WAIT_FOR_CALL(delay_start).RETURN();
auto d = LAUNCH(example.Example1(4));
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 198);
};
coro.WATCH_CALL();
EXPECT_CALL(mock_object, Mock1).WillRepeatedly(Return(99));
// Only call it once all the expectations/watches are added.
delay_start.Call();
}

View File

@ -0,0 +1,351 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int MockA(int x) = 0;
virtual int MockB(int x) = 0;
virtual int MockC(int x) = 0;
virtual int MockD(int x) = 0;
};
class ExampleClass {
public:
ExampleClass(ClassToMock *dep_) : dep(dep_) {}
int A(int x) { return dep->MockA(x); }
int B(int x) { return dep->MockB(x); }
int C(int x) { return dep->MockC(x); }
int D(int x) { return dep->MockD(x); }
private:
ClassToMock *const dep;
};
////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, MockA, (int x), (override));
MOCK_METHOD(int, MockB, (int x), (override));
MOCK_METHOD(int, MockC, (int x), (override));
MOCK_METHOD(int, MockD, (int x), (override));
};
//////////////////////////////////////////////
// The actual tests
/*
* In this example, we demonstate interleaved coroutines that share
* mock calls made by launch sessions.
*/
TEST(LaunchMultiCoroTest, DualInSeqDrop) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
// Notice that in these tests, the observing coroutine (that
// waits for mock calls) and the instigator (that launches calls)
// comes last. This ensures that the observer is not destructed
// before the instigator completes.
auto observer_coro = COROUTINE(Observer) {
WATCH_CALL(mock_object, MockA); // lower prio
// We see this because instigator_coro drops it
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_CALL(mock_object, MockA(1)).RETURN(9));
// We see this because higher prio
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_CALL(mock_object, MockB(2)));
e.DROP();
};
auto instigator_coro = COROUTINE(Instigator) {
WATCH_CALL();
observer_coro.WATCH_CALL(mock_object, MockB); // higher prio
auto da = LAUNCH(example.A(1));
auto ea = NEXT_EVENT();
EXPECT_TRUE(ea.IS_CALL(mock_object, MockA(1)).From(da));
ea.DROP(); // required to avoid deadlocking on GMock's mutex
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(da));
EXPECT_EQ(e(da), 9);
auto db = LAUNCH(example.B(2));
auto eb = NEXT_EVENT();
EXPECT_TRUE(eb.IS_CALL(mock_object, MockB(2)).From(db).RETURN(2000));
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(db));
EXPECT_EQ(e(db), 2000);
};
}
/*
* Here we attempt to reverse the two mock calls using delayed return
* but we can only delay RETURN(), not DROP(), so we can't demonstrate
* a sheared call sequence as seen by watcher.
*/
TEST(LaunchMultiCoroTest, DualInSeqAccept) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
auto observer_coro = COROUTINE(Observer) {
// We don't get this because instigator_coro cannot decide to drop
// MockA based on argument passed to MockB
// auto ea = NEXT_EVENT();
// EXPECT_TRUE( ea.IS_CALL(mock_object, MockA(1)) );
// ea.RETURN(9);
// We see this because higher prio
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_CALL(mock_object, MockB(2)));
e.DROP();
};
auto instigator_coro = COROUTINE(Instigator) {
observer_coro.WATCH_CALL(mock_object, MockA); // lower prio
WATCH_CALL();
observer_coro.WATCH_CALL(mock_object, MockB); // higher prio
auto da = LAUNCH(example.A(1));
auto ea = NEXT_EVENT().IS_CALL(mock_object, MockA(1));
EXPECT_TRUE(ea.From(da));
ea.ACCEPT(); // required to avoid deadlocking on GMock's mutex
auto db = LAUNCH(example.B(2));
auto eb = NEXT_EVENT().IS_CALL(mock_object, MockB(2));
EXPECT_TRUE(eb.From(db));
int x = eb.GetArg<0>();
eb.RETURN(2000);
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(db));
EXPECT_EQ(e(db), 2000);
ea.RETURN(x);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(da));
EXPECT_EQ(e(da), 2);
};
}
class ExampleClassAlternative {
public:
ExampleClassAlternative(ClassToMock *dep_) : dep(dep_) {}
int A(int x) { return dep->MockA(x); }
int B(int x) {
int y = dep->MockB(x);
y += dep->MockC(x);
return y;
}
int C(int x) { return dep->MockC(x); }
int D(int x) { return dep->MockD(x); }
private:
ClassToMock *const dep;
};
TEST(LaunchMultiCoroTest, FollowOn) {
StrictMock<MockClass> mock_object;
ExampleClassAlternative example(&mock_object);
auto observer_coro = COROUTINE(Observer) {
WATCH_CALL(mock_object, MockB); // lower prio
// We see this because higher prio
auto e = NEXT_EVENT();
auto e2 = e.IS_CALL(mock_object, MockB(2));
EXPECT_TRUE(e2.RETURN(1000));
};
auto instigator_coro = COROUTINE(Instigator) {
WATCH_CALL();
auto db = LAUNCH(example.B(2));
auto eb = NEXT_EVENT();
EXPECT_TRUE(eb.IS_CALL(mock_object, MockB(2)).From(db));
eb.DROP();
eb = NEXT_EVENT();
EXPECT_TRUE(eb.IS_CALL(mock_object, MockC(2)).From(db).RETURN(20));
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(db));
EXPECT_EQ(e(db), 1020);
};
}
TEST(LaunchMultiCoroTest, Arbitrary) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
StrictMock<MockClass> mock_object2;
ExampleClass example2(&mock_object2);
::testing::LaunchHandle<int> *pdb = nullptr;
auto observer_coro = COROUTINE(Observer) {
WATCH_CALL(mock_object, MockC);
WATCH_CALL(mock_object2, MockA);
WATCH_CALL(mock_object, MockB);
auto ea = WAIT_FOR_CALL(mock_object2, MockA); // due to Instigator's da
ea.RETURN(567);
auto eb = NEXT_EVENT().IS_CALL(mock_object, MockB(2)); // due to Instigator's db
EXPECT_TRUE(eb.From(*pdb));
eb.ACCEPT();
auto dc = LAUNCH(example.C(7));
auto e = NEXT_EVENT();
auto cc = e.IS_CALL(mock_object, MockC);
e.ACCEPT();
eb.RETURN(123);
// No need for NEXT_EVENT since we're returning
cc.RETURN(99);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(dc));
};
auto instigator_coro = COROUTINE(Instigator) {
auto da = LAUNCH(example2.A(0)); // Observer will handle MockA()
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(da));
EXPECT_EQ(e(da), 567);
auto db = LAUNCH(example.B(2)); // Observer will handle MockB()
pdb = &db;
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(db));
EXPECT_EQ(e(db), 123);
};
}
TEST(LaunchMultiCoroTest, ArbitraryEx) {
StrictMock<MockClass> mock_object;
ExampleClassAlternative example(&mock_object);
StrictMock<MockClass> mock_object2;
ExampleClass example2(&mock_object2);
::testing::LaunchHandle<int> *pdb = nullptr;
auto observer_coro = COROUTINE(Observer) {
auto ea = WAIT_FOR_CALL(mock_object2, MockA); // due to Instigator's da
ea.RETURN(567);
auto eb = NEXT_EVENT().IS_CALL(mock_object, MockB(2)); // due to Instigator's db
EXPECT_TRUE(eb.From(*pdb));
eb.ACCEPT();
auto dc = LAUNCH(example.C(7));
auto e = NEXT_EVENT();
auto cc = e.IS_CALL(mock_object, MockC).From(dc);
e.ACCEPT();
eb.RETURN(123);
auto e3 = NEXT_EVENT();
EXPECT_TRUE(e3.From(*pdb).ACCEPT());
cc.RETURN(99);
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(dc));
e3.IS_CALL(mock_object, MockC).RETURN(23);
};
observer_coro.WATCH_CALL(mock_object, MockC);
observer_coro.WATCH_CALL(mock_object2, MockA);
observer_coro.WATCH_CALL(mock_object, MockB);
auto instigator_coro = COROUTINE(Instigator) {
auto da = LAUNCH(example2.A(0)); // Observer will handle MockA()
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(da));
EXPECT_EQ(e(da), 567);
auto db = LAUNCH(example.B(2)); // Observer will handle MockB()
pdb = &db;
e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(db));
EXPECT_EQ(e(db), 146);
};
}
TEST(LaunchMultiCoroTest, ObserverQueue) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
EXPECT_CALL(mock_object, MockB).WillOnce(Return(102));
auto observer_coro = COROUTINE(Observer) {
WATCH_CALL(mock_object);
WAIT_FOR_CALL(mock_object, MockA(0)).RETURN(10);
auto ea1 = WAIT_FOR_CALL(mock_object, MockA(1));
auto dc = LAUNCH(example.C(3));
auto ec = WAIT_FOR_CALL(mock_object, MockC(3));
ea1.RETURN(11);
// Note: this is a rare case in which we require more than one
// consecutive mock return. The above return permits MockA() to
// return, launch session da2 to return and coro Instigator to exit.
// Since there are no launch sessions in main that might cause mock
// calls, we reach the RAII destructors for the test coroutines.
// These provide an extra iteration to the coro if it has not yet
// exited. A NEXT_EVENT() here would now run, but would not find
// any events waiting. It would request resumption of main hoping
// for more mock calls but destructors would complete leaving
// Observer unsatisfied (didn't exit or SATISFY()) and dc and ec
// uncompleted.
// auto e = NEXT_EVENT();
ec.RETURN(13);
EXPECT_EQ(WAIT_FOR_RESULT()(dc), 13);
};
auto instigator_coro = COROUTINE(Instigator) {
auto da = LAUNCH(example.A(0));
EXPECT_EQ(NEXT_EVENT().IS_RESULT()(da), 10);
auto db = LAUNCH(example.B(2));
EXPECT_EQ(NEXT_EVENT().IS_RESULT()(db), 102);
auto da2 = LAUNCH(example.A(1));
EXPECT_EQ(NEXT_EVENT().IS_RESULT()(da2), 11);
};
}

View File

@ -0,0 +1,305 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1() const = 0;
};
class ExampleClass {
public:
int Example1() { return 6; }
int Example2(int i) { return i * 3; }
void Example3() {}
int m_i = 99;
int& Example4() { return m_i; }
int operator++() { return 7; }
int Example6(int i, int j) { return i * 3 - j; }
};
////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (), (const, override));
};
//////////////////////////////////////////////
// The actual tests
TEST(LaunchTest, Simple) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example2(4));
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 12);
};
}
TEST(LaunchTest, VoidReturn1) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example3());
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
};
}
TEST(LaunchTest, VoidReturn2) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example3());
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
e(d); // this is OK but evaluates to void
};
}
TEST(LaunchTest, RefReturn) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example4());
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 99);
};
}
TEST(LaunchTest, NestedEasy) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 6);
// Easy to support nesting between return value extraction and cleanup
// (but useful: this is when by-reference return objects are valid).
auto d2 = LAUNCH(example.Example2(5));
auto e2 = NEXT_EVENT();
EXPECT_FALSE(e2.IS_CALL());
EXPECT_FALSE(e2.IS_CALL(mock_object));
EXPECT_FALSE(e2.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e2.IS_RESULT());
EXPECT_FALSE(e2.IS_RESULT(d));
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e2(d2), 15);
};
}
TEST(LaunchTest, NestedHard) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
auto d2 = LAUNCH(example.Example2(5));
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 6);
auto e2 = NEXT_EVENT();
EXPECT_FALSE(e2.IS_CALL());
EXPECT_FALSE(e2.IS_CALL(mock_object));
EXPECT_FALSE(e2.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e2.IS_RESULT());
EXPECT_FALSE(e2.IS_RESULT(d));
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e2(d2), 15);
};
}
TEST(LaunchTest, Operator) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(++example);
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 7);
};
}
TEST(LaunchTest, Exit1) {
GTEST_SKIP() << "Not allowed: exiting with a launch session that may not "
"have completed";
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() { auto d = LAUNCH(example.Example2(4)); };
}
TEST(LaunchTest, Exit2) {
GTEST_SKIP() << "Not allowed: exiting with an incompleted event session";
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example2(4));
auto e = NEXT_EVENT();
};
}
TEST(LaunchTest, ReturnOverlapCase1) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
auto d2 = LAUNCH(example.Example2(5));
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e(d), 6);
EXPECT_EQ(e2(d2), 15);
};
}
TEST(LaunchTest, ReturnOverlapCase2) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example1());
auto e = NEXT_EVENT();
EXPECT_TRUE(e.IS_RESULT(d));
auto d2 = LAUNCH(example.Example2(5));
auto e2 = NEXT_EVENT();
EXPECT_TRUE(e2.IS_RESULT(d2));
EXPECT_EQ(e(d), 6);
EXPECT_EQ(e2(d2), 15);
};
}
TEST(LaunchTest, SimpleIR) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example2(4));
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
// Demonstrate use of the return of IS_RESULT
EXPECT_EQ(e.IS_RESULT()(d), 12);
};
}
TEST(LaunchTest, FromMain) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodName) {
auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_TRUE(cs.FromMain());
cs.RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1);
EXPECT_EQ(mock_object.Mock1(), 10);
}
TEST(LaunchTest, CommaInExpr) {
StrictMock<MockClass> mock_object;
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example6(4, 44));
auto e = NEXT_EVENT();
EXPECT_FALSE(e.IS_CALL());
EXPECT_FALSE(e.IS_CALL(mock_object));
EXPECT_FALSE(e.IS_CALL(mock_object, Mock1));
EXPECT_TRUE(e.IS_RESULT());
EXPECT_TRUE(e.IS_RESULT(d));
EXPECT_EQ(e(d), 12 - 44);
};
}
TEST(LaunchTest, ShortForm) {
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example6(4, 44));
auto e = WAIT_FOR_RESULT();
EXPECT_EQ(e(d), 12 - 44);
};
}
TEST(LaunchTest, ShorterForm) {
ExampleClass example;
auto coro = COROUTINE() { EXPECT_EQ(WAIT_FOR_RESULT()(LAUNCH(example.Example6(4, 44))), 12 - 44); };
}
TEST(LaunchTest, ShortForm2) {
ExampleClass example;
auto coro = COROUTINE() {
auto d = LAUNCH(example.Example6(4, 44));
EXPECT_EQ(NEXT_EVENT()(d), 12 - 44);
};
}
COTEST(LaunchTest, VeryShortForm) { EXPECT_EQ(NEXT_EVENT()(LAUNCH(ExampleClass().Example6(4, 44))), 12 - 44); }

View File

@ -0,0 +1,64 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using ::testing::MockFunction;
//////////////////////////////////////////////
// The actual tests
TEST(MockFunctionTest, RunsCallbackWithBarArgument) {
// 1. Create a mock object.
MockFunction<int(string)> mock_function;
// 2. Set expectations on Call() method.
auto coro = COROUTINE() {
auto e = NEXT_EVENT();
auto e2 = e.IS_CALL(mock_function, Call("bar"));
e2.RETURN(1);
WAIT_FOR_CALL(mock_function, Call("foo")).RETURN(2);
WAIT_FOR_CALL(mock_function, Call("Scooby")).RETURN(3);
WAIT_FOR_CALL(mock_function, Call("Doo")).RETURN(4);
};
coro.WATCH_CALL(mock_function);
// 3. Exercise code that uses std::function.
EXPECT_EQ(mock_function.Call("bar"), 1);
EXPECT_EQ(mock_function.AsStdFunction()("foo"), 2);
std::function<int(string)> mf2 = mock_function.AsStdFunction();
EXPECT_EQ(mf2("Scooby"), 3);
EXPECT_EQ(mf2("Doo"), 4);
}
TEST(MockFunctionTest, MockFunc) {
MockFunction<int(string)> mock_function;
auto coro = COROUTINE() {
WATCH_CALL(mock_function);
auto d = LAUNCH(mock_function.Call("testing"));
WAIT_FOR_CALL(mock_function, Call("testing")).RETURN(4);
auto e = WAIT_FOR_RESULT();
EXPECT_EQ(e(d), 4);
};
}
TEST(MockFunctionTest, Minimal) {
// Demonstrates use of GMock's MockFunction as a "semaphore" between
// launch coroutine and test coroutine.
MockFunction<void()> mock_function;
auto coro = COROUTINE() {
WATCH_CALL(mock_function);
auto l = LAUNCH(mock_function.Call()); // Keep launch session in scope
WAIT_FOR_CALL().RETURN(); // return type is void so signature not required
WAIT_FOR_RESULT();
};
}
COTEST(MockFunctionTest, HyperMinimal) {
WATCH_CALL();
LAUNCH(MockFunction<void()>().Call()), // Temporary lasts to the semicolon.
WAIT_FOR_CALL().RETURN(), WAIT_FOR_RESULT();
}

View File

@ -0,0 +1,151 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class MutexInterface {
public:
virtual ~MutexInterface() {}
virtual void lock() = 0;
virtual void unlock() = 0;
};
class ExampleClass {
public:
ExampleClass(MutexInterface *mutex_) : mutex(mutex_) {}
/* The problem with this class:
* We know that Example1() is always called before Example2(), so
* we only need to test with that scenario. The implementation anticipates
* the "medium" difficulty case, in which the methods overlap and
* Example2() has started to run and then been blocked on the
* mutex by Example1(), by placing the var_x increment
* in Example2() at the end, apparently forcing the correct
* sequence of events. But this is wrong, and the "hard" test case
* discovers the problem.
*/
int Example1(int a) {
var_x += a; // unsafe: left outside of mutex
mutex->lock();
var_y += a;
mutex->unlock();
return var_x - var_y;
}
int Example2(int a) {
// Note: if the var_x += a; is moved to here, the medium case fails.
mutex->lock();
var_y += a;
mutex->unlock();
var_x += a; // unsafe: left outside of mutex
return var_x - var_y;
}
private:
int var_x = 0;
int var_y = 0;
MutexInterface *const mutex;
};
////////////////////////////////////////////
// Mocking assets
class MockMutex : public MutexInterface {
public:
MOCK_METHOD(void, lock, (), (override));
MOCK_METHOD(void, unlock, (), (override));
};
//////////////////////////////////////////////
// The actual tests
COTEST(MutexScenarioTest, Simple) {
StrictMock<MockMutex> mock_mutex;
ExampleClass example(&mock_mutex);
WATCH_CALL();
auto d = LAUNCH(example.Example1(22));
WAIT_FOR_CALL_FROM(mock_mutex, lock, d).RETURN();
WAIT_FOR_CALL_FROM(mock_mutex, unlock, d).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(d), 0);
}
// The difficulty levels of the tests can be understood as increasing
// levels of eagerness of the imaginary thread that runs Example2():
// - Easy: Doesn't even schedule until Example1() has finished
// - Medium: Preempts Example1() and then blocks on the mutex
// - Hard: Preempts Example1() and causes Example1() to block on the mutex
COTEST(MutexScenarioTest, Easy) {
StrictMock<MockMutex> mock_mutex;
ExampleClass example(&mock_mutex);
WATCH_CALL();
// Easy case, there is no conflict, Example1() returns before Example2()
// starts
auto l1 = LAUNCH(example.Example1(11));
WAIT_FOR_CALL_FROM(mock_mutex, lock, l1).RETURN();
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l1).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l1), 0);
// Example1() has finished, run Example2()
auto l2 = LAUNCH(example.Example1(22));
WAIT_FOR_CALL_FROM(mock_mutex, lock, l2).RETURN();
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l2).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l2), 0);
}
COTEST(MutexScenarioTest, Medium) { // NOTE: MediumFixedSeq2 is better example
StrictMock<MockMutex> mock_mutex;
ExampleClass example(&mock_mutex);
WATCH_CALL();
// Medium case, Example1() and Example2() overlap, but the lock/unlock
// sequences don't
auto l1 = LAUNCH(example.Example1(11));
auto l1_lock_call = WAIT_FOR_CALL_FROM(mock_mutex, lock, l1);
auto l2 = LAUNCH(example.Example2(22));
auto l2_lock_call = WAIT_FOR_CALL_FROM(mock_mutex, lock, l2);
l1_lock_call.RETURN(); // Example1 gets the lock
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l1).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l1), 0);
// Example1() has unlocked while Example2() is still awaiting the mutex, which
// we now unblock
l2_lock_call.RETURN();
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l2).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l2), 0);
}
COTEST(MutexScenarioTest, Hard) {
StrictMock<MockMutex> mock_mutex;
ExampleClass example(&mock_mutex);
WATCH_CALL();
// Hard case, Example1() starts first but Example2 gets lock first
auto l1 = LAUNCH(example.Example1(11));
auto l1_lock_call = WAIT_FOR_CALL_FROM(mock_mutex, lock, l1);
auto l2 = LAUNCH(example.Example2(22));
auto l2_lock_call = WAIT_FOR_CALL_FROM(mock_mutex, lock, l2);
// Permit Example2 to: take the lock, unlock and return
l2_lock_call.RETURN(); // Example2 gets the lock
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l2).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l2), 11); // Would be 0 if not for the bug
// Example2() has finished while Example1() is still awaiting the mutex, which
// we now unblock
l1_lock_call.RETURN();
WAIT_FOR_CALL_FROM(mock_mutex, unlock, l1).RETURN();
EXPECT_EQ(WAIT_FOR_RESULT()(l1), 0);
}

View File

@ -0,0 +1,170 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using ::testing::StrictMock;
////////////////////////////////////////////
// Code under test
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual void Mock1(int i) const = 0;
// rule: call to this must be followed by call to MockExtra()
virtual void Mock2(int i, int j) const = 0;
virtual void Mock3(int i) const = 0;
virtual void MockExtra() const = 0;
};
class ExampleClass {
public:
ExampleClass(ClassToMock *dep_) : dep(dep_) {}
int Example1() {
// In order
dep->Mock1(1);
dep->Mock2(2, 10);
dep->MockExtra();
dep->Mock3(3);
// More calls, in a random order but obeying rule
dep->Mock3(3);
dep->Mock1(1);
dep->Mock2(2, 10);
dep->MockExtra();
dep->Mock1(1);
dep->Mock3(3);
dep->Mock2(2, 10);
dep->MockExtra();
dep->Mock2(2, 10);
dep->MockExtra();
return 100;
}
int Example2() {
// In order
dep->Mock1(1);
dep->Mock1(2);
dep->MockExtra();
dep->Mock1(3);
// More calls, in a random order but obeying rule
dep->Mock1(3);
dep->Mock1(1);
dep->Mock1(2);
dep->MockExtra();
dep->Mock1(3);
dep->Mock1(2);
dep->MockExtra();
dep->Mock1(2);
dep->MockExtra();
dep->Mock1(3);
dep->Mock1(1);
dep->Mock1(1);
dep->Mock1(1);
dep->Mock1(3);
return 101;
}
private:
ClassToMock *const dep;
};
//////////////////////////////////////////////
// Mocking assets
class MockClass : public ClassToMock {
public:
MOCK_METHOD(void, Mock1, (int i), (const, override));
MOCK_METHOD(void, Mock2, (int i, int j), (const, override));
MOCK_METHOD(void, Mock3, (int i), (const, override));
MOCK_METHOD(void, MockExtra, (), (const, override));
};
//////////////////////////////////////////////
// The actual tests
COTEST(ServerisedTest, Example1) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
EXPECT_CALL(mock_object, Mock3(3)).WillRepeatedly(Return());
WATCH_CALL();
auto l = LAUNCH(example.Example1());
while (true) {
auto e = NEXT_EVENT();
if (auto e1 = e.IS_CALL(mock_object, Mock1)) {
// Mock1() is accepted, checked and returned
e1.ACCEPT();
EXPECT_EQ(e1.GetArg<0>(), 1);
e1.RETURN();
} else if (auto e2 = e.IS_CALL(mock_object, Mock2)) {
// Mock2() is accepted, checked and returned, but we require
// it to be followed by a call to MockExtra()
e2.ACCEPT();
EXPECT_EQ(e2.GetArg<0>(), 2);
e2.RETURN();
WAIT_FOR_CALL(mock_object, MockExtra).RETURN();
} else if (auto e3 = e.IS_CALL(mock_object, Mock3)) {
// Mock3 is dropped and the expectation deals with it
e3.DROP();
} else if (e.IS_RESULT()) {
EXPECT_EQ(e(l), 100);
// Avoid using return, for C++20 coro compatibility.
EXIT_COROUTINE();
} else {
EXPECT_TRUE(!"unexpected event");
}
}
}
COTEST(ServerisedTest, Example2) {
StrictMock<MockClass> mock_object;
ExampleClass example(&mock_object);
EXPECT_CALL(mock_object, Mock1(3)).WillRepeatedly(Return());
WATCH_CALL();
auto l = LAUNCH(example.Example2());
while (true) {
auto e = NEXT_EVENT();
if (auto e1 = e.IS_CALL(mock_object, Mock1)) {
switch (e1.GetArg<0>()) {
case 1:
// When arg is 1, return
e1.RETURN();
break;
case 2:
// When arg is 2, return and expect extra call
e1.RETURN();
WAIT_FOR_CALL(mock_object, MockExtra).RETURN();
break;
case 3:
// When arg is 3, drop and the expectation deals with it
e1.DROP();
break;
default:
EXPECT_TRUE(!"unexpected event");
break;
}
} else if (e.IS_RESULT()) {
EXPECT_EQ(e(l), 101);
// Avoid using return, for C++20 coro compatibility.
EXIT_COROUTINE();
} else {
EXPECT_TRUE(!"unexpected event");
}
}
}
// TODO example in which we build our own version of WAIT_FOR_CALL that bakes in the
// special behaviours seen in these examples (requiring extra mock call, dropping)

View File

@ -0,0 +1,365 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using namespace testing;
using coro_impl::PtrToString;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
struct MyStruct {
int i;
char c;
};
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int MockMethod1() const = 0;
virtual void MockMethod2() const = 0;
virtual int &MockMethod3() const = 0;
virtual int *MockMethod4() const = 0;
virtual unique_ptr<int> MockMethod5() const = 0;
virtual shared_ptr<int> MockMethod6() const = 0;
virtual MyStruct MockMethod7() const = 0;
virtual void MockMethod11(int a) const = 0;
virtual void MockMethod12(int &a) const = 0;
virtual void MockMethod13(int *a) const = 0;
virtual void MockMethod14(unique_ptr<int> a) const = 0;
virtual void MockMethod15(shared_ptr<int> a) const = 0;
virtual void MockMethod16(MyStruct a) const = 0;
virtual unique_ptr<int> MockMethod20(unique_ptr<int> a) const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, MockMethod1, (), (const, override));
MOCK_METHOD(void, MockMethod2, (), (const, override));
MOCK_METHOD(int &, MockMethod3, (), (const, override));
MOCK_METHOD(int *, MockMethod4, (), (const, override));
MOCK_METHOD(unique_ptr<int>, MockMethod5, (), (const, override));
MOCK_METHOD(shared_ptr<int>, MockMethod6, (), (const, override));
MOCK_METHOD(MyStruct, MockMethod7, (), (const, override));
MOCK_METHOD(void, MockMethod11, (int a), (const, override));
MOCK_METHOD(void, MockMethod12, (int &a), (const, override));
MOCK_METHOD(void, MockMethod13, (int *a), (const, override));
MOCK_METHOD(void, MockMethod14, (unique_ptr<int> a), (const, override));
MOCK_METHOD(void, MockMethod15, (shared_ptr<int> a), (const, override));
MOCK_METHOD(void, MockMethod16, (MyStruct a), (const, override));
MOCK_METHOD(unique_ptr<int>, MockMethod20, (unique_ptr<int> a), (const, override));
};
//////////////////////////////////////////////
// The actual tests
TEST(TypesTest, IntReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod1).RETURN(10); };
coro.WATCH_CALL(mock_object, MockMethod1);
EXPECT_EQ(mock_object.MockMethod1(), 10);
}
TEST(TypesTest, IntReturnWild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod1).RETURN(10); };
coro.WATCH_CALL();
EXPECT_EQ(mock_object.MockMethod1(), 10);
}
TEST(TypesTest, VoidReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL().RETURN(); };
coro.WATCH_CALL(mock_object, MockMethod2);
mock_object.MockMethod2();
}
TEST(TypesTest, VoidReturnWild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL().RETURN(); };
coro.WATCH_CALL();
mock_object.MockMethod2();
}
TEST(TypesTest, VoidReturnWildSignature) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod2).RETURN(); };
coro.WATCH_CALL();
mock_object.MockMethod2();
}
TEST(TypesTest, IntRefReturnSig) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() {
std::clog << "address i=" << PtrToString(&i) << std::endl;
WAIT_FOR_CALL(mock_object, MockMethod3).RETURN(i);
};
coro.WATCH_CALL(mock_object, MockMethod3);
int &ri = mock_object.MockMethod3();
std::clog << "address ri=" << PtrToString(&ri) << std::endl;
EXPECT_EQ(ri, 10) << "returned ref has the right value";
EXPECT_EQ(++ri, 11) << "returned ref increments successfully";
EXPECT_EQ(i, 11) << "alias effect shows we didn't make a copy";
}
TEST(TypesTest, IntRefReturnGen) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() {
std::clog << "address i=" << PtrToString(&i) << std::endl;
WAIT_FOR_CALL(mock_object, MockMethod3).RETURN(i);
};
coro.WATCH_CALL();
int &ri = mock_object.MockMethod3();
std::clog << "address ri=" << PtrToString(&ri) << std::endl;
EXPECT_EQ(ri, 10) << "returned ref has the right value";
EXPECT_EQ(++ri, 11) << "returned ref increments successfully";
EXPECT_EQ(i, 11) << "alias effect shows we didn't make a copy";
}
TEST(TypesTest, IntPtrReturn) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod4).RETURN(&i); };
coro.WATCH_CALL(mock_object, MockMethod4);
EXPECT_EQ(mock_object.MockMethod4(), &i);
}
TEST(TypesTest, IntPtrReturnWild) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod4).RETURN(&i); };
coro.WATCH_CALL();
EXPECT_EQ(mock_object.MockMethod4(), &i);
}
TEST(TypesTest, IntUniquePtrReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod5).RETURN(make_unique<int>(6)); };
coro.WATCH_CALL(mock_object, MockMethod5);
EXPECT_EQ(*mock_object.MockMethod5(), 6);
}
TEST(TypesTest, IntUniquePtrReturnWild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod5).RETURN(make_unique<int>(77)); };
coro.WATCH_CALL();
EXPECT_EQ(*mock_object.MockMethod5(), 77);
}
TEST(TypesTest, IntUniquePtrReturnWildSignature) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod5).RETURN(make_unique<int>(34)); };
coro.WATCH_CALL();
EXPECT_EQ(*mock_object.MockMethod5(), 34);
}
TEST(TypesTest, IntSharedPtrReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod6).RETURN(make_shared<int>(63)); };
coro.WATCH_CALL(mock_object, MockMethod6);
EXPECT_EQ(*mock_object.MockMethod6(), 63);
}
TEST(TypesTest, IntSharedPtrReturnWild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod6).RETURN(make_shared<int>(69)); };
coro.WATCH_CALL();
EXPECT_EQ(*mock_object.MockMethod6(), 69);
}
TEST(TypesTest, IntSharedPtrReturnWildSignature) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod6).RETURN(make_shared<int>(3)); };
coro.WATCH_CALL();
EXPECT_EQ(*mock_object.MockMethod6(), 3);
}
TEST(TypesTest, StructReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod7).RETURN(MyStruct{34, 'b'}); };
coro.WATCH_CALL(mock_object, MockMethod7);
auto s = mock_object.MockMethod7();
EXPECT_EQ(s.i, 34);
EXPECT_EQ(s.c, 'b');
}
TEST(TypesTest, StructReturnWild) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() { WAIT_FOR_CALL(mock_object, MockMethod7).RETURN(MyStruct{14, 'L'}); };
coro.WATCH_CALL();
auto s = mock_object.MockMethod7();
EXPECT_EQ(s.i, 14);
EXPECT_EQ(s.c, 'L');
}
TEST(TypesTest, IntArg) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(cg.IS_CALL(mock_object, MockMethod11).GetArg<0>(), 22);
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod11(22);
}
TEST(TypesTest, IntRefArg) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(cg.IS_CALL(mock_object, MockMethod12).GetArg<0>(), 10);
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod12(i);
}
TEST(TypesTest, IntPtrArg) {
StrictMock<MockClass> mock_object;
int i = 10;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(cg.IS_CALL(mock_object, MockMethod13).GetArg<0>(), &i);
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod13(&i);
}
TEST(TypesTest, UniquePtrArg) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(*(cg.IS_CALL(mock_object, MockMethod14).GetArg<0>()), 9);
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod14(make_unique<int>(9));
}
TEST(TypesTest, SharedPtrArg) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(*(cg.IS_CALL(mock_object, MockMethod15).GetArg<0>()), 5);
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod15(make_shared<int>(5));
}
TEST(TypesTest, StructArg) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_EQ(cg.IS_CALL(mock_object, MockMethod16).GetArg<0>().i, 43);
EXPECT_EQ(cg.IS_CALL(mock_object, MockMethod16).GetArg<0>().c, '$');
cg.RETURN();
};
coro.WATCH_CALL();
mock_object.MockMethod16(MyStruct{43, '$'});
}
/*
* TODO I think it just needs a eg MoveArg<>(), obvs should be
* documented that it squishes the arg. */
TEST(TypesTest, UniquePtrArgAndReturn) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE() {
auto cg = WAIT_FOR_CALL();
auto cs = cg.IS_CALL(mock_object, MockMethod20);
// Does not build
// unique_ptr<int> u = std::move( cs.GetArg<0>() );
// Builds and passes but is a bit of a cheat
unique_ptr<int> u = std::make_unique<int>(*(cs.GetArg<0>()));
++*u;
cs.RETURN(std::move(u));
};
coro.WATCH_CALL();
EXPECT_EQ(*mock_object.MockMethod20(make_unique<int>(9)), 10);
}

View File

@ -0,0 +1,116 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using ::testing::StrictMock;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) const = 0;
virtual int Mock2() const = 0;
virtual int Mock3(int x, const char *y, bool z) = 0;
virtual int Mock4(int i) const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (const, override));
MOCK_METHOD(int, Mock2, (), (const, override));
MOCK_METHOD(int, Mock3, (int x, const char *y, bool z), (override));
MOCK_METHOD(int, Mock4, (int i), (const, override));
};
using ::testing::Return;
//////////////////////////////////////////////
// The actual tests
TEST(UserInterfaceTest, MethodSE) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodSE){{auto cs = WAIT_FOR_CALL(mock_object, Mock1);
EXPECT_EQ(cs.GetArg<0>(), 100);
cs.RETURN(10);
}
{
auto cs = WAIT_FOR_CALL(mock_object, Mock3);
EXPECT_EQ(cs.GetArg<0>(), 500);
EXPECT_EQ(cs.GetArg<1>(), "abcd");
EXPECT_FALSE(cs.GetArg<2>());
cs.RETURN(30);
}
}
;
// Note that all mock methods are being sent to the same coroutine:
// Cannot use ON_CALL for these. ON_CALL sets behaviour on "uninteresting"
// calls which are ones with no expectations. But WATCH_CALL actually sets
// an expectation.
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-1));
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object, Mock3);
// This is the body of the test case
EXPECT_EQ(mock_object.Mock2(), -1);
EXPECT_EQ(mock_object.Mock1(100), 10);
EXPECT_EQ(mock_object.Mock1(300), -1);
EXPECT_EQ(mock_object.Mock3(500, "abcd", false), 30);
}
TEST(UserInterfaceTest, MethodCheckNameSE) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(MethodCheckNameSE){{auto cs = WAIT_FOR_CALL(mock_object, Mock4);
EXPECT_EQ(cs.GetArg<0>(), 100);
cs.RETURN(10);
}
{
auto cs = WAIT_FOR_CALL(mock_object, Mock3);
EXPECT_EQ(cs.GetArg<0>(), 500);
EXPECT_EQ(cs.GetArg<1>(), "abcd");
EXPECT_FALSE(cs.GetArg<2>());
cs.RETURN(30);
}
}
;
// absorb mock calls not accepted by coroutine
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-1));
EXPECT_CALL(mock_object, Mock2).WillOnce(Return(-1));
coro.WATCH_CALL(mock_object, Mock1);
coro.WATCH_CALL(mock_object, Mock2);
coro.WATCH_CALL(mock_object, Mock4);
coro.WATCH_CALL(mock_object, Mock3);
// This is the body of the test case
// WAIT_FOR_MOCK_CLASS_SE(MockClass, Mock4); in coro requires Mock4 but MUT
// still calls Mock1 which has same signature. Mock1 should be rejected causing
// MUT to return false.
EXPECT_EQ(mock_object.Mock2(), -1); // Passed by first wait due signature
EXPECT_EQ(mock_object.Mock1(200), -1); // Passed due name (test expects Mock4)
EXPECT_EQ(mock_object.Mock4(100), 10); // Accepted by first wait
EXPECT_EQ(mock_object.Mock3(500, "abcd", false),
30); // Accepted by second wait
}
TEST(UserInterfaceTest, NoMoveFromGenericCallSession) {
StrictMock<MockClass> mock_object;
auto coro = COROUTINE(NoMoveFromGenericCallSession) {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)));
COTEST_ASSERT(cg); // should still be valid now we use shared_ptr
cg.IS_CALL(mock_object, Mock1(200)).RETURN(10);
};
coro.WATCH_CALL(mock_object, Mock1);
EXPECT_EQ(mock_object.Mock1(200), 10);
}

View File

@ -0,0 +1,291 @@
#include <string>
#include "cotest/cotest.h"
#include "gtest/gtest-spi.h"
using namespace std;
using ::testing::StrictMock;
using namespace testing;
//////////////////////////////////////////////
// Mocking assets
class ClassToMock {
public:
virtual ~ClassToMock() {}
virtual int Mock1(int i) const = 0;
virtual int Mock2(int i, int j) const = 0;
virtual int Mock3(int i) const = 0;
virtual int Mock4(int i) const = 0;
virtual int Mock4(int i) = 0;
virtual void Mock5(int i) const = 0;
virtual void Mock6(int i, int j) const = 0;
};
class MockClass : public ClassToMock {
public:
MOCK_METHOD(int, Mock1, (int i), (const, override));
MOCK_METHOD(int, Mock2, (int i, int j), (const, override));
MOCK_METHOD(int, Mock3, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (const, override));
MOCK_METHOD(int, Mock4, (int i), (override));
MOCK_METHOD(void, Mock5, (int i), (const, override));
MOCK_METHOD(void, Mock6, (int i, int j), (const, override));
};
using ::testing::Return;
//////////////////////////////////////////////
// The actual tests
TEST(ExteriorWildcardTest, TwoMethodWaiting) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
// Try doing this early, to simulate a generic setup phase
EXPECT_CALL(mock_object2, Mock1).WillRepeatedly(Return(-2));
auto coro = COROUTINE(TwoMethodWaiting) {
auto cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
auto cg2 = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg2.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
};
EXPECT_CALL(mock_object2, Mock2).Times(2).WillRepeatedly(Return(-3));
coro.WATCH_CALL();
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(ExteriorWildcardTest, TwoMethodWaitingMO) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethodWaiting) {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
auto cg2 = WAIT_FOR_CALL();
EXPECT_TRUE(cg2.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
};
EXPECT_CALL(mock_object2, Mock1).Times(2).WillRepeatedly(Return(-2));
EXPECT_CALL(mock_object2, Mock2).Times(2).WillRepeatedly(Return(-3));
coro.WATCH_CALL(mock_object);
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(ExteriorWildcardTest, TwoMethodWaitingPre) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethodWaitingPre) {
auto cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
};
EXPECT_CALL(mock_object2, Mock1).Times(2).WillRepeatedly(Return(-2));
EXPECT_CALL(mock_object2, Mock2).Times(2).WillRepeatedly(Return(-3));
coro.WATCH_CALL();
EXPECT_CALL(mock_object, Mock1(1000)).WillRepeatedly(Return(-10));
EXPECT_CALL(mock_object2, Mock1(1100)).WillRepeatedly(Return(-11));
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object.Mock1(1000), -10);
EXPECT_EQ(mock_object2.Mock1(1100), -11);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(ExteriorWildcardTest, TwoMethodWaitingPreMO) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro = COROUTINE(TwoMethodWaitingPre) {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock2(200, 400)).RETURN(30));
};
EXPECT_CALL(mock_object2, Mock1).Times(2).WillRepeatedly(Return(-2));
EXPECT_CALL(mock_object2, Mock2).Times(2).WillRepeatedly(Return(-3));
coro.WATCH_CALL(mock_object);
EXPECT_CALL(mock_object, Mock1(1000)).WillRepeatedly(Return(-10));
EXPECT_CALL(mock_object2, Mock1(1100)).WillRepeatedly(Return(-11));
EXPECT_EQ(mock_object2.Mock1(500), -2);
EXPECT_EQ(mock_object2.Mock2(500, 600), -3);
EXPECT_EQ(mock_object.Mock1(200), 20);
EXPECT_EQ(mock_object.Mock1(1000), -10);
EXPECT_EQ(mock_object2.Mock1(1100), -11);
EXPECT_EQ(mock_object2.Mock1(501), -2);
EXPECT_EQ(mock_object2.Mock2(501, 601), -3);
EXPECT_EQ(mock_object.Mock2(200, 400), 30);
}
TEST(ExteriorWildcardTest, MultiPriority) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro1 = COROUTINE() {
auto cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
// Exit without RETIRE() is saturation
};
auto coro2 = COROUTINE() {
auto cg = WAIT_FOR_CALL(mock_object2);
EXPECT_TRUE(cg.IS_CALL(mock_object2, Mock2(_, 400)).With(Lt()).RETURN(30));
RETIRE();
};
coro1.WATCH_CALL();
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-10)).RetiresOnSaturation(); // #1
EXPECT_CALL(mock_object2, Mock2).WillOnce(Return(-11)); // #2
coro2.WATCH_CALL();
EXPECT_EQ(mock_object.Mock1(1000),
-10); // coro2's wait drops; expectation #1 matches and retires
EXPECT_EQ(mock_object.Mock1(200),
20); // coro2's wait drops; expectation #1 has retired; coro1
// accepts and is saturated
EXPECT_EQ(mock_object2.Mock2(200, 400), 30); // coro2 accepts and retires
EXPECT_EQ(mock_object2.Mock2(200, 400),
-11); // coro2 has retired; expectation #2 matches and is saturated
}
TEST(ExteriorWildcardTest, MultiPriorityMO) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro1 = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
// Exit without RETIRE() is saturation
};
auto coro2 = COROUTINE() {
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object2, Mock2(_, 400)).With(Lt()).RETURN(30));
RETIRE();
};
coro1.WATCH_CALL(mock_object);
EXPECT_CALL(mock_object, Mock1).WillOnce(Return(-10)).RetiresOnSaturation(); // #1
EXPECT_CALL(mock_object2, Mock2).WillOnce(Return(-11)); // #2
coro2.WATCH_CALL(mock_object2);
EXPECT_EQ(mock_object.Mock1(1000),
-10); // coro2's wait drops; expectation #1 matches and retires
EXPECT_EQ(mock_object.Mock1(200),
20); // coro2's wait drops; expectation #1 has retired; coro1
// accepts and is saturated
EXPECT_EQ(mock_object2.Mock2(200, 400), 30); // coro2 accepts and retires
EXPECT_EQ(mock_object2.Mock2(200, 400),
-11); // coro2 has retired; expectation #2 matches and is saturated
}
TEST(ExteriorWildcardTest, MockObjectAddressAlias) {
auto coro = COROUTINE() {
WAIT_FOR_CALL().RETURN();
WAIT_FOR_CALL().RETURN();
};
{
StrictMock<MockClass> mock_object;
coro.WATCH_CALL(mock_object);
mock_object.Mock5(200);
}
{
StrictMock<MockClass> mock_object2;
// Known bug with WATCH_CALL( mock object )
// This call should not make it into the coroutine
// but it does because mock_object2 is at the same address as
// the now-deleted mock_object1. Not easy to fix: consider if
// we hadn't made any calls on mock_object - then the GMock code
// that registers the mocker would not have run. If we assume
// a call, then we could maybe use GMock's registry to recover
// the relationship - then we'd also need DetachMocker() so that
// the CotestWatcher can deduce that it needs to detach from the
// mock object too.
// However, the test case to cause this is very strange and seems
// to require the coro to be outside the scope of the mock objects -
// otherwise Watchers will be discarded before here.
mock_object2.Mock6(200, 400);
}
}
TEST(ExteriorWildcardTest, StackedCoros) {
StrictMock<MockClass> mock_object;
StrictMock<MockClass> mock_object2;
auto coro1 = COROUTINE(coro1) {
WATCH_CALL(); // watch all mock calls
auto cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(1000)).RETURN(-10));
cg = WAIT_FOR_CALL(mock_object);
EXPECT_TRUE(cg.IS_CALL(mock_object, Mock1(200)).RETURN(20));
auto cs = WAIT_FOR_CALL(mock_object, Mock2);
EXPECT_TRUE(cs.IS_CALL(mock_object, Mock2(220, _)));
cs.RETURN(-11);
RETIRE();
};
auto coro2 = COROUTINE(coro2) {
WATCH_CALL(); // watch all mock calls
auto cg = WAIT_FOR_CALL(mock_object2);
EXPECT_TRUE(cg.IS_CALL(mock_object2, Mock2(_, 400)).With(Lt()).RETURN(30));
auto cs = WAIT_FOR_CALL(mock_object, Mock1(1100));
cs.RETURN(-5);
cg = WAIT_FOR_CALL();
EXPECT_TRUE(cg.IS_CALL(mock_object2, Mock2(300, 350)).With(Lt()).RETURN(33));
SATISFY();
cg = WAIT_FOR_CALL(mock_object).RETURN();
};
EXPECT_EQ(mock_object.Mock1(1000),
-10); // c2 is looking for any mock call on mo2, so drops it and c1
// is looking for any so accepts, checks, returns
EXPECT_EQ(mock_object2.Mock2(200, 400),
30); // c2 is looking for any mock call on mo2, so accepts, checks,
// returns
EXPECT_EQ(mock_object.Mock1(200),
20); // c2 is now looking for mo.MM1 with arg==1100, so drops it and c1
// is now looking for any call on mo so accepts, checks, returns
EXPECT_EQ(mock_object.Mock2(220, 400),
-11); // c2 is still looking for mo.MM1 with arg==1100, so drops it and
// c1 is now looking for mo.MM2 mo so accepts, checks, returns
EXPECT_EQ(mock_object.Mock1(1100),
-5); // c2 is still looking for mo.MM1 with arg==1100, so accepts,
// checks, returns
EXPECT_EQ(mock_object2.Mock2(300, 350),
33); // c2 is now looking for any call so accepts, checks, returns
// c1 has retired, which means it will drop any further calls (none are made
// here) c2 is left looking for any call on mo, but it's satisfied, so if the
// call never arrives, there's no error
}

View File

@ -0,0 +1,321 @@
#include <string>
#include "cotest/cotest.h"
#include "cotest/internal/cotest-integ-finder.h"
using namespace std;
using namespace testing;
/*
* Note: at the time of writing, this test does not use wildcarded watches
* and so will not engage the untyped expectation finding mechanism. So
* internal::CotestMockHandlerPool::Finder() won't be being used by the test
* harness while we're testing it.
*/
//////////////////////////////////////////////
// The actual tests
TEST(ExpectationFinderTest, NoSchemes) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
std::vector<const internal::MockHandlerScheme *> schemes;
auto coro = COROUTINE(){
// Empty coro oversaturates on any visible call
};
coro.WATCH_CALL(mockLambda);
// No schemes, so no exps. "which" is undefined in this case.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_FALSE(exp);
}
TEST(ExpectationFinderTest, EmptyScheme) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
internal::MockHandlerScheme s1;
std::vector<const internal::MockHandlerScheme *> schemes{&s1};
auto coro = COROUTINE(){
// Empty coro oversaturates on any visible call
};
coro.WATCH_CALL(mockLambda);
// No exps. "which" is undefined in this case.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_FALSE(exp);
}
TEST(ExpectationFinderTest, SimpleSchemeNo) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::MockHandlerScheme s1{e1};
std::vector<const internal::MockHandlerScheme *> schemes{&s1};
auto coro = COROUTINE() {
// One exp was created, and it should be queried
WAIT_FOR_CALL(mockLambda, Call(e1.get())).RETURN(false);
};
coro.WATCH_CALL(mockLambda);
// The exp says no. "which" is undefined in this case.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_FALSE(exp);
}
TEST(ExpectationFinderTest, SimpleSchemeYes) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::MockHandlerScheme s1{e1};
std::vector<const internal::MockHandlerScheme *> schemes{&s1};
auto coro = COROUTINE() {
// One exp was created, and it should be queried
WAIT_FOR_CALL(mockLambda, Call(e1.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_TRUE(exp);
ASSERT_EQ(which, 0);
}
TEST(ExpectationFinderTest, OneSchemeMultiExp) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::MockHandlerScheme s1{e1, e2, e3, e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e4.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e3.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e2.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_TRUE(exp);
ASSERT_EQ(which, 0);
}
TEST(ExpectationFinderTest, OneSchemeMultiExpIncing) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::MockHandlerScheme s1{e1, e2, e3, e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e4.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e3.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e2.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_TRUE(exp);
ASSERT_EQ(which, 0);
}
TEST(ExpectationFinderTest, MultiSchemeNone) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e5 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
internal::MockHandlerScheme s1{e1, e3, e5};
internal::MockHandlerScheme s2{e2};
internal::MockHandlerScheme s3{e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1, &s2, &s3};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e5.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e4.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e3.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e2.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e1.get())).RETURN(false);
};
coro.WATCH_CALL(mockLambda);
// The exps say no.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_FALSE(exp);
}
TEST(ExpectationFinderTest, MultiSchemeLate) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e5 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
internal::MockHandlerScheme s1{e1, e3, e5};
internal::MockHandlerScheme s2{e2};
internal::MockHandlerScheme s3{e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1, &s2, &s3};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e5.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e4.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e3.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e2.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e1.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_EQ(exp, e1.get());
ASSERT_EQ(which, 0);
}
TEST(ExpectationFinderTest, MultiSchemeMid) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e5 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
internal::MockHandlerScheme s1{e1, e3, e5};
internal::MockHandlerScheme s2{e2};
internal::MockHandlerScheme s3{e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1, &s2, &s3};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e5.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e4.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e3.get())).RETURN(false);
WAIT_FOR_CALL(mockLambda, Call(e2.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_EQ(exp, e2.get());
ASSERT_EQ(which, 1);
}
TEST(ExpectationFinderTest, MultiSchemeEarly) {
MockFunction<std::function<bool(internal::ExpectationBase *)>> mockLambda;
auto e1 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e2 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e3 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e4 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
auto e5 = make_shared<internal::TypedExpectation<int()>>(nullptr, nullptr, 0, "",
internal::Function<int()>::ArgumentMatcherTuple());
internal::CotestMockHandlerPool::GetOrCreateInstance()->AddExpectation(
[]() {}); // bumps the static global priority
internal::MockHandlerScheme s1{e1, e3, e5};
internal::MockHandlerScheme s2{e2};
internal::MockHandlerScheme s3{e4};
std::vector<const internal::MockHandlerScheme *> schemes{&s1, &s2, &s3};
auto coro = COROUTINE() {
// Exps queried in reverse order until one says yes
WAIT_FOR_CALL(mockLambda, Call(e5.get())).RETURN(true);
};
coro.WATCH_CALL(mockLambda);
// The exp says yes.
unsigned which;
auto exp = internal::CotestMockHandlerPool::Finder(schemes, mockLambda.AsStdFunction(), &which);
ASSERT_EQ(exp, e5.get());
ASSERT_EQ(which, 0);
}