Commit 48d28bae authored by rogerta@chromium.org's avatar rogerta@chromium.org

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
parent 911f3188
......@@ -1880,8 +1880,11 @@ int BrowserMain(const MainFunctionParams& parameters) {
}
}
}
// TODO(rogerta): For now, always passing false for google_homeapge_default
// argument, so that functionality is disabled. A follow up CL will set it
// correctly.
RLZTracker::InitRlzDelayed(is_first_run, master_prefs.ping_delay,
google_search_default);
google_search_default, false);
#endif // GOOGLE_CHROME_BUILD
#endif // OS_WIN
......
......@@ -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();
}
......@@ -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_
......@@ -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));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment