blob: 9d92f44303dd15c788cf511129d82da6fbfa6af1 [file] [log] [blame]
// Copyright 2014 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/non_blocking_data_type_controller.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread.h"
#include "components/sync_driver/backend_data_type_configurer.h"
#include "components/sync_driver/fake_sync_client.h"
#include "sync/engine/commit_queue.h"
#include "sync/internal_api/public/activation_context.h"
#include "sync/internal_api/public/shared_model_type_processor.h"
#include "sync/internal_api/public/test/fake_model_type_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sync_driver_v2 {
namespace {
// Test controller derived from NonBlockingDataTypeController.
class TestController : public NonBlockingDataTypeController {
public:
TestController(const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread,
const base::Closure& error_callback,
syncer::ModelType model_type,
sync_driver::SyncClient* sync_client)
: NonBlockingDataTypeController(ui_thread, error_callback, sync_client),
model_type_(model_type) {}
// TODO(stanisc): This will likely have to change. It should be controller's
// job to locate the service via SyncClient, create an instance of
// SharedModelTypeProcessor, pass it to the service to own, and to store the
// weak pointer to the type processor. For now this continues using the
// earlier design where the controller and it's task runner are initialized
// from outside.
// Note that for the test purposes a nullptr |model_task_runner| indicates a
// special case of running on UI thread (see RunOnModelThread below).
void Initialize(const scoped_refptr<base::TaskRunner>& model_task_runner,
const base::WeakPtr<syncer_v2::SharedModelTypeProcessor>&
type_processor) {
model_task_runner_ = model_task_runner;
type_processor_ = type_processor;
}
syncer::ModelType type() const override { return model_type_; }
base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor()
const override {
return type_processor_;
}
bool RunOnModelThread(const tracked_objects::Location& from_here,
const base::Closure& task) override {
if (model_task_runner_) {
return model_task_runner_->PostTask(from_here, task);
} else {
// Special case for model running on the UI thread.
task.Run();
return true;
}
}
private:
~TestController() override {}
syncer::ModelType model_type_;
base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor_;
scoped_refptr<base::TaskRunner> model_task_runner_;
};
// A no-op instance of CommitQueue.
class NullCommitQueue : public syncer_v2::CommitQueue {
public:
NullCommitQueue() {}
~NullCommitQueue() override {}
void EnqueueForCommit(const syncer_v2::CommitRequestDataList& list) override {
NOTREACHED() << "Not implemented.";
}
};
// A class that pretends to be the sync backend.
class MockSyncBackend {
public:
void Connect(syncer::ModelType type,
scoped_ptr<syncer_v2::ActivationContext> activation_context) {
enabled_types_.Put(type);
activation_context->type_processor->OnConnect(
make_scoped_ptr(new NullCommitQueue()));
}
void Disconnect(syncer::ModelType type) {
DCHECK(enabled_types_.Has(type));
enabled_types_.Remove(type);
}
private:
syncer::ModelTypeSet enabled_types_;
};
// Fake implementation of BackendDataTypeConfigurer that pretends to be Sync
// backend.
class MockBackendDataTypeConfigurer
: public sync_driver::BackendDataTypeConfigurer {
public:
MockBackendDataTypeConfigurer(
MockSyncBackend* backend,
const scoped_refptr<base::TaskRunner>& sync_task_runner)
: backend_(backend), sync_task_runner_(sync_task_runner) {}
~MockBackendDataTypeConfigurer() override {}
syncer::ModelTypeSet ConfigureDataTypes(
syncer::ConfigureReason reason,
const DataTypeConfigStateMap& config_state_map,
const base::Callback<void(syncer::ModelTypeSet, syncer::ModelTypeSet)>&
ready_task,
const base::Callback<void()>& retry_callback) override {
NOTREACHED() << "Not implemented.";
return syncer::ModelTypeSet();
}
void ActivateDirectoryDataType(
syncer::ModelType type,
syncer::ModelSafeGroup group,
sync_driver::ChangeProcessor* change_processor) override {
NOTREACHED() << "Not implemented.";
}
void DeactivateDirectoryDataType(syncer::ModelType type) override {
NOTREACHED() << "Not implemented.";
}
void ActivateNonBlockingDataType(
syncer::ModelType type,
scoped_ptr<syncer_v2::ActivationContext> activation_context) override {
// Post on Sync thread just like the real implementation does.
sync_task_runner_->PostTask(
FROM_HERE,
base::Bind(&MockSyncBackend::Connect, base::Unretained(backend_), type,
base::Passed(activation_context.Pass())));
}
void DeactivateNonBlockingDataType(syncer::ModelType type) override {
sync_task_runner_->PostTask(FROM_HERE,
base::Bind(&MockSyncBackend::Disconnect,
base::Unretained(backend_), type));
}
private:
MockSyncBackend* backend_;
scoped_refptr<base::TaskRunner> sync_task_runner_;
};
} // namespace
class NonBlockingDataTypeControllerTest : public testing::Test,
public sync_driver::FakeSyncClient {
public:
NonBlockingDataTypeControllerTest()
: auto_run_tasks_(true),
load_models_callback_called_(false),
association_callback_called_(false),
model_thread_("modelthread"),
sync_thread_runner_(new base::TestSimpleTaskRunner()),
configurer_(&backend_, sync_thread_runner_) {}
~NonBlockingDataTypeControllerTest() override {}
void SetUp() override {
controller_ = new TestController(ui_loop_.task_runner(), base::Closure(),
syncer::DICTIONARY, this);
}
void TearDown() override {
ClearTypeProcessor();
controller_ = NULL;
RunQueuedUIThreadTasks();
}
protected:
void CreateTypeProcessor() {
// TODO(stanisc): Controller should discover the service via SyncClient.
type_processor_.reset(
new syncer_v2::SharedModelTypeProcessor(syncer::DICTIONARY, &service_));
type_processor_for_ui_ = type_processor_->AsWeakPtrForUI();
}
void InitTypeProcessorOnUIThread() {
CreateTypeProcessor();
model_thread_runner_ = ui_loop_.task_runner();
// Don't pass task runner to the controller in this case. It will be making
// prompt calls instead of posting tasks.
controller_->Initialize(nullptr, type_processor_for_ui_);
}
void InitTypeProcessorOnBackendThread() {
model_thread_.Start();
model_thread_runner_ = model_thread_.task_runner();
// TODO(stanisc): It should be controller's job to
// create TypeProcessor on the model thread.
model_thread_runner_->PostTask(
FROM_HERE,
base::Bind(&NonBlockingDataTypeControllerTest::CreateTypeProcessor,
base::Unretained(this)));
RunQueuedModelThreadTasks();
controller_->Initialize(model_thread_runner_, type_processor_for_ui_);
}
void ClearTypeProcessor() {
if (!model_thread_runner_ ||
model_thread_runner_->BelongsToCurrentThread()) {
type_processor_.reset();
} else {
model_thread_runner_->PostTask(
FROM_HERE,
base::Bind(&NonBlockingDataTypeControllerTest::ClearTypeProcessor,
base::Unretained(this)));
RunQueuedModelThreadTasks();
}
}
void TestTypeProcessor(bool isEnabled, bool isConnected) {
if (model_thread_runner_->BelongsToCurrentThread()) {
EXPECT_EQ(isEnabled, type_processor_->IsEnabled());
EXPECT_EQ(isConnected, type_processor_->IsConnected());
} else {
model_thread_runner_->PostTask(
FROM_HERE,
base::Bind(&NonBlockingDataTypeControllerTest::TestTypeProcessor,
base::Unretained(this), isEnabled, isConnected));
RunQueuedModelThreadTasks();
}
}
void LoadModels() {
controller_->LoadModels(
base::Bind(&NonBlockingDataTypeControllerTest::LoadModelsDone,
base::Unretained(this)));
if (auto_run_tasks_) {
RunAllTasks();
}
}
void StartAssociating() {
controller_->StartAssociating(
base::Bind(&NonBlockingDataTypeControllerTest::AssociationDone,
base::Unretained(this)));
// The callback is expected to be promptly called.
EXPECT_TRUE(association_callback_called_);
}
void ActivateDataType() {
DCHECK(association_callback_called_);
controller_->ActivateDataType(&configurer_);
if (auto_run_tasks_) {
RunAllTasks();
}
}
void DeactivateDataTypeAndStop() {
controller_->DeactivateDataType(&configurer_);
controller_->Stop();
if (auto_run_tasks_) {
RunAllTasks();
}
}
// These threads can ping-pong for a bit so we run the model thread twice.
void RunAllTasks() {
RunQueuedModelThreadTasks();
RunQueuedUIThreadTasks();
RunQueuedSyncThreadTasks();
RunQueuedModelThreadTasks();
}
// Runs any tasks posted on UI thread.
void RunQueuedUIThreadTasks() { ui_loop_.RunUntilIdle(); }
// Runs any tasks posted on model thread.
void RunQueuedModelThreadTasks() {
base::RunLoop run_loop;
model_thread_runner_->PostTaskAndReply(
FROM_HERE, base::Bind(&base::DoNothing),
base::Bind(&base::RunLoop::Quit, base::Unretained(&run_loop)));
run_loop.Run();
}
// Processes any pending connect or disconnect requests and sends
// responses synchronously.
void RunQueuedSyncThreadTasks() { sync_thread_runner_->RunUntilIdle(); }
void SetAutoRunTasks(bool auto_run_tasks) {
auto_run_tasks_ = auto_run_tasks;
}
void LoadModelsDone(syncer::ModelType type, syncer::SyncError error) {
load_models_callback_called_ = true;
load_models_error_ = error;
}
void AssociationDone(sync_driver::DataTypeController::ConfigureResult result,
const syncer::SyncMergeResult& local_merge_result,
const syncer::SyncMergeResult& syncer_merge_result) {
EXPECT_EQ(sync_driver::DataTypeController::OK, result);
association_callback_called_ = true;
}
scoped_ptr<syncer_v2::SharedModelTypeProcessor> type_processor_;
base::WeakPtr<syncer_v2::SharedModelTypeProcessor> type_processor_for_ui_;
scoped_refptr<TestController> controller_;
bool auto_run_tasks_;
bool load_models_callback_called_;
syncer::SyncError load_models_error_;
bool association_callback_called_;
base::MessageLoopForUI ui_loop_;
base::Thread model_thread_;
scoped_refptr<base::SingleThreadTaskRunner> model_thread_runner_;
scoped_refptr<base::TestSimpleTaskRunner> sync_thread_runner_;
MockSyncBackend backend_;
MockBackendDataTypeConfigurer configurer_;
syncer_v2::FakeModelTypeService service_;
};
TEST_F(NonBlockingDataTypeControllerTest, InitialState) {
EXPECT_EQ(syncer::DICTIONARY, controller_->type());
EXPECT_EQ(sync_driver::DataTypeController::NOT_RUNNING, controller_->state());
}
TEST_F(NonBlockingDataTypeControllerTest, LoadModelsOnUIThread) {
InitTypeProcessorOnUIThread();
TestTypeProcessor(false, false); // not enabled, not connected.
LoadModels();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED,
controller_->state());
EXPECT_TRUE(load_models_callback_called_);
EXPECT_FALSE(load_models_error_.IsSet());
TestTypeProcessor(true, false); // enabled, not connected.
}
TEST_F(NonBlockingDataTypeControllerTest, LoadModelsOnBackendThread) {
InitTypeProcessorOnBackendThread();
TestTypeProcessor(false, false); // not enabled, not connected.
SetAutoRunTasks(false);
LoadModels();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_STARTING,
controller_->state());
RunAllTasks();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED,
controller_->state());
EXPECT_TRUE(load_models_callback_called_);
EXPECT_FALSE(load_models_error_.IsSet());
TestTypeProcessor(true, false); // enabled, not connected.
}
TEST_F(NonBlockingDataTypeControllerTest, LoadModelsTwice) {
InitTypeProcessorOnUIThread();
LoadModels();
SetAutoRunTasks(false);
LoadModels();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED,
controller_->state());
// The second LoadModels call should set the error.
EXPECT_TRUE(load_models_error_.IsSet());
}
TEST_F(NonBlockingDataTypeControllerTest, ActivateDataTypeOnUIThread) {
InitTypeProcessorOnUIThread();
LoadModels();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED,
controller_->state());
StartAssociating();
EXPECT_EQ(sync_driver::DataTypeController::RUNNING, controller_->state());
ActivateDataType();
TestTypeProcessor(true, true); // enabled, connected.
}
TEST_F(NonBlockingDataTypeControllerTest, ActivateDataTypeOnBackendThread) {
InitTypeProcessorOnBackendThread();
LoadModels();
EXPECT_EQ(sync_driver::DataTypeController::MODEL_LOADED,
controller_->state());
StartAssociating();
EXPECT_EQ(sync_driver::DataTypeController::RUNNING, controller_->state());
ActivateDataType();
TestTypeProcessor(true, true); // enabled, connected.
}
TEST_F(NonBlockingDataTypeControllerTest, Stop) {
InitTypeProcessorOnBackendThread();
LoadModels();
StartAssociating();
ActivateDataType();
TestTypeProcessor(true, true); // enabled, connected.
DeactivateDataTypeAndStop();
EXPECT_EQ(sync_driver::DataTypeController::NOT_RUNNING, controller_->state());
TestTypeProcessor(true, false); // enabled, not connected.
}
} // namespace sync_driver_v2