| #include "aos/common/mutex.h" |
| |
| #include <sched.h> |
| #include <math.h> |
| #include <pthread.h> |
| #ifdef __VXWORKS__ |
| #include <taskLib.h> |
| #endif |
| |
| #include "gtest/gtest.h" |
| |
| #include "aos/common/condition.h" |
| |
| namespace aos { |
| namespace testing { |
| |
| class MutexTest : public ::testing::Test { |
| public: |
| Mutex test_mutex; |
| }; |
| |
| typedef MutexTest MutexDeathTest; |
| |
| TEST_F(MutexTest, TryLock) { |
| EXPECT_TRUE(test_mutex.TryLock()); |
| EXPECT_FALSE(test_mutex.TryLock()); |
| } |
| |
| TEST_F(MutexTest, Lock) { |
| test_mutex.Lock(); |
| EXPECT_FALSE(test_mutex.TryLock()); |
| } |
| |
| TEST_F(MutexTest, Unlock) { |
| test_mutex.Lock(); |
| EXPECT_FALSE(test_mutex.TryLock()); |
| test_mutex.Unlock(); |
| EXPECT_TRUE(test_mutex.TryLock()); |
| } |
| |
| #ifndef __VXWORKS__ |
| // Sees what happens with multiple unlocks. |
| TEST_F(MutexDeathTest, RepeatUnlock) { |
| test_mutex.Lock(); |
| test_mutex.Unlock(); |
| EXPECT_DEATH(test_mutex.Unlock(), ".*multiple unlock.*"); |
| } |
| |
| // Sees what happens if you unlock without ever locking (or unlocking) it. |
| TEST_F(MutexDeathTest, NeverLock) { |
| EXPECT_DEATH(test_mutex.Unlock(), ".*multiple unlock.*"); |
| } |
| #endif |
| |
| TEST_F(MutexTest, MutexLocker) { |
| { |
| aos::MutexLocker locker(&test_mutex); |
| EXPECT_FALSE(test_mutex.TryLock()); |
| } |
| EXPECT_TRUE(test_mutex.TryLock()); |
| } |
| TEST_F(MutexTest, MutexUnlocker) { |
| test_mutex.Lock(); |
| { |
| aos::MutexUnlocker unlocker(&test_mutex); |
| // If this fails, then something weird is going on and the next line might |
| // hang. |
| ASSERT_TRUE(test_mutex.TryLock()); |
| test_mutex.Unlock(); |
| } |
| EXPECT_TRUE(test_mutex.TryLock()); |
| } |
| |
| // A worker thread for testing the fairness of the mutex implementation. |
| class MutexFairnessWorkerThread { |
| public: |
| MutexFairnessWorkerThread(int *cycles, int index, |
| Mutex *mutex, Condition *start) |
| : cycles_(cycles), index_(index), mutex_(mutex), start_(start) {} |
| |
| static void *RunStatic(void *self_in) { |
| MutexFairnessWorkerThread *self = |
| static_cast<MutexFairnessWorkerThread *>(self_in); |
| self->Run(); |
| delete self; |
| return NULL; |
| } |
| |
| static void Reset(int cycles) { |
| cyclesRun = 0; |
| totalCycles = cycles; |
| } |
| |
| private: |
| void Run() { |
| cycles_[index_] = 0; |
| start_->Wait(); |
| while (cyclesRun < totalCycles) { |
| { |
| MutexLocker locker(mutex_); |
| ++cyclesRun; |
| } |
| ++cycles_[index_]; |
| // Otherwise the fitpc implementation tends to just relock in the same |
| // thread. |
| sched_yield(); |
| } |
| |
| #ifdef __VXWORKS__ |
| // Without this, all of the "task ... deleted ..." messages come out at |
| // once, and it looks weird and triggers an socat bug (at least for |
| // Squeeze's version 1.7.1.3-1). |
| taskDelay(index_); |
| #endif |
| } |
| |
| int *cycles_; |
| int index_; |
| Mutex *mutex_; |
| Condition *start_; |
| static int cyclesRun, totalCycles; |
| }; |
| int MutexFairnessWorkerThread::cyclesRun; |
| int MutexFairnessWorkerThread::totalCycles; |
| // Tests the fairness of the implementation. It does this by repeatedly locking |
| // and unlocking a mutex in multiple threads and then checking the standard |
| // deviation of the number of times each one locks. |
| // |
| // It is safe to do this with threads because this is the test so it can change |
| // if the implementations ever change to not support that. Fitpc logging calls |
| // are not thread-safe, but it doesn't really matter because the only logging |
| // call that would get made would be a LOG(FATAL) that would still terminate the |
| // process. |
| TEST_F(MutexTest, Fairness) { |
| static const int kThreads = 13; |
| #ifdef __VXWORKS__ |
| static const int kWarmupCycles = 1000, kRunCycles = 60000, kMaxDeviation = 20; |
| #else |
| static const int kWarmupCycles = 30000, kRunCycles = 3000000, kMaxDeviation = 10000; |
| #endif |
| |
| int cycles[kThreads]; |
| pthread_t workers[kThreads]; |
| Condition start; |
| |
| for (int repeats = 0; repeats < 2; ++repeats) { |
| MutexFairnessWorkerThread::Reset(repeats ? kRunCycles : kWarmupCycles); |
| start.Unset(); |
| for (int i = 0; i < kThreads; ++i) { |
| MutexFairnessWorkerThread *c = new MutexFairnessWorkerThread(cycles, i, |
| &test_mutex, |
| &start); |
| ASSERT_EQ(0, pthread_create(&workers[i], NULL, |
| MutexFairnessWorkerThread::RunStatic, c)); |
| } |
| start.Set(); |
| for (int i = 0; i < kThreads; ++i) { |
| ASSERT_EQ(0, pthread_join(workers[i], NULL)); |
| } |
| } |
| |
| double variance = 0; |
| int expected = kRunCycles / kThreads; |
| for (int i = 0; i < kThreads; ++i) { |
| variance += (cycles[i] - expected) * (cycles[i] - expected); |
| } |
| double deviation = sqrt(variance / kThreads); |
| printf("deviation=%f\n", deviation); |
| ASSERT_GT(deviation, 0); |
| EXPECT_LT(deviation, kMaxDeviation); |
| } |
| |
| } // namespace testing |
| } // namespace aos |