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 "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" 13 14 using base::TimeDelta; 15 using base::TimeTicks; 16 using content::BrowserThread; 17 18 namespace extensions { 19 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; 25 26 namespace { 27 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); 34 35 class Mapper : public QuotaLimitHeuristic::BucketMapper { 36 public: 37 Mapper() {} 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++) { 42 int id; 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]); 47 } 48 } 49 50 private: 51 typedef std::map<int, Bucket*> BucketMap; 52 BucketMap buckets_; 53 DISALLOW_COPY_AND_ASSIGN(Mapper); 54 }; 55 56 class MockMapper : public QuotaLimitHeuristic::BucketMapper { 57 public: 58 virtual void GetBucketsForArgs(const base::ListValue* args, 59 BucketList* buckets) OVERRIDE {} 60 }; 61 62 class MockFunction : public ExtensionFunction { 63 public: 64 explicit MockFunction(const std::string& name) { set_name(name); } 65 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 {} 72 73 protected: 74 virtual ~MockFunction() {} 75 }; 76 77 class TimedLimitMockFunction : public MockFunction { 78 public: 79 explicit TimedLimitMockFunction(const std::string& name) 80 : MockFunction(name) {} 81 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const 82 OVERRIDE { 83 heuristics->push_back( 84 new TimedLimit(k2PerMinute, new Mapper(), kGenericName)); 85 } 86 87 private: 88 virtual ~TimedLimitMockFunction() {} 89 }; 90 91 class ChainedLimitsMockFunction : public MockFunction { 92 public: 93 explicit ChainedLimitsMockFunction(const std::string& name) 94 : MockFunction(name) {} 95 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const 96 OVERRIDE { 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)); 103 } 104 105 private: 106 virtual ~ChainedLimitsMockFunction() {} 107 }; 108 109 class FrozenMockFunction : public MockFunction { 110 public: 111 explicit FrozenMockFunction(const std::string& name) : MockFunction(name) {} 112 virtual void GetQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) const 113 OVERRIDE { 114 heuristics->push_back( 115 new TimedLimit(kFrozenConfig, new Mapper(), kGenericName)); 116 } 117 118 private: 119 virtual ~FrozenMockFunction() {} 120 }; 121 } // namespace 122 123 class QuotaServiceTest : public testing::Test { 124 public: 125 QuotaServiceTest() 126 : extension_a_("a"), 127 extension_b_("b"), 128 extension_c_("c"), 129 loop_(), 130 ui_thread_(BrowserThread::UI, &loop_) {} 131 virtual void SetUp() { service_.reset(new QuotaService()); } 132 virtual void TearDown() { 133 loop_.RunUntilIdle(); 134 service_.reset(); 135 } 136 137 protected: 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_; 144 }; 145 146 class QuotaLimitHeuristicTest : public testing::Test { 147 public: 148 static void DoMoreThan2PerMinuteFor5Minutes(const TimeTicks& start_time, 149 QuotaLimitHeuristic* lim, 150 Bucket* b, 151 int an_unexhausted_minute) { 152 for (int i = 0; i < 5; i++) { 153 // Perform one operation in each minute. 154 int m = i * 60; 155 EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(10 + m))); 156 EXPECT_TRUE(b->has_tokens()); 157 158 if (i == an_unexhausted_minute) 159 continue; // Don't exhaust all tokens this minute. 160 161 EXPECT_TRUE(lim->Apply(b, start_time + TimeDelta::FromSeconds(15 + m))); 162 EXPECT_FALSE(b->has_tokens()); 163 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()); 169 } 170 } 171 }; 172 173 TEST_F(QuotaLimitHeuristicTest, Timed) { 174 TimedLimit lim(k2PerMinute, new MockMapper(), kGenericName); 175 Bucket b; 176 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)); 183 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))); 190 } 191 192 TEST_F(QuotaLimitHeuristicTest, Sustained) { 193 SustainedLimit lim( 194 TimeDelta::FromMinutes(5), k2PerMinute, new MockMapper(), kGenericName); 195 Bucket bucket; 196 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))); 201 202 // The heuristic resets itself on a safe request. 203 EXPECT_TRUE(lim.Apply(&bucket, kStartTime + TimeDelta::FromDays(1))); 204 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))); 209 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))); 215 } 216 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)); 221 } 222 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)); 228 } 229 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)); 235 EXPECT_EQ("", 236 service_->Assess(extension_a_, 237 f.get(), 238 &args, 239 kStartTime + TimeDelta::FromSeconds(10))); 240 EXPECT_NE("", 241 service_->Assess(extension_a_, 242 f.get(), 243 &args, 244 kStartTime + TimeDelta::FromSeconds(15))); 245 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)); 250 EXPECT_EQ("", 251 service_->Assess(extension_b_, 252 f.get(), 253 &args2, 254 kStartTime + TimeDelta::FromSeconds(10))); 255 256 TimeDelta peace = TimeDelta::FromMinutes(30); 257 EXPECT_EQ("", 258 service_->Assess(extension_b_, f.get(), &args, kStartTime + peace)); 259 EXPECT_EQ("", 260 service_->Assess(extension_b_, 261 f.get(), 262 &args, 263 kStartTime + peace + TimeDelta::FromSeconds(10))); 264 EXPECT_NE("", 265 service_->Assess(extension_b_, 266 f.get(), 267 &args2, 268 kStartTime + peace + TimeDelta::FromSeconds(15))); 269 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)); 274 EXPECT_EQ("", 275 service_->Assess(extension_c_, 276 f.get(), 277 &args3, 278 kStartTime + TimeDelta::FromSeconds(10))); 279 EXPECT_EQ("", 280 service_->Assess(extension_c_, 281 f.get(), 282 &args, 283 kStartTime + TimeDelta::FromSeconds(15))); 284 EXPECT_EQ("", 285 service_->Assess(extension_c_, 286 f.get(), 287 &args3, 288 kStartTime + TimeDelta::FromSeconds(20))); 289 EXPECT_NE("", 290 service_->Assess(extension_c_, 291 f.get(), 292 &args, 293 kStartTime + TimeDelta::FromSeconds(25))); 294 EXPECT_NE("", 295 service_->Assess(extension_c_, 296 f.get(), 297 &args3, 298 kStartTime + TimeDelta::FromSeconds(30))); 299 } 300 301 TEST_F(QuotaServiceTest, ChainedHeuristics) { 302 scoped_refptr<MockFunction> f(new ChainedLimitsMockFunction("foo")); 303 base::ListValue args; 304 args.Append(new base::FundamentalValue(1)); 305 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++) { 310 EXPECT_EQ( 311 "", 312 service_->Assess(extension_a_, 313 f.get(), 314 &args, 315 kStartTime + TimeDelta::FromSeconds(10 + i * 60))); 316 } 317 318 // This will bring us to 21 events in an hour, which is a violation. 319 EXPECT_NE("", 320 service_->Assess(extension_a_, 321 f.get(), 322 &args, 323 kStartTime + TimeDelta::FromMinutes(30))); 324 325 // Now, check that we can still hit the lower limit. 326 for (int i = 0; i < 5; i++) { 327 EXPECT_EQ( 328 "", 329 service_->Assess(extension_b_, 330 f.get(), 331 &args, 332 kStartTime + TimeDelta::FromSeconds(10 + i * 60))); 333 EXPECT_EQ( 334 "", 335 service_->Assess(extension_b_, 336 f.get(), 337 &args, 338 kStartTime + TimeDelta::FromSeconds(15 + i * 60))); 339 EXPECT_EQ( 340 "", 341 service_->Assess(extension_b_, 342 f.get(), 343 &args, 344 kStartTime + TimeDelta::FromSeconds(20 + i * 60))); 345 } 346 347 EXPECT_NE("", 348 service_->Assess(extension_b_, 349 f.get(), 350 &args, 351 kStartTime + TimeDelta::FromMinutes(6))); 352 } 353 354 TEST_F(QuotaServiceTest, MultipleFunctionsDontInterfere) { 355 scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo")); 356 scoped_refptr<MockFunction> g(new TimedLimitMockFunction("bar")); 357 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)); 362 363 EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &args_f, kStartTime)); 364 EXPECT_EQ("", service_->Assess(extension_a_, g.get(), &args_g, kStartTime)); 365 EXPECT_EQ("", 366 service_->Assess(extension_a_, 367 f.get(), 368 &args_f, 369 kStartTime + TimeDelta::FromSeconds(10))); 370 EXPECT_EQ("", 371 service_->Assess(extension_a_, 372 g.get(), 373 &args_g, 374 kStartTime + TimeDelta::FromSeconds(10))); 375 EXPECT_NE("", 376 service_->Assess(extension_a_, 377 f.get(), 378 &args_f, 379 kStartTime + TimeDelta::FromSeconds(15))); 380 EXPECT_NE("", 381 service_->Assess(extension_a_, 382 g.get(), 383 &args_g, 384 kStartTime + TimeDelta::FromSeconds(15))); 385 } 386 387 TEST_F(QuotaServiceTest, ViolatorsWillBeViolators) { 388 scoped_refptr<MockFunction> f(new TimedLimitMockFunction("foo")); 389 scoped_refptr<MockFunction> g(new TimedLimitMockFunction("bar")); 390 base::ListValue arg; 391 arg.Append(new base::FundamentalValue(1)); 392 EXPECT_EQ("", service_->Assess(extension_a_, f.get(), &arg, kStartTime)); 393 EXPECT_EQ("", 394 service_->Assess(extension_a_, 395 f.get(), 396 &arg, 397 kStartTime + TimeDelta::FromSeconds(10))); 398 EXPECT_NE("", 399 service_->Assess(extension_a_, 400 f.get(), 401 &arg, 402 kStartTime + TimeDelta::FromSeconds(15))); 403 404 // We don't allow this extension to use quota limited functions even if they 405 // wait a while. 406 EXPECT_NE( 407 "", 408 service_->Assess( 409 extension_a_, f.get(), &arg, kStartTime + TimeDelta::FromDays(1))); 410 EXPECT_NE( 411 "", 412 service_->Assess( 413 extension_a_, g.get(), &arg, kStartTime + TimeDelta::FromDays(1))); 414 } 415 416 } // namespace extensions 417