blob: 3dbbe32c7b400297c932b357ae1f0bf6c899d144 [file] [log] [blame]
// Copyright 2014 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/service/model_load_manager.h"
#include <memory>
#include "base/test/task_environment.h"
#include "components/sync/base/sync_stop_metadata_fate.h"
#include "components/sync/service/configure_context.h"
#include "components/sync/service/sync_error.h"
#include "components/sync/test/fake_data_type_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
namespace syncer {
namespace {
ConfigureContext BuildConfigureContext(SyncMode sync_mode = SyncMode::kFull) {
ConfigureContext context;
context.sync_mode = sync_mode;
context.cache_guid = "test_cache_guid";
return context;
}
} // namespace
class MockModelLoadManagerDelegate : public ModelLoadManagerDelegate {
public:
MockModelLoadManagerDelegate() = default;
~MockModelLoadManagerDelegate() override = default;
MOCK_METHOD(void, OnAllDataTypesReadyForConfigure, (), (override));
MOCK_METHOD(void,
OnSingleDataTypeWillStop,
(DataType, const std::optional<SyncError>& error),
(override));
};
class SyncModelLoadManagerTest : public testing::Test {
public:
SyncModelLoadManagerTest() = default;
FakeDataTypeController* GetController(DataType data_type) {
auto it = controllers_.find(data_type);
if (it == controllers_.end()) {
return nullptr;
}
return static_cast<FakeDataTypeController*>(it->second.get());
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::UI,
base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
testing::NiceMock<MockModelLoadManagerDelegate> delegate_;
DataTypeController::TypeMap controllers_;
};
// Start a type and make sure ModelLoadManager callst the `Start`
// method and calls the callback when it is done.
TEST_F(SyncModelLoadManagerTest, SimpleModelStart) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {BOOKMARKS, APPS};
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
// Configure() kicks off model loading.
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
}
// Start a type, let it finish and then call stop.
TEST_F(SyncModelLoadManagerTest, StopAfterFinish) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types;
types.Put(BOOKMARKS);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
// Test that a model that failed to load is reported and stopped properly.
TEST_F(SyncModelLoadManagerTest, ModelLoadFail) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types;
types.Put(BOOKMARKS);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
EXPECT_EQ(DataTypeController::FAILED, GetController(BOOKMARKS)->state());
}
// Test that a runtime error is handled by stopping the type.
TEST_F(SyncModelLoadManagerTest, StopAfterConfiguration) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types;
types.Put(BOOKMARKS);
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
GetController(BOOKMARKS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
}
// Test that OnAllDataTypesReadyForConfigure is called when all datatypes that
// require LoadModels before configuration are loaded.
TEST_F(SyncModelLoadManagerTest, OnAllDataTypesReadyForConfigure) {
// Create two controllers with delayed model load.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
GetController(APPS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {BOOKMARKS, APPS};
// OnAllDataTypesReadyForConfigure shouldn't be called, APPS data type is not
// loaded yet.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_STARTING);
// Finish loading BOOKMARKS, but APPS are still loading.
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
// Finish loading APPS. This should trigger OnAllDataTypesReadyForConfigure.
GetController(APPS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
// Call ModelLoadManager::Configure with reduced set of datatypes.
// All datatypes in reduced set are already loaded.
// OnAllDataTypesReadyForConfigure() should be called.
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
DataTypeSet reduced_types = {APPS};
model_load_manager.Configure(
/*preferred_types_without_errors=*/reduced_types,
/*preferred_types=*/reduced_types, BuildConfigureContext());
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
// Test that OnAllDataTypesReadyForConfigure() is called correctly after
// LoadModels fails for one of datatypes.
TEST_F(SyncModelLoadManagerTest,
OnAllDataTypesReadyForConfigure_FailedLoadModels) {
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
GetController(APPS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {APPS};
// OnAllDataTypesReadyForConfigure shouldn't be called, APPS data type is not
// loaded yet.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_STARTING);
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
// Simulate model load error for APPS and finish loading it. This should
// trigger OnAllDataTypesReadyForConfigure.
GetController(APPS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::FAILED);
}
// Test that if one of the types fails while another is still being loaded then
// OnAllDataTypesReadyForConfgiure is still called correctly.
TEST_F(SyncModelLoadManagerTest,
OnAllDataTypesReadyForConfigure_TypeFailedAfterLoadModels) {
// Create two controllers with delayed model load. Both should block
// configuration.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
GetController(APPS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {BOOKMARKS, APPS};
// Apps will finish loading but bookmarks won't.
// OnAllDataTypesReadyForConfigure shouldn't be called.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
GetController(APPS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(APPS, _));
// Apps datatype reports failure.
GetController(APPS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
testing::Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
// Finish loading BOOKMARKS. This should trigger
// OnAllDataTypesReadyForConfigure().
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
}
// Test that Stop clears metadata for disabled type.
TEST_F(SyncModelLoadManagerTest, StopClearMetadata) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
DataTypeSet types = {BOOKMARKS};
// Configure() kicks off model loading.
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
model_load_manager.Stop(SyncStopMetadataFate::CLEAR_METADATA);
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
// Test that stopping a single type clears the metadata for the disabled type.
TEST_F(SyncModelLoadManagerTest, StopDataType) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
// Configure() kicks off model loading.
model_load_manager.Configure(
/*preferred_types_without_errors=*/{BOOKMARKS},
/*preferred_types=*/{BOOKMARKS}, BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
model_load_manager.StopDatatype(
BOOKMARKS, SyncStopMetadataFate::CLEAR_METADATA,
SyncError(FROM_HERE, SyncError::PRECONDITION_ERROR_WITH_KEEP_DATA,
"Data type is unready."));
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
// Test that stopping a single type is ignored when the type is not running.
TEST_F(SyncModelLoadManagerTest, StopDataType_NotRunning) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
model_load_manager.StopDatatype(
BOOKMARKS, SyncStopMetadataFate::CLEAR_METADATA,
SyncError(FROM_HERE, SyncError::PRECONDITION_ERROR_WITH_KEEP_DATA,
"Data type is unready."));
// The state should still be not running.
EXPECT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
}
// Test that Configure stops controllers with KEEP_METADATA for preferred
// types.
TEST_F(SyncModelLoadManagerTest, KeepsMetadataForPreferredDataType) {
// Configure the manager with two data types.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {BOOKMARKS, APPS};
DataTypeSet desired_types = preferred_types;
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
// Stop one data type without disabling sync.
desired_types.Remove(APPS);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(APPS, _));
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(0, GetController(APPS)->model()->clear_metadata_count());
}
// Test that Configure stops controllers with CLEAR_METADATA for
// no-longer-preferred types.
TEST_F(SyncModelLoadManagerTest, ClearsMetadataForNotPreferredDataType) {
// Configure the manager with two data types.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {BOOKMARKS, APPS};
DataTypeSet desired_types = preferred_types;
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
// Disable one data type.
preferred_types.Remove(APPS);
desired_types.Remove(APPS);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(APPS, _));
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
BuildConfigureContext());
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
EXPECT_EQ(1, GetController(APPS)->model()->clear_metadata_count());
}
TEST_F(SyncModelLoadManagerTest,
SwitchFromFullSyncToTransportModeRestartsTypes) {
// Configure the manager with two data types.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(
BOOKMARKS, /*enable_transport_only_model=*/true);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(
APPS, /*enable_transport_only_model=*/true);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {BOOKMARKS, APPS};
ConfigureContext configure_context;
configure_context.sync_mode = SyncMode::kFull;
configure_context.cache_guid = "test_cache_guid";
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(preferred_types, preferred_types,
configure_context);
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
// Switch to transport mode.
configure_context.sync_mode = SyncMode::kTransportOnly;
// For this test, assume that APPS is not supported in transport mode, but
// BOOKMARKS is.
preferred_types.Remove(APPS);
// Data types should get restarted.
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(APPS, _));
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(preferred_types, preferred_types,
configure_context);
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
// When switching modes, the Sync-the-feature mode metadata should get cleared
// for all datatypes, including datatypes that restarted in transport mode
// (BOOKMARKS) and datatypes that were excluded (APPS).
// Note that for BOOKMARKS, it actually gets cleared twice: Once by
// ModelLoadManager itself, and again via ClearMetadataWhileStopped() from
// DataTypeController::LoadModels().
EXPECT_EQ(
1, GetController(APPS)->model(SyncMode::kFull)->clear_metadata_count());
EXPECT_EQ(
2,
GetController(BOOKMARKS)->model(SyncMode::kFull)->clear_metadata_count());
}
TEST_F(SyncModelLoadManagerTest,
SwitchFromTransportOnlyToFullSyncRestartsTypes) {
// Configure the manager with two data types.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(
BOOKMARKS, /*enable_transport_only_model=*/true);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(
APPS, /*enable_transport_only_model=*/true);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {BOOKMARKS, APPS};
DataTypeSet desired_types = preferred_types;
ConfigureContext configure_context;
configure_context.sync_mode = SyncMode::kTransportOnly;
configure_context.cache_guid = "test_cache_guid";
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
configure_context);
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
testing::Mock::VerifyAndClearExpectations(&delegate_);
// Switch to full-sync mode.
configure_context.sync_mode = SyncMode::kFull;
desired_types.Remove(APPS);
preferred_types.Remove(APPS);
// Data types should get restarted.
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(APPS, _));
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
model_load_manager.Configure(desired_types, preferred_types,
configure_context);
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
// The transport-mode metadata for all types should get cleared. Note that for
// BOOKMARKS, it actually gets cleared twice: Once by ModelLoadManager itself,
// and again via ClearMetadataWhileStopped() from
// DataTypeController::LoadModels().
EXPECT_EQ(1, GetController(APPS)
->model(SyncMode::kTransportOnly)
->clear_metadata_count());
EXPECT_EQ(2, GetController(BOOKMARKS)
->model(SyncMode::kTransportOnly)
->clear_metadata_count());
}
TEST_F(SyncModelLoadManagerTest, ShouldClearMetadataAfterStopped) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types;
types.Put(BOOKMARKS);
// Bring the type to a stopped state.
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
ASSERT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
model_load_manager.Stop(SyncStopMetadataFate::CLEAR_METADATA);
// Clearing metadata should work even though the type is already stopped.
EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
TEST_F(SyncModelLoadManagerTest, ShouldClearMetadataIfNotRunning) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::NOT_RUNNING);
ASSERT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
model_load_manager.Stop(SyncStopMetadataFate::CLEAR_METADATA);
// Clearing metadata should work even though the type is not running.
EXPECT_EQ(1, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
TEST_F(SyncModelLoadManagerTest, ShouldNotClearMetadataIfFailed) {
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _)).Times(2);
// Bring the underlying model to a failed state. Note that this does *not*
// bring the controller into the FAILED state yet.
GetController(BOOKMARKS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types{BOOKMARKS};
ASSERT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
// Trying to configure exposes the error to the controller (which calls back
// into the ModelLoadManager).
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
ASSERT_EQ(DataTypeController::FAILED, GetController(BOOKMARKS)->state());
// Failure during model load does *not* clear metadata (see
// ModelLoadManager::ModelLoadCallback()).
EXPECT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
// Clearing metadata should (again) have no effect since the type is not
// considered stopped.
model_load_manager.Stop(SyncStopMetadataFate::CLEAR_METADATA);
EXPECT_EQ(0, GetController(BOOKMARKS)->model()->clear_metadata_count());
}
// Test that Configure waits for desired types in STOPPING state to stop and
// reload before notifying data type manager.
TEST_F(SyncModelLoadManagerTest,
ShouldWaitForStoppingDesiredTypesBeforeLoading) {
// Create two controllers, one with delayed model load.
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {APPS, BOOKMARKS};
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// Bring BOOKMARKS to a STOPPING state.
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// It should wait for BOOKMARKS to finish loading before notifying the data
// type manager.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure).Times(0);
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// APPS is started right away.
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
// BOOKMARKS needs to finish stopping first before it can start again.
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// Finish loading of BOOKMARKS for the first time. This should first move the
// state to NOT_RUNNING. But, as part of the load callback,
// DataTypeController::LoadModels() will be called which will set its state
// to MODEL_STARTING.
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
// Finish loading of BOOKMARKS. This will lead to a call to notify the
// delegate that all the types are ready.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure);
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
}
// Test that Configure will not wait for no-longer-desired types in STOPPING
// state to stop before loading.
TEST_F(SyncModelLoadManagerTest,
ShouldNotWaitForStoppingUndesiredTypesBeforeLoading) {
// Create two controllers, one with delayed model load.
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {APPS, BOOKMARKS};
DataTypeSet preferred_types_without_errors = preferred_types;
model_load_manager.Configure(preferred_types_without_errors, preferred_types,
BuildConfigureContext());
// Bring BOOKMARKS to a STOPPING state.
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// Remove BOOKMARKS from `preferred_types_without_errors` which may happen in
// case of failures/timeouts.
preferred_types_without_errors.Remove(BOOKMARKS);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure);
model_load_manager.Configure(preferred_types_without_errors, preferred_types,
BuildConfigureContext());
// APPS is started and DataTypeManager informed.
EXPECT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
// BOOKMARKS remains in STOPPING state.
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
}
// Test that if one of the type is stuck at loading,
// OnAllDataTypesReadyForConfigure will get called after a timeout.
TEST_F(SyncModelLoadManagerTest, ShouldTimeoutIfNotAllTypesLoaded) {
// Create two controllers with delayed model load. Both should block
// configuration.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
GetController(APPS)->model()->EnableManualModelStart();
// No calls to OnAllDataTypesReadyForConfigure() yet.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure).Times(0);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {BOOKMARKS, APPS};
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
// Simulate successful loading of APPS only.
GetController(APPS)->model()->SimulateModelStartFinished();
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
// BOOKMARKS blocks the configuration.
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure);
// Types not loaded till now are skipped.
task_environment_.FastForwardBy(kSyncLoadModelsTimeoutDuration);
}
// Test that if Stop() is called before all data types finish loading,
// OnAllDataTypesReadyForConfigure will *not* get called after a timeout.
// Regression test for crbug.com/333865298.
TEST_F(SyncModelLoadManagerTest, ShouldNotTimeoutAfterStop) {
// Create a controllers with delayed model load.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
// No calls to OnAllDataTypesReadyForConfigure() should happen.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure).Times(0);
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet types = {BOOKMARKS};
model_load_manager.Configure(/*preferred_types_without_errors=*/types,
/*preferred_types=*/types,
BuildConfigureContext());
// BOOKMARKS blocks the configuration.
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
// The ModelLoadManager gets stopped again before BOOKMARKS finishes loading
// or times out.
model_load_manager.Stop(SyncStopMetadataFate::CLEAR_METADATA);
// Even after the loading timeout period, OnAllDataTypesReadyForConfigure()
// should *not* get called.
task_environment_.FastForwardBy(kSyncLoadModelsTimeoutDuration);
}
// Regression test for crbug.com/1506701.
// Tests that if LoadModels is called for a failed type, it's a no-op.
TEST_F(SyncModelLoadManagerTest, ShouldNotStartFailedTypesUponLoadModels) {
// Create two controllers, one with delayed model load.
controllers_[APPS] = std::make_unique<FakeDataTypeController>(APPS);
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {APPS, BOOKMARKS};
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// Bring BOOKMARKS to a STOPPING state.
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::NOT_RUNNING);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// APPS is started right away.
ASSERT_EQ(GetController(APPS)->state(), DataTypeController::MODEL_LOADED);
// BOOKMARKS needs to finish stopping first before it can start again.
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// Simulate model error while the type is stopping. ModelLoadManager should
// continue and not wait for the failed type.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure);
GetController(BOOKMARKS)->model()->SimulateModelError(
ModelError(FROM_HERE, syncer::ModelError::Type::kGenericTestError));
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::FAILED);
// No crash from LoadModels.
}
// Regression test for crbug.com/1519806.
// Tests that stop callbacks for a type which is not in NOT_RUNNING state
// anymore are ignored.
TEST_F(SyncModelLoadManagerTest,
ShouldHandleMultipleStopCallbacksForStoppingType) {
// Create a controller with manual loading.
controllers_[BOOKMARKS] = std::make_unique<FakeDataTypeController>(BOOKMARKS);
GetController(BOOKMARKS)->model()->EnableManualModelStart();
ModelLoadManager model_load_manager(&controllers_, &delegate_);
DataTypeSet preferred_types = {BOOKMARKS};
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// Bring BOOKMARKS to a STOPPING state.
model_load_manager.Stop(SyncStopMetadataFate::KEEP_METADATA);
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// It should wait for BOOKMARKS to finish loading before notifying the data
// type manager.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure).Times(0);
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// BOOKMARKS needs to finish stopping first before it can start again.
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// Add the same stop callback again to be called after the type has finished
// stopping.
model_load_manager.Configure(
/*preferred_types_without_errors=*/preferred_types, preferred_types,
BuildConfigureContext());
// BOOKMARKS needs to finish stopping first before it can start again.
ASSERT_EQ(GetController(BOOKMARKS)->state(), DataTypeController::STOPPING);
// Finish loading of BOOKMARKS for the first time. This should first move the
// state to NOT_RUNNING. But, as part of the load callback,
// DataTypeController::LoadModels() will be called which will set its state
// to MODEL_STARTING.
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
EXPECT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_STARTING);
// Finish loading of BOOKMARKS. This will lead to a call to notify the
// delegate that all the types are ready.
EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure);
GetController(BOOKMARKS)->model()->SimulateModelStartFinished();
ASSERT_EQ(GetController(BOOKMARKS)->state(),
DataTypeController::MODEL_LOADED);
// Note: The second stop callback didn't do anything and was a no-op.
}
} // namespace syncer