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