blob: cd787f8fe67f351307dc9015c308e47d90a88488 [file] [log] [blame]
// 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 {
ProxyLock::EnableLockingOnThreadForTest();
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<CallbackRunInfo*>(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> resource(new Resource(OBJECT_IS_IMPL, pp_instance()));
// Set up case (1) (see above).
EXPECT_EQ(0U, info_did_run().run_count);
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);
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<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_;
};
} // 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<CallbackMockResource> 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<CallbackMockResource> 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<CallbackMockResource> 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