summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/profile_sync_service_harness.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/profile_sync_service_harness.cc')
-rw-r--r--chrome/browser/sync/profile_sync_service_harness.cc538
1 files changed, 538 insertions, 0 deletions
diff --git a/chrome/browser/sync/profile_sync_service_harness.cc b/chrome/browser/sync/profile_sync_service_harness.cc
new file mode 100644
index 0000000..4750033
--- /dev/null
+++ b/chrome/browser/sync/profile_sync_service_harness.cc
@@ -0,0 +1,538 @@
+// Copyright (c) 2010 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 "base/message_loop.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/defaults.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/net/gaia/token_service.h"
+#include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/browser/sync/profile_sync_service_harness.h"
+#include "chrome/browser/sync/sessions/session_state.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/net/gaia/gaia_constants.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "chrome/common/pref_names.h"
+
+// The default value for min_timestamp_needed_ when we're not in the
+// WAITING_FOR_UPDATES state.
+static const int kMinTimestampNeededNone = -1;
+
+// The amount of time for which we wait for a live sync operation to complete.
+static const int kLiveSyncOperationTimeoutMs = 30000;
+
+// Simple class to implement a timeout using PostDelayedTask. If it is not
+// aborted before picked up by a message queue, then it asserts with the message
+// provided. This class is not thread safe.
+class StateChangeTimeoutEvent
+ : public base::RefCountedThreadSafe<StateChangeTimeoutEvent> {
+ public:
+ StateChangeTimeoutEvent(ProfileSyncServiceHarness* caller,
+ const std::string& message);
+
+ // The entry point to the class from PostDelayedTask.
+ void Callback();
+
+ // Cancels the actions of the callback. Returns true if success, false
+ // if the callback has already timed out.
+ bool Abort();
+
+ private:
+ friend class base::RefCountedThreadSafe<StateChangeTimeoutEvent>;
+
+ ~StateChangeTimeoutEvent();
+
+ bool aborted_;
+ bool did_timeout_;
+
+ // Due to synchronization of the IO loop, the caller will always be alive
+ // if the class is not aborted.
+ ProfileSyncServiceHarness* caller_;
+
+ // Informative message to assert in the case of a timeout.
+ std::string message_;
+
+ DISALLOW_COPY_AND_ASSIGN(StateChangeTimeoutEvent);
+};
+
+StateChangeTimeoutEvent::StateChangeTimeoutEvent(
+ ProfileSyncServiceHarness* caller,
+ const std::string& message)
+ : aborted_(false), did_timeout_(false), caller_(caller), message_(message) {
+}
+
+StateChangeTimeoutEvent::~StateChangeTimeoutEvent() {
+}
+
+void StateChangeTimeoutEvent::Callback() {
+ if (!aborted_) {
+ if (!caller_->RunStateChangeMachine()) {
+ // Report the message.
+ did_timeout_ = true;
+ DCHECK(!aborted_) << message_;
+ caller_->SignalStateComplete();
+ }
+ }
+}
+
+bool StateChangeTimeoutEvent::Abort() {
+ aborted_ = true;
+ caller_ = NULL;
+ return !did_timeout_;
+}
+
+ProfileSyncServiceHarness::ProfileSyncServiceHarness(
+ Profile* profile,
+ const std::string& username,
+ const std::string& password,
+ int id)
+ : wait_state_(INITIAL_WAIT_STATE),
+ profile_(profile),
+ service_(NULL),
+ last_timestamp_(0),
+ min_timestamp_needed_(kMinTimestampNeededNone),
+ username_(username),
+ password_(password),
+ id_(id) {
+ if (IsSyncAlreadySetup()) {
+ service_ = profile_->GetProfileSyncService();
+ service_->AddObserver(this);
+ wait_state_ = FULLY_SYNCED;
+ }
+}
+
+// static
+ProfileSyncServiceHarness* ProfileSyncServiceHarness::CreateAndAttach(
+ Profile* profile) {
+ if (!profile->HasProfileSyncService()) {
+ NOTREACHED() << "Profile has never signed into sync.";
+ return NULL;
+ }
+ return new ProfileSyncServiceHarness(profile, "", "", 0);
+}
+
+void ProfileSyncServiceHarness::SetCredentials(const std::string& username,
+ const std::string& password) {
+ username_ = username;
+ password_ = password;
+}
+
+bool ProfileSyncServiceHarness::IsSyncAlreadySetup() {
+ return profile_->HasProfileSyncService();
+}
+
+bool ProfileSyncServiceHarness::SetupSync() {
+ syncable::ModelTypeSet synced_datatypes;
+ for (int i = syncable::FIRST_REAL_MODEL_TYPE;
+ i < syncable::MODEL_TYPE_COUNT; ++i) {
+ synced_datatypes.insert(syncable::ModelTypeFromInt(i));
+ }
+ return SetupSync(synced_datatypes);
+}
+
+bool ProfileSyncServiceHarness::SetupSync(
+ const syncable::ModelTypeSet& synced_datatypes) {
+ // Initialize the sync client's profile sync service object.
+ service_ = profile_->GetProfileSyncService();
+ if (service_ == NULL) {
+ LOG(ERROR) << "SetupSync(): service_ is null.";
+ return false;
+ }
+
+ // Subscribe sync client to notifications from the profile sync service.
+ if (!service_->HasObserver(this))
+ service_->AddObserver(this);
+
+ // Authenticate sync client using GAIA credentials.
+ service_->signin()->StartSignIn(username_, password_, "", "");
+
+ // Wait for the OnBackendInitialized() callback.
+ wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
+ if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
+ "Waiting for OnBackendInitialized().")) {
+ LOG(ERROR) << "OnBackendInitialized() not seen after "
+ << kLiveSyncOperationTimeoutMs / 1000
+ << " seconds.";
+ return false;
+ }
+
+ // Choose the datatypes to be synced. If all datatypes are to be synced,
+ // set sync_everything to true; otherwise, set it to false.
+ bool sync_everything = (synced_datatypes.size() ==
+ (syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE));
+ service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
+
+ // Wait for initial sync cycle to complete.
+ DCHECK_EQ(wait_state_, WAITING_FOR_INITIAL_SYNC);
+ if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
+ "Waiting for initial sync cycle to complete.")) {
+ LOG(ERROR) << "Initial sync cycle did not complete after "
+ << kLiveSyncOperationTimeoutMs / 1000
+ << " seconds.";
+ return false;
+ }
+
+ // Indicate to the browser that sync setup is complete.
+ service()->SetSyncSetupCompleted();
+
+ return true;
+}
+
+void ProfileSyncServiceHarness::SignalStateCompleteWithNextState(
+ WaitState next_state) {
+ wait_state_ = next_state;
+ SignalStateComplete();
+}
+
+void ProfileSyncServiceHarness::SignalStateComplete() {
+ MessageLoop::current()->Quit();
+}
+
+bool ProfileSyncServiceHarness::RunStateChangeMachine() {
+ WaitState original_wait_state = wait_state_;
+ switch (wait_state_) {
+ case WAITING_FOR_ON_BACKEND_INITIALIZED: {
+ LogClientInfo("WAITING_FOR_ON_BACKEND_INITIALIZED");
+ if (service()->sync_initialized()) {
+ // The sync backend is initialized. Start waiting for the first sync
+ // cycle to complete.
+ SignalStateCompleteWithNextState(WAITING_FOR_INITIAL_SYNC);
+ }
+ break;
+ }
+ case WAITING_FOR_INITIAL_SYNC: {
+ LogClientInfo("WAITING_FOR_INITIAL_SYNC");
+ if (IsSynced()) {
+ // The first sync cycle is now complete. We can start running tests.
+ SignalStateCompleteWithNextState(FULLY_SYNCED);
+ }
+ break;
+ }
+ case WAITING_FOR_SYNC_TO_FINISH: {
+ LogClientInfo("WAITING_FOR_SYNC_TO_FINISH");
+ if (!IsSynced()) {
+ // The client is not yet fully synced. Continue waiting.
+ if (!GetStatus().server_reachable) {
+ // The client cannot reach the sync server because the network is
+ // disabled. There is no need to wait anymore.
+ SignalStateCompleteWithNextState(SERVER_UNREACHABLE);
+ }
+ break;
+ }
+ GetUpdatedTimestamp();
+ SignalStateCompleteWithNextState(FULLY_SYNCED);
+ break;
+ }
+ case WAITING_FOR_UPDATES: {
+ LogClientInfo("WAITING_FOR_UPDATES");
+ if (!IsSynced() || GetUpdatedTimestamp() < min_timestamp_needed_) {
+ // The client is not yet fully synced. Continue waiting until the client
+ // is at the required minimum timestamp.
+ break;
+ }
+ SignalStateCompleteWithNextState(FULLY_SYNCED);
+ break;
+ }
+ case SERVER_UNREACHABLE: {
+ LogClientInfo("SERVER_UNREACHABLE");
+ if (GetStatus().server_reachable) {
+ // The client was offline due to the network being disabled, but is now
+ // back online. Wait for the pending sync cycle to complete.
+ SignalStateCompleteWithNextState(WAITING_FOR_SYNC_TO_FINISH);
+ }
+ break;
+ }
+ case FULLY_SYNCED: {
+ // The client is online and fully synced. There is nothing to do.
+ LogClientInfo("FULLY_SYNCED");
+ break;
+ }
+ case WAITING_FOR_PASSPHRASE_ACCEPTED: {
+ LogClientInfo("WAITING_FOR_PASSPHRASE_ACCEPTED");
+ if (!service()->observed_passphrase_required())
+ SignalStateCompleteWithNextState(FULLY_SYNCED);
+ break;
+ }
+ case SYNC_DISABLED: {
+ // Syncing is disabled for the client. There is nothing to do.
+ LogClientInfo("SYNC_DISABLED");
+ break;
+ }
+ default:
+ // Invalid state during observer callback which may be triggered by other
+ // classes using the the UI message loop. Defer to their handling.
+ break;
+ }
+ return original_wait_state != wait_state_;
+}
+
+void ProfileSyncServiceHarness::OnStateChanged() {
+ RunStateChangeMachine();
+}
+
+bool ProfileSyncServiceHarness::AwaitPassphraseAccepted() {
+ LogClientInfo("AwaitPassphraseAccepted");
+ if (wait_state_ == SYNC_DISABLED) {
+ LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
+ return false;
+ }
+ if (!service()->observed_passphrase_required())
+ return true;
+ wait_state_ = WAITING_FOR_PASSPHRASE_ACCEPTED;
+ return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
+ "Waiting for passphrase accepted.");
+}
+
+bool ProfileSyncServiceHarness::AwaitSyncCycleCompletion(
+ const std::string& reason) {
+ LogClientInfo("AwaitSyncCycleCompletion");
+ if (wait_state_ == SYNC_DISABLED) {
+ LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
+ return false;
+ }
+ if (!IsSynced()) {
+ if (wait_state_ == SERVER_UNREACHABLE) {
+ // Client was offline; wait for it to go online, and then wait for sync.
+ AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
+ DCHECK_EQ(wait_state_, WAITING_FOR_SYNC_TO_FINISH);
+ return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
+ } else {
+ DCHECK(service()->sync_initialized());
+ wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
+ AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
+ if (wait_state_ == FULLY_SYNCED) {
+ // Client is online; sync was successful.
+ return true;
+ } else if (wait_state_ == SERVER_UNREACHABLE){
+ // Client is offline; sync was unsuccessful.
+ return false;
+ } else {
+ LOG(ERROR) << "Invalid wait state:" << wait_state_;
+ return false;
+ }
+ }
+ } else {
+ // Client is already synced; don't wait.
+ GetUpdatedTimestamp();
+ return true;
+ }
+}
+
+bool ProfileSyncServiceHarness::AwaitMutualSyncCycleCompletion(
+ ProfileSyncServiceHarness* partner) {
+ LogClientInfo("AwaitMutualSyncCycleCompletion");
+ if (!AwaitSyncCycleCompletion("Sync cycle completion on active client."))
+ return false;
+ return partner->WaitUntilTimestampIsAtLeast(last_timestamp_,
+ "Sync cycle completion on passive client.");
+}
+
+bool ProfileSyncServiceHarness::AwaitGroupSyncCycleCompletion(
+ std::vector<ProfileSyncServiceHarness*>& partners) {
+ LogClientInfo("AwaitGroupSyncCycleCompletion");
+ if (!AwaitSyncCycleCompletion("Sync cycle completion on active client."))
+ return false;
+ bool return_value = true;
+ for (std::vector<ProfileSyncServiceHarness*>::iterator it =
+ partners.begin(); it != partners.end(); ++it) {
+ if ((this != *it) && ((*it)->wait_state_ != SYNC_DISABLED)) {
+ return_value = return_value &&
+ (*it)->WaitUntilTimestampIsAtLeast(last_timestamp_,
+ "Sync cycle completion on partner client.");
+ }
+ }
+ return return_value;
+}
+
+// static
+bool ProfileSyncServiceHarness::AwaitQuiescence(
+ std::vector<ProfileSyncServiceHarness*>& clients) {
+ VLOG(1) << "AwaitQuiescence.";
+ bool return_value = true;
+ for (std::vector<ProfileSyncServiceHarness*>::iterator it =
+ clients.begin(); it != clients.end(); ++it) {
+ if ((*it)->wait_state_ != SYNC_DISABLED)
+ return_value = return_value &&
+ (*it)->AwaitGroupSyncCycleCompletion(clients);
+ }
+ return return_value;
+}
+
+bool ProfileSyncServiceHarness::WaitUntilTimestampIsAtLeast(
+ int64 timestamp, const std::string& reason) {
+ LogClientInfo("WaitUntilTimestampIsAtLeast");
+ if (wait_state_ == SYNC_DISABLED) {
+ LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
+ return false;
+ }
+ min_timestamp_needed_ = timestamp;
+ if (GetUpdatedTimestamp() < min_timestamp_needed_) {
+ wait_state_ = WAITING_FOR_UPDATES;
+ return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
+ } else {
+ return true;
+ }
+}
+
+bool ProfileSyncServiceHarness::AwaitStatusChangeWithTimeout(
+ int timeout_milliseconds,
+ const std::string& reason) {
+ LogClientInfo("AwaitStatusChangeWithTimeout");
+ if (wait_state_ == SYNC_DISABLED) {
+ LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
+ return false;
+ }
+ scoped_refptr<StateChangeTimeoutEvent> timeout_signal(
+ new StateChangeTimeoutEvent(this, reason));
+ MessageLoop* loop = MessageLoop::current();
+ bool did_allow_nestable_tasks = loop->NestableTasksAllowed();
+ loop->SetNestableTasksAllowed(true);
+ loop->PostDelayedTask(
+ FROM_HERE,
+ NewRunnableMethod(timeout_signal.get(),
+ &StateChangeTimeoutEvent::Callback),
+ timeout_milliseconds);
+ loop->Run();
+ loop->SetNestableTasksAllowed(did_allow_nestable_tasks);
+ LogClientInfo("AwaitStatusChangeWithTimeout succeeded");
+ return timeout_signal->Abort();
+}
+
+ProfileSyncService::Status ProfileSyncServiceHarness::GetStatus() {
+ DCHECK(service() != NULL) << "GetStatus(): service() is NULL.";
+ return service()->QueryDetailedSyncStatus();
+}
+
+bool ProfileSyncServiceHarness::IsSynced() {
+ if (service() == NULL)
+ return false;
+ const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
+ // TODO(rsimha): Remove additional checks of snap->has_more_to_sync and
+ // snap->unsynced_count once http://crbug.com/48989 is fixed.
+ return (snap &&
+ ServiceIsPushingChanges() &&
+ GetStatus().notifications_enabled &&
+ !service()->backend()->HasUnsyncedItems() &&
+ !snap->has_more_to_sync &&
+ snap->unsynced_count == 0);
+}
+
+const SyncSessionSnapshot*
+ ProfileSyncServiceHarness::GetLastSessionSnapshot() const {
+ DCHECK(service_ != NULL) << "Sync service has not yet been set up.";
+ if (service_->backend()) {
+ return service_->backend()->GetLastSessionSnapshot();
+ }
+ return NULL;
+}
+
+void ProfileSyncServiceHarness::EnableSyncForDatatype(
+ syncable::ModelType datatype) {
+ LogClientInfo("EnableSyncForDatatype");
+ syncable::ModelTypeSet synced_datatypes;
+ if (wait_state_ == SYNC_DISABLED) {
+ wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
+ synced_datatypes.insert(datatype);
+ DCHECK(SetupSync(synced_datatypes)) << "Reinitialization of Client " << id_
+ << " failed.";
+ } else {
+ DCHECK(service() != NULL) << "EnableSyncForDatatype(): service() is null.";
+ service()->GetPreferredDataTypes(&synced_datatypes);
+ syncable::ModelTypeSet::iterator it = synced_datatypes.find(
+ syncable::ModelTypeFromInt(datatype));
+ if (it == synced_datatypes.end()) {
+ synced_datatypes.insert(syncable::ModelTypeFromInt(datatype));
+ service()->OnUserChoseDatatypes(false, synced_datatypes);
+ wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
+ AwaitSyncCycleCompletion("Waiting for datatype configuration.");
+ VLOG(1) << "EnableSyncForDatatype(): Enabled sync for datatype "
+ << syncable::ModelTypeToString(datatype) << " on Client " << id_;
+ } else {
+ VLOG(1) << "EnableSyncForDatatype(): Sync already enabled for datatype "
+ << syncable::ModelTypeToString(datatype) << " on Client " << id_;
+ }
+ }
+}
+
+void ProfileSyncServiceHarness::DisableSyncForDatatype(
+ syncable::ModelType datatype) {
+ LogClientInfo("DisableSyncForDatatype");
+ syncable::ModelTypeSet synced_datatypes;
+ DCHECK(service() != NULL) << "DisableSyncForDatatype(): service() is null.";
+ service()->GetPreferredDataTypes(&synced_datatypes);
+ syncable::ModelTypeSet::iterator it = synced_datatypes.find(datatype);
+ if (it != synced_datatypes.end()) {
+ synced_datatypes.erase(it);
+ service()->OnUserChoseDatatypes(false, synced_datatypes);
+ AwaitSyncCycleCompletion("Waiting for datatype configuration.");
+ VLOG(1) << "DisableSyncForDatatype(): Disabled sync for datatype "
+ << syncable::ModelTypeToString(datatype) << " on Client " << id_;
+ } else {
+ VLOG(1) << "DisableSyncForDatatype(): Sync already disabled for datatype "
+ << syncable::ModelTypeToString(datatype) << " on Client " << id_;
+ }
+}
+
+void ProfileSyncServiceHarness::EnableSyncForAllDatatypes() {
+ LogClientInfo("EnableSyncForAllDatatypes");
+ if (wait_state_ == SYNC_DISABLED) {
+ wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
+ DCHECK(SetupSync()) << "Reinitialization of Client " << id_ << " failed.";
+ } else {
+ syncable::ModelTypeSet synced_datatypes;
+ for (int i = syncable::FIRST_REAL_MODEL_TYPE;
+ i < syncable::MODEL_TYPE_COUNT; ++i) {
+ synced_datatypes.insert(syncable::ModelTypeFromInt(i));
+ }
+ DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is "
+ " null.";
+ service()->OnUserChoseDatatypes(true, synced_datatypes);
+ wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
+ AwaitSyncCycleCompletion("Waiting for datatype configuration.");
+ VLOG(1) << "EnableSyncForAllDatatypes(): Enabled sync for all datatypes on "
+ "Client " << id_;
+ }
+}
+
+void ProfileSyncServiceHarness::DisableSyncForAllDatatypes() {
+ LogClientInfo("DisableSyncForAllDatatypes");
+ DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is "
+ "null.";
+ service()->DisableForUser();
+ wait_state_ = SYNC_DISABLED;
+ VLOG(1) << "DisableSyncForAllDatatypes(): Disabled sync for all datatypes on "
+ "Client " << id_;
+}
+
+int64 ProfileSyncServiceHarness::GetUpdatedTimestamp() {
+ const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
+ DCHECK(snap != NULL) << "GetUpdatedTimestamp(): Sync snapshot is NULL.";
+ DCHECK_LE(last_timestamp_, snap->max_local_timestamp);
+ last_timestamp_ = snap->max_local_timestamp;
+ return last_timestamp_;
+}
+
+void ProfileSyncServiceHarness::LogClientInfo(std::string message) {
+ if (service()) {
+ const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
+ if (snap) {
+ VLOG(1) << "Client " << id_ << ": " << message
+ << ": max_local_timestamp: " << snap->max_local_timestamp
+ << ", has_more_to_sync: " << snap->has_more_to_sync
+ << ", unsynced_count: " << snap->unsynced_count
+ << ", has_unsynced_items: "
+ << service()->backend()->HasUnsyncedItems()
+ << ", notifications_enabled: "
+ << GetStatus().notifications_enabled
+ << ", service_is_pushing_changes: " << ServiceIsPushingChanges();
+ } else {
+ VLOG(1) << "Client " << id_ << ": " << message
+ << ": Sync session snapshot not available.";
+ }
+ } else {
+ VLOG(1) << "Client " << id_ << ": " << message
+ << ": Sync service not available.";
+ }
+}