Removed Common
Change-Id: I01ea8f07220375c2ad9bc0092281d4f27c642303
diff --git a/aos/mutex/mutex_test.cc b/aos/mutex/mutex_test.cc
new file mode 100644
index 0000000..8e3daac
--- /dev/null
+++ b/aos/mutex/mutex_test.cc
@@ -0,0 +1,373 @@
+#include "aos/mutex/mutex.h"
+
+#include <math.h>
+#include <pthread.h>
+#include <sched.h>
+
+#include <chrono>
+#include <thread>
+
+#include "gtest/gtest.h"
+
+#include "aos/die.h"
+#include "aos/time/time.h"
+#include "aos/util/death_test_log_implementation.h"
+#include "aos/util/thread.h"
+#include "aos/linux_code/ipc_lib/aos_sync.h"
+#include "aos/linux_code/ipc_lib/core_lib.h"
+#include "aos/testing/test_logging.h"
+#include "aos/testing/test_shm.h"
+
+namespace aos {
+namespace testing {
+
+namespace chrono = ::std::chrono;
+namespace this_thread = ::std::this_thread;
+
+class MutexTest : public ::testing::Test {
+ public:
+ Mutex test_mutex_;
+
+ protected:
+ void SetUp() override {
+ ::aos::testing::EnableTestLogging();
+ SetDieTestMode(true);
+ }
+};
+
+typedef MutexTest MutexDeathTest;
+typedef MutexTest MutexLockerTest;
+typedef MutexTest MutexLockerDeathTest;
+typedef MutexTest IPCMutexLockerTest;
+typedef MutexTest IPCMutexLockerDeathTest;
+typedef MutexTest IPCRecursiveMutexLockerTest;
+
+TEST_F(MutexTest, TryLock) {
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+TEST_F(MutexTest, Lock) {
+ ASSERT_FALSE(test_mutex_.Lock());
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+TEST_F(MutexTest, Unlock) {
+ ASSERT_FALSE(test_mutex_.Lock());
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ test_mutex_.Unlock();
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+// Sees what happens with multiple unlocks.
+TEST_F(MutexDeathTest, RepeatUnlock) {
+ logging::Init();
+ ASSERT_FALSE(test_mutex_.Lock());
+ test_mutex_.Unlock();
+ EXPECT_DEATH(
+ {
+ logging::AddImplementation(new util::DeathTestLogImplementation());
+ test_mutex_.Unlock();
+ },
+ ".*multiple unlock.*");
+}
+
+// Sees what happens if you unlock without ever locking (or unlocking) it.
+TEST_F(MutexDeathTest, NeverLock) {
+ logging::Init();
+ EXPECT_DEATH(
+ {
+ logging::AddImplementation(new util::DeathTestLogImplementation());
+ test_mutex_.Unlock();
+ },
+ ".*multiple unlock.*");
+}
+
+// Tests that locking a mutex multiple times from the same thread fails nicely.
+TEST_F(MutexDeathTest, RepeatLock) {
+ EXPECT_DEATH(
+ {
+ logging::AddImplementation(new util::DeathTestLogImplementation());
+ ASSERT_FALSE(test_mutex_.Lock());
+ ASSERT_FALSE(test_mutex_.Lock());
+ },
+ ".*multiple lock.*");
+}
+
+// Tests that Lock behaves correctly when the previous owner exits with the lock
+// held (which is the same as dying any other way).
+TEST_F(MutexTest, OwnerDiedDeathLock) {
+ testing::TestSharedMemory my_shm;
+ Mutex *mutex =
+ static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
+ new (mutex) Mutex();
+
+ util::FunctionThread::RunInOtherThread([&]() {
+ ASSERT_FALSE(mutex->Lock());
+ });
+ EXPECT_TRUE(mutex->Lock());
+
+ mutex->Unlock();
+ mutex->~Mutex();
+}
+
+// Tests that TryLock behaves correctly when the previous owner dies.
+TEST_F(MutexTest, OwnerDiedDeathTryLock) {
+ testing::TestSharedMemory my_shm;
+ Mutex *mutex =
+ static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
+ new (mutex) Mutex();
+
+ util::FunctionThread::RunInOtherThread([&]() {
+ ASSERT_FALSE(mutex->Lock());
+ });
+ EXPECT_EQ(Mutex::State::kOwnerDied, mutex->TryLock());
+
+ mutex->Unlock();
+ mutex->~Mutex();
+}
+
+// TODO(brians): Test owner dying by being SIGKILLed and SIGTERMed.
+
+// This sequence of mutex operations used to mess up the robust list and cause
+// one of the mutexes to not get owner-died like it should.
+TEST_F(MutexTest, DontCorruptRobustList) {
+ // I think this was the allocator lock in the original failure.
+ Mutex mutex1;
+ // This one should get owner-died afterwards (iff the kernel accepts the
+ // robust list and uses it). I think it was the task_death_notification lock
+ // in the original failure.
+ Mutex mutex2;
+
+ util::FunctionThread::RunInOtherThread([&]() {
+ ASSERT_FALSE(mutex1.Lock());
+ ASSERT_FALSE(mutex2.Lock());
+ mutex1.Unlock();
+ });
+
+ EXPECT_EQ(Mutex::State::kLocked, mutex1.TryLock());
+ EXPECT_EQ(Mutex::State::kOwnerDied, mutex2.TryLock());
+
+ mutex1.Unlock();
+ mutex2.Unlock();
+}
+
+namespace {
+
+class AdderThread : public ::aos::util::Thread {
+ public:
+ AdderThread(int *counter, Mutex *mutex,
+ monotonic_clock::duration sleep_before_time,
+ monotonic_clock::duration sleep_after_time)
+ : counter_(counter),
+ mutex_(mutex),
+ sleep_before_time_(sleep_before_time),
+ sleep_after_time_(sleep_after_time) {}
+
+ private:
+ virtual void Run() override {
+ this_thread::sleep_for(sleep_before_time_);
+ MutexLocker locker(mutex_);
+ ++(*counter_);
+ this_thread::sleep_for(sleep_after_time_);
+ }
+
+ int *const counter_;
+ Mutex *const mutex_;
+ const monotonic_clock::duration sleep_before_time_, sleep_after_time_;
+};
+
+} // namespace
+
+// Verifies that ThreadSanitizer understands that a contended mutex establishes
+// a happens-before relationship.
+TEST_F(MutexTest, ThreadSanitizerContended) {
+ int counter = 0;
+ AdderThread threads[2]{
+ {&counter, &test_mutex_, chrono::milliseconds(200),
+ chrono::milliseconds(0)},
+ {&counter, &test_mutex_, chrono::milliseconds(0),
+ chrono::milliseconds(0)},
+ };
+ for (auto &c : threads) {
+ c.Start();
+ }
+ for (auto &c : threads) {
+ c.WaitUntilDone();
+ }
+ EXPECT_EQ(2, counter);
+}
+
+// Verifiers that ThreadSanitizer understands how a mutex works.
+// For some reason this used to fail when the other tests didn't...
+// The loops make it fail more reliably when it's going to.
+TEST_F(MutexTest, ThreadSanitizerMutexLocker) {
+ for (int i = 0; i < 100; ++i) {
+ int counter = 0;
+ ::std::thread thread([&counter, this]() {
+ for (int i = 0; i < 300; ++i) {
+ MutexLocker locker(&test_mutex_);
+ ++counter;
+ }
+ });
+ for (int i = 0; i < 300; ++i) {
+ MutexLocker locker(&test_mutex_);
+ --counter;
+ }
+ thread.join();
+ EXPECT_EQ(0, counter);
+ }
+}
+
+// Verifies that ThreadSanitizer understands that an uncontended mutex
+// establishes a happens-before relationship.
+TEST_F(MutexTest, ThreadSanitizerUncontended) {
+ int counter = 0;
+ AdderThread threads[2]{
+ {&counter, &test_mutex_, chrono::milliseconds(0),
+ chrono::milliseconds(0)},
+ {&counter, &test_mutex_, chrono::milliseconds(200),
+ chrono::milliseconds(0)}, };
+ for (auto &c : threads) {
+ c.Start();
+ }
+ for (auto &c : threads) {
+ c.WaitUntilDone();
+ }
+ EXPECT_EQ(2, counter);
+}
+
+namespace {
+
+class LockerThread : public util::Thread {
+ public:
+ LockerThread(Mutex *mutex, bool lock, bool unlock)
+ : mutex_(mutex), lock_(lock), unlock_(unlock) {}
+
+ private:
+ virtual void Run() override {
+ if (lock_) ASSERT_FALSE(mutex_->Lock());
+ if (unlock_) mutex_->Unlock();
+ }
+
+ Mutex *const mutex_;
+ const bool lock_, unlock_;
+};
+
+} // namespace
+
+// Makes sure that we don't SIGSEGV or something with multiple threads.
+TEST_F(MutexTest, MultiThreadedLock) {
+ LockerThread t(&test_mutex_, true, true);
+ t.Start();
+ ASSERT_FALSE(test_mutex_.Lock());
+ test_mutex_.Unlock();
+ t.Join();
+}
+
+TEST_F(MutexLockerTest, Basic) {
+ {
+ aos::MutexLocker locker(&test_mutex_);
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ }
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+// Tests that MutexLocker behaves correctly when the previous owner dies.
+TEST_F(MutexLockerDeathTest, OwnerDied) {
+ testing::TestSharedMemory my_shm;
+ Mutex *mutex =
+ static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
+ new (mutex) Mutex();
+
+ util::FunctionThread::RunInOtherThread([&]() {
+ ASSERT_FALSE(mutex->Lock());
+ });
+ EXPECT_DEATH(
+ {
+ logging::AddImplementation(new util::DeathTestLogImplementation());
+ MutexLocker locker(mutex);
+ },
+ ".*previous owner of mutex [^ ]+ died.*");
+
+ mutex->~Mutex();
+}
+
+TEST_F(IPCMutexLockerTest, Basic) {
+ {
+ aos::IPCMutexLocker locker(&test_mutex_);
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ EXPECT_FALSE(locker.owner_died());
+ }
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+// Tests what happens when the caller doesn't check if the previous owner died
+// with an IPCMutexLocker.
+TEST_F(IPCMutexLockerDeathTest, NoCheckOwnerDied) {
+ EXPECT_DEATH({ aos::IPCMutexLocker locker(&test_mutex_); },
+ "nobody checked if the previous owner of mutex [^ ]+ died.*");
+}
+
+TEST_F(IPCRecursiveMutexLockerTest, Basic) {
+ {
+ aos::IPCRecursiveMutexLocker locker(&test_mutex_);
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ EXPECT_FALSE(locker.owner_died());
+ }
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+// Tests actually locking a mutex recursively with IPCRecursiveMutexLocker.
+TEST_F(IPCRecursiveMutexLockerTest, RecursiveLock) {
+ {
+ aos::IPCRecursiveMutexLocker locker(&test_mutex_);
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ {
+ aos::IPCRecursiveMutexLocker locker(&test_mutex_);
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ EXPECT_FALSE(locker.owner_died());
+ }
+ EXPECT_EQ(Mutex::State::kLockFailed, test_mutex_.TryLock());
+ EXPECT_FALSE(locker.owner_died());
+ }
+ EXPECT_EQ(Mutex::State::kLocked, test_mutex_.TryLock());
+
+ test_mutex_.Unlock();
+}
+
+// Tests that IPCMutexLocker behaves correctly when the previous owner dies.
+TEST_F(IPCMutexLockerTest, OwnerDied) {
+ testing::TestSharedMemory my_shm;
+ Mutex *mutex =
+ static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
+ new (mutex) Mutex();
+
+ util::FunctionThread::RunInOtherThread([&]() {
+ ASSERT_FALSE(mutex->Lock());
+ });
+ {
+ aos::IPCMutexLocker locker(mutex);
+ EXPECT_EQ(Mutex::State::kLockFailed, mutex->TryLock());
+ EXPECT_TRUE(locker.owner_died());
+ }
+ EXPECT_EQ(Mutex::State::kLocked, mutex->TryLock());
+
+ mutex->Unlock();
+ mutex->~Mutex();
+}
+
+} // namespace testing
+} // namespace aos