text: // 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 "chrome/browser/chromeos/power/session_length_limiter.h"
  
  #include <queue>
  #include <utility>
  #include <vector>
  
  #include "base/callback.h"
  #include "base/compiler_specific.h"
  #include "base/location.h"
  #include "base/logging.h"
  #include "base/memory/ref_counted.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/prefs/testing_pref_service.h"
  #include "base/single_thread_task_runner.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/thread_task_runner_handle.h"
  #include "base/values.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/test/base/testing_browser_process.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using ::testing::Invoke;
  using ::testing::Mock;
  using ::testing::NiceMock;
  
  namespace chromeos {
  
  namespace {
  
  class MockSessionLengthLimiterDelegate : public SessionLengthLimiter::Delegate {
   public:
    MOCK_CONST_METHOD0(GetCurrentTime, const base::Time(void));
    MOCK_METHOD0(StopSession, void(void));
  };
  
  // A SingleThreadTaskRunner that mocks the current time and allows it to be
  // fast-forwarded.
  class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
   public:
    MockTimeSingleThreadTaskRunner();
  
    // base::SingleThreadTaskRunner:
    virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
    virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
                                 const base::Closure& task,
                                 base::TimeDelta delay) OVERRIDE;
    virtual bool PostNonNestableDelayedTask(
        const tracked_objects::Location& from_here,
        const base::Closure& task,
        base::TimeDelta delay) OVERRIDE;
  
    const base::Time& GetCurrentTime() const;
  
    void FastForwardBy(int64 milliseconds);
    void FastForwardUntilNoTasksRemain();
  
   private:
    // Strict weak temporal ordering of tasks.
    class TemporalOrder {
     public:
      bool operator()(
          const std::pair<base::Time, base::Closure>& first_task,
          const std::pair<base::Time, base::Closure>& second_task) const;
    };
  
    virtual ~MockTimeSingleThreadTaskRunner();
  
    base::Time now_;
    std::priority_queue<std::pair<base::Time, base::Closure>,
                        std::vector<std::pair<base::Time, base::Closure> >,
                        TemporalOrder> tasks_;
  };
  
  }  // namespace
  
  class SessionLengthLimiterTest : public testing::Test {
   protected:
    SessionLengthLimiterTest();
  
    // testing::Test:
    virtual void SetUp() OVERRIDE;
    virtual void TearDown() OVERRIDE;
  
    void SetSessionStartTimePref(int64 session_start_time);
    void VerifySessionStartTimePref();
    void SetSessionLengthLimitPref(int64 session_length_limit);
  
    void ExpectStopSession();
    void CheckStopSessionTime();
  
    void CreateSessionLengthLimiter(bool browser_restarted);
  
    TestingPrefServiceSimple local_state_;
    scoped_refptr<MockTimeSingleThreadTaskRunner> runner_;
    base::Time session_start_time_;
    base::Time session_end_time_;
  
    MockSessionLengthLimiterDelegate* delegate_;  // Owned by
                                                  // session_length_limiter_.
    scoped_ptr<SessionLengthLimiter> session_length_limiter_;
  };
  
  MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner()
      // Initialize the mock clock to a fixed value, ensuring that timezone
      // differences or DST changes do not affect the test.
      : now_(base::Time::UnixEpoch() + base::TimeDelta::FromDays(40 * 365)) {
  }
  
  bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const {
    return true;
  }
  
  bool MockTimeSingleThreadTaskRunner::PostDelayedTask(
      const tracked_objects::Location& from_here,
      const base::Closure& task,
      base::TimeDelta delay) {
    tasks_.push(std::pair<base::Time, base::Closure>(now_ + delay, task));
    return true;
  }
  
  bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask(
      const tracked_objects::Location& from_here,
      const base::Closure& task,
      base::TimeDelta delay) {
    NOTREACHED();
    return false;
  }
  
  const base::Time& MockTimeSingleThreadTaskRunner::GetCurrentTime() const {
    return now_;
  }
  
  void MockTimeSingleThreadTaskRunner::FastForwardBy(int64 delta) {
    const base::Time latest = now_ + base::TimeDelta::FromMilliseconds(delta);
    while (!tasks_.empty() && tasks_.top().first <= latest) {
      now_ = tasks_.top().first;
      tasks_.top().second.Run();
      tasks_.pop();
    }
    now_ = latest;
  }
  
  void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() {
    while (!tasks_.empty()) {
      now_ = tasks_.top().first;
      tasks_.top().second.Run();
      tasks_.pop();
    }
  }
  
  bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()(
      const std::pair<base::Time, base::Closure>& first_task,
      const std::pair<base::Time, base::Closure>& second_task) const {
    return first_task.first < second_task.first;
  }
  
  MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() {
  }
  
  SessionLengthLimiterTest::SessionLengthLimiterTest() : delegate_(NULL) {
  }
  
  void SessionLengthLimiterTest::SetUp() {
    TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
    SessionLengthLimiter::RegisterPrefs(local_state_.registry());
    runner_ = new MockTimeSingleThreadTaskRunner;
    session_start_time_ = runner_->GetCurrentTime();
  
    delegate_ = new NiceMock<MockSessionLengthLimiterDelegate>;
    ON_CALL(*delegate_, GetCurrentTime())
        .WillByDefault(Invoke(runner_.get(),
                              &MockTimeSingleThreadTaskRunner::GetCurrentTime));
    EXPECT_CALL(*delegate_, StopSession()).Times(0);
  }
  
  void SessionLengthLimiterTest::TearDown() {
    TestingBrowserProcess::GetGlobal()->SetLocalState(NULL);
  }
  
  void SessionLengthLimiterTest::SetSessionStartTimePref(
      int64 session_start_time) {
    local_state_.SetUserPref(prefs::kSessionStartTime,
                             base::Value::CreateStringValue(
                                 base::Int64ToString(session_start_time)));
  }
  
  void SessionLengthLimiterTest::VerifySessionStartTimePref() {
    base::Time session_start_time(base::Time::FromInternalValue(
        local_state_.GetInt64(prefs::kSessionStartTime)));
    EXPECT_EQ(session_start_time_, session_start_time);
  }
  
  void SessionLengthLimiterTest::SetSessionLengthLimitPref(
      int64 session_length_limit) {
    session_end_time_ = session_start_time_ +
        base::TimeDelta::FromMilliseconds(session_length_limit);
    // If the new session end time has passed already, the session should end now.
    if (session_end_time_ < runner_->GetCurrentTime())
      session_end_time_ = runner_->GetCurrentTime();
    local_state_.SetUserPref(prefs::kSessionLengthLimit,
                             base::Value::CreateIntegerValue(
                                 session_length_limit));
  }
  
  void SessionLengthLimiterTest::ExpectStopSession() {
    Mock::VerifyAndClearExpectations(delegate_);
    EXPECT_CALL(*delegate_, StopSession())
      .Times(1)
      .WillOnce(Invoke(this, &SessionLengthLimiterTest::CheckStopSessionTime));
  }
  
  void SessionLengthLimiterTest::CheckStopSessionTime() {
    EXPECT_EQ(session_end_time_, runner_->GetCurrentTime());
  }
  
  void SessionLengthLimiterTest::CreateSessionLengthLimiter(
      bool browser_restarted) {
    session_length_limiter_.reset(
        new SessionLengthLimiter(delegate_, browser_restarted));
  }
  // Verifies that the session start time in local state is updated during login
  // if no session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeUnset) {
    CreateSessionLengthLimiter(false);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during login
  // if an invalid session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeInvalid) {
    SetSessionStartTimePref(0);
    CreateSessionLengthLimiter(false);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during login
  // if a session start time lying in the future has been stored before.
  TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeFuture) {
    SetSessionStartTimePref(
        (session_start_time_ + base::TimeDelta::FromHours(2)).ToInternalValue());
    CreateSessionLengthLimiter(false);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during login
  // if a valid session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, StartWithSessionStartTimeValid) {
    SetSessionStartTimePref(
        (session_start_time_ - base::TimeDelta::FromHours(2)).ToInternalValue());
    CreateSessionLengthLimiter(false);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during restart
  // after a crash if no session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeUnset) {
    CreateSessionLengthLimiter(true);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during restart
  // after a crash if an invalid session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeInvalid) {
    SetSessionStartTimePref(0);
    CreateSessionLengthLimiter(true);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is updated during restart
  // after a crash if a session start time lying in the future has been stored
  // before.
  TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeFuture) {
    SetSessionStartTimePref(
        (session_start_time_ + base::TimeDelta::FromHours(2)).ToInternalValue());
    CreateSessionLengthLimiter(true);
    VerifySessionStartTimePref();
  }
  
  // Verifies that the session start time in local state is *not* updated during
  // restart after a crash if a valid session start time has been stored before.
  TEST_F(SessionLengthLimiterTest, RestartWithSessionStartTimeValid) {
    session_start_time_ -= base::TimeDelta::FromHours(2);
    SetSessionStartTimePref(session_start_time_.ToInternalValue());
    CreateSessionLengthLimiter(true);
    VerifySessionStartTimePref();
  }
  
  // Creates a SessionLengthLimiter without setting a limit. Verifies that the
  // limiter does not start a timer.
  TEST_F(SessionLengthLimiterTest, RunWithoutSessionLengthLimit) {
    base::ThreadTaskRunnerHandle runner_handler(runner_);
  
    // Create a SessionLengthLimiter.
    CreateSessionLengthLimiter(false);
  
    // Verify that no timer fires to terminate the session.
    runner_->FastForwardUntilNoTasksRemain();
  }
  
  // Creates a SessionLengthLimiter after setting a limit. Verifies that the
  // limiter starts a timer and that when the session length reaches the limit,
  // the session is terminated.
  TEST_F(SessionLengthLimiterTest, RunWithSessionLengthLimit) {
    base::ThreadTaskRunnerHandle runner_handler(runner_);
  
    // Set a 60 second session time limit.
    SetSessionLengthLimitPref(60 * 1000);  // 60 seconds.
  
    // Create a SessionLengthLimiter.
    CreateSessionLengthLimiter(false);
  
    // Verify that the timer fires and the session is terminated when the session
    // length limit is reached.
    ExpectStopSession();
    runner_->FastForwardUntilNoTasksRemain();
  }
  
  // Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
  // seconds of session time to pass, then increases the limit to 90 seconds.
  // Verifies that when the session time reaches the new 90 second limit, the
  // session is terminated.
  TEST_F(SessionLengthLimiterTest, RunAndIncreaseSessionLengthLimit) {
    base::ThreadTaskRunnerHandle runner_handler(runner_);
  
    // Set a 60 second session time limit.
    SetSessionLengthLimitPref(60 * 1000);  // 60 seconds.
  
    // Create a SessionLengthLimiter.
    CreateSessionLengthLimiter(false);
  
    // Fast forward the time by 50 seconds, verifying that no timer fires to
    // terminate the session.
    runner_->FastForwardBy(50 * 1000);  // 50 seconds.
  
    // Increase the session length limit to 90 seconds.
    SetSessionLengthLimitPref(90 * 1000);  // 90 seconds.
  
    // Verify that the the timer fires and the session is terminated when the
    // session length limit is reached.
    ExpectStopSession();
    runner_->FastForwardUntilNoTasksRemain();
  }
  
  // Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
  // seconds of session time to pass, then decreases the limit to 40 seconds.
  // Verifies that when the limit is decreased to 40 seconds after 50 seconds of
  // session time have passed, the next timer tick causes the session to be
  // terminated.
  TEST_F(SessionLengthLimiterTest, RunAndDecreaseSessionLengthLimit) {
    base::ThreadTaskRunnerHandle runner_handler(runner_);
  
    // Set a 60 second session time limit.
    SetSessionLengthLimitPref(60 * 1000);  // 60 seconds.
  
    // Create a SessionLengthLimiter.
    CreateSessionLengthLimiter(false);
  
    // Fast forward the time by 50 seconds, verifying that no timer fires to
    // terminate the session.
    runner_->FastForwardBy(50 * 1000);  // 50 seconds.
  
    // Verify that reducing the session length limit below the 50 seconds that
    // have already elapsed causes the session to be terminated immediately.
    ExpectStopSession();
    SetSessionLengthLimitPref(40 * 1000);  // 40 seconds.
  }
  
  // Creates a SessionLengthLimiter after setting a 60 second limit, allows 50
  // seconds of session time to pass, then removes the limit. Verifies that after
  // the limit is removed, the session is not terminated when the session time
  // reaches the original 60 second limit.
  TEST_F(SessionLengthLimiterTest, RunAndRemoveSessionLengthLimit) {
    base::ThreadTaskRunnerHandle runner_handler(runner_);
  
    // Set a 60 second session time limit.
    SetSessionLengthLimitPref(60 * 1000);  // 60 seconds.
  
    // Create a SessionLengthLimiter.
    CreateSessionLengthLimiter(false);
  
    // Fast forward the time by 50 seconds, verifying that no timer fires to
    // terminate the session.
    runner_->FastForwardBy(50 * 1000);  // 50 seconds.
  
    // Remove the session length limit.
    local_state_.RemoveUserPref(prefs::kSessionLengthLimit);
  
    // Verify that no timer fires to terminate the session.
    runner_->FastForwardUntilNoTasksRemain();
  }
  
  }  // namespace chromeos
  