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 std::string
GetError() const OVERRIDE
{ return std::string(); }
68 virtual void SetError(const std::string
& error
) OVERRIDE
{}
69 virtual void Destruct() const OVERRIDE
{ delete this; }
70 virtual ResponseAction
Run() OVERRIDE
{ return RespondLater(); }
71 virtual void SendResponse(bool) OVERRIDE
{}
74 virtual ~MockFunction() {}
77 class TimedLimitMockFunction
: public MockFunction
{
79 explicit TimedLimitMockFunction(const std::string
& name
)
80 : MockFunction(name
) {}
81 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
83 heuristics
->push_back(
84 new TimedLimit(k2PerMinute
, new Mapper(), kGenericName
));
88 virtual ~TimedLimitMockFunction() {}
91 class ChainedLimitsMockFunction
: public MockFunction
{
93 explicit ChainedLimitsMockFunction(const std::string
& name
)
94 : MockFunction(name
) {}
95 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
97 // No more than 2 per minute sustained over 5 minutes.
98 heuristics
->push_back(new SustainedLimit(
99 TimeDelta::FromMinutes(5), k2PerMinute
, new Mapper(), kGenericName
));
100 // No more than 20 per hour.
101 heuristics
->push_back(
102 new TimedLimit(k20PerHour
, new Mapper(), kGenericName
));
106 virtual ~ChainedLimitsMockFunction() {}
109 class FrozenMockFunction
: public MockFunction
{
111 explicit FrozenMockFunction(const std::string
& name
) : MockFunction(name
) {}
112 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics
* heuristics
) const
114 heuristics
->push_back(
115 new TimedLimit(kFrozenConfig
, new Mapper(), kGenericName
));
119 virtual ~FrozenMockFunction() {}
123 class QuotaServiceTest
: public testing::Test
{
130 ui_thread_(BrowserThread::UI
, &loop_
) {}
131 virtual void SetUp() { service_
.reset(new QuotaService()); }
132 virtual void TearDown() {
133 loop_
.RunUntilIdle();
138 std::string extension_a_
;
139 std::string extension_b_
;
140 std::string extension_c_
;
141 scoped_ptr
<QuotaService
> service_
;
142 base::MessageLoop loop_
;
143 content::TestBrowserThread ui_thread_
;
146 class QuotaLimitHeuristicTest
: public testing::Test
{
148 static void DoMoreThan2PerMinuteFor5Minutes(const TimeTicks
& start_time
,
149 QuotaLimitHeuristic
* lim
,
151 int an_unexhausted_minute
) {
152 for (int i
= 0; i
< 5; i
++) {
153 // Perform one operation in each minute.
155 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(10 + m
)));
156 EXPECT_TRUE(b
->has_tokens());
158 if (i
== an_unexhausted_minute
)
159 continue; // Don't exhaust all tokens this minute.
161 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(15 + m
)));
162 EXPECT_FALSE(b
->has_tokens());
164 // These are OK because we haven't exhausted all buckets.
165 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(20 + m
)));
166 EXPECT_FALSE(b
->has_tokens());
167 EXPECT_TRUE(lim
->Apply(b
, start_time
+ TimeDelta::FromSeconds(50 + m
)));
168 EXPECT_FALSE(b
->has_tokens());
173 TEST_F(QuotaLimitHeuristicTest
, Timed
) {
174 TimedLimit
lim(k2PerMinute
, new MockMapper(), kGenericName
);
177 b
.Reset(k2PerMinute
, kStartTime
);
178 EXPECT_TRUE(lim
.Apply(&b
, kStartTime
));
179 EXPECT_TRUE(b
.has_tokens());
180 EXPECT_TRUE(lim
.Apply(&b
, kStartTime
+ TimeDelta::FromSeconds(30)));
181 EXPECT_FALSE(b
.has_tokens());
182 EXPECT_FALSE(lim
.Apply(&b
, k1MinuteAfterStart
));
184 b
.Reset(k2PerMinute
, kStartTime
);
185 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
- TimeDelta::FromSeconds(1)));
186 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
));
187 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(1)));
188 EXPECT_TRUE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(2)));
189 EXPECT_FALSE(lim
.Apply(&b
, k1MinuteAfterStart
+ TimeDelta::FromSeconds(3)));
192 TEST_F(QuotaLimitHeuristicTest
, Sustained
) {
194 TimeDelta::FromMinutes(5), k2PerMinute
, new MockMapper(), kGenericName
);
197 bucket
.Reset(k2PerMinute
, kStartTime
);
198 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, -1);
199 // This straw breaks the camel's back.
200 EXPECT_FALSE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(6)));
202 // The heuristic resets itself on a safe request.
203 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromDays(1)));
205 // Do the same as above except don't exhaust final bucket.
206 bucket
.Reset(k2PerMinute
, kStartTime
);
207 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, -1);
208 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(7)));
210 // Do the same as above except don't exhaust the 3rd (w.l.o.g) bucket.
211 bucket
.Reset(k2PerMinute
, kStartTime
);
212 DoMoreThan2PerMinuteFor5Minutes(kStartTime
, &lim
, &bucket
, 3);
213 // If the 3rd bucket were exhausted, this would fail (see first test).
214 EXPECT_TRUE(lim
.Apply(&bucket
, kStartTime
+ TimeDelta::FromMinutes(6)));
217 TEST_F(QuotaServiceTest
, NoHeuristic
) {
218 scoped_refptr
<MockFunction
> f(new MockFunction("foo"));
219 base::ListValue args
;
220 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
223 TEST_F(QuotaServiceTest
, FrozenHeuristic
) {
224 scoped_refptr
<MockFunction
> f(new FrozenMockFunction("foo"));
225 base::ListValue args
;
226 args
.Append(new base::FundamentalValue(1));
227 EXPECT_NE("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
230 TEST_F(QuotaServiceTest
, SingleHeuristic
) {
231 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
232 base::ListValue args
;
233 args
.Append(new base::FundamentalValue(1));
234 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args
, kStartTime
));
236 service_
->Assess(extension_a_
,
239 kStartTime
+ TimeDelta::FromSeconds(10)));
241 service_
->Assess(extension_a_
,
244 kStartTime
+ TimeDelta::FromSeconds(15)));
246 base::ListValue args2
;
247 args2
.Append(new base::FundamentalValue(1));
248 args2
.Append(new base::FundamentalValue(2));
249 EXPECT_EQ("", service_
->Assess(extension_b_
, f
.get(), &args2
, kStartTime
));
251 service_
->Assess(extension_b_
,
254 kStartTime
+ TimeDelta::FromSeconds(10)));
256 TimeDelta peace
= TimeDelta::FromMinutes(30);
258 service_
->Assess(extension_b_
, f
.get(), &args
, kStartTime
+ peace
));
260 service_
->Assess(extension_b_
,
263 kStartTime
+ peace
+ TimeDelta::FromSeconds(10)));
265 service_
->Assess(extension_b_
,
268 kStartTime
+ peace
+ TimeDelta::FromSeconds(15)));
270 // Test that items are independent.
271 base::ListValue args3
;
272 args3
.Append(new base::FundamentalValue(3));
273 EXPECT_EQ("", service_
->Assess(extension_c_
, f
.get(), &args
, kStartTime
));
275 service_
->Assess(extension_c_
,
278 kStartTime
+ TimeDelta::FromSeconds(10)));
280 service_
->Assess(extension_c_
,
283 kStartTime
+ TimeDelta::FromSeconds(15)));
285 service_
->Assess(extension_c_
,
288 kStartTime
+ TimeDelta::FromSeconds(20)));
290 service_
->Assess(extension_c_
,
293 kStartTime
+ TimeDelta::FromSeconds(25)));
295 service_
->Assess(extension_c_
,
298 kStartTime
+ TimeDelta::FromSeconds(30)));
301 TEST_F(QuotaServiceTest
, ChainedHeuristics
) {
302 scoped_refptr
<MockFunction
> f(new ChainedLimitsMockFunction("foo"));
303 base::ListValue args
;
304 args
.Append(new base::FundamentalValue(1));
306 // First, test that the low limit can be avoided but the higher one is hit.
307 // One event per minute for 20 minutes comes in under the sustained limit,
308 // but is equal to the timed limit.
309 for (int i
= 0; i
< 20; i
++) {
312 service_
->Assess(extension_a_
,
315 kStartTime
+ TimeDelta::FromSeconds(10 + i
* 60)));
318 // This will bring us to 21 events in an hour, which is a violation.
320 service_
->Assess(extension_a_
,
323 kStartTime
+ TimeDelta::FromMinutes(30)));
325 // Now, check that we can still hit the lower limit.
326 for (int i
= 0; i
< 5; i
++) {
329 service_
->Assess(extension_b_
,
332 kStartTime
+ TimeDelta::FromSeconds(10 + i
* 60)));
335 service_
->Assess(extension_b_
,
338 kStartTime
+ TimeDelta::FromSeconds(15 + i
* 60)));
341 service_
->Assess(extension_b_
,
344 kStartTime
+ TimeDelta::FromSeconds(20 + i
* 60)));
348 service_
->Assess(extension_b_
,
351 kStartTime
+ TimeDelta::FromMinutes(6)));
354 TEST_F(QuotaServiceTest
, MultipleFunctionsDontInterfere
) {
355 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
356 scoped_refptr
<MockFunction
> g(new TimedLimitMockFunction("bar"));
358 base::ListValue args_f
;
359 base::ListValue args_g
;
360 args_f
.Append(new base::FundamentalValue(1));
361 args_g
.Append(new base::FundamentalValue(2));
363 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &args_f
, kStartTime
));
364 EXPECT_EQ("", service_
->Assess(extension_a_
, g
.get(), &args_g
, kStartTime
));
366 service_
->Assess(extension_a_
,
369 kStartTime
+ TimeDelta::FromSeconds(10)));
371 service_
->Assess(extension_a_
,
374 kStartTime
+ TimeDelta::FromSeconds(10)));
376 service_
->Assess(extension_a_
,
379 kStartTime
+ TimeDelta::FromSeconds(15)));
381 service_
->Assess(extension_a_
,
384 kStartTime
+ TimeDelta::FromSeconds(15)));
387 TEST_F(QuotaServiceTest
, ViolatorsWillBeViolators
) {
388 scoped_refptr
<MockFunction
> f(new TimedLimitMockFunction("foo"));
389 scoped_refptr
<MockFunction
> g(new TimedLimitMockFunction("bar"));
391 arg
.Append(new base::FundamentalValue(1));
392 EXPECT_EQ("", service_
->Assess(extension_a_
, f
.get(), &arg
, kStartTime
));
394 service_
->Assess(extension_a_
,
397 kStartTime
+ TimeDelta::FromSeconds(10)));
399 service_
->Assess(extension_a_
,
402 kStartTime
+ TimeDelta::FromSeconds(15)));
404 // We don't allow this extension to use quota limited functions even if they
409 extension_a_
, f
.get(), &arg
, kStartTime
+ TimeDelta::FromDays(1)));
413 extension_a_
, g
.get(), &arg
, kStartTime
+ TimeDelta::FromDays(1)));
416 } // namespace extensions