1// Copyright (c) 2012 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 "chrome/browser/metrics/variations/variations_service.h"
6
7#include <vector>
8
9#include "base/base64.h"
10#include "base/prefs/testing_pref_service.h"
11#include "base/sha1.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "chrome/browser/web_resource/resource_request_allowed_notifier_test_util.h"
15#include "chrome/common/pref_names.h"
16#include "chrome/test/base/testing_browser_process.h"
17#include "chrome/test/base/testing_pref_service_syncable.h"
18#include "components/variations/proto/study.pb.h"
19#include "components/variations/proto/variations_seed.pb.h"
20#include "content/public/test/test_browser_thread.h"
21#include "net/base/url_util.h"
22#include "net/http/http_response_headers.h"
23#include "net/http/http_status_code.h"
24#include "net/url_request/test_url_fetcher_factory.h"
25#include "testing/gtest/include/gtest/gtest.h"
26
27#if defined(OS_CHROMEOS)
28#include "chrome/browser/chromeos/settings/cros_settings.h"
29#include "chrome/browser/chromeos/settings/device_settings_service.h"
30#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
31#endif
32
33namespace chrome_variations {
34
35namespace {
36
37// A test class used to validate expected functionality in VariationsService.
38class TestVariationsService : public VariationsService {
39 public:
40  TestVariationsService(TestRequestAllowedNotifier* test_notifier,
41                        PrefService* local_state)
42      : VariationsService(test_notifier, local_state, NULL),
43        intercepts_fetch_(true),
44        fetch_attempted_(false),
45        seed_stored_(false) {
46    // Set this so StartRepeatedVariationsSeedFetch can be called in tests.
47    SetCreateTrialsFromSeedCalledForTesting(true);
48  }
49
50  virtual ~TestVariationsService() {
51  }
52
53  void set_intercepts_fetch(bool value) {
54    intercepts_fetch_ = value;
55  }
56
57  bool fetch_attempted() const { return fetch_attempted_; }
58
59  bool seed_stored() const { return seed_stored_; }
60
61  virtual void DoActualFetch() OVERRIDE {
62    if (intercepts_fetch_) {
63      fetch_attempted_ = true;
64      return;
65    }
66
67    VariationsService::DoActualFetch();
68  }
69
70 protected:
71  virtual void StoreSeed(const std::string& seed_data,
72                         const std::string& seed_signature,
73                         const base::Time& date_fetched) OVERRIDE {
74    seed_stored_ = true;
75  }
76
77 private:
78  bool intercepts_fetch_;
79  bool fetch_attempted_;
80  bool seed_stored_;
81
82  DISALLOW_COPY_AND_ASSIGN(TestVariationsService);
83};
84
85class TestVariationsServiceObserver : public VariationsService::Observer {
86 public:
87  TestVariationsServiceObserver()
88      : best_effort_changes_notified_(0),
89        crticial_changes_notified_(0) {
90  }
91  virtual ~TestVariationsServiceObserver() {
92  }
93
94  virtual void OnExperimentChangesDetected(Severity severity) OVERRIDE {
95    switch (severity) {
96      case BEST_EFFORT:
97        ++best_effort_changes_notified_;
98        break;
99      case CRITICAL:
100        ++crticial_changes_notified_;
101        break;
102    }
103  }
104
105  int best_effort_changes_notified() const {
106    return best_effort_changes_notified_;
107  }
108
109  int crticial_changes_notified() const {
110    return crticial_changes_notified_;
111  }
112
113 private:
114  // Number of notification received with BEST_EFFORT severity.
115  int best_effort_changes_notified_;
116
117  // Number of notification received with CRITICAL severity.
118  int crticial_changes_notified_;
119
120  DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceObserver);
121};
122
123// Populates |seed| with simple test data. The resulting seed will contain one
124// study called "test", which contains one experiment called "abc" with
125// probability weight 100. |seed|'s study field will be cleared before adding
126// the new study.
127variations::VariationsSeed CreateTestSeed() {
128  variations::VariationsSeed seed;
129  variations::Study* study = seed.add_study();
130  study->set_name("test");
131  study->set_default_experiment_name("abc");
132  variations::Study_Experiment* experiment = study->add_experiment();
133  experiment->set_name("abc");
134  experiment->set_probability_weight(100);
135  seed.set_serial_number("123");
136  return seed;
137}
138
139// Serializes |seed| to protobuf binary format.
140std::string SerializeSeed(const variations::VariationsSeed& seed) {
141  std::string serialized_seed;
142  seed.SerializeToString(&serialized_seed);
143  return serialized_seed;
144}
145
146// Simulates a variations service response by setting a date header and the
147// specified HTTP |response_code| on |fetcher|.
148void SimulateServerResponse(int response_code, net::TestURLFetcher* fetcher) {
149  ASSERT_TRUE(fetcher);
150  scoped_refptr<net::HttpResponseHeaders> headers(
151      new net::HttpResponseHeaders("date:Wed, 13 Feb 2013 00:25:24 GMT\0\0"));
152  fetcher->set_response_headers(headers);
153  fetcher->set_response_code(response_code);
154}
155
156}  // namespace
157
158class VariationsServiceTest : public ::testing::Test {
159 protected:
160  VariationsServiceTest() {}
161
162 private:
163#if defined(OS_CHROMEOS)
164  // Not used directly. Initializes CrosSettings for testing.
165  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
166  chromeos::ScopedTestCrosSettings test_cros_settings_;
167#endif
168
169  DISALLOW_COPY_AND_ASSIGN(VariationsServiceTest);
170};
171
172#if !defined(OS_CHROMEOS)
173TEST_F(VariationsServiceTest, VariationsURLIsValid) {
174#if defined(OS_ANDROID)
175  // Android uses profile prefs as the PrefService to generate the URL.
176  TestingPrefServiceSyncable prefs;
177  VariationsService::RegisterProfilePrefs(prefs.registry());
178#else
179  TestingPrefServiceSimple prefs;
180  VariationsService::RegisterPrefs(prefs.registry());
181#endif
182  const std::string default_variations_url =
183      VariationsService::GetDefaultVariationsServerURLForTesting();
184
185  std::string value;
186  GURL url = VariationsService::GetVariationsServerURL(&prefs);
187  EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true));
188  EXPECT_FALSE(net::GetValueForKeyInQuery(url, "restrict", &value));
189
190  prefs.SetString(prefs::kVariationsRestrictParameter, "restricted");
191  url = VariationsService::GetVariationsServerURL(&prefs);
192  EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true));
193  EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value));
194  EXPECT_EQ("restricted", value);
195}
196#else
197class VariationsServiceTestChromeOS : public VariationsServiceTest {
198 protected:
199  VariationsServiceTestChromeOS() {}
200
201  virtual void SetUp() OVERRIDE {
202    cros_settings_ = chromeos::CrosSettings::Get();
203    DCHECK(cros_settings_ != NULL);
204    // Remove the real DeviceSettingsProvider and replace it with a stub that
205    // allows modifications in a test.
206    device_settings_provider_ = cros_settings_->GetProvider(
207        chromeos::kReportDeviceVersionInfo);
208    EXPECT_TRUE(device_settings_provider_ != NULL);
209    EXPECT_TRUE(cros_settings_->RemoveSettingsProvider(
210        device_settings_provider_));
211    cros_settings_->AddSettingsProvider(&stub_settings_provider_);
212  }
213
214  virtual void TearDown() OVERRIDE {
215    // Restore the real DeviceSettingsProvider.
216    EXPECT_TRUE(
217        cros_settings_->RemoveSettingsProvider(&stub_settings_provider_));
218    cros_settings_->AddSettingsProvider(device_settings_provider_);
219  }
220
221  void SetVariationsRestrictParameterPolicyValue(std::string value) {
222    cros_settings_->SetString(chromeos::kVariationsRestrictParameter, value);
223  }
224
225 private:
226  chromeos::CrosSettings* cros_settings_;
227  chromeos::StubCrosSettingsProvider stub_settings_provider_;
228  chromeos::CrosSettingsProvider* device_settings_provider_;
229
230  DISALLOW_COPY_AND_ASSIGN(VariationsServiceTestChromeOS);
231};
232
233TEST_F(VariationsServiceTestChromeOS, VariationsURLIsValid) {
234  TestingPrefServiceSimple prefs;
235  VariationsService::RegisterPrefs(prefs.registry());
236  const std::string default_variations_url =
237      VariationsService::GetDefaultVariationsServerURLForTesting();
238
239  std::string value;
240  GURL url = VariationsService::GetVariationsServerURL(&prefs);
241  EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true));
242  EXPECT_FALSE(net::GetValueForKeyInQuery(url, "restrict", &value));
243
244  SetVariationsRestrictParameterPolicyValue("restricted");
245  url = VariationsService::GetVariationsServerURL(&prefs);
246  EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true));
247  EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value));
248  EXPECT_EQ("restricted", value);
249}
250#endif
251
252TEST_F(VariationsServiceTest, VariationsURLHasOSNameParam) {
253  TestingPrefServiceSimple prefs;
254  VariationsService::RegisterPrefs(prefs.registry());
255  const GURL url = VariationsService::GetVariationsServerURL(&prefs);
256
257  std::string value;
258  EXPECT_TRUE(net::GetValueForKeyInQuery(url, "osname", &value));
259  EXPECT_FALSE(value.empty());
260}
261
262TEST_F(VariationsServiceTest, RequestsInitiallyNotAllowed) {
263  base::MessageLoopForUI message_loop;
264  content::TestBrowserThread ui_thread(content::BrowserThread::UI,
265                                       &message_loop);
266  TestingPrefServiceSimple prefs;
267  VariationsService::RegisterPrefs(prefs.registry());
268
269  // Pass ownership to TestVariationsService, but keep a weak pointer to
270  // manipulate it for this test.
271  TestRequestAllowedNotifier* test_notifier = new TestRequestAllowedNotifier;
272  TestVariationsService test_service(test_notifier, &prefs);
273
274  // Force the notifier to initially disallow requests.
275  test_notifier->SetRequestsAllowedOverride(false);
276  test_service.StartRepeatedVariationsSeedFetch();
277  EXPECT_FALSE(test_service.fetch_attempted());
278
279  test_notifier->NotifyObserver();
280  EXPECT_TRUE(test_service.fetch_attempted());
281}
282
283TEST_F(VariationsServiceTest, RequestsInitiallyAllowed) {
284  base::MessageLoopForUI message_loop;
285  content::TestBrowserThread ui_thread(content::BrowserThread::UI,
286                                       &message_loop);
287  TestingPrefServiceSimple prefs;
288  VariationsService::RegisterPrefs(prefs.registry());
289
290  // Pass ownership to TestVariationsService, but keep a weak pointer to
291  // manipulate it for this test.
292  TestRequestAllowedNotifier* test_notifier = new TestRequestAllowedNotifier;
293  TestVariationsService test_service(test_notifier, &prefs);
294
295  test_notifier->SetRequestsAllowedOverride(true);
296  test_service.StartRepeatedVariationsSeedFetch();
297  EXPECT_TRUE(test_service.fetch_attempted());
298}
299
300TEST_F(VariationsServiceTest, SeedStoredWhenOKStatus) {
301  base::MessageLoop message_loop;
302  content::TestBrowserThread io_thread(content::BrowserThread::IO,
303                                       &message_loop);
304  TestingPrefServiceSimple prefs;
305  VariationsService::RegisterPrefs(prefs.registry());
306
307  TestVariationsService service(new TestRequestAllowedNotifier, &prefs);
308  service.set_intercepts_fetch(false);
309
310  net::TestURLFetcherFactory factory;
311  service.DoActualFetch();
312
313  net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
314  SimulateServerResponse(net::HTTP_OK, fetcher);
315  fetcher->SetResponseString(SerializeSeed(CreateTestSeed()));
316
317  EXPECT_FALSE(service.seed_stored());
318  service.OnURLFetchComplete(fetcher);
319  EXPECT_TRUE(service.seed_stored());
320}
321
322TEST_F(VariationsServiceTest, SeedNotStoredWhenNonOKStatus) {
323  const int non_ok_status_codes[] = {
324    net::HTTP_NO_CONTENT,
325    net::HTTP_NOT_MODIFIED,
326    net::HTTP_NOT_FOUND,
327    net::HTTP_INTERNAL_SERVER_ERROR,
328    net::HTTP_SERVICE_UNAVAILABLE,
329  };
330
331  base::MessageLoop message_loop;
332  content::TestBrowserThread io_thread(content::BrowserThread::IO,
333                                       &message_loop);
334  TestingPrefServiceSimple prefs;
335  VariationsService::RegisterPrefs(prefs.registry());
336
337  VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL);
338  for (size_t i = 0; i < arraysize(non_ok_status_codes); ++i) {
339    net::TestURLFetcherFactory factory;
340    service.DoActualFetch();
341    EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue());
342
343    net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
344    SimulateServerResponse(non_ok_status_codes[i], fetcher);
345    service.OnURLFetchComplete(fetcher);
346
347    EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue());
348  }
349}
350
351TEST_F(VariationsServiceTest, SeedDateUpdatedOn304Status) {
352  base::MessageLoop message_loop;
353  content::TestBrowserThread io_thread(content::BrowserThread::IO,
354                                       &message_loop);
355  TestingPrefServiceSimple prefs;
356  VariationsService::RegisterPrefs(prefs.registry());
357
358  VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL);
359  net::TestURLFetcherFactory factory;
360  service.DoActualFetch();
361  EXPECT_TRUE(
362      prefs.FindPreference(prefs::kVariationsSeedDate)->IsDefaultValue());
363
364  net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
365  SimulateServerResponse(net::HTTP_NOT_MODIFIED, fetcher);
366  service.OnURLFetchComplete(fetcher);
367  EXPECT_FALSE(
368      prefs.FindPreference(prefs::kVariationsSeedDate)->IsDefaultValue());
369}
370
371TEST_F(VariationsServiceTest, Observer) {
372  TestingPrefServiceSimple prefs;
373  VariationsService::RegisterPrefs(prefs.registry());
374  VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL);
375
376  struct {
377    int normal_count;
378    int best_effort_count;
379    int critical_count;
380    int expected_best_effort_notifications;
381    int expected_crtical_notifications;
382  } cases[] = {
383      {0, 0, 0, 0, 0},
384      {1, 0, 0, 0, 0},
385      {10, 0, 0, 0, 0},
386      {0, 1, 0, 1, 0},
387      {0, 10, 0, 1, 0},
388      {0, 0, 1, 0, 1},
389      {0, 0, 10, 0, 1},
390      {0, 1, 1, 0, 1},
391      {1, 1, 1, 0, 1},
392      {1, 1, 0, 1, 0},
393      {1, 0, 1, 0, 1},
394  };
395
396  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
397    TestVariationsServiceObserver observer;
398    service.AddObserver(&observer);
399
400    variations::VariationsSeedSimulator::Result result;
401    result.normal_group_change_count = cases[i].normal_count;
402    result.kill_best_effort_group_change_count = cases[i].best_effort_count;
403    result.kill_critical_group_change_count = cases[i].critical_count;
404    service.NotifyObservers(result);
405
406    EXPECT_EQ(cases[i].expected_best_effort_notifications,
407              observer.best_effort_changes_notified()) << i;
408    EXPECT_EQ(cases[i].expected_crtical_notifications,
409              observer.crticial_changes_notified()) << i;
410
411    service.RemoveObserver(&observer);
412  }
413}
414
415}  // namespace chrome_variations
416