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.
5 #include "base/message_loop/message_loop.h"
6 #include "base/process/process.h"
7 #include "base/stl_util.h"
8 #include "base/strings/string_util.h"
9 #include "content/public/test/test_browser_thread.h"
10 #include "extensions/browser/extension_function.h"
11 #include "extensions/browser/quota_service.h"
12 #include "testing/gtest/include/gtest/gtest.h"
14 using base::TimeDelta
;
15 using base::TimeTicks
;
16 using content::BrowserThread
;
18 namespace extensions
{
20 typedef QuotaLimitHeuristic::Bucket Bucket
;
21 typedef QuotaLimitHeuristic::Config Config
;
22 typedef QuotaLimitHeuristic::BucketList BucketList
;
23 typedef QuotaService::TimedLimit TimedLimit
;
24 typedef QuotaService::SustainedLimit SustainedLimit
;
28 const char kGenericName
[] = "name";
29 const Config kFrozenConfig
= {0, TimeDelta::FromDays(0)};
30 const Config k2PerMinute
= {2, TimeDelta::FromMinutes(1)};
31 const Config k20PerHour
= {20, TimeDelta::FromHours(1)};
32 const TimeTicks kStartTime
= TimeTicks();
33 const TimeTicks k1MinuteAfterStart
= kStartTime
+ TimeDelta::FromMinutes(1);
35 class Mapper
: public QuotaLimitHeuristic::BucketMapper
{
38 virtual ~Mapper() { STLDeleteValues(&buckets_
); }
39 virtual void GetBucketsForArgs(const base::ListValue
* args
,
40 BucketList
* buckets
) OVERRIDE
{
41 for (size_t i
= 0; i
< args
->GetSize(); i
++) {
43 ASSERT_TRUE(args
->GetInteger(i
, &id
));
44 if (buckets_
.find(id
) == buckets_
.end())
45 buckets_
[id
] = new Bucket();
46 buckets
->push_back(buckets_
[id
]);
51 typedef std::map
<int, Bucket
*> BucketMap
;
53 DISALLOW_COPY_AND_ASSIGN(Mapper
);
56 class MockMapper
: public QuotaLimitHeuristic::BucketMapper
{
58 virtual void GetBucketsForArgs(const base::ListValue
* args
,
59 BucketList
* buckets
) OVERRIDE
{}
62 class MockFunction
: public ExtensionFunction
{
64 explicit MockFunction(const std::string
& name
) { set_name(name
); }
66 virtual void SetArgs(const base::ListValue
* args
) OVERRIDE
{}
67 virtual const std::string
GetError() OVERRIDE
{ return std::string(); }
68 virtual void SetError(const std::string
& error
) OVERRIDE
{}
69 virtual void Run() OVERRIDE
{}
70 virtual void Destruct() const OVERRIDE
{ delete this; }
71 virtual bool RunImpl() OVERRIDE
{ return true; }
72 virtual void SendResponse(bool) OVERRIDE
{}
75 virtual ~MockFunction() {}
78 class TimedLimitMockFunction
: public MockFunction
{
80 explicit TimedLimitMockFunction(const std::string
& name
)
81 : MockFunction(name
) {}
82 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
84 heuristics
->push_back(
85 new TimedLimit(k2PerMinute
, new Mapper(), kGenericName
));
89 virtual ~TimedLimitMockFunction() {}
92 class ChainedLimitsMockFunction
: public MockFunction
{
94 explicit ChainedLimitsMockFunction(const std::string
& name
)
95 : MockFunction(name
) {}
96 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
98 // No more than 2 per minute sustained over 5 minutes.
99 heuristics
->push_back(new SustainedLimit(
100 TimeDelta::FromMinutes(5), k2PerMinute
, new Mapper(), kGenericName
));
101 // No more than 20 per hour.
102 heuristics
->push_back(
103 new TimedLimit(k20PerHour
, new Mapper(), kGenericName
));
107 virtual ~ChainedLimitsMockFunction() {}
110 class FrozenMockFunction
: public MockFunction
{
112 explicit FrozenMockFunction(const std::string
& name
) : MockFunction(name
) {}
113 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
115 heuristics
->push_back(
116 new TimedLimit(kFrozenConfig
, new Mapper(), kGenericName
));
120 virtual ~FrozenMockFunction() {}
124 class QuotaServiceTest
: public testing::Test
{
131 ui_thread_(BrowserThread::UI
, &loop_
) {}
132 virtual void SetUp() { service_
.reset(new QuotaService()); }
133 virtual void TearDown() {
134 loop_
.RunUntilIdle();
139 std::string extension_a_
;
140 std::string extension_b_
;
141 std::string extension_c_
;
142 scoped_ptr
<QuotaService
> service_
;
143 base::MessageLoop loop_
;
144 content::TestBrowserThread ui_thread_
;
147 class QuotaLimitHeuristicTest
: public testing::Test
{
149 static void DoMoreThan2PerMinuteFor5Minutes(const TimeTicks
& start_time
,
150 QuotaLimitHeuristic
* lim
,
152 int an_unexhausted_minute
) {
153 for (int i
= 0; i
< 5; i
++) {
154 // Perform one operation in each minute.
156 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(10 + m
)));
157 EXPECT_TRUE(b
->has_tokens());
159 if (i
== an_unexhausted_minute
)
160 continue; // Don't exhaust all tokens this minute.
162 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(15 + m
)));
163 EXPECT_FALSE(b
->has_tokens());
165 // These are OK because we haven't exhausted all buckets.
166 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(20 + m
)));
167 EXPECT_FALSE(b
->has_tokens());
168 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(50 + m
)));
169 EXPECT_FALSE(b
->has_tokens());
174 TEST_F(QuotaLimitHeuristicTest
, Timed
) {
175 TimedLimit
lim(k2PerMinute
, new MockMapper(), kGenericName
);
178 b
.Reset(k2PerMinute
, kStartTime
);
179 EXPECT_TRUE(lim
.Apply(&b
, kStartTime
));
180 EXPECT_TRUE(b
.has_tokens());
181 EXPECT_TRUE(lim
.Apply(&b
, kStartTime
+ TimeDelta::FromSeconds(30)));
182 EXPECT_FALSE(b
.has_tokens());
183 EXPECT_FALSE(lim
.Apply(&b
, k1MinuteAfterStart
));
185 b
.Reset(k2PerMinute
, kStartTime
);
186 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
- TimeDelta::FromSeconds(1)));
187 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
));
188 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(1)));
189 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(2)));
190 EXPECT_FALSE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(3)));
193 TEST_F(QuotaLimitHeuristicTest
, Sustained
) {
195 TimeDelta::FromMinutes(5), k2PerMinute
, new MockMapper(), kGenericName
);
198 bucket
.Reset(k2PerMinute
, kStartTime
);
199 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, -1);
200 // This straw breaks the camel's back.
201 EXPECT_FALSE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(6)));
203 // The heuristic resets itself on a safe request.
204 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromDays(1)));
206 // Do the same as above except don't exhaust final bucket.
207 bucket
.Reset(k2PerMinute
, kStartTime
);
208 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, -1);
209 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(7)));
211 // Do the same as above except don't exhaust the 3rd (w.l.o.g) bucket.
212 bucket
.Reset(k2PerMinute
, kStartTime
);
213 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, 3);
214 // If the 3rd bucket were exhausted, this would fail (see first test).
215 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(6)));
218 TEST_F(QuotaServiceTest
, NoHeuristic
) {
219 scoped_refptr
<MockFunction
> f(new MockFunction("foo"));
220 base::ListValue args
;
221 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
224 TEST_F(QuotaServiceTest
, FrozenHeuristic
) {
225 scoped_refptr
<MockFunction
> f(new FrozenMockFunction("foo"));
226 base::ListValue args
;
227 args
.Append(new base::FundamentalValue(1));
228 EXPECT_NE("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
231 TEST_F(QuotaServiceTest
, SingleHeuristic
) {
232 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
233 base::ListValue args
;
234 args
.Append(new base::FundamentalValue(1));
235 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
237 service_
->Assess(extension_a_
,
240 kStartTime
+ TimeDelta::FromSeconds(10)));
242 service_
->Assess(extension_a_
,
245 kStartTime
+ TimeDelta::FromSeconds(15)));
247 base::ListValue args2
;
248 args2
.Append(new base::FundamentalValue(1));
249 args2
.Append(new base::FundamentalValue(2));
250 EXPECT_EQ("", service_
->Assess(extension_b_
, f
.get(), &args2
, kStartTime
));
252 service_
->Assess(extension_b_
,
255 kStartTime
+ TimeDelta::FromSeconds(10)));
257 TimeDelta peace
= TimeDelta::FromMinutes(30);
259 service_
->Assess(extension_b_
, f
.get(), &args
, kStartTime
+ peace
));
261 service_
->Assess(extension_b_
,
264 kStartTime
+ peace
+ TimeDelta::FromSeconds(10)));
266 service_
->Assess(extension_b_
,
269 kStartTime
+ peace
+ TimeDelta::FromSeconds(15)));
271 // Test that items are independent.
272 base::ListValue args3
;
273 args3
.Append(new base::FundamentalValue(3));
274 EXPECT_EQ("", service_
->Assess(extension_c_
, f
.get(), &args
, kStartTime
));
276 service_
->Assess(extension_c_
,
279 kStartTime
+ TimeDelta::FromSeconds(10)));
281 service_
->Assess(extension_c_
,
284 kStartTime
+ TimeDelta::FromSeconds(15)));
286 service_
->Assess(extension_c_
,
289 kStartTime
+ TimeDelta::FromSeconds(20)));
291 service_
->Assess(extension_c_
,
294 kStartTime
+ TimeDelta::FromSeconds(25)));
296 service_
->Assess(extension_c_
,
299 kStartTime
+ TimeDelta::FromSeconds(30)));
302 TEST_F(QuotaServiceTest
, ChainedHeuristics
) {
303 scoped_refptr
<MockFunction
> f(new ChainedLimitsMockFunction("foo"));
304 base::ListValue args
;
305 args
.Append(new base::FundamentalValue(1));
307 // First, test that the low limit can be avoided but the higher one is hit.
308 // One event per minute for 20 minutes comes in under the sustained limit,
309 // but is equal to the timed limit.
310 for (int i
= 0; i
< 20; i
++) {
313 service_
->Assess(extension_a_
,
316 kStartTime
+ TimeDelta::FromSeconds(10 + i
* 60)));
319 // This will bring us to 21 events in an hour, which is a violation.
321 service_
->Assess(extension_a_
,
324 kStartTime
+ TimeDelta::FromMinutes(30)));
326 // Now, check that we can still hit the lower limit.
327 for (int i
= 0; i
< 5; i
++) {
330 service_
->Assess(extension_b_
,
333 kStartTime
+ TimeDelta::FromSeconds(10 + i
* 60)));
336 service_
->Assess(extension_b_
,
339 kStartTime
+ TimeDelta::FromSeconds(15 + i
* 60)));
342 service_
->Assess(extension_b_
,
345 kStartTime
+ TimeDelta::FromSeconds(20 + i
* 60)));
349 service_
->Assess(extension_b_
,
352 kStartTime
+ TimeDelta::FromMinutes(6)));
355 TEST_F(QuotaServiceTest
, MultipleFunctionsDontInterfere
) {
356 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
357 scoped_refptr
<MockFunction
> g(new TimedLimitMockFunction("bar"));
359 base::ListValue args_f
;
360 base::ListValue args_g
;
361 args_f
.Append(new base::FundamentalValue(1));
362 args_g
.Append(new base::FundamentalValue(2));
364 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args_f
, kStartTime
));
365 EXPECT_EQ("", service_
->Assess(extension_a_
, g
.get(), &args_g
, kStartTime
));
367 service_
->Assess(extension_a_
,
370 kStartTime
+ TimeDelta::FromSeconds(10)));
372 service_
->Assess(extension_a_
,
375 kStartTime
+ TimeDelta::FromSeconds(10)));
377 service_
->Assess(extension_a_
,
380 kStartTime
+ TimeDelta::FromSeconds(15)));
382 service_
->Assess(extension_a_
,
385 kStartTime
+ TimeDelta::FromSeconds(15)));
388 TEST_F(QuotaServiceTest
, ViolatorsWillBeViolators
) {
389 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
390 scoped_refptr
<MockFunction
> g(new TimedLimitMockFunction("bar"));
392 arg
.Append(new base::FundamentalValue(1));
393 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &arg
, kStartTime
));
395 service_
->Assess(extension_a_
,
398 kStartTime
+ TimeDelta::FromSeconds(10)));
400 service_
->Assess(extension_a_
,
403 kStartTime
+ TimeDelta::FromSeconds(15)));
405 // We don't allow this extension to use quota limited functions even if they
410 extension_a_
, f
.get(), &arg
, kStartTime
+ TimeDelta::FromDays(1)));
414 extension_a_
, g
.get(), &arg
, kStartTime
+ TimeDelta::FromDays(1)));
417 } // namespace extensions