root/ppapi/shared_impl/tracked_callback_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pp_instance
  2. SetUp
  3. TearDown
  4. TestCallback
  5. info_did_run
  6. info_did_abort
  7. info_didnt_run
  8. TEST_F
  9. SetupForTest
  10. CompletionTask
  11. CheckIntermediateState
  12. CheckFinalState
  13. TEST_F
  14. TEST_F

// 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

/* [<][>][^][v][top][bottom][index][help] */