Created
May 23, 2015 00:15
-
-
Save robwiss/06ef1cc830f43a91a57b to your computer and use it in GitHub Desktop.
code that will create race condition involving turtle mock::sequence
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define BOOST_TEST_DYN_LINK 1 | |
#define BOOST_TEST_MODULE test_sequence | |
#define MOCK_THREAD_SAFE | |
#include <boost/test/unit_test.hpp> | |
#include <turtle/mock.hpp> | |
#include <thread> | |
#include <mutex> | |
#include <atomic> | |
#include <condition_variable> | |
#include <memory> | |
#include <queue> | |
#include <chrono> | |
// a dummy class with two methods | |
class A | |
{ | |
public: | |
A() {} | |
virtual ~A() {} | |
virtual void a() = 0; | |
virtual void b() = 0; | |
}; | |
MOCK_BASE_CLASS(AMock, A) | |
{ | |
MOCK_METHOD(a, 0); | |
MOCK_METHOD(b, 0); | |
}; | |
// a simple blocking queue | |
template <typename T> | |
class bqueue | |
{ | |
public: | |
virtual ~bqueue() | |
{ | |
close(); | |
} | |
void close() | |
{ | |
std::unique_lock<std::mutex> lk(m_); | |
closed_ = true; | |
cv_.notify_all(); | |
cv_.wait(lk, [&] { return poppers == 0; }); | |
} | |
void push(const T &data) | |
{ | |
std::unique_lock<std::mutex> lk(m_); | |
if (closed_) | |
{ | |
throw "pushed to a closed queue"; | |
} | |
q_.push( data ); | |
lk.unlock(); | |
cv_.notify_one(); | |
} | |
bool pop(T &data) | |
{ | |
std::unique_lock<std::mutex> lk(m_); | |
++poppers; | |
cv_.wait(lk, [&] { return !q_.empty() || closed_; }); | |
--poppers; | |
if ( closed_ ) | |
{ | |
cv_.notify_all(); | |
return false; | |
} | |
data = std::move(q_.front()); | |
q_.pop(); | |
return true; | |
} | |
private: | |
bool closed_ = false; | |
std::mutex m_; | |
std::condition_variable cv_; | |
std::queue<T> q_; | |
int poppers = 0; | |
}; | |
static bqueue<std::shared_ptr<A>> q; | |
// a thread that consumes from the queue and runs the A::a() method | |
static void thread_run() | |
{ | |
for (;;) | |
{ | |
std::shared_ptr<A> a; | |
if (!q.pop(a)) | |
{ | |
break; | |
} | |
// in reality this is a thread keeping track of timeouts so sleep to | |
// simulate a timeout occurring | |
std::this_thread::sleep_for(std::chrono::milliseconds(100)); | |
a->a(); // run a() functionality | |
} | |
} | |
// initialization for the thread to run detached | |
static std::atomic_flag thread_init_lock = ATOMIC_FLAG_INIT; | |
static std::thread *t = nullptr; | |
static void init_thread() | |
{ | |
if (!thread_init_lock.test_and_set()) | |
{ | |
t = new std::thread(thread_run); | |
t->detach(); | |
} | |
} | |
// a test containing the use of a mock::sequence to track order of operations | |
BOOST_AUTO_TEST_CASE( sequence_with_threads ) | |
{ | |
mock::sequence seq; | |
std::mutex m; | |
std::condition_variable cv; | |
bool value = false; | |
auto a_mock = std::make_shared<AMock>(); | |
// EXPECT a_mock->b() will be called and will wait for value to be set by | |
// the call to a_mock->a() that happens in the detached thread | |
MOCK_EXPECT( a_mock->b ).once().in(seq).calls( | |
[&]() { | |
std::unique_lock<std::mutex> lk(m); | |
cv.wait(lk, [&] { return value; } ); | |
} | |
); | |
// EXPECT a_mock->a() will be called, thereby setting value to true | |
MOCK_EXPECT( a_mock->a ).once().in(seq).calls( | |
[&]() { | |
std::lock_guard<std::mutex> lk(m); | |
value = true; | |
cv.notify_one(); | |
} | |
); | |
// create processing thread | |
init_thread(); | |
// put a_mock on queue to be processed | |
q.push(a_mock); | |
// call a_mock->b() to wait for a_mock->a() to happen | |
a_mock->b(); | |
// ensure the function ran | |
BOOST_CHECK( value == true ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Set TURTLE_INCLUDE as the path to turtle's include dir and compile with the thread sanitizer turned on to see the issue:
I tested with gcc 4.8.2 and gcc 4.9.2