// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/metrics/variations/variations_service.h" #include #include "base/base64.h" #include "base/prefs/testing_pref_service.h" #include "base/sha1.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "chrome/browser/web_resource/resource_request_allowed_notifier_test_util.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/testing_pref_service_syncable.h" #include "components/variations/proto/study.pb.h" #include "components/variations/proto/variations_seed.pb.h" #include "content/public/test/test_browser_thread.h" #include "net/base/url_util.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/url_request/test_url_fetcher_factory.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h" #endif namespace chrome_variations { namespace { // A test class used to validate expected functionality in VariationsService. class TestVariationsService : public VariationsService { public: TestVariationsService(TestRequestAllowedNotifier* test_notifier, PrefService* local_state) : VariationsService(test_notifier, local_state, NULL), intercepts_fetch_(true), fetch_attempted_(false), seed_stored_(false) { // Set this so StartRepeatedVariationsSeedFetch can be called in tests. SetCreateTrialsFromSeedCalledForTesting(true); } virtual ~TestVariationsService() { } void set_intercepts_fetch(bool value) { intercepts_fetch_ = value; } bool fetch_attempted() const { return fetch_attempted_; } bool seed_stored() const { return seed_stored_; } virtual void DoActualFetch() OVERRIDE { if (intercepts_fetch_) { fetch_attempted_ = true; return; } VariationsService::DoActualFetch(); } protected: virtual void StoreSeed(const std::string& seed_data, const std::string& seed_signature, const base::Time& date_fetched) OVERRIDE { seed_stored_ = true; } private: bool intercepts_fetch_; bool fetch_attempted_; bool seed_stored_; DISALLOW_COPY_AND_ASSIGN(TestVariationsService); }; class TestVariationsServiceObserver : public VariationsService::Observer { public: TestVariationsServiceObserver() : best_effort_changes_notified_(0), crticial_changes_notified_(0) { } virtual ~TestVariationsServiceObserver() { } virtual void OnExperimentChangesDetected(Severity severity) OVERRIDE { switch (severity) { case BEST_EFFORT: ++best_effort_changes_notified_; break; case CRITICAL: ++crticial_changes_notified_; break; } } int best_effort_changes_notified() const { return best_effort_changes_notified_; } int crticial_changes_notified() const { return crticial_changes_notified_; } private: // Number of notification received with BEST_EFFORT severity. int best_effort_changes_notified_; // Number of notification received with CRITICAL severity. int crticial_changes_notified_; DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceObserver); }; // Populates |seed| with simple test data. The resulting seed will contain one // study called "test", which contains one experiment called "abc" with // probability weight 100. |seed|'s study field will be cleared before adding // the new study. variations::VariationsSeed CreateTestSeed() { variations::VariationsSeed seed; variations::Study* study = seed.add_study(); study->set_name("test"); study->set_default_experiment_name("abc"); variations::Study_Experiment* experiment = study->add_experiment(); experiment->set_name("abc"); experiment->set_probability_weight(100); seed.set_serial_number("123"); return seed; } // Serializes |seed| to protobuf binary format. std::string SerializeSeed(const variations::VariationsSeed& seed) { std::string serialized_seed; seed.SerializeToString(&serialized_seed); return serialized_seed; } // Simulates a variations service response by setting a date header and the // specified HTTP |response_code| on |fetcher|. void SimulateServerResponse(int response_code, net::TestURLFetcher* fetcher) { ASSERT_TRUE(fetcher); scoped_refptr headers( new net::HttpResponseHeaders("date:Wed, 13 Feb 2013 00:25:24 GMT\0\0")); fetcher->set_response_headers(headers); fetcher->set_response_code(response_code); } } // namespace class VariationsServiceTest : public ::testing::Test { protected: VariationsServiceTest() {} private: #if defined(OS_CHROMEOS) // Not used directly. Initializes CrosSettings for testing. chromeos::ScopedTestDeviceSettingsService test_device_settings_service_; chromeos::ScopedTestCrosSettings test_cros_settings_; #endif DISALLOW_COPY_AND_ASSIGN(VariationsServiceTest); }; #if !defined(OS_CHROMEOS) TEST_F(VariationsServiceTest, VariationsURLIsValid) { #if defined(OS_ANDROID) // Android uses profile prefs as the PrefService to generate the URL. TestingPrefServiceSyncable prefs; VariationsService::RegisterProfilePrefs(prefs.registry()); #else TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); #endif const std::string default_variations_url = VariationsService::GetDefaultVariationsServerURLForTesting(); std::string value; GURL url = VariationsService::GetVariationsServerURL(&prefs); EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true)); EXPECT_FALSE(net::GetValueForKeyInQuery(url, "restrict", &value)); prefs.SetString(prefs::kVariationsRestrictParameter, "restricted"); url = VariationsService::GetVariationsServerURL(&prefs); EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true)); EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value)); EXPECT_EQ("restricted", value); } #else class VariationsServiceTestChromeOS : public VariationsServiceTest { protected: VariationsServiceTestChromeOS() {} virtual void SetUp() OVERRIDE { cros_settings_ = chromeos::CrosSettings::Get(); DCHECK(cros_settings_ != NULL); // Remove the real DeviceSettingsProvider and replace it with a stub that // allows modifications in a test. device_settings_provider_ = cros_settings_->GetProvider( chromeos::kReportDeviceVersionInfo); EXPECT_TRUE(device_settings_provider_ != NULL); EXPECT_TRUE(cros_settings_->RemoveSettingsProvider( device_settings_provider_)); cros_settings_->AddSettingsProvider(&stub_settings_provider_); } virtual void TearDown() OVERRIDE { // Restore the real DeviceSettingsProvider. EXPECT_TRUE( cros_settings_->RemoveSettingsProvider(&stub_settings_provider_)); cros_settings_->AddSettingsProvider(device_settings_provider_); } void SetVariationsRestrictParameterPolicyValue(std::string value) { cros_settings_->SetString(chromeos::kVariationsRestrictParameter, value); } private: chromeos::CrosSettings* cros_settings_; chromeos::StubCrosSettingsProvider stub_settings_provider_; chromeos::CrosSettingsProvider* device_settings_provider_; DISALLOW_COPY_AND_ASSIGN(VariationsServiceTestChromeOS); }; TEST_F(VariationsServiceTestChromeOS, VariationsURLIsValid) { TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); const std::string default_variations_url = VariationsService::GetDefaultVariationsServerURLForTesting(); std::string value; GURL url = VariationsService::GetVariationsServerURL(&prefs); EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true)); EXPECT_FALSE(net::GetValueForKeyInQuery(url, "restrict", &value)); SetVariationsRestrictParameterPolicyValue("restricted"); url = VariationsService::GetVariationsServerURL(&prefs); EXPECT_TRUE(StartsWithASCII(url.spec(), default_variations_url, true)); EXPECT_TRUE(net::GetValueForKeyInQuery(url, "restrict", &value)); EXPECT_EQ("restricted", value); } #endif TEST_F(VariationsServiceTest, VariationsURLHasOSNameParam) { TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); const GURL url = VariationsService::GetVariationsServerURL(&prefs); std::string value; EXPECT_TRUE(net::GetValueForKeyInQuery(url, "osname", &value)); EXPECT_FALSE(value.empty()); } TEST_F(VariationsServiceTest, RequestsInitiallyNotAllowed) { base::MessageLoopForUI message_loop; content::TestBrowserThread ui_thread(content::BrowserThread::UI, &message_loop); TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); // Pass ownership to TestVariationsService, but keep a weak pointer to // manipulate it for this test. TestRequestAllowedNotifier* test_notifier = new TestRequestAllowedNotifier; TestVariationsService test_service(test_notifier, &prefs); // Force the notifier to initially disallow requests. test_notifier->SetRequestsAllowedOverride(false); test_service.StartRepeatedVariationsSeedFetch(); EXPECT_FALSE(test_service.fetch_attempted()); test_notifier->NotifyObserver(); EXPECT_TRUE(test_service.fetch_attempted()); } TEST_F(VariationsServiceTest, RequestsInitiallyAllowed) { base::MessageLoopForUI message_loop; content::TestBrowserThread ui_thread(content::BrowserThread::UI, &message_loop); TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); // Pass ownership to TestVariationsService, but keep a weak pointer to // manipulate it for this test. TestRequestAllowedNotifier* test_notifier = new TestRequestAllowedNotifier; TestVariationsService test_service(test_notifier, &prefs); test_notifier->SetRequestsAllowedOverride(true); test_service.StartRepeatedVariationsSeedFetch(); EXPECT_TRUE(test_service.fetch_attempted()); } TEST_F(VariationsServiceTest, SeedStoredWhenOKStatus) { base::MessageLoop message_loop; content::TestBrowserThread io_thread(content::BrowserThread::IO, &message_loop); TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); TestVariationsService service(new TestRequestAllowedNotifier, &prefs); service.set_intercepts_fetch(false); net::TestURLFetcherFactory factory; service.DoActualFetch(); net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); SimulateServerResponse(net::HTTP_OK, fetcher); fetcher->SetResponseString(SerializeSeed(CreateTestSeed())); EXPECT_FALSE(service.seed_stored()); service.OnURLFetchComplete(fetcher); EXPECT_TRUE(service.seed_stored()); } TEST_F(VariationsServiceTest, SeedNotStoredWhenNonOKStatus) { const int non_ok_status_codes[] = { net::HTTP_NO_CONTENT, net::HTTP_NOT_MODIFIED, net::HTTP_NOT_FOUND, net::HTTP_INTERNAL_SERVER_ERROR, net::HTTP_SERVICE_UNAVAILABLE, }; base::MessageLoop message_loop; content::TestBrowserThread io_thread(content::BrowserThread::IO, &message_loop); TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL); for (size_t i = 0; i < arraysize(non_ok_status_codes); ++i) { net::TestURLFetcherFactory factory; service.DoActualFetch(); EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue()); net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); SimulateServerResponse(non_ok_status_codes[i], fetcher); service.OnURLFetchComplete(fetcher); EXPECT_TRUE(prefs.FindPreference(prefs::kVariationsSeed)->IsDefaultValue()); } } TEST_F(VariationsServiceTest, SeedDateUpdatedOn304Status) { base::MessageLoop message_loop; content::TestBrowserThread io_thread(content::BrowserThread::IO, &message_loop); TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL); net::TestURLFetcherFactory factory; service.DoActualFetch(); EXPECT_TRUE( prefs.FindPreference(prefs::kVariationsSeedDate)->IsDefaultValue()); net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); SimulateServerResponse(net::HTTP_NOT_MODIFIED, fetcher); service.OnURLFetchComplete(fetcher); EXPECT_FALSE( prefs.FindPreference(prefs::kVariationsSeedDate)->IsDefaultValue()); } TEST_F(VariationsServiceTest, Observer) { TestingPrefServiceSimple prefs; VariationsService::RegisterPrefs(prefs.registry()); VariationsService service(new TestRequestAllowedNotifier, &prefs, NULL); struct { int normal_count; int best_effort_count; int critical_count; int expected_best_effort_notifications; int expected_crtical_notifications; } cases[] = { {0, 0, 0, 0, 0}, {1, 0, 0, 0, 0}, {10, 0, 0, 0, 0}, {0, 1, 0, 1, 0}, {0, 10, 0, 1, 0}, {0, 0, 1, 0, 1}, {0, 0, 10, 0, 1}, {0, 1, 1, 0, 1}, {1, 1, 1, 0, 1}, {1, 1, 0, 1, 0}, {1, 0, 1, 0, 1}, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { TestVariationsServiceObserver observer; service.AddObserver(&observer); variations::VariationsSeedSimulator::Result result; result.normal_group_change_count = cases[i].normal_count; result.kill_best_effort_group_change_count = cases[i].best_effort_count; result.kill_critical_group_change_count = cases[i].critical_count; service.NotifyObservers(result); EXPECT_EQ(cases[i].expected_best_effort_notifications, observer.best_effort_changes_notified()) << i; EXPECT_EQ(cases[i].expected_crtical_notifications, observer.crticial_changes_notified()) << i; service.RemoveObserver(&observer); } } } // namespace chrome_variations