| /* |
| * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "rtc_base/sequenced_task_checker.h" |
| |
| #include "rtc_base/checks.h" |
| #include "rtc_base/constructormagic.h" |
| #include "rtc_base/event.h" |
| #include "rtc_base/platform_thread.h" |
| #include "rtc_base/task_queue.h" |
| #include "rtc_base/thread_checker.h" |
| #include "test/gtest.h" |
| |
| namespace rtc { |
| |
| namespace { |
| |
| // This class is dead code, but its purpose is to make sure that |
| // SequencedTaskChecker is compatible with the RTC_GUARDED_BY and RTC_RUN_ON |
| // attributes that are checked at compile-time. |
| class CompileTimeTestForGuardedBy { |
| public: |
| int CalledOnSequence() RTC_RUN_ON(sequence_checker_) { return guarded_; } |
| |
| void CallMeFromSequence() { |
| RTC_DCHECK_RUN_ON(&sequence_checker_) << "Should be called on sequence"; |
| } |
| |
| private: |
| int guarded_ RTC_GUARDED_BY(sequence_checker_); |
| rtc::SequencedTaskChecker sequence_checker_; |
| }; |
| |
| // Calls SequencedTaskChecker::CalledSequentially on another thread. |
| class CallCalledSequentiallyOnThread { |
| public: |
| CallCalledSequentiallyOnThread(bool expect_true, |
| SequencedTaskChecker* sequenced_task_checker) |
| : expect_true_(expect_true), |
| thread_has_run_event_(false, false), |
| thread_(&Run, this, "call_do_stuff_on_thread"), |
| sequenced_task_checker_(sequenced_task_checker) { |
| thread_.Start(); |
| } |
| ~CallCalledSequentiallyOnThread() { |
| EXPECT_TRUE(thread_has_run_event_.Wait(1000)); |
| thread_.Stop(); |
| } |
| |
| private: |
| static void Run(void* obj) { |
| CallCalledSequentiallyOnThread* call_stuff_on_thread = |
| static_cast<CallCalledSequentiallyOnThread*>(obj); |
| EXPECT_EQ( |
| call_stuff_on_thread->expect_true_, |
| call_stuff_on_thread->sequenced_task_checker_->CalledSequentially()); |
| call_stuff_on_thread->thread_has_run_event_.Set(); |
| } |
| |
| const bool expect_true_; |
| Event thread_has_run_event_; |
| PlatformThread thread_; |
| SequencedTaskChecker* const sequenced_task_checker_; |
| |
| RTC_DISALLOW_COPY_AND_ASSIGN(CallCalledSequentiallyOnThread); |
| }; |
| |
| // Deletes SequencedTaskChecker on a different thread. |
| class DeleteSequencedCheckerOnThread { |
| public: |
| explicit DeleteSequencedCheckerOnThread( |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker) |
| : thread_(&Run, this, "delete_sequenced_task_checker_on_thread"), |
| thread_has_run_event_(false, false), |
| sequenced_task_checker_(std::move(sequenced_task_checker)) { |
| thread_.Start(); |
| } |
| |
| ~DeleteSequencedCheckerOnThread() { |
| EXPECT_TRUE(thread_has_run_event_.Wait(1000)); |
| thread_.Stop(); |
| } |
| |
| private: |
| static bool Run(void* obj) { |
| DeleteSequencedCheckerOnThread* instance = |
| static_cast<DeleteSequencedCheckerOnThread*>(obj); |
| instance->sequenced_task_checker_.reset(); |
| instance->thread_has_run_event_.Set(); |
| return false; |
| } |
| |
| private: |
| PlatformThread thread_; |
| Event thread_has_run_event_; |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker_; |
| |
| RTC_DISALLOW_COPY_AND_ASSIGN(DeleteSequencedCheckerOnThread); |
| }; |
| |
| void RunMethodOnDifferentThread(bool expect_true) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| CallCalledSequentiallyOnThread call_on_thread(expect_true, |
| sequenced_task_checker.get()); |
| } |
| |
| void RunMethodOnDifferentTaskQueue(bool expect_true) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| static const char kQueueName[] = "MethodNotAllowedOnDifferentTq"; |
| TaskQueue queue(kQueueName); |
| Event done_event(false, false); |
| queue.PostTask([&sequenced_task_checker, &done_event, expect_true] { |
| if (expect_true) |
| EXPECT_TRUE(sequenced_task_checker->CalledSequentially()); |
| else |
| EXPECT_FALSE(sequenced_task_checker->CalledSequentially()); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| } |
| |
| void DetachThenCallFromDifferentTaskQueue(bool expect_true) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| sequenced_task_checker->Detach(); |
| |
| Event done_event(false, false); |
| TaskQueue queue1("DetachThenCallFromDifferentTaskQueueImpl1"); |
| queue1.PostTask([&sequenced_task_checker, &done_event] { |
| EXPECT_TRUE(sequenced_task_checker->CalledSequentially()); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| |
| // CalledSequentially should return false in debug builds after moving to |
| // another task queue. |
| TaskQueue queue2("DetachThenCallFromDifferentTaskQueueImpl2"); |
| queue2.PostTask([&sequenced_task_checker, &done_event, expect_true] { |
| if (expect_true) |
| EXPECT_TRUE(sequenced_task_checker->CalledSequentially()); |
| else |
| EXPECT_FALSE(sequenced_task_checker->CalledSequentially()); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| } |
| } // namespace |
| |
| TEST(SequencedTaskCheckerTest, CallsAllowedOnSameThread) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| EXPECT_TRUE(sequenced_task_checker->CalledSequentially()); |
| |
| // Verify that the destructor doesn't assert. |
| sequenced_task_checker.reset(); |
| } |
| |
| TEST(SequencedTaskCheckerTest, DestructorAllowedOnDifferentThread) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| // Verify that the destructor doesn't assert when called on a different |
| // thread. |
| DeleteSequencedCheckerOnThread delete_on_thread( |
| std::move(sequenced_task_checker)); |
| } |
| |
| TEST(SequencedTaskCheckerTest, DetachFromThread) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| sequenced_task_checker->Detach(); |
| CallCalledSequentiallyOnThread call_on_thread(true, |
| sequenced_task_checker.get()); |
| } |
| |
| TEST(SequencedTaskCheckerTest, DetachFromThreadAndUseOnTaskQueue) { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| sequenced_task_checker->Detach(); |
| static const char kQueueName[] = "DetachFromThreadAndUseOnTaskQueue"; |
| TaskQueue queue(kQueueName); |
| Event done_event(false, false); |
| queue.PostTask([&sequenced_task_checker, &done_event] { |
| EXPECT_TRUE(sequenced_task_checker->CalledSequentially()); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| } |
| |
| TEST(SequencedTaskCheckerTest, DetachFromTaskQueueAndUseOnThread) { |
| TaskQueue queue("DetachFromTaskQueueAndUseOnThread"); |
| Event done_event(false, false); |
| queue.PostTask([&done_event] { |
| std::unique_ptr<SequencedTaskChecker> sequenced_task_checker( |
| new SequencedTaskChecker()); |
| |
| sequenced_task_checker->Detach(); |
| CallCalledSequentiallyOnThread call_on_thread(true, |
| sequenced_task_checker.get()); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| } |
| |
| #if RTC_DCHECK_IS_ON |
| TEST(SequencedTaskCheckerTest, MethodNotAllowedOnDifferentThreadInDebug) { |
| RunMethodOnDifferentThread(false); |
| } |
| #else |
| TEST(SequencedTaskCheckerTest, MethodAllowedOnDifferentThreadInRelease) { |
| RunMethodOnDifferentThread(true); |
| } |
| #endif |
| |
| #if RTC_DCHECK_IS_ON |
| TEST(SequencedTaskCheckerTest, MethodNotAllowedOnDifferentTaskQueueInDebug) { |
| RunMethodOnDifferentTaskQueue(false); |
| } |
| #else |
| TEST(SequencedTaskCheckerTest, MethodAllowedOnDifferentTaskQueueInRelease) { |
| RunMethodOnDifferentTaskQueue(true); |
| } |
| #endif |
| |
| #if RTC_DCHECK_IS_ON |
| TEST(SequencedTaskCheckerTest, DetachFromTaskQueueInDebug) { |
| DetachThenCallFromDifferentTaskQueue(false); |
| } |
| #else |
| TEST(SequencedTaskCheckerTest, DetachFromTaskQueueInRelease) { |
| DetachThenCallFromDifferentTaskQueue(true); |
| } |
| #endif |
| |
| class TestAnnotations { |
| public: |
| TestAnnotations() : test_var_(false) {} |
| |
| void ModifyTestVar() { |
| RTC_DCHECK_CALLED_SEQUENTIALLY(&checker_); |
| test_var_ = true; |
| } |
| |
| private: |
| bool test_var_ RTC_GUARDED_BY(&checker_); |
| SequencedTaskChecker checker_; |
| }; |
| |
| TEST(SequencedTaskCheckerTest, TestAnnotations) { |
| TestAnnotations annotations; |
| annotations.ModifyTestVar(); |
| } |
| |
| #if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |
| |
| void TestAnnotationsOnWrongQueue() { |
| TestAnnotations annotations; |
| static const char kQueueName[] = "TestAnnotationsOnWrongQueueDebug"; |
| TaskQueue queue(kQueueName); |
| Event done_event(false, false); |
| queue.PostTask([&annotations, &done_event] { |
| annotations.ModifyTestVar(); |
| done_event.Set(); |
| }); |
| EXPECT_TRUE(done_event.Wait(1000)); |
| } |
| |
| #if RTC_DCHECK_IS_ON |
| TEST(SequencedTaskCheckerTest, TestAnnotationsOnWrongQueueDebug) { |
| ASSERT_DEATH({ TestAnnotationsOnWrongQueue(); }, ""); |
| } |
| #else |
| TEST(SequencedTaskCheckerTest, TestAnnotationsOnWrongQueueRelease) { |
| TestAnnotationsOnWrongQueue(); |
| } |
| #endif |
| #endif // GTEST_HAS_DEATH_TEST |
| } // namespace rtc |