// 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 "ppapi/c/pp_completion_callback.h" #include "ppapi/c/pp_errors.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/test_globals.h" #include "ppapi/shared_impl/tracked_callback.h" #include "testing/gtest/include/gtest/gtest.h" namespace ppapi { namespace { class TrackedCallbackTest : public testing::Test { public: TrackedCallbackTest() : pp_instance_(1234) {} PP_Instance pp_instance() const { return pp_instance_; } virtual void SetUp() override { ProxyAutoLock lock; globals_.GetResourceTracker()->DidCreateInstance(pp_instance_); } virtual void TearDown() override { ProxyAutoLock lock; globals_.GetResourceTracker()->DidDeleteInstance(pp_instance_); } private: base::MessageLoop message_loop_; TestGlobals globals_; PP_Instance pp_instance_; }; // All valid results (PP_OK, PP_ERROR_...) are nonpositive. const int32_t kInitializedResultValue = 1; const int32_t kOverrideResultValue = 2; struct CallbackRunInfo { CallbackRunInfo() : run_count(0), result(kInitializedResultValue), completion_task_run_count(0), completion_task_result(kInitializedResultValue) {} unsigned run_count; int32_t result; unsigned completion_task_run_count; int32_t completion_task_result; }; void TestCallback(void* user_data, int32_t result) { CallbackRunInfo* info = reinterpret_cast(user_data); info->run_count++; if (info->run_count == 1) info->result = result; } } // namespace // CallbackShutdownTest -------------------------------------------------------- namespace { class CallbackShutdownTest : public TrackedCallbackTest { public: CallbackShutdownTest() {} // 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: 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(new Resource(OBJECT_IS_IMPL, pp_instance())); // Set up case (1) (see above). EXPECT_EQ(0U, info_did_run().run_count); scoped_refptr 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 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 callback_didnt_run = new TrackedCallback( resource.get(), PP_MakeCompletionCallback(&TestCallback, &info_didnt_run())); EXPECT_EQ(0U, info_didnt_run().run_count); PpapiGlobals::Get()->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: CallbackMockResource(PP_Instance instance) : Resource(OBJECT_IS_IMPL, instance) {} ~CallbackMockResource() {} PP_Resource SetupForTest() { PP_Resource resource_id = GetReference(); EXPECT_NE(0, resource_id); callback_did_run_ = new TrackedCallback( this, PP_MakeCompletionCallback(&TestCallback, &info_did_run_)); EXPECT_EQ(0U, info_did_run_.run_count); EXPECT_EQ(0U, info_did_run_.completion_task_run_count); // 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_)); EXPECT_EQ(0U, info_did_run_with_completion_task_.run_count); EXPECT_EQ(0U, info_did_run_with_completion_task_.completion_task_run_count); 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_)); EXPECT_EQ(0U, info_did_abort_.run_count); EXPECT_EQ(0U, info_did_abort_.completion_task_run_count); 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_)); EXPECT_EQ(0U, info_didnt_run_.run_count); EXPECT_EQ(0U, info_didnt_run_.completion_task_run_count); callback_did_run_->Run(PP_OK); callback_did_run_with_completion_task_->Run(PP_OK); callback_did_abort_->Abort(); CheckIntermediateState(); return resource_id; } int32_t CompletionTask(CallbackRunInfo* info, int32_t result) { // We should run before the callback. EXPECT_EQ(0U, info->run_count); info->completion_task_run_count++; if (info->completion_task_run_count == 1) info->completion_task_result = result; return kOverrideResultValue; } 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() { EXPECT_EQ(1U, info_did_run_.run_count); EXPECT_EQ(PP_OK, info_did_run_.result); EXPECT_EQ(1U, info_did_abort_.run_count); EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result); EXPECT_EQ(1U, info_didnt_run_.run_count); EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run_.result); } scoped_refptr callback_did_run_; CallbackRunInfo info_did_run_; scoped_refptr callback_did_run_with_completion_task_; CallbackRunInfo info_did_run_with_completion_task_; scoped_refptr callback_did_abort_; CallbackRunInfo info_did_abort_; scoped_refptr callback_didnt_run_; CallbackRunInfo info_didnt_run_; }; } // namespace // Test that callbacks get aborted on the last resource unref. TEST_F(CallbackResourceTest, AbortOnNoRef) { ProxyAutoLock lock; ResourceTracker* resource_tracker = PpapiGlobals::Get()->GetResourceTracker(); // 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 resource_1( new CallbackMockResource(pp_instance())); PP_Resource resource_1_id = resource_1->SetupForTest(); // 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 resource_2( new CallbackMockResource(pp_instance())); PP_Resource resource_2_id = resource_2->SetupForTest(); // 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_tracker->ReleaseResource(resource_1_id); { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } resource_1->CheckFinalState(); resource_2->CheckIntermediateState(); // Kill resource #2. resource_tracker->ReleaseResource(resource_2_id); { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } resource_1->CheckFinalState(); resource_2->CheckFinalState(); // This shouldn't be needed, but make sure there are no stranded tasks. { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } } // Test that "resurrecting" a resource (getting a new ID for a |Resource|) // doesn't resurrect callbacks. TEST_F(CallbackResourceTest, Resurrection) { ProxyAutoLock lock; ResourceTracker* resource_tracker = PpapiGlobals::Get()->GetResourceTracker(); scoped_refptr resource( new CallbackMockResource(pp_instance())); PP_Resource resource_id = resource->SetupForTest(); // Unref it, spin the message loop to run posted calls, and check that things // are in the expected states. resource_tracker->ReleaseResource(resource_id); { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } resource->CheckFinalState(); // "Resurrect" it and check that the callbacks are still dead. PP_Resource new_resource_id = resource->GetReference(); { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } resource->CheckFinalState(); // Unref it again and do the same. resource_tracker->ReleaseResource(new_resource_id); { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } resource->CheckFinalState(); // This shouldn't be needed, but make sure there are no stranded tasks. { ProxyAutoUnlock unlock; base::MessageLoop::current()->RunUntilIdle(); } } } // namespace ppapi