mirror of
https://github.com/google/googletest.git
synced 2025-12-08 17:56:50 +08:00
225 lines
8.3 KiB
C++
225 lines
8.3 KiB
C++
#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
|