blob: 89cc5222edd2d7a0df88855c0ed2a2e3e34e558f [file] [log] [blame]
// Copyright 2016 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 "components/sync/driver/model_type_controller.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/sync/driver/configure_context.h"
#include "components/sync/engine/commit_queue.h"
#include "components/sync/engine/data_type_activation_response.h"
#include "components/sync/engine/fake_model_type_processor.h"
#include "components/sync/engine/model_type_configurer.h"
#include "components/sync/engine/model_type_processor_proxy.h"
#include "components/sync/model/data_type_activation_request.h"
#include "components/sync/model/sync_merge_result.h"
#include "components/sync/model_impl/forwarding_model_type_controller_delegate.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using testing::NiceMock;
using testing::_;
const ModelType kTestModelType = AUTOFILL;
const char kCacheGuid[] = "SomeCacheGuid";
const char kAccountId[] = "SomeAccountId";
const char kStartFailuresHistogram[] = "Sync.DataTypeStartFailures2";
const char kRunFailuresHistogram[] = "Sync.DataTypeRunFailures2";
MATCHER(ErrorIsSet, "") {
return arg.IsSet();
}
class MockDelegate : public ModelTypeControllerDelegate {
public:
MOCK_METHOD2(OnSyncStarting,
void(const DataTypeActivationRequest& request,
StartCallback callback));
MOCK_METHOD1(OnSyncStopping, void(SyncStopMetadataFate metadata_fate));
MOCK_METHOD1(GetAllNodesForDebugging, void(AllNodesCallback callback));
MOCK_METHOD1(GetStatusCountersForDebugging,
void(StatusCountersCallback callback));
MOCK_METHOD0(RecordMemoryUsageAndCountsHistograms, void());
};
// A simple processor that trackes connected state.
class TestModelTypeProcessor
: public FakeModelTypeProcessor,
public base::SupportsWeakPtr<TestModelTypeProcessor> {
public:
TestModelTypeProcessor() {}
bool is_connected() const { return is_connected_; }
// ModelTypeProcessor implementation.
void ConnectSync(std::unique_ptr<CommitQueue> commit_queue) override {
is_connected_ = true;
}
void DisconnectSync() override { is_connected_ = false; }
private:
bool is_connected_ = false;
DISALLOW_COPY_AND_ASSIGN(TestModelTypeProcessor);
};
// A ModelTypeConfigurer that just connects USS types.
class TestModelTypeConfigurer : public ModelTypeConfigurer {
public:
TestModelTypeConfigurer() {}
~TestModelTypeConfigurer() override {}
void ConfigureDataTypes(ConfigureParams params) override {
NOTREACHED() << "Not implemented.";
}
void RegisterDirectoryDataType(ModelType type,
ModelSafeGroup group) override {
NOTREACHED() << "Not implemented.";
}
void UnregisterDirectoryDataType(ModelType type) override {
NOTREACHED() << "Not implemented.";
}
void ActivateDirectoryDataType(ModelType type,
ModelSafeGroup group,
ChangeProcessor* change_processor) override {
NOTREACHED() << "Not implemented.";
}
void DeactivateDirectoryDataType(ModelType type) override {
NOTREACHED() << "Not implemented.";
}
void ActivateNonBlockingDataType(ModelType type,
std::unique_ptr<DataTypeActivationResponse>
activation_response) override {
DCHECK_EQ(kTestModelType, type);
DCHECK(!processor_);
processor_ = std::move(activation_response->type_processor);
processor_->ConnectSync(nullptr);
}
void DeactivateNonBlockingDataType(ModelType type) override {
DCHECK_EQ(kTestModelType, type);
DCHECK(processor_);
processor_->DisconnectSync();
processor_.reset();
}
private:
std::unique_ptr<ModelTypeProcessor> processor_;
};
ConfigureContext MakeConfigureContext() {
ConfigureContext context;
context.authenticated_account_id = kAccountId;
context.cache_guid = kCacheGuid;
return context;
}
} // namespace
class ModelTypeControllerTest : public testing::Test {
public:
ModelTypeControllerTest()
: controller_(kTestModelType,
std::make_unique<ForwardingModelTypeControllerDelegate>(
&mock_delegate_)) {}
~ModelTypeControllerTest() {
// Since we use ModelTypeProcessorProxy, which posts tasks, make sure we
// don't have anything pending on teardown that would make a test fail or
// crash.
base::RunLoop().RunUntilIdle();
}
bool LoadModels(bool initial_sync_done = false) {
base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
ModelTypeControllerDelegate::StartCallback start_callback;
EXPECT_CALL(mock_delegate_, OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
});
controller_.LoadModels(MakeConfigureContext(), load_models_done.Get());
if (!start_callback) {
return false;
}
// Prepare an activation response, which is the outcome of OnSyncStarting().
auto activation_response = std::make_unique<DataTypeActivationResponse>();
activation_response->model_type_state.set_initial_sync_done(
initial_sync_done);
activation_response->type_processor =
std::make_unique<ModelTypeProcessorProxy>(
base::AsWeakPtr(&processor_),
base::SequencedTaskRunnerHandle::Get());
// Mimic completion for OnSyncStarting().
EXPECT_CALL(load_models_done, Run(_, _));
std::move(start_callback).Run(std::move(activation_response));
return true;
}
void RegisterWithBackend(bool expect_downloaded) {
base::MockCallback<base::RepeatingCallback<void(bool)>> callback;
EXPECT_CALL(callback, Run(expect_downloaded));
controller_.RegisterWithBackend(callback.Get(), &configurer_);
// ModelTypeProcessorProxy does posting of tasks.
base::RunLoop().RunUntilIdle();
}
void StartAssociating() {
base::MockCallback<DataTypeController::StartCallback> callback;
EXPECT_CALL(callback, Run(DataTypeController::OK, _, _));
controller_.StartAssociating(callback.Get());
}
void StopAndWait(ShutdownReason shutdown_reason) {
// ModelTypeProcessorProxy does posting of tasks, so we need a runloop. This
// also verifies that the completion callback is run.
base::RunLoop loop;
controller_.Stop(shutdown_reason, loop.QuitClosure());
loop.Run();
}
void DeactivateDataTypeAndStop(ShutdownReason shutdown_reason) {
controller_.DeactivateDataType(&configurer_);
StopAndWait(shutdown_reason);
}
MockDelegate* delegate() { return &mock_delegate_; }
TestModelTypeProcessor* processor() { return &processor_; }
DataTypeController* controller() { return &controller_; }
private:
base::test::ScopedTaskEnvironment task_environment_;
NiceMock<MockDelegate> mock_delegate_;
TestModelTypeConfigurer configurer_;
TestModelTypeProcessor processor_;
ModelTypeController controller_;
};
TEST_F(ModelTypeControllerTest, InitialState) {
EXPECT_EQ(kTestModelType, controller()->type());
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
}
TEST_F(ModelTypeControllerTest, LoadModelsOnBackendThread) {
base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
ModelTypeControllerDelegate::StartCallback start_callback;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
});
controller()->LoadModels(MakeConfigureContext(), load_models_done.Get());
EXPECT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(start_callback);
// Mimic completion for OnSyncStarting().
EXPECT_CALL(load_models_done, Run(kTestModelType, _));
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
}
TEST_F(ModelTypeControllerTest, Activate) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(LoadModels());
EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
RegisterWithBackend(/*expect_downloaded=*/false);
EXPECT_TRUE(processor()->is_connected());
StartAssociating();
EXPECT_EQ(DataTypeController::RUNNING, controller()->state());
histogram_tester.ExpectTotalCount(kStartFailuresHistogram, 0);
}
TEST_F(ModelTypeControllerTest, ActivateWithInitialSyncDone) {
base::HistogramTester histogram_tester;
ASSERT_TRUE(LoadModels(/*initial_sync_done=*/true));
EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
RegisterWithBackend(/*expect_downloaded=*/true);
EXPECT_TRUE(processor()->is_connected());
histogram_tester.ExpectTotalCount(kStartFailuresHistogram, 0);
}
TEST_F(ModelTypeControllerTest, ActivateWithError) {
ModelErrorHandler error_handler;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
error_handler = request.error_handler;
});
base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
controller()->LoadModels(MakeConfigureContext(), load_models_done.Get());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(error_handler);
base::HistogramTester histogram_tester;
// Mimic completion for OnSyncStarting(), with an error.
EXPECT_CALL(*delegate(), OnSyncStopping(_)).Times(0);
EXPECT_CALL(load_models_done, Run(_, ErrorIsSet()));
error_handler.Run(ModelError(FROM_HERE, "Test error"));
// TODO(mastiz): We shouldn't need RunUntilIdle() here, but
// ModelTypeController currently uses task-posting for errors.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DataTypeController::FAILED, controller()->state());
histogram_tester.ExpectBucketCount(
kStartFailuresHistogram, ModelTypeToHistogramInt(kTestModelType), 1);
histogram_tester.ExpectTotalCount(kRunFailuresHistogram, 0);
}
TEST_F(ModelTypeControllerTest, Stop) {
ASSERT_TRUE(LoadModels());
RegisterWithBackend(/*expect_downloaded=*/false);
EXPECT_TRUE(processor()->is_connected());
StartAssociating();
DeactivateDataTypeAndStop(STOP_SYNC);
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
}
// Test emulates normal browser shutdown. Ensures that metadata was not cleared.
TEST_F(ModelTypeControllerTest, StopWhenDatatypeEnabled) {
ASSERT_TRUE(LoadModels());
StartAssociating();
// Ensures that metadata was not cleared.
EXPECT_CALL(*delegate(), OnSyncStopping(KEEP_METADATA));
DeactivateDataTypeAndStop(STOP_SYNC);
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
EXPECT_FALSE(processor()->is_connected());
}
// Test emulates scenario when user disables datatype. Metadata should be
// cleared.
TEST_F(ModelTypeControllerTest, StopWhenDatatypeDisabled) {
ASSERT_TRUE(LoadModels());
StartAssociating();
EXPECT_CALL(*delegate(), OnSyncStopping(CLEAR_METADATA));
DeactivateDataTypeAndStop(DISABLE_SYNC);
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
EXPECT_FALSE(processor()->is_connected());
}
// Test emulates disabling sync when datatype is not loaded yet. Metadata should
// not be cleared as the delegate is potentially not ready to handle it.
TEST_F(ModelTypeControllerTest, StopBeforeLoadModels) {
EXPECT_CALL(*delegate(), OnSyncStopping(CLEAR_METADATA)).Times(0);
ASSERT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
StopAndWait(DISABLE_SYNC);
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
}
// Test emulates disabling sync when datatype is in error state. Metadata should
// not be cleared as the delegate is potentially not ready to handle it.
TEST_F(ModelTypeControllerTest, StopDuringFailedState) {
EXPECT_CALL(*delegate(), OnSyncStopping(CLEAR_METADATA)).Times(0);
ModelErrorHandler error_handler;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
error_handler = request.error_handler;
});
base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
controller()->LoadModels(MakeConfigureContext(), load_models_done.Get());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(error_handler);
// Mimic completion for OnSyncStarting(), with an error.
error_handler.Run(ModelError(FROM_HERE, "Test error"));
// TODO(mastiz): We shouldn't need RunUntilIdle() here, but
// ModelTypeController currently uses task-posting for errors.
base::RunLoop().RunUntilIdle();
ASSERT_EQ(DataTypeController::FAILED, controller()->state());
StopAndWait(DISABLE_SYNC);
EXPECT_EQ(DataTypeController::FAILED, controller()->state());
}
// Test emulates disabling sync when datatype is loading. The controller should
// wait for completion of the delegate, before stopping it.
TEST_F(ModelTypeControllerTest, StopWhileStarting) {
ModelTypeControllerDelegate::StartCallback start_callback;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
});
controller()->LoadModels(MakeConfigureContext(), base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(start_callback);
// Stop() should be deferred until OnSyncStarting() finishes.
base::MockCallback<base::OnceClosure> stop_completion;
EXPECT_CALL(stop_completion, Run()).Times(0);
EXPECT_CALL(*delegate(), OnSyncStopping(_)).Times(0);
controller()->Stop(DISABLE_SYNC, stop_completion.Get());
EXPECT_EQ(DataTypeController::STOPPING, controller()->state());
// Mimic completion for OnSyncStarting().
EXPECT_CALL(*delegate(), OnSyncStopping(_));
EXPECT_CALL(stop_completion, Run());
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
}
// Test emulates disabling sync when datatype is loading. The controller should
// wait for completion of the delegate, before stopping it. In this test,
// loading produces an error, so the resulting state should be FAILED.
TEST_F(ModelTypeControllerTest, StopWhileStartingWithError) {
ModelErrorHandler error_handler;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
error_handler = request.error_handler;
});
controller()->LoadModels(MakeConfigureContext(), base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(error_handler);
// Stop() should be deferred until OnSyncStarting() finishes.
base::MockCallback<base::OnceClosure> stop_completion;
EXPECT_CALL(stop_completion, Run()).Times(0);
EXPECT_CALL(*delegate(), OnSyncStopping(_)).Times(0);
controller()->Stop(DISABLE_SYNC, stop_completion.Get());
EXPECT_EQ(DataTypeController::STOPPING, controller()->state());
base::HistogramTester histogram_tester;
// Mimic completion for OnSyncStarting(), with an error.
EXPECT_CALL(*delegate(), OnSyncStopping(_)).Times(0);
EXPECT_CALL(stop_completion, Run());
error_handler.Run(ModelError(FROM_HERE, "Test error"));
// TODO(mastiz): We shouldn't need RunUntilIdle() here, but
// ModelTypeController currently uses task-posting for errors.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DataTypeController::FAILED, controller()->state());
histogram_tester.ExpectBucketCount(kStartFailuresHistogram,
ModelTypeToHistogramInt(kTestModelType),
/*count=*/1);
histogram_tester.ExpectTotalCount(kRunFailuresHistogram, 0);
}
// Test emulates a controller talking to a delegate (processor) in a backend
// thread, which necessarily involves task posting (usually via
// ProxyModelTypeControllerDelegate), where the backend posts an error
// simultaneously to the UI stopping the datatype.
TEST_F(ModelTypeControllerTest, StopWhileErrorInFlight) {
ModelTypeControllerDelegate::StartCallback start_callback;
ModelErrorHandler error_handler;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
error_handler = request.error_handler;
});
controller()->LoadModels(MakeConfigureContext(), base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(start_callback);
ASSERT_TRUE(error_handler);
// Mimic completion for OnSyncStarting().
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
ASSERT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
// At this point, the UI stops the datatype, but it's possible that the
// backend has already posted a task to the UI thread, which we'll process
// later below.
StopAndWait(DISABLE_SYNC);
ASSERT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
base::HistogramTester histogram_tester;
// In the next loop iteration, the UI thread receives the error.
error_handler.Run(ModelError(FROM_HERE, "Test error"));
// TODO(mastiz): We shouldn't need RunUntilIdle() here, but
// ModelTypeController currently uses task-posting for errors.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DataTypeController::FAILED, controller()->state());
histogram_tester.ExpectTotalCount(kStartFailuresHistogram, 0);
histogram_tester.ExpectTotalCount(kRunFailuresHistogram, 0);
}
// Tests that StorageOption is honored when the controller has been constructed
// with two delegates.
TEST(ModelTypeControllerWithMultiDelegateTest, ToggleStorageOption) {
base::test::ScopedTaskEnvironment task_environment;
NiceMock<MockDelegate> delegate_on_disk;
NiceMock<MockDelegate> delegate_in_memory;
ModelTypeController controller(
kTestModelType,
std::make_unique<ForwardingModelTypeControllerDelegate>(
&delegate_on_disk),
std::make_unique<ForwardingModelTypeControllerDelegate>(
&delegate_in_memory));
ConfigureContext context;
context.authenticated_account_id = kAccountId;
context.cache_guid = kCacheGuid;
ModelTypeControllerDelegate::StartCallback start_callback;
// Start sync with STORAGE_IN_MEMORY.
EXPECT_CALL(delegate_on_disk, OnSyncStarting(_, _)).Times(0);
EXPECT_CALL(delegate_in_memory, OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
});
context.storage_option = STORAGE_IN_MEMORY;
controller.LoadModels(context, base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller.state());
ASSERT_TRUE(start_callback);
// Mimic completion for OnSyncStarting().
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
ASSERT_EQ(DataTypeController::MODEL_LOADED, controller.state());
// Stop sync.
EXPECT_CALL(delegate_on_disk, OnSyncStopping(_)).Times(0);
EXPECT_CALL(delegate_in_memory, OnSyncStopping(_));
controller.Stop(DISABLE_SYNC, base::DoNothing());
ASSERT_EQ(DataTypeController::NOT_RUNNING, controller.state());
// Start sync with STORAGE_ON_DISK.
EXPECT_CALL(delegate_in_memory, OnSyncStarting(_, _)).Times(0);
EXPECT_CALL(delegate_on_disk, OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
start_callback = std::move(callback);
});
context.storage_option = STORAGE_ON_DISK;
controller.LoadModels(context, base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller.state());
ASSERT_TRUE(start_callback);
// Mimic completion for OnSyncStarting().
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
ASSERT_EQ(DataTypeController::MODEL_LOADED, controller.state());
// Stop sync.
EXPECT_CALL(delegate_in_memory, OnSyncStopping(_)).Times(0);
EXPECT_CALL(delegate_on_disk, OnSyncStopping(_));
controller.Stop(DISABLE_SYNC, base::DoNothing());
ASSERT_EQ(DataTypeController::NOT_RUNNING, controller.state());
}
TEST_F(ModelTypeControllerTest, ReportErrorAfterLoaded) {
base::HistogramTester histogram_tester;
// Capture the callbacks.
ModelErrorHandler error_handler;
ModelTypeControllerDelegate::StartCallback start_callback;
EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
.WillOnce([&](const DataTypeActivationRequest& request,
ModelTypeControllerDelegate::StartCallback callback) {
error_handler = request.error_handler;
start_callback = std::move(callback);
});
controller()->LoadModels(MakeConfigureContext(), base::DoNothing());
ASSERT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
ASSERT_TRUE(error_handler);
ASSERT_TRUE(start_callback);
// Mimic completion for OnSyncStarting().
std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
ASSERT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
StartAssociating();
ASSERT_EQ(DataTypeController::RUNNING, controller()->state());
// Now trigger the run-time error.
error_handler.Run(ModelError(FROM_HERE, "Test error"));
// TODO(mastiz): We shouldn't need RunUntilIdle() here, but
// ModelTypeController currently uses task-posting for errors.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DataTypeController::FAILED, controller()->state());
histogram_tester.ExpectTotalCount(kStartFailuresHistogram, 0);
histogram_tester.ExpectBucketCount(kRunFailuresHistogram,
ModelTypeToHistogramInt(kTestModelType),
/*count=*/1);
}
} // namespace syncer