mirror of
https://github.com/google/googletest.git
synced 2025-12-07 17:26:53 +08:00
add coroutines source and tests to repo as a sub-project alongside googletest and googlemock
This commit is contained in:
parent
9756ee7cba
commit
615c5bc563
168
coroutines/.clang-format
Normal file
168
coroutines/.clang-format
Normal 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
149
coroutines/CMakeLists.txt
Normal 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()
|
||||
|
||||
607
coroutines/include/cotest/cotest.h
Normal file
607
coroutines/include/cotest/cotest.h
Normal 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
|
||||
99
coroutines/include/cotest/internal/cotest-coro-common.h
Normal file
99
coroutines/include/cotest/internal/cotest-coro-common.h
Normal 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
|
||||
69
coroutines/include/cotest/internal/cotest-coro-thread.h
Normal file
69
coroutines/include/cotest/internal/cotest-coro-thread.h
Normal 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
|
||||
132
coroutines/include/cotest/internal/cotest-crf-core.h
Normal file
132
coroutines/include/cotest/internal/cotest-crf-core.h
Normal 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
|
||||
76
coroutines/include/cotest/internal/cotest-crf-launch.h
Normal file
76
coroutines/include/cotest/internal/cotest-crf-launch.h
Normal 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
|
||||
97
coroutines/include/cotest/internal/cotest-crf-mock.h
Normal file
97
coroutines/include/cotest/internal/cotest-crf-mock.h
Normal 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
|
||||
218
coroutines/include/cotest/internal/cotest-crf-payloads.h
Normal file
218
coroutines/include/cotest/internal/cotest-crf-payloads.h
Normal 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
|
||||
49
coroutines/include/cotest/internal/cotest-crf-synch.h
Normal file
49
coroutines/include/cotest/internal/cotest-crf-synch.h
Normal 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
|
||||
273
coroutines/include/cotest/internal/cotest-crf-test.h
Normal file
273
coroutines/include/cotest/internal/cotest-crf-test.h
Normal 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
|
||||
66
coroutines/include/cotest/internal/cotest-integ-finder.h
Normal file
66
coroutines/include/cotest/internal/cotest-integ-finder.h
Normal 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
|
||||
445
coroutines/include/cotest/internal/cotest-integ-mock.h
Normal file
445
coroutines/include/cotest/internal/cotest-integ-mock.h
Normal 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
|
||||
48
coroutines/include/cotest/internal/cotest-util-logging.h
Normal file
48
coroutines/include/cotest/internal/cotest-util-logging.h
Normal 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
|
||||
96
coroutines/include/cotest/internal/cotest-util-types.h
Normal file
96
coroutines/include/cotest/internal/cotest-util-types.h
Normal 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
|
||||
10
coroutines/src/cotest-all.cc
Normal file
10
coroutines/src/cotest-all.cc
Normal 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"
|
||||
123
coroutines/src/cotest-coro-thread.cc
Normal file
123
coroutines/src/cotest-coro-thread.cc
Normal 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
|
||||
147
coroutines/src/cotest-crf-core.cc
Normal file
147
coroutines/src/cotest-crf-core.cc
Normal 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
|
||||
218
coroutines/src/cotest-crf-launch.cc
Normal file
218
coroutines/src/cotest-crf-launch.cc
Normal 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
|
||||
154
coroutines/src/cotest-crf-mock.cc
Normal file
154
coroutines/src/cotest-crf-mock.cc
Normal 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
|
||||
184
coroutines/src/cotest-crf-payloads.cc
Normal file
184
coroutines/src/cotest-crf-payloads.cc
Normal 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
|
||||
141
coroutines/src/cotest-crf-synch.cc
Normal file
141
coroutines/src/cotest-crf-synch.cc
Normal 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
|
||||
391
coroutines/src/cotest-crf-test.cc
Normal file
391
coroutines/src/cotest-crf-test.cc
Normal 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
|
||||
139
coroutines/src/cotest-integ-finder.cc
Normal file
139
coroutines/src/cotest-integ-finder.cc
Normal 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
|
||||
224
coroutines/src/cotest-integ-mock.cc
Normal file
224
coroutines/src/cotest-integ-mock.cc
Normal 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
63
coroutines/src/cotest.cc
Normal 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
|
||||
160
coroutines/test/coro-test-thread.cc
Normal file
160
coroutines/test/coro-test-thread.cc
Normal 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);
|
||||
}
|
||||
118
coroutines/test/cotest-action-functor.cc
Normal file
118
coroutines/test/cotest-action-functor.cc
Normal 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);
|
||||
}
|
||||
102
coroutines/test/cotest-action-macro.cc
Normal file
102
coroutines/test/cotest-action-macro.cc
Normal 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);
|
||||
}
|
||||
126
coroutines/test/cotest-action-poly.cc
Normal file
126
coroutines/test/cotest-action-poly.cc
Normal 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);
|
||||
}
|
||||
147
coroutines/test/cotest-all-in.cc
Normal file
147
coroutines/test/cotest-all-in.cc
Normal 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);
|
||||
}
|
||||
516
coroutines/test/cotest-cardinality.cc
Normal file
516
coroutines/test/cotest-cardinality.cc
Normal 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();
|
||||
}
|
||||
235
coroutines/test/cotest-ext-filter.cc
Normal file
235
coroutines/test/cotest-ext-filter.cc
Normal 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);
|
||||
}
|
||||
347
coroutines/test/cotest-int-filter.cc
Normal file
347
coroutines/test/cotest-int-filter.cc
Normal 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);
|
||||
}
|
||||
88
coroutines/test/cotest-lambda.cc
Normal file
88
coroutines/test/cotest-lambda.cc
Normal 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);
|
||||
}
|
||||
111
coroutines/test/cotest-launch-lifetime.cc
Normal file
111
coroutines/test/cotest-launch-lifetime.cc
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
361
coroutines/test/cotest-launch-mock.cc
Normal file
361
coroutines/test/cotest-launch-mock.cc
Normal 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();
|
||||
}
|
||||
351
coroutines/test/cotest-launch-multi-coro.cc
Normal file
351
coroutines/test/cotest-launch-multi-coro.cc
Normal 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);
|
||||
};
|
||||
}
|
||||
305
coroutines/test/cotest-launch.cc
Normal file
305
coroutines/test/cotest-launch.cc
Normal 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); }
|
||||
64
coroutines/test/cotest-mockfunction.cc
Normal file
64
coroutines/test/cotest-mockfunction.cc
Normal 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();
|
||||
}
|
||||
151
coroutines/test/cotest-mutex.cc
Normal file
151
coroutines/test/cotest-mutex.cc
Normal 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);
|
||||
}
|
||||
170
coroutines/test/cotest-serverised.cc
Normal file
170
coroutines/test/cotest-serverised.cc
Normal 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)
|
||||
365
coroutines/test/cotest-types.cc
Normal file
365
coroutines/test/cotest-types.cc
Normal 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);
|
||||
}
|
||||
116
coroutines/test/cotest-ui.cc
Normal file
116
coroutines/test/cotest-ui.cc
Normal 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);
|
||||
}
|
||||
291
coroutines/test/cotest-wild.cc
Normal file
291
coroutines/test/cotest-wild.cc
Normal 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
|
||||
}
|
||||
321
coroutines/test/exp-finder-test.cc
Normal file
321
coroutines/test/exp-finder-test.cc
Normal 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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user