1// Copyright (c) 2011 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// TODO(rickcam): Bug 73183: Add unit tests for image loading
6
7#include <cstdlib>
8#include <set>
9
10#include "chrome/browser/background_application_list_model.h"
11
12#include "base/command_line.h"
13#include "base/file_path.h"
14#include "base/file_util.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/message_loop.h"
17#include "base/stl_util-inl.h"
18#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/common/extensions/extension.h"
20#include "chrome/test/testing_profile.h"
21#include "content/browser/browser_thread.h"
22#include "content/common/notification_registrar.h"
23#include "content/common/notification_service.h"
24#include "content/common/notification_type.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27// This value is used to seed the PRNG at the beginning of a sequence of
28// operations to produce a repeatable sequence.
29#define RANDOM_SEED (0x33F7A7A7)
30
31// For ExtensionService interface when it requires a path that is not used.
32FilePath bogus_file_path() {
33  return FilePath(FILE_PATH_LITERAL("//foobar_nonexistent"));
34}
35
36class BackgroundApplicationListModelTest : public testing::Test {
37 public:
38  BackgroundApplicationListModelTest();
39  ~BackgroundApplicationListModelTest();
40
41  virtual void InitializeEmptyExtensionService();
42
43 protected:
44  scoped_ptr<Profile> profile_;
45  scoped_refptr<ExtensionService> service_;
46  MessageLoop loop_;
47  BrowserThread ui_thread_;
48};
49
50// The message loop may be used in tests which require it to be an IO loop.
51BackgroundApplicationListModelTest::BackgroundApplicationListModelTest()
52    : loop_(MessageLoop::TYPE_IO),
53      ui_thread_(BrowserThread::UI, &loop_) {
54}
55
56BackgroundApplicationListModelTest::~BackgroundApplicationListModelTest() {
57  // Drop reference to ExtensionService and TestingProfile, so that they can be
58  // destroyed while BrowserThreads and MessageLoop are still around.  They
59  // are used in the destruction process.
60  service_ = NULL;
61  profile_.reset(NULL);
62  MessageLoop::current()->RunAllPending();
63}
64
65// This is modeled on a similar routine in ExtensionServiceTestBase.
66void BackgroundApplicationListModelTest::InitializeEmptyExtensionService() {
67  TestingProfile* profile = new TestingProfile();
68  profile_.reset(profile);
69  service_ = profile->CreateExtensionService(
70      CommandLine::ForCurrentProcess(),
71      bogus_file_path(), false);
72  service_->set_extensions_enabled(true);
73  service_->set_show_extensions_prompts(false);
74  service_->OnLoadedInstalledExtensions(); /* Sends EXTENSIONS_READY */
75}
76
77// Returns a barebones test Extension object with the specified |name|.  The
78// returned extension will include background permission iff
79// |background_permission| is true.
80static scoped_refptr<Extension> CreateExtension(const std::string& name,
81                                                bool background_permission) {
82  DictionaryValue manifest;
83  manifest.SetString(extension_manifest_keys::kVersion, "1.0.0.0");
84  manifest.SetString(extension_manifest_keys::kName, name);
85  if (background_permission) {
86    ListValue* permissions = new ListValue();
87    manifest.Set(extension_manifest_keys::kPermissions, permissions);
88    permissions->Append(Value::CreateStringValue("background"));
89  }
90  std::string error;
91  scoped_refptr<Extension> extension = Extension::Create(
92      bogus_file_path().AppendASCII(name), Extension::INVALID, manifest,
93      Extension::STRICT_ERROR_CHECKS, &error);
94  // Cannot ASSERT_* here because that attempts an illegitimate return.
95  // Cannot EXPECT_NE here because that assumes non-pointers unlike EXPECT_EQ
96  EXPECT_TRUE(extension.get() != NULL) << error;
97  return extension;
98}
99
100// With minimal test logic, verifies behavior over an explicit set of
101// extensions, of which some are Background Apps and others are not.
102TEST_F(BackgroundApplicationListModelTest, LoadExplicitExtensions) {
103  InitializeEmptyExtensionService();
104  ExtensionService* service = profile_->GetExtensionService();
105  ASSERT_TRUE(service);
106  ASSERT_TRUE(service->is_ready());
107  ASSERT_TRUE(service->extensions());
108  ASSERT_TRUE(service->extensions()->empty());
109  scoped_ptr<BackgroundApplicationListModel> model(
110      new BackgroundApplicationListModel(profile_.get()));
111  ASSERT_EQ(0U, model->size());
112
113  scoped_refptr<Extension> ext1 = CreateExtension("alpha", false);
114  scoped_refptr<Extension> ext2 = CreateExtension("bravo", false);
115  scoped_refptr<Extension> ext3 = CreateExtension("charlie", false);
116  scoped_refptr<Extension> bgapp1 = CreateExtension("delta", true);
117  scoped_refptr<Extension> bgapp2 = CreateExtension("echo", true);
118  ASSERT_TRUE(service->extensions() != NULL);
119  ASSERT_EQ(0U, service->extensions()->size());
120  ASSERT_EQ(0U, model->size());
121  // Add alternating Extensions and Background Apps
122  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext1));
123  service->AddExtension(ext1);
124  ASSERT_EQ(1U, service->extensions()->size());
125  ASSERT_EQ(0U, model->size());
126  ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp1));
127  service->AddExtension(bgapp1);
128  ASSERT_EQ(2U, service->extensions()->size());
129  ASSERT_EQ(1U, model->size());
130  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext2));
131  service->AddExtension(ext2);
132  ASSERT_EQ(3U, service->extensions()->size());
133  ASSERT_EQ(1U, model->size());
134  ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp2));
135  service->AddExtension(bgapp2);
136  ASSERT_EQ(4U, service->extensions()->size());
137  ASSERT_EQ(2U, model->size());
138  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext3));
139  service->AddExtension(ext3);
140  ASSERT_EQ(5U, service->extensions()->size());
141  ASSERT_EQ(2U, model->size());
142  // Remove in FIFO order.
143  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext1));
144  service->UninstallExtension(ext1->id(), false, NULL);
145  ASSERT_EQ(4U, service->extensions()->size());
146  ASSERT_EQ(2U, model->size());
147  ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp1));
148  service->UninstallExtension(bgapp1->id(), false, NULL);
149  ASSERT_EQ(3U, service->extensions()->size());
150  ASSERT_EQ(1U, model->size());
151  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext2));
152  service->UninstallExtension(ext2->id(), false, NULL);
153  ASSERT_EQ(2U, service->extensions()->size());
154  ASSERT_EQ(1U, model->size());
155  ASSERT_TRUE(BackgroundApplicationListModel::IsBackgroundApp(*bgapp2));
156  service->UninstallExtension(bgapp2->id(), false, NULL);
157  ASSERT_EQ(1U, service->extensions()->size());
158  ASSERT_EQ(0U, model->size());
159  ASSERT_FALSE(BackgroundApplicationListModel::IsBackgroundApp(*ext3));
160  service->UninstallExtension(ext3->id(), false, NULL);
161  ASSERT_EQ(0U, service->extensions()->size());
162  ASSERT_EQ(0U, model->size());
163}
164
165typedef std::set<scoped_refptr<Extension> > ExtensionSet;
166
167namespace {
168std::string GenerateUniqueExtensionName() {
169  static int uniqueness = 0;
170  std::ostringstream output;
171  output << "Unique Named Extension " << uniqueness;
172  ++uniqueness;
173  return output.str();
174}
175}
176
177// Verifies behavior with a pseudo-randomly generated set of actions: Adding and
178// removing extensions, of which some are Background Apps and others are not.
179TEST_F(BackgroundApplicationListModelTest, LoadRandomExtension) {
180  InitializeEmptyExtensionService();
181  ExtensionService* service = profile_->GetExtensionService();
182  ASSERT_TRUE(service);
183  ASSERT_TRUE(service->is_ready());
184  ASSERT_TRUE(service->extensions());
185  ASSERT_TRUE(service->extensions()->empty());
186  scoped_ptr<BackgroundApplicationListModel> model(
187      new BackgroundApplicationListModel(profile_.get()));
188  ASSERT_EQ(0U, model->size());
189
190  static const int kIterations = 500;
191  ExtensionSet extensions;
192  size_t count = 0;
193  size_t expected = 0;
194  srand(RANDOM_SEED);
195  for (int index = 0; index < kIterations; ++index) {
196    if (rand() % 2) {  // Add an extension
197      std::string name = GenerateUniqueExtensionName();
198      bool create_background = false;
199      if (rand() % 2) {
200        create_background = true;
201        ++expected;
202      }
203      scoped_refptr<Extension> extension =
204          CreateExtension(name, create_background);
205      ASSERT_EQ(BackgroundApplicationListModel::IsBackgroundApp(*extension),
206                create_background);
207      extensions.insert(extension);
208      ++count;
209      ASSERT_EQ(count, extensions.size());
210      service->AddExtension(extension);
211      ASSERT_EQ(count, service->extensions()->size());
212      ASSERT_EQ(expected, model->size());
213    } else {  // Maybe remove an extension.
214      ExtensionSet::iterator cursor = extensions.begin();
215      if (cursor == extensions.end()) {
216        // Nothing to remove.  Just verify accounting.
217        ASSERT_EQ(0U, count);
218        ASSERT_EQ(0U, expected);
219        ASSERT_EQ(0U, service->extensions()->size());
220        ASSERT_EQ(0U, model->size());
221      } else {
222        // Randomly select which extension to remove
223        if (extensions.size() > 1) {
224          int offset = rand() % (extensions.size() - 1);
225          for (int index = 0; index < offset; ++index)
226            ++cursor;
227        }
228        scoped_refptr<Extension> extension = cursor->get();
229        std::string id = extension->id();
230        if (BackgroundApplicationListModel::IsBackgroundApp(*extension))
231          --expected;
232        extensions.erase(cursor);
233        --count;
234        ASSERT_EQ(count, extensions.size());
235        service->UninstallExtension(extension->id(), false, NULL);
236        ASSERT_EQ(count, service->extensions()->size());
237        ASSERT_EQ(expected, model->size());
238      }
239    }
240  }
241}
242