Home | History | Annotate | Download | only in browser
      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