1// Copyright 2013 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 <list>
6#include <map>
7#include <utility>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/memory/weak_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "base/message_loop/message_loop_proxy.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#include "webkit/browser/quota/mock_storage_client.h"
17#include "webkit/browser/quota/quota_manager.h"
18#include "webkit/browser/quota/quota_temporary_storage_evictor.h"
19
20namespace quota {
21
22class QuotaTemporaryStorageEvictorTest;
23
24namespace {
25
26class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler {
27 public:
28  explicit MockQuotaEvictionHandler(QuotaTemporaryStorageEvictorTest *test)
29      : quota_(0),
30        available_space_(0),
31        error_on_evict_origin_data_(false),
32        error_on_get_usage_and_quota_(false) {}
33
34  virtual void EvictOriginData(
35      const GURL& origin,
36      StorageType type,
37      const EvictOriginDataCallback& callback) OVERRIDE {
38    if (error_on_evict_origin_data_) {
39      callback.Run(quota::kQuotaErrorInvalidModification);
40      return;
41    }
42    int64 origin_usage = EnsureOriginRemoved(origin);
43    if (origin_usage >= 0)
44      available_space_ += origin_usage;
45    callback.Run(quota::kQuotaStatusOk);
46  }
47
48  virtual void GetUsageAndQuotaForEviction(
49      const UsageAndQuotaCallback& callback) OVERRIDE {
50    if (error_on_get_usage_and_quota_) {
51      callback.Run(quota::kQuotaErrorInvalidAccess, UsageAndQuota());
52      return;
53    }
54    if (!task_for_get_usage_and_quota_.is_null())
55      task_for_get_usage_and_quota_.Run();
56    UsageAndQuota quota_and_usage(-1, GetUsage(), quota_, available_space_);
57    callback.Run(quota::kQuotaStatusOk, quota_and_usage);
58  }
59
60  virtual void GetLRUOrigin(
61      StorageType type,
62      const GetLRUOriginCallback& callback) OVERRIDE {
63    if (origin_order_.empty())
64      callback.Run(GURL());
65    else
66      callback.Run(GURL(origin_order_.front()));
67  }
68
69  int64 GetUsage() const {
70    int64 total_usage = 0;
71    for (std::map<GURL, int64>::const_iterator p = origins_.begin();
72         p != origins_.end();
73         ++p)
74      total_usage += p->second;
75    return total_usage;
76  }
77
78  void set_quota(int64 quota) {
79    quota_ = quota;
80  }
81  void set_available_space(int64 available_space) {
82    available_space_ = available_space;
83  }
84  void set_task_for_get_usage_and_quota(const base::Closure& task) {
85    task_for_get_usage_and_quota_= task;
86  }
87  void set_error_on_evict_origin_data(bool error_on_evict_origin_data) {
88    error_on_evict_origin_data_ = error_on_evict_origin_data;
89  }
90  void set_error_on_get_usage_and_quota(bool error_on_get_usage_and_quota) {
91    error_on_get_usage_and_quota_ = error_on_get_usage_and_quota;
92  }
93
94  // Simulates an access to |origin|.  It reorders the internal LRU list.
95  // It internally uses AddOrigin().
96  void AccessOrigin(const GURL& origin) {
97    std::map<GURL, int64>::iterator found = origins_.find(origin);
98    EXPECT_TRUE(origins_.end() != found);
99    AddOrigin(origin, found->second);
100  }
101
102  // Simulates adding or overwriting the |origin| to the internal origin set
103  // with the |usage|.  It also adds or moves the |origin| to the end of the
104  // LRU list.
105  void AddOrigin(const GURL& origin, int64 usage) {
106    EnsureOriginRemoved(origin);
107    origin_order_.push_back(origin);
108    origins_[origin] = usage;
109  }
110
111 private:
112  int64 EnsureOriginRemoved(const GURL& origin) {
113    int64 origin_usage;
114    if (origins_.find(origin) == origins_.end())
115      return -1;
116    else
117      origin_usage = origins_[origin];
118
119    origins_.erase(origin);
120    origin_order_.remove(origin);
121    return origin_usage;
122  }
123
124  int64 quota_;
125  int64 available_space_;
126  std::list<GURL> origin_order_;
127  std::map<GURL, int64> origins_;
128  bool error_on_evict_origin_data_;
129  bool error_on_get_usage_and_quota_;
130
131  base::Closure task_for_get_usage_and_quota_;
132};
133
134}  // anonymous namespace
135
136class QuotaTemporaryStorageEvictorTest : public testing::Test {
137 public:
138  QuotaTemporaryStorageEvictorTest()
139      : num_get_usage_and_quota_for_eviction_(0),
140        weak_factory_(this) {}
141
142  virtual void SetUp() {
143    quota_eviction_handler_.reset(new MockQuotaEvictionHandler(this));
144
145    // Run multiple evictions in a single RunUntilIdle() when interval_ms == 0
146    temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
147        quota_eviction_handler_.get(), 0));
148  }
149
150  virtual void TearDown() {
151    temporary_storage_evictor_.reset();
152    quota_eviction_handler_.reset();
153    base::MessageLoop::current()->RunUntilIdle();
154  }
155
156  void TaskForRepeatedEvictionTest(
157      const std::pair<GURL, int64>& origin_to_be_added,
158      const GURL& origin_to_be_accessed,
159      int expected_usage_after_first,
160      int expected_usage_after_second) {
161    EXPECT_GE(4, num_get_usage_and_quota_for_eviction_);
162    switch (num_get_usage_and_quota_for_eviction_) {
163    case 2:
164      EXPECT_EQ(expected_usage_after_first,
165                quota_eviction_handler()->GetUsage());
166      if (!origin_to_be_added.first.is_empty())
167        quota_eviction_handler()->AddOrigin(origin_to_be_added.first,
168                                            origin_to_be_added.second);
169      if (!origin_to_be_accessed.is_empty())
170        quota_eviction_handler()->AccessOrigin(origin_to_be_accessed);
171      break;
172    case 3:
173      EXPECT_EQ(expected_usage_after_second,
174                quota_eviction_handler()->GetUsage());
175      temporary_storage_evictor()->set_repeated_eviction(false);
176      break;
177    }
178    ++num_get_usage_and_quota_for_eviction_;
179  }
180
181 protected:
182  MockQuotaEvictionHandler* quota_eviction_handler() const {
183    return static_cast<MockQuotaEvictionHandler*>(
184        quota_eviction_handler_.get());
185  }
186
187  QuotaTemporaryStorageEvictor* temporary_storage_evictor() const {
188    return temporary_storage_evictor_.get();
189  }
190
191  const QuotaTemporaryStorageEvictor::Statistics& statistics() const {
192    return temporary_storage_evictor()->statistics_;
193  }
194
195  void set_repeated_eviction(bool repeated_eviction) const {
196    return temporary_storage_evictor_->set_repeated_eviction(repeated_eviction);
197  }
198
199  int num_get_usage_and_quota_for_eviction() const {
200    return num_get_usage_and_quota_for_eviction_;
201  }
202
203  int64 default_min_available_disk_space_to_start_eviction() const {
204    return 1000 * 1000 * 500;
205  }
206
207  void set_min_available_disk_space_to_start_eviction(int64 value) const {
208    temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction(
209        value);
210  }
211
212  void reset_min_available_disk_space_to_start_eviction() const {
213    temporary_storage_evictor_->
214        reset_min_available_disk_space_to_start_eviction();
215  }
216
217  base::MessageLoop message_loop_;
218  scoped_ptr<MockQuotaEvictionHandler> quota_eviction_handler_;
219  scoped_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_;
220
221  int num_get_usage_and_quota_for_eviction_;
222
223  base::WeakPtrFactory<QuotaTemporaryStorageEvictorTest> weak_factory_;
224
225  DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictorTest);
226};
227
228TEST_F(QuotaTemporaryStorageEvictorTest, SimpleEvictionTest) {
229  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 3000);
230  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 200);
231  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 500);
232  quota_eviction_handler()->set_quota(4000);
233  quota_eviction_handler()->set_available_space(1000000000);
234  EXPECT_EQ(3000 + 200 + 500, quota_eviction_handler()->GetUsage());
235  set_repeated_eviction(false);
236  temporary_storage_evictor()->Start();
237  base::MessageLoop::current()->RunUntilIdle();
238  EXPECT_EQ(200 + 500, quota_eviction_handler()->GetUsage());
239
240  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
241  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
242  EXPECT_EQ(1, statistics().num_evicted_origins);
243  EXPECT_EQ(1, statistics().num_eviction_rounds);
244  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
245}
246
247TEST_F(QuotaTemporaryStorageEvictorTest, MultipleEvictionTest) {
248  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 20);
249  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 2900);
250  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450);
251  quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 400);
252  quota_eviction_handler()->set_quota(4000);
253  quota_eviction_handler()->set_available_space(1000000000);
254  EXPECT_EQ(20 + 2900 + 450 + 400, quota_eviction_handler()->GetUsage());
255  set_repeated_eviction(false);
256  temporary_storage_evictor()->Start();
257  base::MessageLoop::current()->RunUntilIdle();
258  EXPECT_EQ(450 + 400, quota_eviction_handler()->GetUsage());
259
260  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
261  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
262  EXPECT_EQ(2, statistics().num_evicted_origins);
263  EXPECT_EQ(1, statistics().num_eviction_rounds);
264  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
265}
266
267TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionTest) {
268  const int64 a_size = 400;
269  const int64 b_size = 150;
270  const int64 c_size = 120;
271  const int64 d_size = 292;
272  const int64 initial_total_size = a_size + b_size + c_size + d_size;
273  const int64 e_size = 275;
274
275  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
276  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
277  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
278  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
279  quota_eviction_handler()->set_quota(1000);
280  quota_eviction_handler()->set_available_space(1000000000);
281  quota_eviction_handler()->set_task_for_get_usage_and_quota(
282      base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
283                 weak_factory_.GetWeakPtr(),
284                 std::make_pair(GURL("http://www.e.com"), e_size), GURL(),
285                 initial_total_size - d_size,
286                 initial_total_size - d_size + e_size - c_size));
287  EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
288  temporary_storage_evictor()->Start();
289  base::MessageLoop::current()->RunUntilIdle();
290  EXPECT_EQ(initial_total_size - d_size + e_size - c_size - b_size,
291            quota_eviction_handler()->GetUsage());
292  EXPECT_EQ(5, num_get_usage_and_quota_for_eviction());
293
294  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
295  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
296  EXPECT_EQ(3, statistics().num_evicted_origins);
297  EXPECT_EQ(2, statistics().num_eviction_rounds);
298  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
299}
300
301TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest) {
302  const int64 a_size = 400;
303  const int64 b_size = 150;
304  const int64 c_size = 120;
305  const int64 d_size = 292;
306  const int64 initial_total_size = a_size + b_size + c_size + d_size;
307
308  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
309  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
310  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
311  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
312  quota_eviction_handler()->set_quota(1000);
313  quota_eviction_handler()->set_available_space(1000000000);
314  quota_eviction_handler()->set_task_for_get_usage_and_quota(
315      base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
316                 weak_factory_.GetWeakPtr(), std::make_pair(GURL(), 0), GURL(),
317                 initial_total_size - d_size, initial_total_size - d_size));
318  EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
319  set_repeated_eviction(true);
320  temporary_storage_evictor()->Start();
321  base::MessageLoop::current()->RunUntilIdle();
322  EXPECT_EQ(initial_total_size - d_size, quota_eviction_handler()->GetUsage());
323  EXPECT_EQ(4, num_get_usage_and_quota_for_eviction());
324
325  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
326  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
327  EXPECT_EQ(1, statistics().num_evicted_origins);
328  EXPECT_EQ(3, statistics().num_eviction_rounds);
329  EXPECT_EQ(2, statistics().num_skipped_eviction_rounds);
330}
331
332TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionWithAccessOriginTest) {
333  const int64 a_size = 400;
334  const int64 b_size = 150;
335  const int64 c_size = 120;
336  const int64 d_size = 292;
337  const int64 initial_total_size = a_size + b_size + c_size + d_size;
338  const int64 e_size = 275;
339
340  quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size);
341  quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size);
342  quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size);
343  quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size);
344  quota_eviction_handler()->set_quota(1000);
345  quota_eviction_handler()->set_available_space(1000000000);
346  quota_eviction_handler()->set_task_for_get_usage_and_quota(
347      base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
348                 weak_factory_.GetWeakPtr(),
349                 std::make_pair(GURL("http://www.e.com"), e_size),
350                 GURL("http://www.c.com"),
351                 initial_total_size - d_size,
352                 initial_total_size - d_size + e_size - b_size));
353  EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
354  temporary_storage_evictor()->Start();
355  base::MessageLoop::current()->RunUntilIdle();
356  EXPECT_EQ(initial_total_size - d_size + e_size - b_size - a_size,
357            quota_eviction_handler()->GetUsage());
358  EXPECT_EQ(5, num_get_usage_and_quota_for_eviction());
359
360  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
361  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
362  EXPECT_EQ(3, statistics().num_evicted_origins);
363  EXPECT_EQ(2, statistics().num_eviction_rounds);
364  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
365}
366
367TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceNonEvictionTest) {
368  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 414);
369  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450);
370  quota_eviction_handler()->set_quota(10000);
371  quota_eviction_handler()->set_available_space(
372      default_min_available_disk_space_to_start_eviction() - 350);
373  EXPECT_EQ(414 + 450, quota_eviction_handler()->GetUsage());
374  reset_min_available_disk_space_to_start_eviction();
375  set_repeated_eviction(false);
376  temporary_storage_evictor()->Start();
377  base::MessageLoop::current()->RunUntilIdle();
378  EXPECT_EQ(414 + 450, quota_eviction_handler()->GetUsage());
379
380  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
381  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
382  EXPECT_EQ(0, statistics().num_evicted_origins);
383  EXPECT_EQ(1, statistics().num_eviction_rounds);
384  EXPECT_EQ(1, statistics().num_skipped_eviction_rounds);
385}
386
387TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) {
388  quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 294);
389  quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 120);
390  quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 150);
391  quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 300);
392  quota_eviction_handler()->set_quota(10000);
393  quota_eviction_handler()->set_available_space(
394      default_min_available_disk_space_to_start_eviction() - 350);
395  EXPECT_EQ(294 + 120 + 150 + 300, quota_eviction_handler()->GetUsage());
396  set_min_available_disk_space_to_start_eviction(
397      default_min_available_disk_space_to_start_eviction());
398  set_repeated_eviction(false);
399  temporary_storage_evictor()->Start();
400  base::MessageLoop::current()->RunUntilIdle();
401  EXPECT_EQ(150 + 300, quota_eviction_handler()->GetUsage());
402
403  EXPECT_EQ(0, statistics().num_errors_on_evicting_origin);
404  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
405  EXPECT_EQ(2, statistics().num_evicted_origins);
406  EXPECT_EQ(1, statistics().num_eviction_rounds);
407  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
408}
409
410}  // namespace quota
411