mirror of
https://github.com/google/googletest.git
synced 2025-12-06 16:57:00 +08:00
152 lines
4.8 KiB
C++
152 lines
4.8 KiB
C++
#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);
|
|
}
|