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