diff options
author | rogerta@chromium.org <rogerta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-30 04:41:11 +0000 |
---|---|---|
committer | rogerta@chromium.org <rogerta@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-30 04:41:11 +0000 |
commit | 48d28bae330ad492e76a340e3d0b7c02cd049bb1 (patch) | |
tree | 0cbd67e5fac6804048be460d81db56231408fba1 /chrome/browser/rlz | |
parent | 911f3188a95bda8d6b259e590e8dce22c83eb92a (diff) | |
download | chromium_src-48d28bae330ad492e76a340e3d0b7c02cd049bb1.zip chromium_src-48d28bae330ad492e76a340e3d0b7c02cd049bb1.tar.gz chromium_src-48d28bae330ad492e76a340e3d0b7c02cd049bb1.tar.bz2 |
Adding unit tests to RLZ code. Refactoring RLZ code to make it more testable.
There is one new functionality, which is to support the CHROME_HOME_PAGE access
point, but in this CL that new function is disabled. However, it is unit
tested.
BUG=None
TEST=See new unit tests
Review URL: http://codereview.chromium.org/7736001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@98775 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/rlz')
-rw-r--r-- | chrome/browser/rlz/rlz.cc | 520 | ||||
-rw-r--r-- | chrome/browser/rlz/rlz.h | 122 | ||||
-rw-r--r-- | chrome/browser/rlz/rlz_unittest.cc | 547 |
3 files changed, 893 insertions, 296 deletions
diff --git a/chrome/browser/rlz/rlz.cc b/chrome/browser/rlz/rlz.cc index acdaf7f..1af7d7b 100644 --- a/chrome/browser/rlz/rlz.cc +++ b/chrome/browser/rlz/rlz.cc @@ -31,23 +31,78 @@ #include "chrome/common/env_vars.h" #include "chrome/installer/util/google_update_settings.h" #include "content/browser/browser_thread.h" -#include "content/common/notification_registrar.h" +#include "content/browser/tab_contents/navigation_entry.h" #include "content/common/notification_service.h" namespace { -enum { - ACCESS_VALUES_STALE, // Possibly new values available. - ACCESS_VALUES_FRESH // The cached values are current. -}; +// Organic brands all start with GG, such as GGCM. +static bool is_organic(const std::wstring& brand) { + return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG"); +} + +void RecordProductEvents(bool first_run, bool google_default_search, + bool google_default_homepage, bool already_ran, + bool omnibox_used, bool homepage_used) { + // Record the installation of chrome. We call this all the time but the rlz + // lib should ingore all but the first one. + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_OMNIBOX, + rlz_lib::INSTALL); + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_HOME_PAGE, + rlz_lib::INSTALL); + + if (!already_ran) { + // Do the initial event recording if is the first run or if we have an + // empty rlz which means we haven't got a chance to do it. + char omnibox_rlz[rlz_lib::kMaxRlzLength + 1]; + if (!rlz_lib::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, omnibox_rlz, + rlz_lib::kMaxRlzLength, NULL)) { + omnibox_rlz[0] = 0; + } -// Tracks if we have tried and succeeded sending the ping. This helps us -// decide if we need to refresh the cached RLZ string. -volatile int access_values_state = ACCESS_VALUES_STALE; -base::Lock rlz_lock; + // Record if google is the initial search provider and/or home page. + if ((first_run || omnibox_rlz[0] == 0) && google_default_search) { + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_OMNIBOX, + rlz_lib::SET_TO_GOOGLE); + } -bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang, - const std::wstring& referral, bool exclude_id) { + char homepage_rlz[rlz_lib::kMaxRlzLength + 1]; + if (!rlz_lib::GetAccessPointRlz(rlz_lib::CHROME_HOME_PAGE, homepage_rlz, + rlz_lib::kMaxRlzLength, NULL)) { + homepage_rlz[0] = 0; + } + + if ((first_run || homepage_rlz[0] == 0) && google_default_homepage) { + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_HOME_PAGE, + rlz_lib::SET_TO_GOOGLE); + } + } + + // Record first user interaction with the omnibox. We call this all the + // time but the rlz lib should ingore all but the first one. + if (omnibox_used) { + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_OMNIBOX, + rlz_lib::FIRST_SEARCH); + } + + // Record first user interaction with the home page. We call this all the + // time but the rlz lib should ingore all but the first one. + if (homepage_used) { + rlz_lib::RecordProductEvent(rlz_lib::CHROME, + rlz_lib::CHROME_HOME_PAGE, + rlz_lib::FIRST_SEARCH); + } +} + +bool SendFinancialPing(const std::wstring& brand, + const std::wstring& lang, + const std::wstring& referral, + bool exclude_id) { rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX, rlz_lib::CHROME_HOME_PAGE, rlz_lib::NO_ACCESS_POINT}; @@ -57,7 +112,7 @@ bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang, // If chrome has been reactivated, send a ping for this brand as well. // We ignore the return value of SendFinancialPing() since we'll try again - // later anyway. Callers of this function are only interested in whether + // later anyway. Callers of this function are only interested in whether // the ping for the main brand succeeded or not. std::wstring reactivation_brand; if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand)) { @@ -74,248 +129,196 @@ bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang, lang_ascii.c_str(), exclude_id, NULL, true); } -// This class leverages the AutocompleteEditModel notification to know when -// the user first interacted with the omnibox and set a global accordingly. -class OmniBoxUsageObserver : public NotificationObserver { - public: - OmniBoxUsageObserver(bool first_run, bool send_ping_immediately, - bool google_default_search) - : first_run_(first_run), - send_ping_immediately_(send_ping_immediately), - google_default_search_(google_default_search) { - registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, - NotificationService::AllSources()); - // If instant is enabled we'll start searching as soon as the user starts - // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED). - registrar_.Add(this, chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, - NotificationService::AllSources()); - omnibox_used_ = false; - DCHECK(!instance_); - instance_ = this; - } - - virtual void Observe(int type, - const NotificationSource& source, - const NotificationDetails& details); - - static bool used() { - return omnibox_used_; - } - - // Deletes the single instance of OmniBoxUsageObserver. - static void DeleteInstance() { - delete instance_; - } +} // namespace - private: - // Dtor is private so the object cannot be created on the stack. - ~OmniBoxUsageObserver() { - instance_ = NULL; - } +RLZTracker* RLZTracker::tracker_ = NULL; - static bool omnibox_used_; +// static +RLZTracker* RLZTracker::GetInstance() { + return tracker_ ? tracker_ : Singleton<RLZTracker>::get(); +} - // There should only be one instance created at a time, and instance_ points - // to that instance. - // NOTE: this is only non-null for the amount of time it is needed. Once the - // instance_ is no longer needed (or Chrome is exiting), this is null. - static OmniBoxUsageObserver* instance_; +RLZTracker::RLZTracker() + : first_run_(false), + send_ping_immediately_(false), + google_default_search_(false), + already_ran_(false), + omnibox_used_(false), + homepage_used_(false) { +} - NotificationRegistrar registrar_; - bool first_run_; - bool send_ping_immediately_; - bool google_default_search_; -}; +RLZTracker::~RLZTracker() { +} -bool OmniBoxUsageObserver::omnibox_used_ = false; -OmniBoxUsageObserver* OmniBoxUsageObserver::instance_ = NULL; +bool RLZTracker::InitRlzDelayed(bool first_run, int delay, + bool google_default_search, + bool google_default_homepage) { + return GetInstance()->Init(first_run, delay, google_default_search, + google_default_homepage); +} -// This task is run in the file thread, so to not block it for a long time -// we use a throwaway thread to do the blocking url request. -class DailyPingTask : public Task { - public: - virtual ~DailyPingTask() { - } - virtual void Run() { - // We use a transient thread because we have no guarantees about - // how long the RLZ lib can block us. - _beginthread(PingNow, 0, NULL); - } +bool RLZTracker::Init(bool first_run, int delay, bool google_default_search, + bool google_default_homepage) { + first_run_ = first_run; + google_default_search_ = google_default_search; + google_default_homepage_ = google_default_homepage; - private: - // Causes a ping to the server using WinInet. - static void _cdecl PingNow(void*) { - // Needs to be evaluated. See http://crbug.com/62328. - base::ThreadRestrictions::ScopedAllowIO allow_io; - - std::wstring lang; - GoogleUpdateSettings::GetLanguage(&lang); - if (lang.empty()) - lang = L"en"; - std::wstring brand; - GoogleUpdateSettings::GetBrand(&brand); - std::wstring referral; - GoogleUpdateSettings::GetReferral(&referral); - if (SendFinancialPing(brand, lang, referral, is_organic(brand))) { - base::AutoLock lock(rlz_lock); - access_values_state = ACCESS_VALUES_STALE; - GoogleUpdateSettings::ClearReferral(); - } + // A negative delay means that a financial ping should be sent immediately + // after a first search is recorded, without waiting for the next restart + // of chrome. However, we only want this behaviour on first run. + send_ping_immediately_ = false; + if (delay < 0) { + send_ping_immediately_ = true; + delay = -delay; } - // Organic brands all start with GG, such as GGCM. - static bool is_organic(const std::wstring& brand) { - return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG"); - } -}; - -// Performs late RLZ initialization and RLZ event recording for chrome. -// This task needs to run on the UI thread. -class DelayedInitTask : public Task { - public: - DelayedInitTask(bool first_run, bool google_default_search) - : first_run_(first_run), - google_default_search_(google_default_search) { - } - virtual ~DelayedInitTask() { - } - virtual void Run() { - // For non-interactive tests we don't do the rest of the initialization - // because sometimes the very act of loading the dll causes QEMU to crash. - if (::GetEnvironmentVariableW(ASCIIToWide(env_vars::kHeadless).c_str(), - NULL, 0)) { - return; - } - // For organic brandcodes do not use rlz at all. Empty brandcode usually - // means a chromium install. This is ok. - std::wstring brand; - if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() || - GoogleUpdateSettings::IsOrganic(brand)) - return; - - RecordProductEvents(first_run_, google_default_search_, already_ran_); - - // If chrome has been reactivated, record the events for this brand - // as well. - std::wstring reactivation_brand; - if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand)) { - rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); - RecordProductEvents(first_run_, google_default_search_, already_ran_); - } + // Maximum and minimum delay we would allow to be set through master + // preferences. Somewhat arbitrary, may need to be adjusted in future. + const int kMaxDelay = 200 * 1000; + const int kMinDelay = 20 * 1000; - already_ran_ = true; + delay *= 1000; + delay = (delay < kMinDelay) ? kMinDelay : delay; + delay = (delay > kMaxDelay) ? kMaxDelay : delay; - // Schedule the daily RLZ ping. - MessageLoop::current()->PostTask(FROM_HERE, new DailyPingTask()); - } + // Register for notifications from the omnibox so that we can record when + // the user performs a first search. + registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, + NotificationService::AllSources()); + // If instant is enabled we'll start searching as soon as the user starts + // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED). + registrar_.Add(this, chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, + NotificationService::AllSources()); + + // Register for notifications from navigations, to see if the user has used + // the home page. + registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, + NotificationService::AllSources()); + + ScheduleDelayedInit(delay); + return true; +} - private: - static void RecordProductEvents(bool first_run, bool google_default_search, - bool already_ran) { - // Record the installation of chrome. We call this all the time but the rlz - // lib should ingore all but the first one. - rlz_lib::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_OMNIBOX, - rlz_lib::INSTALL); - rlz_lib::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_HOME_PAGE, - rlz_lib::INSTALL); +void RLZTracker::ScheduleDelayedInit(int delay) { + BrowserThread::PostDelayedTask( + BrowserThread::FILE, + FROM_HERE, + NewRunnableMethod(this, &RLZTracker::DelayedInit), + delay); +} - // Do the initial event recording if is the first run or if we have an - // empty rlz which means we haven't got a chance to do it. - char omnibox_rlz[rlz_lib::kMaxRlzLength + 1]; - if (!rlz_lib::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, omnibox_rlz, - rlz_lib::kMaxRlzLength, NULL)) { - omnibox_rlz[0] = 0; - } +void RLZTracker::DelayedInit() { + // For organic brandcodes do not use rlz at all. Empty brandcode usually + // means a chromium install. This is ok. + std::wstring brand; + if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() || + GoogleUpdateSettings::IsOrganic(brand)) + return; - // Record if google is the initial search provider. - if ((first_run || omnibox_rlz[0] == 0) && google_default_search && - !already_ran) { - rlz_lib::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_OMNIBOX, - rlz_lib::SET_TO_GOOGLE); - } + RecordProductEvents(first_run_, google_default_search_, + google_default_homepage_, already_ran_, + omnibox_used_, homepage_used_); - // Record first user interaction with the omnibox. We call this all the - // time but the rlz lib should ingore all but the first one. - if (OmniBoxUsageObserver::used()) { - rlz_lib::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_OMNIBOX, - rlz_lib::FIRST_SEARCH); - } + // If chrome has been reactivated, record the events for this brand + // as well. + std::wstring reactivation_brand; + if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand)) { + rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); + RecordProductEvents(first_run_, google_default_search_, + google_default_homepage_, already_ran_, + omnibox_used_, homepage_used_); } - // Flag that remembers if the delayed task already ran or not. This is - // needed only in the first_run case, since we don't want to record the - // set-to-google event more than once. We need to worry about this event - // (and not the others) because it is not a stateful RLZ event. - static bool already_ran_; - - bool first_run_; + already_ran_ = true; - // True if Google is the default search engine for the first profile starting - // in a browser during first run. - bool google_default_search_; + ScheduleFinancialPing(); +} -}; +void RLZTracker::ScheduleFinancialPing() { + _beginthread(PingNow, 0, this); +} -bool DelayedInitTask::already_ran_ = false; +// static +void _cdecl RLZTracker::PingNow(void* arg) { + RLZTracker* tracker = reinterpret_cast<RLZTracker*>(arg); + tracker->PingNowImpl(); +} -void OmniBoxUsageObserver::Observe(int type, - const NotificationSource& source, - const NotificationDetails& details) { +void RLZTracker::PingNowImpl() { // Needs to be evaluated. See http://crbug.com/62328. base::ThreadRestrictions::ScopedAllowIO allow_io; - // Try to record event now, else set the flag to try later when we - // attempt the ping. - if (!RLZTracker::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_OMNIBOX, - rlz_lib::FIRST_SEARCH)) - omnibox_used_ = true; - else if (send_ping_immediately_) { - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run_, - google_default_search_)); + std::wstring lang; + GoogleUpdateSettings::GetLanguage(&lang); + if (lang.empty()) + lang = L"en"; + std::wstring brand; + GoogleUpdateSettings::GetBrand(&brand); + std::wstring referral; + GoogleUpdateSettings::GetReferral(&referral); + if (SendFinancialPing(brand, lang, referral, is_organic(brand))) { + GoogleUpdateSettings::ClearReferral(); + base::AutoLock lock(cache_lock_); + rlz_cache_.clear(); } +} - delete this; +bool RLZTracker::SendFinancialPing(const std::wstring& brand, + const std::wstring& lang, + const std::wstring& referral, + bool exclude_id) { + return ::SendFinancialPing(brand, lang, referral, exclude_id); } -} // namespace +void RLZTracker::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + // Needs to be evaluated. See http://crbug.com/62328. + base::ThreadRestrictions::ScopedAllowIO allow_io; -bool RLZTracker::InitRlzDelayed(bool first_run, int delay, - bool google_default_search) { - // A negative delay means that a financial ping should be sent immediately - // after a first search is recorded, without waiting for the next restart - // of chrome. However, we only want this behaviour on first run. - bool send_ping_immediately = false; - if (delay < 0) { - send_ping_immediately = true; - delay = -delay; + rlz_lib::AccessPoint point; + bool* record_used = NULL; + bool call_record = false; + + switch (type) { + case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: + case chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED: + point = rlz_lib::CHROME_OMNIBOX; + record_used = &omnibox_used_; + call_record = true; + + registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, + NotificationService::AllSources()); + registrar_.Remove(this, chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, + NotificationService::AllSources()); + break; + case content::NOTIFICATION_NAV_ENTRY_PENDING: { + const NavigationEntry* entry = Details<NavigationEntry>(details).ptr(); + if (entry != NULL && + ((entry->transition_type() & RLZ_PAGETRANSITION_HOME_PAGE) != 0)) { + point = rlz_lib::CHROME_HOME_PAGE; + record_used = &homepage_used_; + call_record = true; + + registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING, + NotificationService::AllSources()); + } + break; + } + default: + NOTREACHED(); + break; } - // Maximum and minimum delay we would allow to be set through master - // preferences. Somewhat arbitrary, may need to be adjusted in future. - const int kMaxDelay = 200 * 1000; - const int kMinDelay = 20 * 1000; - - delay *= 1000; - delay = (delay < kMinDelay) ? kMinDelay : delay; - delay = (delay > kMaxDelay) ? kMaxDelay : delay; - - if (!OmniBoxUsageObserver::used()) - new OmniBoxUsageObserver(first_run, send_ping_immediately, - google_default_search); - - // Schedule the delayed init items. - BrowserThread::PostDelayedTask( - BrowserThread::FILE, - FROM_HERE, - new DelayedInitTask(first_run, google_default_search), - delay); - return true; + if (call_record) { + // Try to record event now, else set the flag to try later when we + // attempt the ping. + if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) + *record_used = true; + else if (send_ping_immediately_ && point == rlz_lib::CHROME_OMNIBOX) { + ScheduleDelayedInit(0); + } + } } bool RLZTracker::RecordProductEvent(rlz_lib::Product product, @@ -333,52 +336,59 @@ bool RLZTracker::RecordProductEvent(rlz_lib::Product product, return ret; } -// We implement caching of the answer of get_access_point() if the request -// is for CHROME_OMNIBOX. If we had a successful ping, then we update the -// cached value. - +// GetAccessPointRlz() caches RLZ strings for all access points. If we had +// a successful ping, then we update the cached value. bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, std::wstring* rlz) { - static std::wstring cached_ommibox_rlz; - if (rlz_lib::CHROME_OMNIBOX == point) { - base::AutoLock lock(rlz_lock); - if (access_values_state == ACCESS_VALUES_FRESH) { - *rlz = cached_ommibox_rlz; + return GetInstance()->GetAccessPointRlzImpl(point, rlz); +} + +// GetAccessPointRlz() caches RLZ strings for all access points. If we had +// a successful ping, then we update the cached value. +bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point, + std::wstring* rlz) { + // If the RLZ string for the specified access point is already cached, + // simply return its value. + { + base::AutoLock lock(cache_lock_); + if (rlz_cache_.find(point) != rlz_cache_.end()) { + if (rlz) + *rlz = rlz_cache_[point]; return true; } } - // Make sure we don't access disk outside of the file context. + // Make sure we don't access disk outside of the I/O thread. // In such case we repost the task on the right thread and return error. - if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { - // Caching of access points is now only implemented for the CHROME_OMNIBOX. - // Thus it is not possible to call this function on another thread for - // other access points until proper caching for these has been implemented - // and the code that calls this function can handle synchronous fetching - // of the access point. - DCHECK_EQ(rlz_lib::CHROME_OMNIBOX, point); - - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - NewRunnableFunction(&RLZTracker::GetAccessPointRlz, - point, &cached_ommibox_rlz)); - rlz->erase(); - return false; - } + if (ScheduleGetAccessPointRlz(point)) + return false; char str_rlz[rlz_lib::kMaxRlzLength + 1]; if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength, NULL)) return false; - *rlz = ASCIIToWide(std::string(str_rlz)); - if (rlz_lib::CHROME_OMNIBOX == point) { - base::AutoLock lock(rlz_lock); - cached_ommibox_rlz.assign(*rlz); - access_values_state = ACCESS_VALUES_FRESH; - } + + std::wstring rlz_local(ASCIIToWide(std::string(str_rlz))); + if (rlz) + *rlz = rlz_local; + + base::AutoLock lock(cache_lock_); + rlz_cache_[point] = rlz_local; + return true; +} + +bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) { + if (BrowserThread::CurrentlyOn(BrowserThread::FILE)) + return false; + + std::wstring* not_used = NULL; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableFunction(&RLZTracker::GetAccessPointRlz, point, not_used)); return true; } // static void RLZTracker::CleanupRlz() { - OmniBoxUsageObserver::DeleteInstance(); + GetInstance()->rlz_cache_.clear(); + GetInstance()->registrar_.RemoveAll(); } diff --git a/chrome/browser/rlz/rlz.h b/chrome/browser/rlz/rlz.h index ca2658d..cbbec70 100644 --- a/chrome/browser/rlz/rlz.h +++ b/chrome/browser/rlz/rlz.h @@ -10,9 +10,15 @@ #if defined(OS_WIN) +#include <map> #include <string> #include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/task.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" #include "rlz/win/lib/rlz_lib.h" // RLZ is a library which is used to measure distribution scenarios. @@ -25,18 +31,16 @@ // For partner or bundled installs, the RLZ might send more information // according to the terms disclosed in the EULA. -class RLZTracker { - +class RLZTracker : public NotificationObserver { public: - // Like InitRlz() this function initializes the RLZ library services for use - // in chrome. Besides binding the dll, it schedules a delayed task (delayed - // by |delay| seconds) that performs the ping and registers some events - // when 'first-run' is true. + // Initializes the RLZ library services for use in chrome. Schedules a + // delayed task (delayed by |delay| seconds) that performs the ping and + // registers some events when 'first-run' is true. // - // If the chrome brand is organic (no partners) then the RLZ library is not - // loaded or initialized and the pings don't ocurr. + // If the chrome brand is organic (no partners) then the pings don't occur. static bool InitRlzDelayed(bool first_run, int delay, - bool google_default_search); + bool google_default_search, + bool google_default_homepage); // Records an RLZ event. Some events can be access point independent. // Returns false it the event could not be recorded. Requires write access @@ -53,10 +57,108 @@ class RLZTracker { // Invoked during shutdown to clean up any state created by RLZTracker. static void CleanupRlz(); + // This method is public for use by the Singleton class. + static RLZTracker* GetInstance(); + + // The following methods are made protected so that they can be used for + // testing purposes. Production code should never need to call these. + protected: + RLZTracker(); + ~RLZTracker(); + + // This is a temporary constant used here until the home page change is + // committed, which will happen before 2011-09-01. This constant will be + // replaced with PageTransition::HOME_PAGE. + static const int RLZ_PAGETRANSITION_HOME_PAGE = 0x02000000; + + // Thread function entry point, see ScheduleFinancialPing(). Assumes argument + // is a pointer to an RLZTracker. + static void _cdecl PingNow(void* tracker); + + // Performs initialization of RLZ tracker that is purposefully delayed so + // that it does not interfere with chrome startup time. + virtual void DelayedInit(); + + // NotificationObserver implementation: + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // Used by test code to override the default RLZTracker instance returned + // by GetInstance(). + void set_tracker(RLZTracker* tracker) { + tracker_ = tracker; + } + private: - DISALLOW_IMPLICIT_CONSTRUCTORS(RLZTracker); + friend struct DefaultSingletonTraits<RLZTracker>; + friend class base::RefCountedThreadSafe<RLZTracker>; + + // Implementation called from InitRlzDelayed() static method. + bool Init(bool first_run, int delay, bool google_default_search, + bool google_default_homepage); + + // Implementation called from RecordProductEvent() static method. + bool GetAccessPointRlzImpl(rlz_lib::AccessPoint point, std::wstring* rlz); + + // Schedules the delayed initialization. This method is virtual to allow + // tests to override how the scheduling is done. + virtual void ScheduleDelayedInit(int delay); + + // Schedules a call to rlz_lib::SendFinancialPing(). This method is virtual + // to allow tests to override how the scheduling is done. + virtual void ScheduleFinancialPing(); + + // Schedules a call to GetAccessPointRlz() on the I/O thread if the current + // thread is not already the I/O thread, otherwise does nothing. Returns + // true if the call was scheduled, and false otherwise. This method is + // virtual to allow tests to override how the scheduling is done. + virtual bool ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point); + + // Sends the financial ping to the RLZ servers and invalidates the RLZ string + // cache since the response from the RLZ server may have changed then. + void PingNowImpl(); + + // Sends the financial ping to the RLZ servers. This method is virtual to + // allow tests to override. + virtual bool SendFinancialPing(const std::wstring& brand, + const std::wstring& lang, + const std::wstring& referral, + bool exclude_id); + + // Tracker used for testing purposes only. If this value is non-NULL, it + // will be returned from GetInstance() instead of the regular singleton. + static RLZTracker* tracker_; + + // Configuation data for RLZ tracker. Set by call to Init(). + bool first_run_; + bool send_ping_immediately_; + bool google_default_search_; + bool google_default_homepage_; + + // Keeps track if the RLZ tracker has already performed its delayed + // initialization. + bool already_ran_; + + // Keeps a cache of RLZ access point strings, since they rarely change. + // The cache must be protected by a lock since it may be accessed from + // the UI thread for reading and the IO thread for reading and/or writing. + base::Lock cache_lock_; + std::map<rlz_lib::AccessPoint, std::wstring> rlz_cache_; + + // Keeps track of whether the omnibox or host page have been used. + bool omnibox_used_; + bool homepage_used_; + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(RLZTracker); }; +// The RLZTracker is a singleton object that outlives any runnable tasks +// that will be queued up. +DISABLE_RUNNABLE_METHOD_REFCOUNT(RLZTracker); + #endif // defined(OS_WIN) #endif // CHROME_BROWSER_RLZ_RLZ_H_ diff --git a/chrome/browser/rlz/rlz_unittest.cc b/chrome/browser/rlz/rlz_unittest.cc index 6a5e85d..96816a4 100644 --- a/chrome/browser/rlz/rlz_unittest.cc +++ b/chrome/browser/rlz/rlz_unittest.cc @@ -4,48 +4,533 @@ #include "chrome/browser/rlz/rlz.h" +#include "base/memory/scoped_ptr.h" +#include "base/stringprintf.h" #include "base/path_service.h" +#include "base/test/test_reg_util_win.h" +#include "base/utf_string_conversions.h" #include "base/win/registry.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/env_vars.h" +#include "chrome/installer/util/browser_distribution.h" +#include "chrome/installer/util/google_update_constants.h" +#include "content/browser/tab_contents/navigation_entry.h" +#include "content/common/notification_details.h" +#include "content/common/notification_service.h" +#include "content/common/notification_source.h" #include "testing/gtest/include/gtest/gtest.h" using base::win::RegKey; +using registry_util::RegistryOverrideManager; +using testing::AssertionResult; +using testing::AssertionSuccess; +using testing::AssertionFailure; namespace { -// Gets rid of registry leftovers from testing. Returns false if there -// is nothing to clean. -bool CleanValue(const wchar_t* key_name, const wchar_t* value) { - RegKey key; - if (key.Open(HKEY_CURRENT_USER, key_name, KEY_READ | KEY_WRITE) != - ERROR_SUCCESS) - return false; - EXPECT_EQ(ERROR_SUCCESS, key.DeleteValue(value)); - return true; +// Registry path to overridden hive. +const wchar_t kRlzTempHkcu[] = L"rlz_hkcu"; +const wchar_t kRlzTempHklm[] = L"rlz_hklm"; + +// Dummy RLZ string for the access points. +const char kOmniboxRlzString[] = "test_omnibox"; +const char kHomepageRlzString[] = "test_homepage"; + +// Some helper macros to test it a string contains/does not contain a substring. + +AssertionResult CmpHelperSTRC(const char* str_expression, + const char* substr_expression, + const char* str, + const char* substr) { + if (NULL != strstr(str, substr)) { + return AssertionSuccess(); + } + + return AssertionFailure() << "Expected: (" << substr_expression << ") in (" + << str_expression << "), actual: '" + << substr << "' not in '" << str << "'"; } -// The chrome events RLZ key lives here. -const wchar_t kKeyName[] = L"Software\\Google\\Common\\Rlz\\Events\\C"; +AssertionResult CmpHelperSTRNC(const char* str_expression, + const char* substr_expression, + const char* str, + const char* substr) { + if (NULL == strstr(str, substr)) { + return AssertionSuccess(); + } + + return AssertionFailure() << "Expected: (" << substr_expression + << ") not in (" << str_expression << "), actual: '" + << substr << "' in '" << str << "'"; +} + +#define EXPECT_STR_CONTAINS(str, substr) \ + EXPECT_PRED_FORMAT2(CmpHelperSTRC, str, substr) + +#define EXPECT_STR_NOT_CONTAIN(str, substr) \ + EXPECT_PRED_FORMAT2(CmpHelperSTRNC, str, substr) } // namespace -TEST(RlzLibTest, RecordProductEvent) { - DWORD recorded_value = 0; - EXPECT_TRUE(RLZTracker::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_OMNIBOX, rlz_lib::FIRST_SEARCH)); - const wchar_t kEvent1[] = L"C1F"; - RegKey key1; - EXPECT_EQ(ERROR_SUCCESS, key1.Open(HKEY_CURRENT_USER, kKeyName, KEY_READ)); - EXPECT_EQ(ERROR_SUCCESS, key1.ReadValueDW(kEvent1, &recorded_value)); - EXPECT_EQ(1, recorded_value); - EXPECT_TRUE(CleanValue(kKeyName, kEvent1)); - - EXPECT_TRUE(RLZTracker::RecordProductEvent(rlz_lib::CHROME, - rlz_lib::CHROME_HOME_PAGE, rlz_lib::SET_TO_GOOGLE)); - const wchar_t kEvent2[] = L"C2S"; - RegKey key2; - EXPECT_EQ(ERROR_SUCCESS, key2.Open(HKEY_CURRENT_USER, kKeyName, KEY_READ)); - DWORD value = 0; - EXPECT_EQ(ERROR_SUCCESS, key2.ReadValueDW(kEvent2, &recorded_value)); - EXPECT_EQ(1, recorded_value); - EXPECT_TRUE(CleanValue(kKeyName, kEvent2)); +// Test class for RLZ tracker. Makes some member functions public and +// overrides others to make it easier to test. +class TestRLZTracker : public RLZTracker { + public: + using RLZTracker::DelayedInit; + using RLZTracker::Observe; + using RLZTracker::RLZ_PAGETRANSITION_HOME_PAGE; + + TestRLZTracker() : pingnow_called_(false), assume_io_thread_(false) { + set_tracker(this); + } + + virtual ~TestRLZTracker() { + set_tracker(NULL); + } + + bool pingnow_called() const { + return pingnow_called_; + } + + bool assume_io_thread() const { + return assume_io_thread_; + } + + void set_assume_io_thread(bool assume_io_thread) { + assume_io_thread_ = assume_io_thread; + } + + private: + virtual void ScheduleDelayedInit(int delay) OVERRIDE { + // If the delay is 0, invoke the delayed init now. Otherwise, + // don't schedule anything, it will be manually called during tests. + if (delay == 0) + DelayedInit(); + } + + virtual void ScheduleFinancialPing() OVERRIDE { + PingNow(this); + } + + virtual bool ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) OVERRIDE { + return !assume_io_thread_; + } + + virtual bool SendFinancialPing(const std::wstring& brand, + const std::wstring& lang, + const std::wstring& referral, + bool exclude_id) OVERRIDE { + // Don't ping the server during tests. + pingnow_called_ = true; + return true; + } + + bool pingnow_called_; + bool assume_io_thread_; + + DISALLOW_COPY_AND_ASSIGN(TestRLZTracker); +}; + +class RlzLibTest : public testing::Test { + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + protected: + void SimulateOmniboxUsage(); + void SimulateHomepageUsage(); + void InvokeDelayedInit(); + + void ExpectEventRecorded(const char* event_name, bool expected); + void ExpectRlzPingSent(bool expected); + + TestRLZTracker tracker_; + RegistryOverrideManager override_manager_; +}; + +void RlzLibTest::SetUp() { + testing::Test::SetUp(); + + // Before overriding HKLM for the tests, we need to set it up correctly + // so that the rlz_lib calls work. This needs to be done before we do the + // override. + + std::wstring temp_hklm_path = base::StringPrintf( + L"%ls\\%ls", + RegistryOverrideManager::kTempTestKeyPath, + kRlzTempHklm); + + base::win::RegKey hklm; + ASSERT_EQ(ERROR_SUCCESS, hklm.Create(HKEY_CURRENT_USER, + temp_hklm_path.c_str(), + KEY_READ)); + + std::wstring temp_hkcu_path = base::StringPrintf( + L"%ls\\%ls", + RegistryOverrideManager::kTempTestKeyPath, + kRlzTempHkcu); + + base::win::RegKey hkcu; + ASSERT_EQ(ERROR_SUCCESS, hkcu.Create(HKEY_CURRENT_USER, + temp_hkcu_path.c_str(), + KEY_READ)); + + rlz_lib::InitializeTempHivesForTesting(hklm, hkcu); + + // Its important to override HKLM before HKCU because of the registry + // initialization performed above. + override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE, kRlzTempHklm); + override_manager_.OverrideRegistry(HKEY_CURRENT_USER, kRlzTempHkcu); + + // Make sure a non-organic brand code is set in the registry or the RLZTracker + // is pretty much a no-op. + BrowserDistribution* dist = BrowserDistribution::GetDistribution(); + std::wstring reg_path = dist->GetStateKey(); + RegKey key(HKEY_CURRENT_USER, reg_path.c_str(), KEY_SET_VALUE); + ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(google_update::kRegRLZBrandField, + L"TEST")); +} + +void RlzLibTest::TearDown() { + testing::Test::TearDown(); +} + +void RlzLibTest::SimulateOmniboxUsage() { + tracker_.Observe(chrome::NOTIFICATION_OMNIBOX_OPENED_URL, + NotificationService::AllSources(), + Details<AutocompleteLog>(NULL)); +} + +void RlzLibTest::SimulateHomepageUsage() { + NavigationEntry entry(NULL, 0, GURL(), GURL(), string16(), + TestRLZTracker::RLZ_PAGETRANSITION_HOME_PAGE); + tracker_.Observe(content::NOTIFICATION_NAV_ENTRY_PENDING, + NotificationService::AllSources(), + Details<NavigationEntry>(&entry)); +} + +void RlzLibTest::InvokeDelayedInit() { + tracker_.DelayedInit(); +} + +void RlzLibTest::ExpectEventRecorded(const char* event_name, bool expected) { + char cgi[rlz_lib::kMaxCgiLength]; + GetProductEventsAsCgi(rlz_lib::CHROME, cgi, arraysize(cgi)); + if (expected) { + EXPECT_STR_CONTAINS(cgi, event_name); + } else { + EXPECT_STR_NOT_CONTAIN(cgi, event_name); + } +} + +void RlzLibTest::ExpectRlzPingSent(bool expected) { + EXPECT_EQ(expected, tracker_.pingnow_called()); +} + +TEST_F(RlzLibTest, RecordProductEvent) { + RLZTracker::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_OMNIBOX, + rlz_lib::FIRST_SEARCH); + + ExpectEventRecorded("C1F", true); +} + +// The events that affect the different RLZ scenarios are the following: +// +// A: the user starts chrome for the first time +// B: the user stops chrome +// C: the user start a subsequent time +// D: the user stops chrome again +// I: the RLZTracker::DelayedInit() method is invoked +// X: the user performs a search using the omnibox +// Y: the user performs a search using the home page +// +// The events A to D happen in chronological order, but the other events +// may happen at any point between A-B or C-D, in no particular order. +// +// The visible results of the scenarios are: +// +// C1I event is recorded +// C2I event is recorded +// C1F event is recorded +// C2F event is recorded +// C1S event is recorded +// C2S event is recorded +// RLZ ping sent +// +// Variations on the above scenarios: +// +// - if the delay specified to InitRlzDelayed() is negative, then the RLZ +// ping should be sent out at the time of event X and not wait for I +// +// Also want to test that pre-warming the RLZ string cache works correctly. + +TEST_F(RlzLibTest, QuickStopAfterStart) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + + // Omnibox events. + ExpectEventRecorded("C1I", false); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", false); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(false); +} + +TEST_F(RlzLibTest, DelayedInitOnly) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + InvokeDelayedInit(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", true); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", true); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, DelayedInitOnlyNoFirstRunNoRlzStrings) { + RLZTracker::InitRlzDelayed(false, 20, true, true); + InvokeDelayedInit(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", true); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", true); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, DelayedInitOnlyNoFirstRun) { + // Set some dummy RLZ strings to simulate that we already ran before and + // performed a successful ping to the RLZ server. + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, kOmniboxRlzString); + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_HOME_PAGE, kHomepageRlzString); + + RLZTracker::InitRlzDelayed(false, 20, true, true); + InvokeDelayedInit(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, DelayedInitOnlyNoGoogleDefaultSearchOrHomepage) { + RLZTracker::InitRlzDelayed(true, 20, false, false); + InvokeDelayedInit(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, OmniboxUsageOnly) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + SimulateOmniboxUsage(); + + // Omnibox events. + ExpectEventRecorded("C1I", false); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", true); + + // Home page events. + ExpectEventRecorded("C2I", false); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(false); +} + +TEST_F(RlzLibTest, HomepageUsageOnly) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + SimulateHomepageUsage(); + + // Omnibox events. + ExpectEventRecorded("C1I", false); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", false); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", true); + + ExpectRlzPingSent(false); +} + +TEST_F(RlzLibTest, UsageBeforeDelayedInit) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + SimulateOmniboxUsage(); + SimulateHomepageUsage(); + InvokeDelayedInit(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", true); + ExpectEventRecorded("C1F", true); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", true); + ExpectEventRecorded("C2F", true); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, OmniboxUsageAfterDelayedInit) { + RLZTracker::InitRlzDelayed(true, 20, true, true); + InvokeDelayedInit(); + SimulateOmniboxUsage(); + SimulateHomepageUsage(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", true); + ExpectEventRecorded("C1F", true); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", true); + ExpectEventRecorded("C2F", true); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, OmniboxUsageSendsPingWhenDelayNegative) { + RLZTracker::InitRlzDelayed(true, -20, true, true); + SimulateOmniboxUsage(); + + // Omnibox events. + ExpectEventRecorded("C1I", true); + ExpectEventRecorded("C1S", true); + ExpectEventRecorded("C1F", true); + + // Home page events. + ExpectEventRecorded("C2I", true); + ExpectEventRecorded("C2S", true); + ExpectEventRecorded("C2F", false); + + ExpectRlzPingSent(true); +} + +TEST_F(RlzLibTest, HomepageUsageDoesNotSendPingWhenDelayNegative) { + RLZTracker::InitRlzDelayed(true, -20, true, true); + SimulateHomepageUsage(); + + // Omnibox events. + ExpectEventRecorded("C1I", false); + ExpectEventRecorded("C1S", false); + ExpectEventRecorded("C1F", false); + + // Home page events. + ExpectEventRecorded("C2I", false); + ExpectEventRecorded("C2S", false); + ExpectEventRecorded("C2F", true); + + ExpectRlzPingSent(false); +} + +TEST_F(RlzLibTest, GetAccessPointRlzOnIoThread) { + // Set dummy RLZ string. + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, kOmniboxRlzString); + + std::wstring rlz; + + tracker_.set_assume_io_thread(true); + EXPECT_TRUE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + EXPECT_STREQ(kOmniboxRlzString, WideToUTF8(rlz).c_str()); +} + +TEST_F(RlzLibTest, GetAccessPointRlzNotOnIoThread) { + // Set dummy RLZ string. + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, kOmniboxRlzString); + + std::wstring rlz; + + tracker_.set_assume_io_thread(false); + EXPECT_FALSE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); +} + +TEST_F(RlzLibTest, GetAccessPointRlzIsCached) { + // Set dummy RLZ string. + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, kOmniboxRlzString); + + std::wstring rlz; + + tracker_.set_assume_io_thread(false); + EXPECT_FALSE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + + tracker_.set_assume_io_thread(true); + EXPECT_TRUE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + EXPECT_STREQ(kOmniboxRlzString, WideToUTF8(rlz).c_str()); + + tracker_.set_assume_io_thread(false); + EXPECT_TRUE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + EXPECT_STREQ(kOmniboxRlzString, WideToUTF8(rlz).c_str()); +} + +TEST_F(RlzLibTest, PingInvalidatesRlzCache) { + // Set dummy RLZ string. + rlz_lib::SetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, kOmniboxRlzString); + + std::wstring rlz; + + // Prime the cache. + tracker_.set_assume_io_thread(true); + EXPECT_TRUE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + EXPECT_STREQ(kOmniboxRlzString, WideToUTF8(rlz).c_str()); + + // Make sure cache is valid. + tracker_.set_assume_io_thread(false); + EXPECT_TRUE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); + EXPECT_STREQ(kOmniboxRlzString, WideToUTF8(rlz).c_str()); + + // Perform ping. + RLZTracker::InitRlzDelayed(true, 20, true, true); + InvokeDelayedInit(); + ExpectRlzPingSent(true); + + // Make sure cache is now invalid. + EXPECT_FALSE(RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &rlz)); +} + +TEST_F(RlzLibTest, ObserveHandlesBadArgs) { + NavigationEntry entry(NULL, 0, GURL(), GURL(), string16(), + PageTransition::LINK); + tracker_.Observe(content::NOTIFICATION_NAV_ENTRY_PENDING, + NotificationService::AllSources(), + Details<NavigationEntry>(NULL)); + tracker_.Observe(content::NOTIFICATION_NAV_ENTRY_PENDING, + NotificationService::AllSources(), + Details<NavigationEntry>(&entry)); } |