/* * 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(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 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(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 sequenced_task_checker_; RTC_DISALLOW_COPY_AND_ASSIGN(DeleteSequencedCheckerOnThread); }; void RunMethodOnDifferentThread(bool expect_true) { std::unique_ptr sequenced_task_checker( new SequencedTaskChecker()); CallCalledSequentiallyOnThread call_on_thread(expect_true, sequenced_task_checker.get()); } void RunMethodOnDifferentTaskQueue(bool expect_true) { std::unique_ptr 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 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 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 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 sequenced_task_checker( new SequencedTaskChecker()); sequenced_task_checker->Detach(); CallCalledSequentiallyOnThread call_on_thread(true, sequenced_task_checker.get()); } TEST(SequencedTaskCheckerTest, DetachFromThreadAndUseOnTaskQueue) { std::unique_ptr 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 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