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 21 namespace ash { 22 namespace { 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). 27 class 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. 67 class 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 82 MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner() { 83 } 84 85 bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const { 86 return true; 87 } 88 89 bool 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 97 bool 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 105 const base::TimeTicks& MockTimeSingleThreadTaskRunner::GetCurrentTime() const { 106 return now_; 107 } 108 109 void 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 120 void 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 129 bool 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 135 MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() { 136 } 137 138 MockClock::MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner) 139 : task_runner_(task_runner) { 140 } 141 142 MockClock::~MockClock() { 143 } 144 145 base::TimeTicks MockClock::NowTicks() { 146 return task_runner_->GetCurrentTime(); 147 } 148 149 } // namespace 150 151 class 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 169 LogoutConfirmationControllerTest::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 179 LogoutConfirmationControllerTest::~LogoutConfirmationControllerTest() { 180 } 181 182 void 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. 188 TEST_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. 196 TEST_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. 209 TEST_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. 226 TEST_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. 240 TEST_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. 251 TEST_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. 261 TEST_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. 273 TEST_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