1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ash/system/chromeos/session/logout_confirmation_controller.h" 6 7#include <queue> 8#include <utility> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/bind_helpers.h" 13#include "base/compiler_specific.h" 14#include "base/location.h" 15#include "base/memory/ref_counted.h" 16#include "base/single_thread_task_runner.h" 17#include "base/thread_task_runner_handle.h" 18#include "base/time/tick_clock.h" 19#include "testing/gtest/include/gtest/gtest.h" 20 21namespace ash { 22namespace { 23 24// A SingleThreadTaskRunner that mocks the current time and allows it to be 25// fast-forwarded. TODO(bartfab): Copies of this class exist in several tests. 26// Consolidate them (crbug.com/329911). 27class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner { 28 public: 29 MockTimeSingleThreadTaskRunner(); 30 31 // base::SingleThreadTaskRunner: 32 virtual bool RunsTasksOnCurrentThread() const OVERRIDE; 33 virtual bool PostDelayedTask(const tracked_objects::Location& from_here, 34 const base::Closure& task, 35 base::TimeDelta delay) OVERRIDE; 36 virtual bool PostNonNestableDelayedTask( 37 const tracked_objects::Location& from_here, 38 const base::Closure& task, 39 base::TimeDelta delay) OVERRIDE; 40 41 const base::TimeTicks& GetCurrentTime() const; 42 43 void FastForwardBy(base::TimeDelta delta); 44 void FastForwardUntilNoTasksRemain(); 45 46 private: 47 // Strict weak temporal ordering of tasks. 48 class TemporalOrder { 49 public: 50 bool operator()( 51 const std::pair<base::TimeTicks, base::Closure>& first_task, 52 const std::pair<base::TimeTicks, base::Closure>& second_task) const; 53 }; 54 55 virtual ~MockTimeSingleThreadTaskRunner(); 56 57 base::TimeTicks now_; 58 std::priority_queue<std::pair<base::TimeTicks, base::Closure>, 59 std::vector<std::pair<base::TimeTicks, base::Closure> >, 60 TemporalOrder> tasks_; 61 62 DISALLOW_COPY_AND_ASSIGN(MockTimeSingleThreadTaskRunner); 63}; 64 65// A base::TickClock that uses a MockTimeSingleThreadTaskRunner as the source of 66// the current time. 67class MockClock : public base::TickClock { 68 public: 69 explicit MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner); 70 virtual ~MockClock(); 71 72 // base::TickClock: 73 virtual base::TimeTicks NowTicks() OVERRIDE; 74 75 private: 76 scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner_; 77 78 DISALLOW_COPY_AND_ASSIGN(MockClock); 79}; 80 81 82MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner() { 83} 84 85bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const { 86 return true; 87} 88 89bool MockTimeSingleThreadTaskRunner::PostDelayedTask( 90 const tracked_objects::Location& from_here, 91 const base::Closure& task, 92 base::TimeDelta delay) { 93 tasks_.push(std::make_pair(now_ + delay, task)); 94 return true; 95} 96 97bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask( 98 const tracked_objects::Location& from_here, 99 const base::Closure& task, 100 base::TimeDelta delay) { 101 NOTREACHED(); 102 return false; 103} 104 105const base::TimeTicks& MockTimeSingleThreadTaskRunner::GetCurrentTime() const { 106 return now_; 107} 108 109void MockTimeSingleThreadTaskRunner::FastForwardBy(base::TimeDelta delta) { 110 const base::TimeTicks latest = now_ + delta; 111 while (!tasks_.empty() && tasks_.top().first <= latest) { 112 now_ = tasks_.top().first; 113 base::Closure task = tasks_.top().second; 114 tasks_.pop(); 115 task.Run(); 116 } 117 now_ = latest; 118} 119 120void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() { 121 while (!tasks_.empty()) { 122 now_ = tasks_.top().first; 123 base::Closure task = tasks_.top().second; 124 tasks_.pop(); 125 task.Run(); 126 } 127} 128 129bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()( 130 const std::pair<base::TimeTicks, base::Closure>& first_task, 131 const std::pair<base::TimeTicks, base::Closure>& second_task) const { 132 return first_task.first > second_task.first; 133} 134 135MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() { 136} 137 138MockClock::MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner) 139 : task_runner_(task_runner) { 140} 141 142MockClock::~MockClock() { 143} 144 145base::TimeTicks MockClock::NowTicks() { 146 return task_runner_->GetCurrentTime(); 147} 148 149} // namespace 150 151class LogoutConfirmationControllerTest : public testing::Test { 152 protected: 153 LogoutConfirmationControllerTest(); 154 virtual ~LogoutConfirmationControllerTest(); 155 156 void LogOut(); 157 158 bool log_out_called_; 159 160 scoped_refptr<MockTimeSingleThreadTaskRunner> runner_; 161 base::ThreadTaskRunnerHandle runner_handle_; 162 163 LogoutConfirmationController controller_; 164 165 private: 166 DISALLOW_COPY_AND_ASSIGN(LogoutConfirmationControllerTest); 167}; 168 169LogoutConfirmationControllerTest::LogoutConfirmationControllerTest() 170 : log_out_called_(false), 171 runner_(new MockTimeSingleThreadTaskRunner), 172 runner_handle_(runner_), 173 controller_(base::Bind(&LogoutConfirmationControllerTest::LogOut, 174 base::Unretained(this))) { 175 controller_.SetClockForTesting( 176 scoped_ptr<base::TickClock>(new MockClock(runner_))); 177} 178 179LogoutConfirmationControllerTest::~LogoutConfirmationControllerTest() { 180} 181 182void LogoutConfirmationControllerTest::LogOut() { 183 log_out_called_ = true; 184} 185 186// Verifies that the user is logged out immediately if logout confirmation with 187// a zero-length countdown is requested. 188TEST_F(LogoutConfirmationControllerTest, ZeroDuration) { 189 controller_.ConfirmLogout(runner_->GetCurrentTime()); 190 EXPECT_FALSE(log_out_called_); 191 runner_->FastForwardBy(base::TimeDelta()); 192 EXPECT_TRUE(log_out_called_); 193} 194 195// Verifies that the user is logged out when the countdown expires. 196TEST_F(LogoutConfirmationControllerTest, DurationExpired) { 197 controller_.ConfirmLogout( 198 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 199 EXPECT_FALSE(log_out_called_); 200 runner_->FastForwardBy(base::TimeDelta::FromSeconds(9)); 201 EXPECT_FALSE(log_out_called_); 202 runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); 203 EXPECT_TRUE(log_out_called_); 204} 205 206// Verifies that when a second request to confirm logout is made and the second 207// request's countdown ends before the original request's, the user is logged 208// out when the new countdown expires. 209TEST_F(LogoutConfirmationControllerTest, DurationShortened) { 210 controller_.ConfirmLogout( 211 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(30)); 212 EXPECT_FALSE(log_out_called_); 213 runner_->FastForwardBy(base::TimeDelta::FromSeconds(9)); 214 EXPECT_FALSE(log_out_called_); 215 controller_.ConfirmLogout( 216 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 217 runner_->FastForwardBy(base::TimeDelta::FromSeconds(9)); 218 EXPECT_FALSE(log_out_called_); 219 runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); 220 EXPECT_TRUE(log_out_called_); 221} 222 223// Verifies that when a second request to confirm logout is made and the second 224// request's countdown ends after the original request's, the user is logged 225// out when the original countdown expires. 226TEST_F(LogoutConfirmationControllerTest, DurationExtended) { 227 controller_.ConfirmLogout( 228 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 229 EXPECT_FALSE(log_out_called_); 230 runner_->FastForwardBy(base::TimeDelta::FromSeconds(9)); 231 EXPECT_FALSE(log_out_called_); 232 controller_.ConfirmLogout( 233 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 234 runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); 235 EXPECT_TRUE(log_out_called_); 236} 237 238// Verifies that when the screen is locked while the countdown is running, the 239// user is not logged out, even when the original countdown expires. 240TEST_F(LogoutConfirmationControllerTest, Lock) { 241 controller_.ConfirmLogout( 242 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 243 EXPECT_FALSE(log_out_called_); 244 controller_.OnLockStateChanged(true); 245 runner_->FastForwardUntilNoTasksRemain(); 246 EXPECT_FALSE(log_out_called_); 247} 248 249// Verifies that when the user confirms the logout request, the user is logged 250// out immediately. 251TEST_F(LogoutConfirmationControllerTest, UserAccepted) { 252 controller_.ConfirmLogout( 253 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 254 EXPECT_FALSE(log_out_called_); 255 controller_.OnLogoutConfirmed(); 256 EXPECT_TRUE(log_out_called_); 257} 258 259// Verifies that when the user denies the logout request, the user is not logged 260// out, even when the original countdown expires. 261TEST_F(LogoutConfirmationControllerTest, UserDenied) { 262 controller_.ConfirmLogout( 263 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 264 EXPECT_FALSE(log_out_called_); 265 controller_.OnDialogClosed(); 266 runner_->FastForwardUntilNoTasksRemain(); 267 EXPECT_FALSE(log_out_called_); 268} 269 270// Verifies that after the user has denied a logout request, a subsequent logout 271// request is handled correctly and the user is logged out when the countdown 272// expires. 273TEST_F(LogoutConfirmationControllerTest, DurationExpiredAfterDeniedRequest) { 274 controller_.ConfirmLogout( 275 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 276 EXPECT_FALSE(log_out_called_); 277 controller_.OnDialogClosed(); 278 runner_->FastForwardUntilNoTasksRemain(); 279 EXPECT_FALSE(log_out_called_); 280 281 controller_.ConfirmLogout( 282 runner_->GetCurrentTime() + base::TimeDelta::FromSeconds(10)); 283 EXPECT_FALSE(log_out_called_); 284 runner_->FastForwardBy(base::TimeDelta::FromSeconds(9)); 285 EXPECT_FALSE(log_out_called_); 286 runner_->FastForwardBy(base::TimeDelta::FromSeconds(2)); 287 EXPECT_TRUE(log_out_called_); 288} 289 290} // namespace ash 291