blob: a3a7a37599fdaaca381563072f9a2d633795cd44 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/model/syncable_service_based_bridge.h"
#include <string_view>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/sync/base/client_tag_hash.h"
#include "components/sync/engine/data_type_activation_response.h"
#include "components/sync/model/client_tag_based_data_type_processor.h"
#include "components/sync/model/conflict_resolution.h"
#include "components/sync/model/model_error.h"
#include "components/sync/model/sync_change.h"
#include "components/sync/model/syncable_service.h"
#include "components/sync/protocol/persisted_entity_data.pb.h"
#include "components/sync/test/data_type_store_test_util.h"
#include "components/sync/test/mock_data_type_local_change_processor.h"
#include "components/sync/test/mock_data_type_worker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::_;
using testing::ElementsAre;
using testing::Eq;
using testing::IsEmpty;
using testing::NotNull;
using testing::Pair;
using testing::Return;
const DataType kDataType = PREFERENCES;
const std::string_view kSyncableServiceStartTimeHistogramName =
"Sync.SyncableServiceStartTime.PREFERENCE";
const std::string_view kMaybeClearDataHistogramName =
"Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.PREFERENCE";
sync_pb::EntitySpecifics GetTestSpecifics(const std::string& name = "name") {
sync_pb::EntitySpecifics specifics;
// Make specifics non empty, to avoid it being interpreted as a tombstone.
specifics.mutable_preference()->set_name(name);
return specifics;
}
MATCHER_P(SyncDataMatches, name, "") {
return arg.IsValid() && arg.GetDataType() == kDataType &&
arg.GetSpecifics().preference().name() == name;
}
MATCHER_P2(SyncChangeMatches, change_type, name, "") {
return arg.change_type() == change_type &&
arg.sync_data().GetDataType() == kDataType &&
arg.sync_data().GetSpecifics().preference().name() == name;
}
MATCHER_P(HasName, name, "") {
return arg && arg->specifics.preference().name() == name;
}
class MockSyncableService : public SyncableService {
public:
MOCK_METHOD(void, WaitUntilReadyToSync, (base::OnceClosure done), (override));
MOCK_METHOD(void, WillStartInitialSync, (), (override));
MOCK_METHOD(std::optional<syncer::ModelError>,
MergeDataAndStartSyncing,
(DataType type,
const SyncDataList& initial_sync_data,
std::unique_ptr<SyncChangeProcessor> sync_processor),
(override));
MOCK_METHOD(void, StopSyncing, (DataType type), (override));
MOCK_METHOD(void, StayStoppedAndMaybeClearData, (DataType type), (override));
MOCK_METHOD(std::optional<ModelError>,
ProcessSyncChanges,
(const base::Location& from_here,
const SyncChangeList& change_list),
(override));
MOCK_METHOD(SyncDataList, GetAllSyncData, (DataType type), (const override));
base::WeakPtr<SyncableService> AsWeakPtr() override {
return weak_ptr_factory_.GetWeakPtr();
}
MOCK_METHOD(std::string,
GetClientTag,
(const EntityData& entity_data),
(const override));
MOCK_METHOD(bool, SupportsGetClientTag, (), (const override));
private:
base::WeakPtrFactory<MockSyncableService> weak_ptr_factory_{this};
};
class SyncableServiceBasedBridgeTest : public ::testing::Test {
public:
SyncableServiceBasedBridgeTest(const SyncableServiceBasedBridgeTest&) =
delete;
SyncableServiceBasedBridgeTest& operator=(
const SyncableServiceBasedBridgeTest&) = delete;
protected:
SyncableServiceBasedBridgeTest()
: store_(DataTypeStoreTestUtil::CreateInMemoryStoreForTest()) {
ON_CALL(syncable_service_, WaitUntilReadyToSync)
.WillByDefault([](base::OnceClosure done) { std::move(done).Run(); });
ON_CALL(syncable_service_, MergeDataAndStartSyncing)
.WillByDefault(
[&](DataType type, const SyncDataList& initial_sync_data,
std::unique_ptr<SyncChangeProcessor> sync_processor) {
start_syncing_sync_processor_ = std::move(sync_processor);
return std::nullopt;
});
}
~SyncableServiceBasedBridgeTest() override = default;
void InitializeBridge(DataType data_type = kDataType) {
real_processor_ = std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
data_type, /*dump_stack=*/base::DoNothing());
mock_processor_.DelegateCallsByDefaultTo(real_processor_.get());
bridge_ = std::make_unique<SyncableServiceBasedBridge>(
data_type,
DataTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
mock_processor_.CreateForwardingProcessor(), &syncable_service_);
}
void ShutdownBridge() {
// `bridge_` must outlive `start_syncing_sync_processor_`, so reset it
// first.
start_syncing_sync_processor_.reset();
bridge_.reset();
// The mock is still delegating to `real_processor_`, so we reset it too.
ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_processor_));
real_processor_.reset();
}
syncer::DataTypeActivationRequest GetTestActivationRequest() {
syncer::DataTypeActivationRequest request;
request.error_handler = mock_error_handler_.Get();
request.cache_guid = "TestCacheGuid";
request.authenticated_gaia_id = GaiaId("SomeGaiaId");
return request;
}
void StartSyncing() {
base::RunLoop loop;
real_processor_->OnSyncStarting(
GetTestActivationRequest(),
base::BindLambdaForTesting(
[&](std::unique_ptr<syncer::DataTypeActivationResponse> response) {
worker_ = MockDataTypeWorker::CreateWorkerAndConnectSync(
std::move(response));
loop.Quit();
}));
loop.Run();
ASSERT_NE(nullptr, worker_);
}
std::map<std::string, std::unique_ptr<EntityData>> GetAllData() {
std::unique_ptr<DataBatch> batch = bridge_->GetAllDataForDebugging();
EXPECT_NE(nullptr, batch);
std::map<std::string, std::unique_ptr<EntityData>> storage_key_to_data;
while (batch && batch->HasNext()) {
storage_key_to_data.insert(batch->Next());
}
return storage_key_to_data;
}
const std::string kClientTag = "clienttag";
const ClientTagHash kClientTagHash =
ClientTagHash::FromUnhashed(kDataType, kClientTag);
base::test::SingleThreadTaskEnvironment task_environment_;
testing::NiceMock<MockSyncableService> syncable_service_;
testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor_;
base::MockCallback<ModelErrorHandler> mock_error_handler_;
const std::unique_ptr<DataTypeStore> store_;
std::unique_ptr<syncer::ClientTagBasedDataTypeProcessor> real_processor_;
std::unique_ptr<SyncableServiceBasedBridge> bridge_;
std::unique_ptr<MockDataTypeWorker> worker_;
// SyncChangeProcessor received via MergeDataAndStartSyncing(), or null if it
// hasn't been called.
std::unique_ptr<SyncChangeProcessor> start_syncing_sync_processor_;
};
TEST_F(SyncableServiceBasedBridgeTest,
ShouldStartSyncingWithEmptyInitialRemoteData) {
// Bridge initialization alone, without sync itself starting, should not
// issue calls to the syncable service.
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
InitializeBridge();
// Starting sync itself is also not sufficient, until initial remote data is
// received.
StartSyncing();
// Once the initial data is fetched from the server,
// MergeDataAndStartSyncing() should be exercised.
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(kDataType, IsEmpty(), NotNull()));
worker_->UpdateFromServer();
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldStartSyncingWithNonEmptyInitialRemoteData) {
InitializeBridge();
StartSyncing();
// Once the initial data is fetched from the server,
// MergeDataAndStartSyncing() should be exercised.
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(
kDataType, ElementsAre(SyncDataMatches("name1")), NotNull()));
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _)));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldWaitUntilModelReadyToSync) {
base::OnceClosure syncable_service_ready_cb;
ON_CALL(syncable_service_, WaitUntilReadyToSync)
.WillByDefault([&](base::OnceClosure done) {
syncable_service_ready_cb = std::move(done);
});
EXPECT_CALL(mock_processor_, ModelReadyToSync).Times(0);
EXPECT_CALL(syncable_service_, WaitUntilReadyToSync).Times(0);
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
// Bridge initialization alone, without sync itself starting, should not
// issue calls to the syncable service.
InitializeBridge();
EXPECT_CALL(syncable_service_, WaitUntilReadyToSync);
// Required to initialize the store.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(syncable_service_ready_cb);
// Sync itself starting should wait until the syncable service becomes ready,
// before issuing any other call (e.g. MergeDataAndStartSyncing()).
real_processor_->OnSyncStarting(GetTestActivationRequest(),
base::DoNothing());
// When the SyncableService gets ready, the bridge should propagate this
// information to the processor.
EXPECT_CALL(mock_processor_, ModelReadyToSync);
std::move(syncable_service_ready_cb).Run();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldStopSyncableServiceIfPreviouslyStarted) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
EXPECT_CALL(syncable_service_, StopSyncing(kDataType));
real_processor_->OnSyncStopping(CLEAR_METADATA);
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldStopSyncableServiceDuringShutdownIfPreviouslyStarted) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
EXPECT_CALL(syncable_service_, StopSyncing(kDataType));
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotStopSyncableServiceIfNotPreviouslyStarted) {
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
InitializeBridge();
StartSyncing();
real_processor_->OnSyncStopping(KEEP_METADATA);
}
// Regression test for crbug.com/401453180.
TEST_F(SyncableServiceBasedBridgeTest,
ShouldMaybeClearDataIfPendingClearMetadataOnStart) {
// Simulate prior initial sync done.
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ShutdownBridge();
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData).Times(0);
InitializeBridge();
base::RunLoop loop;
ON_CALL(syncable_service_, WaitUntilReadyToSync)
.WillByDefault([&](base::OnceClosure done) {
// This should mark sync metadata as pending clear on start.
real_processor_->ClearMetadataIfStopped();
// Metadata has not been cleared yet.
ASSERT_THAT(GetAllData(), Not(IsEmpty()));
std::move(done).Run();
loop.Quit();
});
base::HistogramTester histogram_tester;
EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType));
// The processor should clear the pre-existing metadata and lead to
// StayStoppedAndMaybeClearData() being called.
loop.Run();
histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName,
/*sample=*/true,
/*expected_bucket_count=*/1);
ASSERT_THAT(GetAllData(), IsEmpty());
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldMaybeClearDataIfMetadataEmpty) {
base::RunLoop loop;
EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType))
.WillOnce([&]() { loop.Quit(); });
base::HistogramTester histogram_tester;
// No metadata exists, thus any account data in the syncable service should be
// cleared.
InitializeBridge();
loop.Run();
histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName,
/*sample=*/true,
/*expected_bucket_count=*/1);
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldMaybeClearDataIfMetadataInconsistentOnNextStartup) {
// Simulate pre-existing metadata in the store without initial sync done.
{
std::unique_ptr<DataTypeStore::WriteBatch> batch =
store_->CreateWriteBatch();
batch->WriteData(kClientTagHash.value(),
GetTestSpecifics().SerializeAsString());
base::RunLoop loop;
store_->CommitWriteBatch(
std::move(batch),
base::BindLambdaForTesting(
[&](const std::optional<ModelError>& error) { loop.Quit(); }));
loop.Run();
}
// Simulate a successful initial sync.
InitializeBridge();
StartSyncing();
ASSERT_THAT(GetAllData(), IsEmpty());
ShutdownBridge();
base::RunLoop loop;
// On the next startup, the metadata is currently empty, which should trigger
// StayStoppedAndMaybeClearData().
EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType))
.WillOnce([&]() { loop.Quit(); });
base::HistogramTester histogram_tester;
// Metadata exists but initial sync is not done, thus leading to an
// inconsistent state, which should trigger StayStoppedAndMaybeClearData().
InitializeBridge();
loop.Run();
histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName,
/*sample=*/true,
/*expected_bucket_count=*/1);
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotMaybeClearDataIfMetadataNotEmpty) {
// Simulate prior initial sync done.
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ShutdownBridge();
base::RunLoop loop;
ON_CALL(syncable_service_, WaitUntilReadyToSync)
.WillByDefault([&](base::OnceClosure done) {
std::move(done).Run();
loop.Quit();
});
EXPECT_CALL(syncable_service_, StayStoppedAndMaybeClearData(kDataType))
.Times(0);
base::HistogramTester histogram_tester;
InitializeBridge();
// Metadata exists and initial sync is done, thus leading to no need to clear
// data.
loop.Run();
histogram_tester.ExpectUniqueSample(kMaybeClearDataHistogramName,
/*sample=*/false,
/*expected_bucket_count=*/1);
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotStopSyncableServiceDuringShutdownIfNotPreviouslyStarted) {
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
InitializeBridge();
StartSyncing();
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateErrorDuringStart) {
// Instrument MergeDataAndStartSyncing() to return an error.
ON_CALL(syncable_service_, MergeDataAndStartSyncing)
.WillByDefault(Return(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError)));
EXPECT_CALL(mock_error_handler_, Run);
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
// Since the syncable service failed to start, it shouldn't be stopped.
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldKeepSyncingWhenSyncStoppedTemporarily) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// Stopping Sync temporarily (KEEP_METADATA) should *not* result in the
// SyncableService being stopped.
EXPECT_CALL(syncable_service_, StopSyncing).Times(0);
real_processor_->OnSyncStopping(KEEP_METADATA);
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _)));
// Since the SyncableService wasn't stopped, it shouldn't get restarted either
// when Sync starts up again.
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
StartSyncing();
// Finally, shutting down the bridge (during browser shutdown) should also
// stop the SyncableService.
EXPECT_CALL(syncable_service_, StopSyncing(kDataType));
ShutdownBridge();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldStartSyncingWithPreviousDirectoryDataAfterRestart) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// Mimic restart, which shouldn't start syncing until OnSyncStarting() is
// received (exercised in StartSyncing()).
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
ShutdownBridge();
InitializeBridge();
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(
kDataType, ElementsAre(SyncDataMatches("name1")), NotNull()));
StartSyncing();
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldSupportDisableReenableSequence) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics());
real_processor_->OnSyncStopping(CLEAR_METADATA);
EXPECT_THAT(GetAllData(), IsEmpty());
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
StartSyncing();
EXPECT_CALL(syncable_service_,
MergeDataAndStartSyncing(kDataType, IsEmpty(), NotNull()));
worker_->UpdateFromServer();
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldPropagateLocalEntitiesDuringMerge) {
ON_CALL(syncable_service_, MergeDataAndStartSyncing)
.WillByDefault([&](DataType type, const SyncDataList& initial_sync_data,
std::unique_ptr<SyncChangeProcessor> sync_processor) {
SyncChangeList change_list;
change_list.emplace_back(
FROM_HERE, SyncChange::ACTION_ADD,
SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics()));
const std::optional<ModelError> error =
sync_processor->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_FALSE(error.has_value());
return std::nullopt;
});
InitializeBridge();
StartSyncing();
EXPECT_CALL(mock_processor_,
Put(kClientTagHash.value(), NotNull(), NotNull()));
worker_->UpdateFromServer();
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _)));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalCreation) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(), IsEmpty());
EXPECT_CALL(mock_processor_,
Put(kClientTagHash.value(), NotNull(), NotNull()));
SyncChangeList change_list;
change_list.emplace_back(
FROM_HERE, SyncChange::ACTION_ADD,
SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics()));
const std::optional<ModelError> error =
start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_FALSE(error.has_value());
EXPECT_THAT(GetAllData(), ElementsAre(Pair(kClientTagHash.value(), _)));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalUpdate) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name1"))));
EXPECT_CALL(mock_processor_,
Put(kClientTagHash.value(), NotNull(), NotNull()));
SyncChangeList change_list;
change_list.emplace_back(FROM_HERE, SyncChange::ACTION_UPDATE,
SyncData::CreateLocalData(
kClientTag, "title", GetTestSpecifics("name2")));
const std::optional<ModelError> error =
start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_FALSE(error.has_value());
EXPECT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name2"))));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateLocalDeletion) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name1"))));
EXPECT_CALL(mock_processor_, Delete(kClientTagHash.value(), _, NotNull()));
SyncChangeList change_list;
change_list.emplace_back(FROM_HERE, SyncChange::ACTION_DELETE,
SyncData::CreateLocalDelete(kClientTag, kDataType));
const std::optional<ModelError> error =
start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_FALSE(error.has_value());
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldIgnoreLocalCreationIfPreviousError) {
EXPECT_CALL(mock_processor_, Put).Times(0);
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(), IsEmpty());
// We fake an error, reported by the bridge.
EXPECT_CALL(mock_error_handler_, Run);
real_processor_->ReportError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
ASSERT_TRUE(real_processor_->GetError());
// Further local changes should be ignored.
SyncChangeList change_list;
change_list.emplace_back(
FROM_HERE, SyncChange::ACTION_ADD,
SyncData::CreateLocalData(kClientTag, "title", GetTestSpecifics()));
const std::optional<ModelError> error =
start_syncing_sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_TRUE(error.has_value());
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteCreation) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer();
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(), IsEmpty());
EXPECT_CALL(syncable_service_,
ProcessSyncChanges(_, ElementsAre(SyncChangeMatches(
SyncChange::ACTION_ADD, "name1"))));
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
EXPECT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name1"))));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteUpdates) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name1"))));
EXPECT_CALL(syncable_service_,
ProcessSyncChanges(_, ElementsAre(SyncChangeMatches(
SyncChange::ACTION_UPDATE, "name2"))));
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name2"));
EXPECT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name2"))));
// A second update for the same entity.
EXPECT_CALL(syncable_service_,
ProcessSyncChanges(_, ElementsAre(SyncChangeMatches(
SyncChange::ACTION_UPDATE, "name3"))));
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name3"));
EXPECT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name3"))));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldPropagateRemoteDeletion) {
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
ASSERT_THAT(start_syncing_sync_processor_, NotNull());
ASSERT_THAT(GetAllData(),
ElementsAre(Pair(kClientTagHash.value(), HasName("name1"))));
EXPECT_CALL(syncable_service_,
ProcessSyncChanges(_, ElementsAre(SyncChangeMatches(
SyncChange::ACTION_DELETE, "name1"))));
worker_->TombstoneFromServer(kClientTagHash);
EXPECT_THAT(GetAllData(), IsEmpty());
}
TEST(SyncableServiceBasedBridgeLocalChangeProcessorTest,
ShouldDropIfCommitted) {
const std::string kClientTagHash = "clienttaghash1";
base::test::SingleThreadTaskEnvironment task_environment;
std::unique_ptr<DataTypeStore> store =
DataTypeStoreTestUtil::CreateInMemoryStoreForTest();
SyncableServiceBasedBridge::InMemoryStore in_memory_store;
testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor;
in_memory_store[kClientTagHash] = sync_pb::PersistedEntityData();
std::unique_ptr<SyncChangeProcessor> sync_change_processor =
SyncableServiceBasedBridge::CreateLocalChangeProcessorForTesting(
HISTORY_DELETE_DIRECTIVES, store.get(), &in_memory_store,
&mock_processor);
EXPECT_CALL(mock_processor, IsEntityUnsynced(kClientTagHash))
.WillOnce(Return(false));
EXPECT_CALL(mock_processor, UntrackEntityForStorageKey(kClientTagHash));
sync_pb::EntitySpecifics specifics;
specifics.mutable_history_delete_directive();
SyncChangeList change_list;
change_list.push_back(
SyncChange(FROM_HERE, SyncChange::ACTION_DELETE,
SyncData::CreateRemoteData(
specifics, ClientTagHash::FromHashed(kClientTagHash))));
sync_change_processor->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_EQ(0U, in_memory_store.count(kClientTagHash));
}
TEST(SyncableServiceBasedBridgeLocalChangeProcessorTest,
ShouldNotDropIfUnsynced) {
const std::string kClientTagHash = "clienttaghash1";
base::test::SingleThreadTaskEnvironment task_environment;
std::unique_ptr<DataTypeStore> store =
DataTypeStoreTestUtil::CreateInMemoryStoreForTest();
SyncableServiceBasedBridge::InMemoryStore in_memory_store;
testing::NiceMock<MockDataTypeLocalChangeProcessor> mock_processor;
in_memory_store[kClientTagHash] = sync_pb::PersistedEntityData();
std::unique_ptr<SyncChangeProcessor> sync_change_processor =
SyncableServiceBasedBridge::CreateLocalChangeProcessorForTesting(
HISTORY_DELETE_DIRECTIVES, store.get(), &in_memory_store,
&mock_processor);
EXPECT_CALL(mock_processor, IsEntityUnsynced(kClientTagHash))
.WillOnce(Return(true));
EXPECT_CALL(mock_processor, UntrackEntityForStorageKey).Times(0);
sync_pb::EntitySpecifics specifics;
specifics.mutable_history_delete_directive();
SyncChangeList change_list;
change_list.push_back(
SyncChange(FROM_HERE, SyncChange::ACTION_DELETE,
SyncData::CreateRemoteData(
specifics, ClientTagHash::FromHashed(kClientTagHash))));
sync_change_processor->ProcessSyncChanges(FROM_HERE, change_list);
EXPECT_EQ(1U, in_memory_store.count(kClientTagHash));
}
TEST_F(SyncableServiceBasedBridgeTest, ConflictShouldUseRemote) {
InitializeBridge();
EntityData remote_data;
remote_data.client_tag_hash = kClientTagHash;
remote_data.specifics = GetTestSpecifics();
ASSERT_FALSE(remote_data.is_deleted());
EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data),
Eq(ConflictResolution::kUseRemote));
}
TEST_F(SyncableServiceBasedBridgeTest,
ConflictWithRemoteDeletionShouldUseLocal) {
InitializeBridge();
EntityData remote_data;
remote_data.client_tag_hash = kClientTagHash;
ASSERT_TRUE(remote_data.is_deleted());
EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data),
Eq(ConflictResolution::kUseLocal));
}
// This ensures that for extensions, the conflict is resolved in favor of the
// server, to prevent extensions from being reinstalled after uninstall.
TEST_F(SyncableServiceBasedBridgeTest,
ConflictWithRemoteExtensionUninstallShouldUseRemote) {
InitializeBridge(EXTENSIONS);
EntityData remote_data;
remote_data.client_tag_hash = kClientTagHash;
ASSERT_TRUE(remote_data.is_deleted());
EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data),
Eq(ConflictResolution::kUseRemote));
}
// Same as above but for APPS.
TEST_F(SyncableServiceBasedBridgeTest,
ConflictWithRemoteAppUninstallShouldUseRemote) {
InitializeBridge(APPS);
EntityData remote_data;
remote_data.client_tag_hash = kClientTagHash;
ASSERT_TRUE(remote_data.is_deleted());
EXPECT_THAT(bridge_->ResolveConflict("storagekey1", remote_data),
Eq(ConflictResolution::kUseRemote));
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldMeasureSyncableServiceStartTime) {
// The following writes data into store for the next run.
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// Mimic restart.
ShutdownBridge();
base::RunLoop loop;
ON_CALL(syncable_service_, WaitUntilReadyToSync)
.WillByDefault([&](base::OnceClosure done) {
std::move(done).Run();
loop.Quit();
});
base::HistogramTester histogram_tester;
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
// Initial data is loaded from the store.
InitializeBridge();
loop.Run();
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 1);
}
// This also covers the case where the user opts in for sync later.
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotMeasureSyncableServiceStartTimeIfNoInitialData) {
base::HistogramTester histogram_tester;
// No initial data.
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing).Times(0);
InitializeBridge();
StartSyncing();
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0);
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
// Initial merge happens on response from server.
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0);
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotMeasureSyncableServiceStartTimeOnSyncRestart) {
// The following writes data into store for the next run.
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// Mimic restart, which shouldn't start syncing until OnSyncStarting() is
// received (exercised in StartSyncing()).
ShutdownBridge();
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
// Initial data is loaded from the store.
InitializeBridge();
StartSyncing();
base::HistogramTester histogram_tester;
// Mimic sync restart.
real_processor_->OnSyncStopping(CLEAR_METADATA);
StartSyncing();
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// This case shouldn't be logged into the metric.
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0);
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotMeasureSyncableServiceStartTimeOnError) {
base::HistogramTester histogram_tester;
InitializeBridge();
StartSyncing();
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0);
// Instrument MergeDataAndStartSyncing() to return an error.
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing)
.WillOnce(Return(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError)));
EXPECT_CALL(mock_error_handler_, Run);
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
histogram_tester.ExpectTotalCount(kSyncableServiceStartTimeHistogramName, 0);
}
TEST_F(SyncableServiceBasedBridgeTest, ShouldCallWillStartInitialSync) {
::testing::InSequence in_sequence;
EXPECT_CALL(syncable_service_, WillStartInitialSync);
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
}
TEST_F(SyncableServiceBasedBridgeTest,
ShouldNotCallWillStartInitialSyncUponBrowserRestart) {
// The following writes data into store for the next run.
InitializeBridge();
StartSyncing();
worker_->UpdateFromServer(kClientTagHash, GetTestSpecifics("name1"));
// Mimic restart.
ShutdownBridge();
EXPECT_CALL(syncable_service_, MergeDataAndStartSyncing);
EXPECT_CALL(syncable_service_, WillStartInitialSync).Times(0);
// Initial data is loaded from the store.
InitializeBridge();
StartSyncing();
}
} // namespace
} // namespace syncer