普通文本  |  1287行  |  47.76 KB

// Copyright 2011 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 "cc/scheduler/scheduler.h"

#include <string>
#include <vector>

#include "base/logging.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "cc/test/scheduler_test_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#define EXPECT_ACTION(action, client, action_index, expected_num_actions) \
  EXPECT_EQ(expected_num_actions, client.num_actions_());                 \
  ASSERT_LT(action_index, client.num_actions_());                         \
  do {                                                                    \
    EXPECT_STREQ(action, client.Action(action_index));                    \
    for (int i = expected_num_actions; i < client.num_actions_(); ++i)    \
      ADD_FAILURE() << "Unexpected action: " << client.Action(i) <<       \
          " with state:\n" << client.StateForAction(action_index);        \
  } while (false)

#define EXPECT_SINGLE_ACTION(action, client) \
  EXPECT_ACTION(action, client, 0, 1)

namespace cc {
namespace {

void InitializeOutputSurfaceAndFirstCommit(Scheduler* scheduler) {
  scheduler->DidCreateAndInitializeOutputSurface();
  scheduler->SetNeedsCommit();
  scheduler->FinishCommit();
  // Go through the motions to draw the commit.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  // We need another BeginImplFrame so Scheduler calls
  // SetNeedsBeginImplFrame(false).
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
}

class FakeSchedulerClient : public SchedulerClient {
 public:
  FakeSchedulerClient()
  : needs_begin_impl_frame_(false) {
    Reset();
  }

  void Reset() {
    actions_.clear();
    states_.clear();
    draw_will_happen_ = true;
    swap_will_happen_if_draw_happens_ = true;
    num_draws_ = 0;
    log_anticipated_draw_time_change_ = false;
  }

  Scheduler* CreateScheduler(const SchedulerSettings& settings) {
    scheduler_ = Scheduler::Create(this, settings, 0);
    return scheduler_.get();
  }

  // Most tests don't care about DidAnticipatedDrawTimeChange, so only record it
  // for tests that do.
  void set_log_anticipated_draw_time_change(bool log) {
    log_anticipated_draw_time_change_ = log;
  }
  bool needs_begin_impl_frame() { return needs_begin_impl_frame_; }
  int num_draws() const { return num_draws_; }
  int num_actions_() const { return static_cast<int>(actions_.size()); }
  const char* Action(int i) const { return actions_[i]; }
  base::Value& StateForAction(int i) const { return *states_[i]; }

  int ActionIndex(const char* action) const {
    for (size_t i = 0; i < actions_.size(); i++)
      if (!strcmp(actions_[i], action))
        return i;
    return -1;
  }

  bool HasAction(const char* action) const {
    return ActionIndex(action) >= 0;
  }

  void SetDrawWillHappen(bool draw_will_happen) {
    draw_will_happen_ = draw_will_happen;
  }
  void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) {
    swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens;
  }

  // SchedulerClient implementation.
  virtual void SetNeedsBeginImplFrame(bool enable) OVERRIDE {
    actions_.push_back("SetNeedsBeginImplFrame");
    states_.push_back(scheduler_->StateAsValue().release());
    needs_begin_impl_frame_ = enable;
  }
  virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {
    actions_.push_back("ScheduledActionSendBeginMainFrame");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible()
      OVERRIDE {
    actions_.push_back("ScheduledActionDrawAndSwapIfPossible");
    states_.push_back(scheduler_->StateAsValue().release());
    num_draws_++;
    bool did_readback = false;
    return DrawSwapReadbackResult(
        draw_will_happen_,
        draw_will_happen_ && swap_will_happen_if_draw_happens_,
        did_readback);
  }
  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE {
    actions_.push_back("ScheduledActionDrawAndSwapForced");
    states_.push_back(scheduler_->StateAsValue().release());
    bool did_draw = true;
    bool did_swap = swap_will_happen_if_draw_happens_;
    bool did_readback = false;
    return DrawSwapReadbackResult(did_draw, did_swap, did_readback);
  }
  virtual DrawSwapReadbackResult ScheduledActionDrawAndReadback() OVERRIDE {
    actions_.push_back("ScheduledActionDrawAndReadback");
    states_.push_back(scheduler_->StateAsValue().release());
    bool did_draw = true;
    bool did_swap = false;
    bool did_readback = true;
    return DrawSwapReadbackResult(did_draw, did_swap, did_readback);
  }
  virtual void ScheduledActionCommit() OVERRIDE {
    actions_.push_back("ScheduledActionCommit");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void ScheduledActionUpdateVisibleTiles() OVERRIDE {
    actions_.push_back("ScheduledActionUpdateVisibleTiles");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void ScheduledActionActivatePendingTree() OVERRIDE {
    actions_.push_back("ScheduledActionActivatePendingTree");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {
    actions_.push_back("ScheduledActionBeginOutputSurfaceCreation");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void ScheduledActionAcquireLayerTexturesForMainThread() OVERRIDE {
    actions_.push_back("ScheduledActionAcquireLayerTexturesForMainThread");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void ScheduledActionManageTiles() OVERRIDE {
    actions_.push_back("ScheduledActionManageTiles");
    states_.push_back(scheduler_->StateAsValue().release());
  }
  virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {
    if (log_anticipated_draw_time_change_)
      actions_.push_back("DidAnticipatedDrawTimeChange");
  }
  virtual base::TimeDelta DrawDurationEstimate() OVERRIDE {
    return base::TimeDelta();
  }
  virtual base::TimeDelta BeginMainFrameToCommitDurationEstimate() OVERRIDE {
    return base::TimeDelta();
  }
  virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE {
    return base::TimeDelta();
  }

  virtual void PostBeginImplFrameDeadline(const base::Closure& closure,
                                          base::TimeTicks deadline) OVERRIDE {
    actions_.push_back("PostBeginImplFrameDeadlineTask");
    states_.push_back(scheduler_->StateAsValue().release());
  }

  virtual void DidBeginImplFrameDeadline() OVERRIDE {}

 protected:
  bool needs_begin_impl_frame_;
  bool draw_will_happen_;
  bool swap_will_happen_if_draw_happens_;
  int num_draws_;
  bool log_anticipated_draw_time_change_;
  std::vector<const char*> actions_;
  ScopedVector<base::Value> states_;
  scoped_ptr<Scheduler> scheduler_;
};

TEST(SchedulerTest, InitializeOutputSurfaceDoesNotBeginImplFrame) {
  FakeSchedulerClient client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);

  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
  client.Reset();
  scheduler->DidCreateAndInitializeOutputSurface();
  EXPECT_EQ(0, client.num_actions_());
}

void RequestCommit(bool deadline_scheduling_enabled) {
  FakeSchedulerClient client;
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);

  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
  InitializeOutputSurfaceAndFirstCommit(scheduler);

  // SetNeedsCommit should begin the frame on the next BeginImplFrame.
  client.Reset();
  scheduler->SetNeedsCommit();
  EXPECT_TRUE(client.needs_begin_impl_frame());
  if (deadline_scheduling_enabled) {
    EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  } else {
    EXPECT_EQ(client.num_actions_(), 2);
    EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));
    EXPECT_TRUE(client.HasAction("SetNeedsBeginImplFrame"));
  }
  client.Reset();

  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // If we don't swap on the deadline, we need to request another
  // BeginImplFrame.
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // FinishCommit should commit
  scheduler->FinishCommit();
  EXPECT_SINGLE_ACTION("ScheduledActionCommit", client);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // BeginImplFrame should prepare the draw.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // BeginImplFrame deadline should draw.
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // The following BeginImplFrame deadline should SetNeedsBeginImplFrame(false)
  // to avoid excessive toggles.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();

  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_FALSE(client.needs_begin_impl_frame());
  client.Reset();
}

TEST(SchedulerTest, RequestCommit) {
  bool deadline_scheduling_enabled = false;
  RequestCommit(deadline_scheduling_enabled);
}

TEST(SchedulerTest, RequestCommit_Deadline) {
  bool deadline_scheduling_enabled = true;
  RequestCommit(deadline_scheduling_enabled);
}

void RequestCommitAfterBeginMainFrameSent(
    bool deadline_scheduling_enabled) {
  FakeSchedulerClient client;
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);

  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  // SetNeedsCommit should begin the frame.
  scheduler->SetNeedsCommit();
  if (deadline_scheduling_enabled) {
    EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  } else {
    EXPECT_EQ(client.num_actions_(), 2);
    EXPECT_TRUE(client.HasAction("SetNeedsBeginImplFrame"));
    EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));
  }

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_EQ(client.num_actions_(), 2);
    EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));
    EXPECT_TRUE(client.HasAction("PostBeginImplFrameDeadlineTask"));
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }

  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // Now SetNeedsCommit again. Calling here means we need a second commit.
  scheduler->SetNeedsCommit();
  EXPECT_EQ(client.num_actions_(), 0);
  client.Reset();

  // Finish the first commit.
  scheduler->FinishCommit();
  EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
  EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  if (deadline_scheduling_enabled) {
    EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
    EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  } else {
    EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 3);
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 1, 3);
    EXPECT_ACTION("SetNeedsBeginImplFrame", client, 2, 3);
  }

  // Because we just swapped, the Scheduler should also request the next
  // BeginImplFrame from the OutputSurface.
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // Since another commit is needed, the next BeginImplFrame should initiate
  // the second commit.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_EQ(client.num_actions_(), 2);
    EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));
    EXPECT_TRUE(client.HasAction("PostBeginImplFrameDeadlineTask"));
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }
  client.Reset();

  // Finishing the commit before the deadline should post a new deadline task
  // to trigger the deadline early.
  scheduler->FinishCommit();
  EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
  EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // On the next BeginImplFrame, verify we go back to a quiescent state and
  // no longer request BeginImplFrames.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_FALSE(client.needs_begin_impl_frame());
  client.Reset();
}

TEST(SchedulerTest, RequestCommitAfterBeginMainFrameSent) {
  bool deadline_scheduling_enabled = false;
  RequestCommitAfterBeginMainFrameSent(deadline_scheduling_enabled);
}

TEST(SchedulerTest, RequestCommitAfterBeginMainFrameSent_Deadline) {
  bool deadline_scheduling_enabled = true;
  RequestCommitAfterBeginMainFrameSent(deadline_scheduling_enabled);
}

void TextureAcquisitionCausesCommitInsteadOfDraw(
    bool deadline_scheduling_enabled) {
  FakeSchedulerClient client;
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);

  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();
  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_TRUE(client.needs_begin_impl_frame());

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(client.needs_begin_impl_frame());

  client.Reset();
  scheduler->SetMainThreadNeedsLayerTextures();
  EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
                       client);

  // We should request a BeginImplFrame in anticipation of a draw.
  client.Reset();
  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // No draw happens since the textures are acquired by the main thread.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  client.Reset();
  scheduler->SetNeedsCommit();
  if (deadline_scheduling_enabled) {
    EXPECT_EQ(0, client.num_actions_());
  } else {
    EXPECT_SINGLE_ACTION("ScheduledActionSendBeginMainFrame", client);
  }

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }

  // Commit will release the texture.
  client.Reset();
  scheduler->FinishCommit();
  EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
  EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  EXPECT_TRUE(scheduler->RedrawPending());

  // Now we can draw again after the commit happens.
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Make sure we stop requesting BeginImplFrames if we don't swap.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_FALSE(client.needs_begin_impl_frame());
}

TEST(SchedulerTest, TextureAcquisitionCausesCommitInsteadOfDraw) {
  bool deadline_scheduling_enabled = false;
  TextureAcquisitionCausesCommitInsteadOfDraw(deadline_scheduling_enabled);
}

TEST(SchedulerTest, TextureAcquisitionCausesCommitInsteadOfDraw_Deadline) {
  bool deadline_scheduling_enabled = true;
  TextureAcquisitionCausesCommitInsteadOfDraw(deadline_scheduling_enabled);
}

void TextureAcquisitionCollision(bool deadline_scheduling_enabled) {
  FakeSchedulerClient client;
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);

  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
  InitializeOutputSurfaceAndFirstCommit(scheduler);

  client.Reset();
  scheduler->SetNeedsCommit();
if (deadline_scheduling_enabled) {
    EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  } else {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  }

  client.Reset();
  scheduler->SetMainThreadNeedsLayerTextures();
  EXPECT_SINGLE_ACTION(
      "ScheduledActionAcquireLayerTexturesForMainThread", client);

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }

  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);

  // Although the compositor cannot draw because textures are locked by main
  // thread, we continue requesting SetNeedsBeginImplFrame in anticipation of
  // the unlock.
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Trigger the commit
  scheduler->FinishCommit();
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Between commit and draw, texture acquisition for main thread delayed,
  // and main thread blocks.
  client.Reset();
  scheduler->SetMainThreadNeedsLayerTextures();
  EXPECT_EQ(0, client.num_actions_());

  // No implicit commit is expected.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);

  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 3);
  EXPECT_ACTION(
      "ScheduledActionAcquireLayerTexturesForMainThread", client, 1, 3);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 2, 3);
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // The compositor should not draw because textures are locked by main
  // thread.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  EXPECT_FALSE(client.needs_begin_impl_frame());

  // The impl thread need an explicit commit from the main thread to lock
  // the textures.
  client.Reset();
  scheduler->SetNeedsCommit();
  if (deadline_scheduling_enabled) {
    EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);
  } else {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  }
  EXPECT_TRUE(client.needs_begin_impl_frame());

  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  if (deadline_scheduling_enabled) {
    EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
    EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  } else {
    EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  }
  client.Reset();

  // Trigger the commit, which will trigger the deadline task early.
  scheduler->FinishCommit();
  EXPECT_ACTION("ScheduledActionCommit", client, 0, 2);
  EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();

  // Verify we draw on the next BeginImplFrame deadline
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_ACTION("ScheduledActionDrawAndSwapIfPossible", client, 0, 2);
  EXPECT_ACTION("SetNeedsBeginImplFrame", client, 1, 2);
  EXPECT_TRUE(client.needs_begin_impl_frame());
  client.Reset();
}

TEST(SchedulerTest, TextureAcquisitionCollision) {
  bool deadline_scheduling_enabled = false;
  TextureAcquisitionCollision(deadline_scheduling_enabled);
}

TEST(SchedulerTest, TextureAcquisitionCollision_Deadline) {
  bool deadline_scheduling_enabled = true;
  TextureAcquisitionCollision(deadline_scheduling_enabled);
}

void VisibilitySwitchWithTextureAcquisition(bool deadline_scheduling_enabled) {
  FakeSchedulerClient client;
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = deadline_scheduling_enabled;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);

  EXPECT_SINGLE_ACTION("ScheduledActionBeginOutputSurfaceCreation", client);
  client.Reset();
  scheduler->DidCreateAndInitializeOutputSurface();

  scheduler->SetNeedsCommit();
  if (deadline_scheduling_enabled) {
    scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
    scheduler->OnBeginImplFrameDeadline();
  }
  scheduler->FinishCommit();
  scheduler->SetMainThreadNeedsLayerTextures();
  scheduler->SetNeedsCommit();
  client.Reset();
  // Verify that pending texture acquisition fires when visibility
  // is lost in order to avoid a deadlock.
  scheduler->SetVisible(false);
  EXPECT_SINGLE_ACTION("ScheduledActionAcquireLayerTexturesForMainThread",
                       client);

  client.Reset();
  scheduler->SetVisible(true);
  EXPECT_EQ(0, client.num_actions_());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Regaining visibility with textures acquired by main thread while
  // compositor is waiting for first draw should result in a request
  // for a new frame in order to escape a deadlock.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_ACTION("ScheduledActionSendBeginMainFrame", client, 0, 2);
  EXPECT_ACTION("PostBeginImplFrameDeadlineTask", client, 1, 2);
}

TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition) {
  bool deadline_scheduling_enabled = false;
  VisibilitySwitchWithTextureAcquisition(deadline_scheduling_enabled);
}

TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition_Deadline) {
  bool deadline_scheduling_enabled = true;
  VisibilitySwitchWithTextureAcquisition(deadline_scheduling_enabled);
}

class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient {
 public:
  virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {}
  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible()
      OVERRIDE {
    // Only SetNeedsRedraw the first time this is called
    if (!num_draws_)
      scheduler_->SetNeedsRedraw();
    return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
  }

  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE {
    NOTREACHED();
    bool did_draw = true;
    bool did_swap = true;
    bool did_readback = false;
    return DrawSwapReadbackResult(did_draw, did_swap, did_readback);
  }

  virtual void ScheduledActionCommit() OVERRIDE {}
  virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {}
  virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
};

// Tests for two different situations:
// 1. the scheduler dropping SetNeedsRedraw requests that happen inside
//    a ScheduledActionDrawAndSwap
// 2. the scheduler drawing twice inside a single tick
TEST(SchedulerTest, RequestRedrawInsideDraw) {
  SchedulerClientThatsetNeedsDrawInsideDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());

  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // We stop requesting BeginImplFrames after a BeginImplFrame where we don't
  // swap.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(client.needs_begin_impl_frame());
}

// Test that requesting redraw inside a failed draw doesn't lose the request.
TEST(SchedulerTest, RequestRedrawInsideFailedDraw) {
  SchedulerClientThatsetNeedsDrawInsideDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  client.SetDrawWillHappen(false);

  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());

  // Fail the draw.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());

  // We have a commit pending and the draw failed, and we didn't lose the redraw
  // request.
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Fail the draw again.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Draw successfully.
  client.SetDrawWillHappen(true);
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(3, client.num_draws());
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
}

class SchedulerClientThatSetNeedsCommitInsideDraw : public FakeSchedulerClient {
 public:
  SchedulerClientThatSetNeedsCommitInsideDraw()
      : set_needs_commit_on_next_draw_(false) {}

  virtual void ScheduledActionSendBeginMainFrame() OVERRIDE {}
  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible()
      OVERRIDE {
    // Only SetNeedsCommit the first time this is called
    if (set_needs_commit_on_next_draw_) {
      scheduler_->SetNeedsCommit();
      set_needs_commit_on_next_draw_ = false;
    }
    return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
  }

  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapForced() OVERRIDE {
    NOTREACHED();
    bool did_draw = true;
    bool did_swap = false;
    bool did_readback = false;
    return DrawSwapReadbackResult(did_draw, did_swap, did_readback);
  }

  virtual void ScheduledActionCommit() OVERRIDE {}
  virtual void ScheduledActionBeginOutputSurfaceCreation() OVERRIDE {}
  virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}

  void SetNeedsCommitOnNextDraw() { set_needs_commit_on_next_draw_ = true; }

 private:
  bool set_needs_commit_on_next_draw_;
};

// Tests for the scheduler infinite-looping on SetNeedsCommit requests that
// happen inside a ScheduledActionDrawAndSwap
TEST(SchedulerTest, RequestCommitInsideDraw) {
  SchedulerClientThatSetNeedsCommitInsideDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  EXPECT_FALSE(client.needs_begin_impl_frame());
  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_EQ(0, client.num_draws());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  client.SetNeedsCommitOnNextDraw();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  client.SetNeedsCommitOnNextDraw();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  scheduler->FinishCommit();

  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());

  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->CommitPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // We stop requesting BeginImplFrames after a BeginImplFrame where we don't
  // swap.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->CommitPending());
  EXPECT_FALSE(client.needs_begin_impl_frame());
}

// Tests that when a draw fails then the pending commit should not be dropped.
TEST(SchedulerTest, RequestCommitInsideFailedDraw) {
  SchedulerClientThatsetNeedsDrawInsideDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  client.SetDrawWillHappen(false);

  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());

  // Fail the draw.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());

  // We have a commit pending and the draw failed, and we didn't lose the commit
  // request.
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Fail the draw again.
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Draw successfully.
  client.SetDrawWillHappen(true);
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(3, client.num_draws());
  EXPECT_TRUE(scheduler->CommitPending());
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
}

TEST(SchedulerTest, NoSwapWhenDrawFails) {
  SchedulerClientThatSetNeedsCommitInsideDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);
  client.Reset();

  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());

  // Draw successfully, this starts a new frame.
  client.SetNeedsCommitOnNextDraw();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());

  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());

  // Fail to draw, this should not start a frame.
  client.SetDrawWillHappen(false);
  client.SetNeedsCommitOnNextDraw();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(2, client.num_draws());
}

TEST(SchedulerTest, NoSwapWhenSwapFailsDuringForcedCommit) {
  FakeSchedulerClient client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);

  // Tell the client that it will fail to swap.
  client.SetDrawWillHappen(true);
  client.SetSwapWillHappenIfDrawHappens(false);

  // Get the compositor to do a ScheduledActionDrawAndReadback.
  scheduler->SetCanDraw(true);
  scheduler->SetNeedsRedraw();
  scheduler->SetNeedsForcedCommitForReadback();
  scheduler->FinishCommit();
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback"));
}

TEST(SchedulerTest, BackToBackReadbackAllowed) {
  // Some clients call readbacks twice in a row before the replacement
  // commit comes in.  Make sure it is allowed.
  FakeSchedulerClient client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);

  // Get the compositor to do 2 ScheduledActionDrawAndReadbacks before
  // the replacement commit comes in.
  scheduler->SetCanDraw(true);
  scheduler->SetNeedsRedraw();
  scheduler->SetNeedsForcedCommitForReadback();
  scheduler->FinishCommit();
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback"));

  client.Reset();
  scheduler->SetNeedsForcedCommitForReadback();
  scheduler->FinishCommit();
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndReadback"));

  // The replacement commit comes in after 2 readbacks.
  client.Reset();
  scheduler->FinishCommit();
}


class SchedulerClientNeedsManageTilesInDraw : public FakeSchedulerClient {
 public:
  virtual DrawSwapReadbackResult ScheduledActionDrawAndSwapIfPossible()
      OVERRIDE {
    scheduler_->SetNeedsManageTiles();
    return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
  }
};

// Test manage tiles is independant of draws.
TEST(SchedulerTest, ManageTiles) {
  SchedulerClientNeedsManageTilesInDraw client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);

  // Request both draw and manage tiles. ManageTiles shouldn't
  // be trigged until BeginImplFrame.
  client.Reset();
  scheduler->SetNeedsManageTiles();
  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_TRUE(scheduler->ManageTilesPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());
  EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles"));
  EXPECT_FALSE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));

  // We have no immediate actions to perform, so the BeginImplFrame should post
  // the deadline task.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);

  // On the deadline, he actions should have occured in the right order.
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
  EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles"));
  EXPECT_LT(client.ActionIndex("ScheduledActionDrawAndSwapIfPossible"),
            client.ActionIndex("ScheduledActionManageTiles"));
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->ManageTilesPending());

  // Request a draw. We don't need a ManageTiles yet.
  client.Reset();
  scheduler->SetNeedsRedraw();
  EXPECT_TRUE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->ManageTilesPending());
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_EQ(0, client.num_draws());

  // We have no immediate actions to perform, so the BeginImplFrame should post
  // the deadline task.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);

  // Draw. The draw will trigger SetNeedsManageTiles, and
  // then the ManageTiles action will be triggered after the Draw.
  // Afterwards, neither a draw nor ManageTiles are pending.
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
  EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles"));
  EXPECT_LT(client.ActionIndex("ScheduledActionDrawAndSwapIfPossible"),
            client.ActionIndex("ScheduledActionManageTiles"));
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->ManageTilesPending());

  // We need a BeginImplFrame where we don't swap to go idle.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_SINGLE_ACTION("SetNeedsBeginImplFrame", client);;
  EXPECT_EQ(0, client.num_draws());

  // Now trigger a ManageTiles outside of a draw. We will then need
  // a begin-frame for the ManageTiles, but we don't need a draw.
  client.Reset();
  EXPECT_FALSE(client.needs_begin_impl_frame());
  scheduler->SetNeedsManageTiles();
  EXPECT_TRUE(client.needs_begin_impl_frame());
  EXPECT_TRUE(scheduler->ManageTilesPending());
  EXPECT_FALSE(scheduler->RedrawPending());

  // BeginImplFrame. There will be no draw, only ManageTiles.
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);
  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(0, client.num_draws());
  EXPECT_FALSE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
  EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles"));
}

// Test that ManageTiles only happens once per frame.  If an external caller
// initiates it, then the state machine should not on that frame.
TEST(SchedulerTest, ManageTilesOncePerFrame) {
  FakeSchedulerClient client;
  SchedulerSettings default_scheduler_settings;
  Scheduler* scheduler = client.CreateScheduler(default_scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);

  // If DidManageTiles during a frame, then ManageTiles should not occur again.
  scheduler->SetNeedsManageTiles();
  scheduler->SetNeedsRedraw();
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);

  EXPECT_TRUE(scheduler->ManageTilesPending());
  scheduler->DidManageTiles();
  EXPECT_FALSE(scheduler->ManageTilesPending());

  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
  EXPECT_FALSE(client.HasAction("ScheduledActionManageTiles"));
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->ManageTilesPending());

  // Next frame without DidManageTiles should ManageTiles with draw.
  scheduler->SetNeedsManageTiles();
  scheduler->SetNeedsRedraw();
  client.Reset();
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_SINGLE_ACTION("PostBeginImplFrameDeadlineTask", client);

  client.Reset();
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(1, client.num_draws());
  EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapIfPossible"));
  EXPECT_TRUE(client.HasAction("ScheduledActionManageTiles"));
  EXPECT_LT(client.ActionIndex("ScheduledActionDrawAndSwapIfPossible"),
            client.ActionIndex("ScheduledActionManageTiles"));
  EXPECT_FALSE(scheduler->RedrawPending());
  EXPECT_FALSE(scheduler->ManageTilesPending());
}

class SchedulerClientWithFixedEstimates : public FakeSchedulerClient {
 public:
  SchedulerClientWithFixedEstimates(
      base::TimeDelta draw_duration,
      base::TimeDelta begin_main_frame_to_commit_duration,
      base::TimeDelta commit_to_activate_duration)
      : draw_duration_(draw_duration),
        begin_main_frame_to_commit_duration_(
            begin_main_frame_to_commit_duration),
        commit_to_activate_duration_(commit_to_activate_duration) {}

  virtual base::TimeDelta DrawDurationEstimate() OVERRIDE {
    return draw_duration_;
  }
  virtual base::TimeDelta BeginMainFrameToCommitDurationEstimate() OVERRIDE {
    return begin_main_frame_to_commit_duration_;
  }
  virtual base::TimeDelta CommitToActivateDurationEstimate() OVERRIDE {
    return commit_to_activate_duration_;
  }

 private:
    base::TimeDelta draw_duration_;
    base::TimeDelta begin_main_frame_to_commit_duration_;
    base::TimeDelta commit_to_activate_duration_;
};

void MainFrameInHighLatencyMode(int64 begin_main_frame_to_commit_estimate_in_ms,
                                int64 commit_to_activate_estimate_in_ms,
                                bool should_send_begin_main_frame) {
  // Set up client with specified estimates (draw duration is set to 1).
  SchedulerClientWithFixedEstimates client(
      base::TimeDelta::FromMilliseconds(1),
      base::TimeDelta::FromMilliseconds(
          begin_main_frame_to_commit_estimate_in_ms),
      base::TimeDelta::FromMilliseconds(commit_to_activate_estimate_in_ms));
  SchedulerSettings scheduler_settings;
  scheduler_settings.deadline_scheduling_enabled = true;
  scheduler_settings.switch_to_low_latency_if_possible = true;
  Scheduler* scheduler = client.CreateScheduler(scheduler_settings);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->SetCanDraw(true);
  InitializeOutputSurfaceAndFirstCommit(scheduler);

  // Impl thread hits deadline before commit finishes.
  client.Reset();
  scheduler->SetNeedsCommit();
  EXPECT_FALSE(scheduler->MainThreadIsInHighLatencyMode());
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_FALSE(scheduler->MainThreadIsInHighLatencyMode());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
  scheduler->FinishCommit();
  EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
  EXPECT_TRUE(client.HasAction("ScheduledActionSendBeginMainFrame"));

  client.Reset();
  scheduler->SetNeedsCommit();
  EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
  scheduler->BeginImplFrame(BeginFrameArgs::CreateForTesting());
  EXPECT_TRUE(scheduler->MainThreadIsInHighLatencyMode());
  scheduler->OnBeginImplFrameDeadline();
  EXPECT_EQ(scheduler->MainThreadIsInHighLatencyMode(),
            should_send_begin_main_frame);
  EXPECT_EQ(client.HasAction("ScheduledActionSendBeginMainFrame"),
            should_send_begin_main_frame);
}

TEST(SchedulerTest,
    SkipMainFrameIfHighLatencyAndCanCommitAndActivateBeforeDeadline) {
  // Set up client so that estimates indicate that we can commit and activate
  // before the deadline (~8ms by default).
  MainFrameInHighLatencyMode(1, 1, false);
}

TEST(SchedulerTest, NotSkipMainFrameIfHighLatencyAndCanCommitTooLong) {
  // Set up client so that estimates indicate that the commit cannot finish
  // before the deadline (~8ms by default).
  MainFrameInHighLatencyMode(10, 1, true);
}

TEST(SchedulerTest, NotSkipMainFrameIfHighLatencyAndCanActivateTooLong) {
  // Set up client so that estimates indicate that the activate cannot finish
  // before the deadline (~8ms by default).
  MainFrameInHighLatencyMode(1, 10, true);
}

void SpinForMillis(int millis) {
  base::RunLoop run_loop;
  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      run_loop.QuitClosure(),
      base::TimeDelta::FromMilliseconds(millis));
  run_loop.Run();
}

TEST(SchedulerTest, PollForCommitCompletion) {
  FakeSchedulerClient client;
  client.set_log_anticipated_draw_time_change(true);
  SchedulerSettings settings = SchedulerSettings();
  settings.throttle_frame_production = false;
  Scheduler* scheduler = client.CreateScheduler(settings);

  scheduler->SetCanDraw(true);
  scheduler->SetCanStart();
  scheduler->SetVisible(true);
  scheduler->DidCreateAndInitializeOutputSurface();

  scheduler->SetNeedsCommit();
  EXPECT_TRUE(scheduler->CommitPending());
  scheduler->FinishCommit();
  scheduler->SetNeedsRedraw();
  BeginFrameArgs impl_frame_args = BeginFrameArgs::CreateForTesting();
  const int interval = 1;
  impl_frame_args.interval = base::TimeDelta::FromMilliseconds(interval);
  scheduler->BeginImplFrame(impl_frame_args);
  scheduler->OnBeginImplFrameDeadline();

  // At this point, we've drawn a frame.  Start another commit, but hold off on
  // the FinishCommit for now.
  EXPECT_FALSE(scheduler->CommitPending());
  scheduler->SetNeedsCommit();
  EXPECT_TRUE(scheduler->CommitPending());

  // Spin the event loop a few times and make sure we get more
  // DidAnticipateDrawTimeChange calls every time.
  int actions_so_far = client.num_actions_();

  // Does three iterations to make sure that the timer is properly repeating.
  for (int i = 0; i < 3; ++i) {
    // Wait for 2x the frame interval to match
    // Scheduler::advance_commit_state_timer_'s rate.
    SpinForMillis(interval * 2);
    EXPECT_GT(client.num_actions_(), actions_so_far);
    EXPECT_STREQ(client.Action(client.num_actions_() - 1),
                 "DidAnticipatedDrawTimeChange");
    actions_so_far = client.num_actions_();
  }
}

}  // namespace
}  // namespace cc