Index: ppapi/proxy/tracked_callback_unittest.cc |
diff --git a/ppapi/proxy/tracked_callback_unittest.cc b/ppapi/proxy/tracked_callback_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dfd6c26b8bc0824562136aeeb9e05ef4b871a7d0 |
--- /dev/null |
+++ b/ppapi/proxy/tracked_callback_unittest.cc |
@@ -0,0 +1,471 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/bind.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/run_loop.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/simple_thread.h" |
+#include "ppapi/c/pp_completion_callback.h" |
+#include "ppapi/c/pp_errors.h" |
+#include "ppapi/proxy/ppapi_proxy_test.h" |
+#include "ppapi/proxy/ppb_message_loop_proxy.h" |
+#include "ppapi/shared_impl/callback_tracker.h" |
+#include "ppapi/shared_impl/proxy_lock.h" |
+#include "ppapi/shared_impl/resource.h" |
+#include "ppapi/shared_impl/resource_tracker.h" |
+#include "ppapi/shared_impl/scoped_pp_resource.h" |
+#include "ppapi/shared_impl/test_globals.h" |
+#include "ppapi/shared_impl/tracked_callback.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+// Note, this file tests TrackedCallback which lives in ppapi/shared_impl. |
+// Unfortunately, we need the test to live in ppapi/proxy so that it can use |
+// the thread support there. |
+namespace ppapi { |
+namespace proxy { |
+ |
+namespace { |
+ |
+class CallbackThread : public base::SimpleThread { |
+ public: |
+ explicit CallbackThread(PP_Instance instance) |
+ : SimpleThread("CallbackThread"), instance_(instance) {} |
+ ~CallbackThread() override {} |
+ |
+ // base::SimpleThread overrides. |
+ void Start() override { |
+ { |
+ ProxyAutoLock acquire; |
+ // Create the message loop here, after PpapiGlobals has been created. |
+ message_loop_ = new MessageLoopResource(instance_); |
+ } |
+ base::SimpleThread::Start(); |
+ } |
+ void Join() override { |
+ { |
+ ProxyAutoLock acquire; |
+ message_loop()->PostQuit(PP_TRUE); |
+ message_loop_ = nullptr; |
+ } |
+ base::SimpleThread::Join(); |
+ } |
+ void Run() override { |
+ ProxyAutoLock acquire; |
+ // Make a local copy of message_loop_ for this thread so we can interact |
+ // with it even after the main thread releases it. |
+ scoped_refptr<MessageLoopResource> message_loop(message_loop_); |
+ message_loop->AttachToCurrentThread(); |
+ // Note, run releases the lock to run events. |
+ message_loop->Run(); |
+ message_loop->DetachFromThread(); |
+ } |
+ |
+ MessageLoopResource* message_loop() { return message_loop_.get(); } |
+ |
+ private: |
+ PP_Instance instance_; |
+ scoped_refptr<MessageLoopResource> message_loop_; |
+}; |
+ |
+class TrackedCallbackTest : public PluginProxyTest { |
+ public: |
+ TrackedCallbackTest() : thread_(pp_instance()) {} |
+ CallbackThread& thread() { return thread_; } |
+ |
+ private: |
+ // PluginProxyTest overrides. |
+ void SetUp() override { |
+ PluginProxyTest::SetUp(); |
+ thread_.Start(); |
+ } |
+ void TearDown() override { |
+ thread_.Join(); |
+ PluginProxyTest::TearDown(); |
+ base::RunLoop run_loop; |
+ run_loop.RunUntilIdle(); |
+ } |
+ CallbackThread thread_; |
+}; |
+ |
+// All valid results (PP_OK, PP_ERROR_...) are nonpositive. |
+const int32_t kInitializedResultValue = 1; |
+const int32_t kOverrideResultValue = 2; |
+ |
+struct CallbackRunInfo { |
+ explicit CallbackRunInfo(base::ThreadChecker* thread_checker) |
+ : run_count_(0), |
+ result_(kInitializedResultValue), |
+ completion_task_run_count_(0), |
+ completion_task_result_(kInitializedResultValue), |
+ thread_checker_(thread_checker), |
+ callback_did_run_event_(true, false) {} |
+ void CallbackDidRun(int32_t result) { |
+ CHECK(thread_checker_->CalledOnValidThread()); |
+ if (!run_count_) |
+ result_ = result; |
+ ++run_count_; |
+ callback_did_run_event_.Signal(); |
+ } |
+ void CompletionTaskDidRun(int32_t result) { |
+ CHECK(thread_checker_->CalledOnValidThread()); |
+ if (!completion_task_run_count_) |
+ completion_task_result_ = result; |
+ ++completion_task_run_count_; |
+ } |
+ void WaitUntilCompleted() { callback_did_run_event_.Wait(); } |
+ unsigned run_count() { return run_count_; } |
+ int32_t result() { return result_; } |
+ unsigned completion_task_run_count() { return completion_task_run_count_; } |
+ int32_t completion_task_result() { return completion_task_result_; } |
+ |
+ private: |
+ unsigned run_count_; |
+ int32_t result_; |
+ unsigned completion_task_run_count_; |
+ int32_t completion_task_result_; |
+ // Weak; owned by the creator of CallbackRunInfo. |
+ base::ThreadChecker* thread_checker_; |
+ |
+ base::WaitableEvent callback_did_run_event_; |
+}; |
+ |
+void TestCallback(void* user_data, int32_t result) { |
+ CallbackRunInfo* info = static_cast<CallbackRunInfo*>(user_data); |
+ info->CallbackDidRun(result); |
+} |
+ |
+// CallbackShutdownTest -------------------------------------------------------- |
+ |
+class CallbackShutdownTest : public TrackedCallbackTest { |
+ public: |
+ CallbackShutdownTest() |
+ : info_did_run_(&thread_checker_), |
+ info_did_abort_(&thread_checker_), |
+ info_didnt_run_(&thread_checker_) {} |
+ |
+ // Cases: |
+ // (1) A callback which is run (so shouldn't be aborted on shutdown). |
+ // (2) A callback which is aborted (so shouldn't be aborted on shutdown). |
+ // (3) A callback which isn't run (so should be aborted on shutdown). |
+ CallbackRunInfo& info_did_run() { return info_did_run_; } // (1) |
+ CallbackRunInfo& info_did_abort() { return info_did_abort_; } // (2) |
+ CallbackRunInfo& info_didnt_run() { return info_didnt_run_; } // (3) |
+ |
+ private: |
+ base::ThreadChecker thread_checker_; |
+ CallbackRunInfo info_did_run_; |
+ CallbackRunInfo info_did_abort_; |
+ CallbackRunInfo info_didnt_run_; |
+}; |
+ |
+} // namespace |
+ |
+// Tests that callbacks are properly aborted on module shutdown. |
+TEST_F(CallbackShutdownTest, AbortOnShutdown) { |
+ ProxyAutoLock lock; |
+ scoped_refptr<Resource> resource( |
+ new Resource(OBJECT_IS_PROXY, pp_instance())); |
+ |
+ // Set up case (1) (see above). |
+ EXPECT_EQ(0U, info_did_run().run_count()); |
+ // TODO(dmichael): Test this on a background thread? |
+ scoped_refptr<TrackedCallback> callback_did_run = new TrackedCallback( |
+ resource.get(), |
+ PP_MakeCompletionCallback(&TestCallback, &info_did_run())); |
+ EXPECT_EQ(0U, info_did_run().run_count()); |
+ callback_did_run->Run(PP_OK); |
+ EXPECT_EQ(1U, info_did_run().run_count()); |
+ EXPECT_EQ(PP_OK, info_did_run().result()); |
+ |
+ // Set up case (2). |
+ EXPECT_EQ(0U, info_did_abort().run_count()); |
+ scoped_refptr<TrackedCallback> callback_did_abort = new TrackedCallback( |
+ resource.get(), |
+ PP_MakeCompletionCallback(&TestCallback, &info_did_abort())); |
+ EXPECT_EQ(0U, info_did_abort().run_count()); |
+ callback_did_abort->Abort(); |
+ EXPECT_EQ(1U, info_did_abort().run_count()); |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort().result()); |
+ |
+ // Set up case (3). |
+ EXPECT_EQ(0U, info_didnt_run().run_count()); |
+ scoped_refptr<TrackedCallback> callback_didnt_run = new TrackedCallback( |
+ resource.get(), |
+ PP_MakeCompletionCallback(&TestCallback, &info_didnt_run())); |
+ EXPECT_EQ(0U, info_didnt_run().run_count()); |
+ |
+ GetGlobals()->GetCallbackTrackerForInstance(pp_instance())->AbortAll(); |
+ |
+ // Check case (1). |
+ EXPECT_EQ(1U, info_did_run().run_count()); |
+ |
+ // Check case (2). |
+ EXPECT_EQ(1U, info_did_abort().run_count()); |
+ |
+ // Check case (3). |
+ EXPECT_EQ(1U, info_didnt_run().run_count()); |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run().result()); |
+} |
+ |
+// CallbackResourceTest -------------------------------------------------------- |
+ |
+namespace { |
+ |
+class CallbackResourceTest : public TrackedCallbackTest { |
+ public: |
+ CallbackResourceTest() {} |
+}; |
+ |
+class CallbackMockResource : public Resource { |
+ public: |
+ static scoped_refptr<CallbackMockResource> Create(PP_Instance instance) { |
+ ProxyAutoLock acquire; |
+ return scoped_refptr<CallbackMockResource>( |
+ new CallbackMockResource(instance)); |
+ } |
+ ~CallbackMockResource() {} |
+ |
+ // Take a reference to this resource, which will add it to the tracker. |
+ void TakeRef() { |
+ ProxyAutoLock acquire; |
+ ScopedPPResource temp_resource(ScopedPPResource::PassRef(), GetReference()); |
+ EXPECT_NE(0, temp_resource.get()); |
+ reference_holder_ = temp_resource; |
+ } |
+ // Release it, removing it from the tracker. |
+ void ReleaseRef() { |
+ ProxyAutoLock acquire; |
+ reference_holder_ = 0; |
+ } |
+ |
+ // Create the test callbacks on a background thread, so that we can verify |
+ // they are run on the same thread where they were created. |
+ void CreateCallbacksOnLoop(MessageLoopResource* loop_resource) { |
+ ProxyAutoLock acquire; |
+ // |thread_checker_| will bind to the background thread. |
+ thread_checker_.DetachFromThread(); |
+ loop_resource->message_loop_proxy()->PostTask( |
+ FROM_HERE, RunWhileLocked(base::Bind( |
+ &CallbackMockResource::CreateCallbacks, this))); |
+ } |
+ |
+ int32_t CompletionTask(CallbackRunInfo* info, int32_t result) { |
+ // The completion task must run on the thread where the callback was |
+ // created, and must hold the proxy lock. |
+ CHECK(thread_checker_.CalledOnValidThread()); |
+ ProxyLock::AssertAcquired(); |
+ |
+ // We should run before the callback. |
+ CHECK_EQ(0U, info->run_count()); |
+ info->CompletionTaskDidRun(result); |
+ return kOverrideResultValue; |
+ } |
+ |
+ void CheckInitialState() { |
+ callbacks_created_event_.Wait(); |
+ EXPECT_EQ(0U, info_did_run_.run_count()); |
+ EXPECT_EQ(0U, info_did_run_.completion_task_run_count()); |
+ |
+ EXPECT_EQ(0U, info_did_run_with_completion_task_.run_count()); |
+ EXPECT_EQ(0U, |
+ info_did_run_with_completion_task_.completion_task_run_count()); |
+ |
+ EXPECT_EQ(0U, info_did_abort_.run_count()); |
+ EXPECT_EQ(0U, info_did_abort_.completion_task_run_count()); |
+ |
+ EXPECT_EQ(0U, info_didnt_run_.run_count()); |
+ EXPECT_EQ(0U, info_didnt_run_.completion_task_run_count()); |
+ } |
+ |
+ void RunCallbacks() { |
+ callback_did_run_->Run(PP_OK); |
+ callback_did_run_with_completion_task_->Run(PP_OK); |
+ callback_did_abort_->Abort(); |
+ info_did_run_.WaitUntilCompleted(); |
+ info_did_run_with_completion_task_.WaitUntilCompleted(); |
+ info_did_abort_.WaitUntilCompleted(); |
+ } |
+ |
+ void CheckIntermediateState() { |
+ EXPECT_EQ(1U, info_did_run_.run_count()); |
+ EXPECT_EQ(PP_OK, info_did_run_.result()); |
+ EXPECT_EQ(0U, info_did_run_.completion_task_run_count()); |
+ |
+ EXPECT_EQ(1U, info_did_run_with_completion_task_.run_count()); |
+ // completion task should override the result. |
+ EXPECT_EQ(kOverrideResultValue, |
+ info_did_run_with_completion_task_.result()); |
+ EXPECT_EQ(1U, |
+ info_did_run_with_completion_task_.completion_task_run_count()); |
+ EXPECT_EQ(PP_OK, |
+ info_did_run_with_completion_task_.completion_task_result()); |
+ |
+ EXPECT_EQ(1U, info_did_abort_.run_count()); |
+ // completion task shouldn't override an abort. |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result()); |
+ EXPECT_EQ(1U, info_did_abort_.completion_task_run_count()); |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.completion_task_result()); |
+ |
+ EXPECT_EQ(0U, info_didnt_run_.completion_task_run_count()); |
+ EXPECT_EQ(0U, info_didnt_run_.run_count()); |
+ } |
+ |
+ void CheckFinalState() { |
+ info_didnt_run_.WaitUntilCompleted(); |
+ EXPECT_EQ(1U, info_did_run_with_completion_task_.run_count()); |
+ EXPECT_EQ(kOverrideResultValue, |
+ info_did_run_with_completion_task_.result()); |
+ callback_did_run_with_completion_task_ = nullptr; |
+ EXPECT_EQ(1U, info_did_run_.run_count()); |
+ EXPECT_EQ(PP_OK, info_did_run_.result()); |
+ callback_did_run_ = nullptr; |
+ EXPECT_EQ(1U, info_did_abort_.run_count()); |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result()); |
+ callback_did_abort_ = nullptr; |
+ EXPECT_EQ(1U, info_didnt_run_.run_count()); |
+ EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run_.result()); |
+ callback_didnt_run_ = nullptr; |
+ } |
+ |
+ private: |
+ explicit CallbackMockResource(PP_Instance instance) |
+ : Resource(OBJECT_IS_PROXY, instance), |
+ info_did_run_(&thread_checker_), |
+ info_did_run_with_completion_task_(&thread_checker_), |
+ info_did_abort_(&thread_checker_), |
+ info_didnt_run_(&thread_checker_), |
+ callbacks_created_event_(true, false) {} |
+ void CreateCallbacks() { |
+ // Bind thread_checker_ to the thread where we create the callbacks. |
+ // Later, when the callback runs, it will check that it was invoked on this |
+ // same thread. |
+ CHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ callback_did_run_ = new TrackedCallback( |
+ this, PP_MakeCompletionCallback(&TestCallback, &info_did_run_)); |
+ |
+ // In order to test that the completion task can override the callback |
+ // result, we need to test callbacks with and without a completion task. |
+ callback_did_run_with_completion_task_ = new TrackedCallback( |
+ this, PP_MakeCompletionCallback(&TestCallback, |
+ &info_did_run_with_completion_task_)); |
+ callback_did_run_with_completion_task_->set_completion_task( |
+ Bind(&CallbackMockResource::CompletionTask, this, |
+ &info_did_run_with_completion_task_)); |
+ |
+ callback_did_abort_ = new TrackedCallback( |
+ this, PP_MakeCompletionCallback(&TestCallback, &info_did_abort_)); |
+ callback_did_abort_->set_completion_task( |
+ Bind(&CallbackMockResource::CompletionTask, this, &info_did_abort_)); |
+ |
+ callback_didnt_run_ = new TrackedCallback( |
+ this, PP_MakeCompletionCallback(&TestCallback, &info_didnt_run_)); |
+ callback_didnt_run_->set_completion_task( |
+ Bind(&CallbackMockResource::CompletionTask, this, &info_didnt_run_)); |
+ |
+ callbacks_created_event_.Signal(); |
+ } |
+ |
+ // Used to verify that the callback runs on the same thread where it is |
+ // created. |
+ base::ThreadChecker thread_checker_; |
+ |
+ scoped_refptr<TrackedCallback> callback_did_run_; |
+ CallbackRunInfo info_did_run_; |
+ |
+ scoped_refptr<TrackedCallback> callback_did_run_with_completion_task_; |
+ CallbackRunInfo info_did_run_with_completion_task_; |
+ |
+ scoped_refptr<TrackedCallback> callback_did_abort_; |
+ CallbackRunInfo info_did_abort_; |
+ |
+ scoped_refptr<TrackedCallback> callback_didnt_run_; |
+ CallbackRunInfo info_didnt_run_; |
+ |
+ base::WaitableEvent callbacks_created_event_; |
+ |
+ ScopedPPResource reference_holder_; |
+}; |
+ |
+} // namespace |
+ |
+// Test that callbacks get aborted on the last resource unref. |
+TEST_F(CallbackResourceTest, AbortOnNoRef) { |
+ // Test several things: Unref-ing a resource (to zero refs) with callbacks |
+ // which (1) have been run, (2) have been aborted, (3) haven't been completed. |
+ // Check that the uncompleted one gets aborted, and that the others don't get |
+ // called again. |
+ scoped_refptr<CallbackMockResource> resource_1( |
+ CallbackMockResource::Create(pp_instance())); |
+ resource_1->CreateCallbacksOnLoop(thread().message_loop()); |
+ resource_1->CheckInitialState(); |
+ resource_1->RunCallbacks(); |
+ resource_1->TakeRef(); |
+ resource_1->CheckIntermediateState(); |
+ |
+ // Also do the same for a second resource, and make sure that unref-ing the |
+ // first resource doesn't much up the second resource. |
+ scoped_refptr<CallbackMockResource> resource_2( |
+ CallbackMockResource::Create(pp_instance())); |
+ resource_2->CreateCallbacksOnLoop(thread().message_loop()); |
+ resource_2->CheckInitialState(); |
+ resource_2->RunCallbacks(); |
+ resource_2->TakeRef(); |
+ resource_2->CheckIntermediateState(); |
+ |
+ // Double-check that resource #1 is still okay. |
+ resource_1->CheckIntermediateState(); |
+ |
+ // Kill resource #1, spin the message loop to run posted calls, and check that |
+ // things are in the expected states. |
+ resource_1->ReleaseRef(); |
+ |
+ resource_1->CheckFinalState(); |
+ resource_2->CheckIntermediateState(); |
+ |
+ // Kill resource #2. |
+ resource_2->ReleaseRef(); |
+ |
+ resource_1->CheckFinalState(); |
+ resource_2->CheckFinalState(); |
+ |
+ { |
+ ProxyAutoLock lock; |
+ resource_1 = nullptr; |
+ resource_2 = nullptr; |
+ } |
+} |
+ |
+// Test that "resurrecting" a resource (getting a new ID for a |Resource|) |
+// doesn't resurrect callbacks. |
+TEST_F(CallbackResourceTest, Resurrection) { |
+ scoped_refptr<CallbackMockResource> resource( |
+ CallbackMockResource::Create(pp_instance())); |
+ resource->CreateCallbacksOnLoop(thread().message_loop()); |
+ resource->CheckInitialState(); |
+ resource->RunCallbacks(); |
+ resource->TakeRef(); |
+ resource->CheckIntermediateState(); |
+ |
+ // Unref it and check that things are in the expected states. |
+ resource->ReleaseRef(); |
+ resource->CheckFinalState(); |
+ |
+ // "Resurrect" it and check that the callbacks are still dead. |
+ resource->TakeRef(); |
+ resource->CheckFinalState(); |
+ |
+ // Unref it again and do the same. |
+ resource->ReleaseRef(); |
+ resource->CheckFinalState(); |
+ { |
+ ProxyAutoLock lock; |
+ resource = nullptr; |
+ } |
+} |
+ |
+} // namespace proxy |
+} // namespace ppapi |