blob: 0b0a3b0b5a5700033d84b7065d165101ad0d3ae7 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
#include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h"
#include "chrome/browser/sync/test/integration/preferences_helper.h"
#include "chrome/browser/sync/test/integration/sync_disabled_checker.h"
#include "chrome/browser/sync/test/integration/sync_engine_stopped_checker.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/browser/sync/test/integration/user_events_helper.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/common/pref_names.h"
#include "components/history/core/common/pref_names.h"
#include "components/prefs/pref_member.h"
#include "components/prefs/pref_service.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/engine/sync_protocol_error.h"
#include "components/sync/model/data_type_controller_delegate.h"
#include "components/sync/model/type_entities_count.h"
#include "components/sync/protocol/user_event_specifics.pb.h"
#include "components/sync/service/sync_service_impl.h"
#include "components/sync_user_events/user_event_service.h"
#include "content/public/test/browser_test.h"
#include "google_apis/gaia/google_service_auth_error.h"
using bookmarks::BookmarkNode;
using bookmarks_helper::AddFolder;
using bookmarks_helper::SetTitle;
using syncer::SyncServiceImpl;
using testing::IsEmpty;
using user_events_helper::CreateTestEvent;
namespace {
constexpr int64_t kUserEventTimeUsec = 123456;
syncer::DataTypeSet GetThrottledDataTypes(
syncer::SyncServiceImpl* sync_service) {
base::RunLoop loop;
syncer::DataTypeSet throttled_types;
sync_service->GetThrottledDataTypesForTest(
base::BindLambdaForTesting([&](syncer::DataTypeSet result) {
throttled_types = result;
loop.Quit();
}));
loop.Run();
return throttled_types;
}
size_t GetTypeNonTombstoneEntitiesCount(
syncer::DataTypeControllerDelegate* data_type_controller_delegate) {
base::RunLoop loop;
size_t result = 0;
data_type_controller_delegate->GetTypeEntitiesCountForDebugging(
base::BindLambdaForTesting(
[&result, &loop](const syncer::TypeEntitiesCount& count) {
result = count.non_tombstone_entities;
loop.Quit();
}));
loop.Run();
return result;
}
class TypeDisabledChecker : public SingleClientStatusChangeChecker {
public:
explicit TypeDisabledChecker(SyncServiceImpl* service, syncer::DataType type)
: SingleClientStatusChangeChecker(service), type_(type) {}
// StatusChangeChecker implementation.
bool IsExitConditionSatisfied(std::ostream* os) override {
*os << "Waiting for type " << syncer::DataTypeToDebugString(type_)
<< " to become disabled";
return !service()->GetActiveDataTypes().Has(type_);
}
private:
syncer::DataType type_;
};
// Wait for a commit message containing the expected user event (even if the
// commit request fails).
class UserEventCommitChecker : public SingleClientStatusChangeChecker {
public:
UserEventCommitChecker(SyncServiceImpl* service,
fake_server::FakeServer* fake_server,
int64_t expected_event_time_usec)
: SingleClientStatusChangeChecker(service),
fake_server_(fake_server),
expected_event_time_usec_(expected_event_time_usec) {}
bool IsExitConditionSatisfied(std::ostream* os) override {
*os << "Waiting for user event to be committed";
sync_pb::ClientToServerMessage message;
fake_server_->GetLastCommitMessage(&message);
for (const sync_pb::SyncEntity& entity : message.commit().entries()) {
if (entity.specifics().user_event().event_time_usec() ==
expected_event_time_usec_) {
return true;
}
}
return false;
}
private:
const raw_ptr<fake_server::FakeServer> fake_server_ = nullptr;
const int64_t expected_event_time_usec_;
};
class SyncErrorTest : public SyncTest {
public:
SyncErrorTest() : SyncTest(SINGLE_CLIENT) {}
SyncErrorTest(const SyncErrorTest&) = delete;
SyncErrorTest& operator=(const SyncErrorTest&) = delete;
~SyncErrorTest() override = default;
};
// Helper class that waits until the sync engine has hit an actionable error.
class ActionableProtocolErrorChecker : public SingleClientStatusChangeChecker {
public:
explicit ActionableProtocolErrorChecker(SyncServiceImpl* service)
: SingleClientStatusChangeChecker(service) {}
ActionableProtocolErrorChecker(const ActionableProtocolErrorChecker&) =
delete;
ActionableProtocolErrorChecker& operator=(
const ActionableProtocolErrorChecker&) = delete;
~ActionableProtocolErrorChecker() override = default;
// Checks if an actionable error has been hit. Called repeatedly each time PSS
// notifies observers of a state change.
bool IsExitConditionSatisfied(std::ostream* os) override {
*os << "Waiting until sync hits an actionable error";
syncer::SyncStatus status;
service()->QueryDetailedSyncStatusForDebugging(&status);
return (status.sync_protocol_error.action != syncer::UNKNOWN_ACTION &&
service()->HasUnrecoverableError());
}
};
IN_PROC_BROWSER_TEST_F(SyncErrorTest, BirthdayErrorTest) {
ASSERT_TRUE(SetupSync());
// Clearing the server data resets the server birthday and triggers a NIGORI
// invalidation. This triggers a sync cycle and a GetUpdates request that runs
// into NOT_MY_BIRTHDAY.
GetFakeServer()->ClearServerData();
ASSERT_TRUE(SyncDisabledChecker(GetSyncService(0)).Wait());
#if BUILDFLAG(IS_CHROMEOS)
EXPECT_TRUE(GetSyncService(0)
->GetUserSettings()
->IsSyncFeatureDisabledViaDashboard());
#endif // BUILDFLAG(IS_CHROMEOS)
}
IN_PROC_BROWSER_TEST_F(SyncErrorTest, UpgradeClientErrorDuringIncrementalSync) {
ASSERT_TRUE(SetupSync());
const BookmarkNode* node1 = AddFolder(0, 0, u"title1");
SetTitle(0, node1, u"new_title1");
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
std::string description = "Not My Fault";
std::string url = "www.google.com";
GetFakeServer()->TriggerActionableProtocolError(
sync_pb::SyncEnums::THROTTLED, description, url,
sync_pb::SyncEnums::UPGRADE_CLIENT);
// Now make one more change so we will do another sync.
const BookmarkNode* node2 = AddFolder(0, 0, u"title2");
SetTitle(0, node2, u"new_title2");
// Wait until an actionable error is encountered.
EXPECT_TRUE(ActionableProtocolErrorChecker(GetSyncService(0)).Wait());
// UPGRADE_CLIENT gets mapped to an unrecoverable error, so Sync will *not*
// start up again in transport-only mode (which would clear the cached error).
EXPECT_EQ(syncer::SyncService::TransportState::DISABLED,
GetSyncService(0)->GetTransportState());
syncer::SyncStatus status;
GetSyncService(0)->QueryDetailedSyncStatusForDebugging(&status);
EXPECT_EQ(status.sync_protocol_error.error_type, syncer::THROTTLED);
EXPECT_EQ(status.sync_protocol_error.action, syncer::UPGRADE_CLIENT);
EXPECT_EQ(status.sync_protocol_error.error_description, description);
}
IN_PROC_BROWSER_TEST_F(SyncErrorTest, UpgradeClientErrorDuringInitialSync) {
std::string description = "Not My Fault";
std::string url = "www.google.com";
GetFakeServer()->TriggerActionableProtocolError(
sync_pb::SyncEnums::THROTTLED, description, url,
sync_pb::SyncEnums::UPGRADE_CLIENT);
ASSERT_TRUE(SetupClients());
// Signing in should start sync-the-transport, which should fail with an
// error.
ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
// Wait until an actionable error is encountered.
EXPECT_TRUE(ActionableProtocolErrorChecker(GetSyncService(0)).Wait());
// UPGRADE_CLIENT gets mapped to an unrecoverable error, so Sync will *not*
// start up again in transport-only mode (which would clear the cached error).
EXPECT_EQ(syncer::SyncService::TransportState::DISABLED,
GetSyncService(0)->GetTransportState());
syncer::SyncStatus status;
GetSyncService(0)->QueryDetailedSyncStatusForDebugging(&status);
EXPECT_EQ(status.sync_protocol_error.error_type, syncer::THROTTLED);
EXPECT_EQ(status.sync_protocol_error.action, syncer::UPGRADE_CLIENT);
EXPECT_EQ(status.sync_protocol_error.error_description, description);
}
// This test verifies that sync keeps retrying if it encounters error during
// setup.
// crbug.com/689662
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_ErrorWhileSettingUp DISABLED_ErrorWhileSettingUp
#else
#define MAYBE_ErrorWhileSettingUp ErrorWhileSettingUp
#endif
IN_PROC_BROWSER_TEST_F(SyncErrorTest, MAYBE_ErrorWhileSettingUp) {
ASSERT_TRUE(SetupClients());
#if !BUILDFLAG(IS_CHROMEOS)
// On non auto start enabled environments if the setup sync fails then
// the setup would fail. So setup sync normally.
// In contrast on auto start enabled platforms like chrome os we should be
// able to set up even if the first sync while setting up fails.
ASSERT_TRUE(SetupSync()) << "Setup sync failed";
ASSERT_TRUE(
GetClient(0)->DisableSyncForType(syncer::UserSelectableType::kAutofill));
#endif
GetFakeServer()->TriggerError(sync_pb::SyncEnums::TRANSIENT_ERROR);
EXPECT_TRUE(GetFakeServer()->EnableAlternatingTriggeredErrors());
#if BUILDFLAG(IS_CHROMEOS)
// Now setup sync and it should succeed.
ASSERT_TRUE(SetupSync());
#else
// Now enable a datatype, whose first 2 syncs would fail, but we should
// recover and setup succesfully on the third attempt.
ASSERT_TRUE(
GetClient(0)->EnableSyncForType(syncer::UserSelectableType::kAutofill));
#endif
}
// Tests that on receiving CLIENT_DATA_OBSOLETE sync engine gets restarted and
// initialized with different cache_guid.
IN_PROC_BROWSER_TEST_F(SyncErrorTest, ClientDataObsoleteTest) {
ASSERT_TRUE(SetupSync());
const BookmarkNode* node1 = AddFolder(0, 0, u"title1");
SetTitle(0, node1, u"new_title1");
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
std::string description = "Not My Fault";
std::string url = "www.google.com";
// Remember cache_guid before actionable error.
syncer::SyncStatus status;
GetSyncService(0)->QueryDetailedSyncStatusForDebugging(&status);
std::string old_cache_guid = status.cache_guid;
GetFakeServer()->TriggerError(sync_pb::SyncEnums::CLIENT_DATA_OBSOLETE);
// Trigger sync by making one more change.
const BookmarkNode* node2 = AddFolder(0, 0, u"title2");
SetTitle(0, node2, u"new_title2");
ASSERT_TRUE(syncer::SyncEngineStoppedChecker(GetSyncService(0)).Wait());
// Make server return SUCCESS so that sync can initialize.
GetFakeServer()->TriggerError(sync_pb::SyncEnums::SUCCESS);
ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization());
// Ensure cache_guid changed.
GetSyncService(0)->QueryDetailedSyncStatusForDebugging(&status);
ASSERT_NE(old_cache_guid, status.cache_guid);
}
IN_PROC_BROWSER_TEST_F(SyncErrorTest, EncryptionObsoleteErrorTest) {
ASSERT_TRUE(SetupSync());
const BookmarkNode* node1 = AddFolder(0, 0, u"title1");
SetTitle(0, node1, u"new_title1");
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
GetFakeServer()->TriggerActionableProtocolError(
sync_pb::SyncEnums::ENCRYPTION_OBSOLETE, "Not My Fault", "www.google.com",
sync_pb::SyncEnums::UNKNOWN_ACTION);
// Now make one more change so we will do another sync.
const BookmarkNode* node2 = AddFolder(0, 0, u"title2");
SetTitle(0, node2, u"new_title2");
SyncDisabledChecker sync_disabled(GetSyncService(0));
sync_disabled.Wait();
// On receiving the error, the SyncService will immediately start up again
// in transport mode, which resets the status. So check the status that the
// checker recorded at the time Sync was off.
syncer::SyncStatus status = sync_disabled.status_on_sync_disabled();
EXPECT_EQ(status.sync_protocol_error.error_type, syncer::ENCRYPTION_OBSOLETE);
EXPECT_EQ(status.sync_protocol_error.action, syncer::DISABLE_SYNC_ON_CLIENT);
}
IN_PROC_BROWSER_TEST_F(SyncErrorTest, DisableDatatypeWhileRunning) {
ASSERT_TRUE(SetupSync());
syncer::DataTypeSet synced_datatypes =
GetSyncService(0)->GetActiveDataTypes();
ASSERT_TRUE(synced_datatypes.Has(syncer::HISTORY));
ASSERT_TRUE(synced_datatypes.Has(syncer::SESSIONS));
GetProfile(0)->GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled,
true);
// Wait for reconfigurations.
ASSERT_TRUE(TypeDisabledChecker(GetSyncService(0), syncer::HISTORY).Wait());
ASSERT_TRUE(TypeDisabledChecker(GetSyncService(0), syncer::SESSIONS).Wait());
const BookmarkNode* node1 = AddFolder(0, 0, u"title1");
SetTitle(0, node1, u"new_title1");
ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
}
// Tests that the unsynced entity will be eventually committed even after failed
// commit request.
IN_PROC_BROWSER_TEST_F(SyncErrorTest,
PRE_ShouldResendUncommittedEntitiesAfterBrowserRestart) {
ASSERT_TRUE(SetupSync());
GetFakeServer()->TriggerCommitError(sync_pb::SyncEnums::TRANSIENT_ERROR);
syncer::UserEventService* event_service =
browser_sync::UserEventServiceFactory::GetForProfile(GetProfile(0));
const sync_pb::UserEventSpecifics specifics =
CreateTestEvent(base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(kUserEventTimeUsec)));
event_service->RecordUserEvent(
std::make_unique<sync_pb::UserEventSpecifics>(specifics));
// Wait for a commit message containing the user event. However the commit
// request will fail.
ASSERT_TRUE(UserEventCommitChecker(GetSyncService(0), GetFakeServer(),
kUserEventTimeUsec)
.Wait());
// Check that the server doesn't have this event yet.
for (const sync_pb::SyncEntity& entity :
GetFakeServer()->GetSyncEntitiesByDataType(syncer::USER_EVENTS)) {
ASSERT_NE(kUserEventTimeUsec,
entity.specifics().user_event().event_time_usec());
}
}
IN_PROC_BROWSER_TEST_F(SyncErrorTest,
ShouldResendUncommittedEntitiesAfterBrowserRestart) {
// Make sure the PRE_ test didn't successfully commit the event.
ASSERT_THAT(GetFakeServer()->GetSyncEntitiesByDataType(syncer::USER_EVENTS),
IsEmpty());
ASSERT_TRUE(SetupClients());
ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization());
const sync_pb::UserEventSpecifics expected_specifics =
CreateTestEvent(base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(kUserEventTimeUsec)));
EXPECT_TRUE(UserEventEqualityChecker(GetSyncService(0), GetFakeServer(),
{{expected_specifics}})
.Wait())
<< "Non-tombstone entities: "
<< GetTypeNonTombstoneEntitiesCount(
browser_sync::UserEventServiceFactory::GetForProfile(GetProfile(0))
->GetControllerDelegate()
.get());
}
// Tests that throttling one datatype does not influence other datatypes.
IN_PROC_BROWSER_TEST_F(SyncErrorTest, ShouldThrottleOneDatatypeButNotOthers) {
const std::u16string kBookmarkFolderTitle = u"title1";
ASSERT_TRUE(SetupSync());
// Set the preference to false initially which should get synced.
GetProfile(0)->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, false);
EXPECT_TRUE(FakeServerPrefMatchesValueChecker(
syncer::DataType::PREFERENCES, prefs::kHomePageIsNewTabPage,
preferences_helper::ConvertPrefValueToValueInSpecifics(
base::Value(false)))
.Wait());
// Start throttling PREFERENCES so further commits will be rejected by the
// server.
GetFakeServer()->SetThrottledTypes({syncer::PREFERENCES});
// Make local changes for PREFERENCES and BOOKMARKS, but the first is
// throttled.
GetProfile(0)->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage, true);
AddFolder(0, 0, kBookmarkFolderTitle);
// The bookmark should get committed successfully.
EXPECT_TRUE(bookmarks_helper::ServerBookmarksEqualityChecker(
{{kBookmarkFolderTitle, GURL()}},
/*cryptographer=*/nullptr)
.Wait());
// The preference should remain unsynced (still set to the previous value).
EXPECT_EQ(preferences_helper::GetPreferenceInFakeServer(
syncer::DataType::PREFERENCES, prefs::kHomePageIsNewTabPage,
GetFakeServer())
->value(),
"false");
// PREFERENCES should now be throttled.
EXPECT_EQ(GetThrottledDataTypes(GetSyncService(0)),
syncer::DataTypeSet({syncer::PREFERENCES}));
// Unthrottle PREFERENCES to verify that sync can resume.
GetFakeServer()->SetThrottledTypes(syncer::DataTypeSet());
// Eventually (depending on throttling delay, which is short in tests) the
// preference should be committed.
EXPECT_TRUE(FakeServerPrefMatchesValueChecker(syncer::DataType::PREFERENCES,
prefs::kHomePageIsNewTabPage,
"true")
.Wait());
EXPECT_EQ(GetThrottledDataTypes(GetSyncService(0)), syncer::DataTypeSet());
}
} // namespace