blob: 382e63c1cb065955915314222c8441f12622117c [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/sync/glue/chrome_sync_notification_bridge.h"
#include <cstddef>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/message_loop_proxy.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/thread.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/test/base/profile_mock.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/test_browser_thread.h"
#include "sync/internal_api/public/base/model_type.h"
#include "sync/internal_api/public/base/model_type_state_map.h"
#include "sync/notifier/fake_invalidation_handler.h"
#include "sync/notifier/object_id_state_map_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace browser_sync {
namespace {
using ::testing::NiceMock;
// Needed by BlockForSyncThread().
void DoNothing() {}
// Since all the interesting stuff happens on the sync thread, we have
// to be careful to use GTest/GMock only on the main thread since they
// are not thread-safe.
class ChromeSyncNotificationBridgeTest : public testing::Test {
public:
ChromeSyncNotificationBridgeTest()
: ui_thread_(content::BrowserThread::UI, &ui_loop_),
sync_thread_("Sync thread"),
sync_handler_notification_success_(false) {}
virtual ~ChromeSyncNotificationBridgeTest() {}
protected:
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(sync_thread_.Start());
bridge_.reset(
new ChromeSyncNotificationBridge(
&mock_profile_, sync_thread_.message_loop_proxy()));
}
virtual void TearDown() OVERRIDE {
bridge_->StopForShutdown();
sync_thread_.Stop();
// Must be reset only after the sync thread is stopped.
bridge_.reset();
EXPECT_EQ(NULL, sync_handler_.get());
if (!sync_handler_notification_success_)
ADD_FAILURE() << "Sync handler did not receive proper notification.";
}
void VerifyAndDestroyObserver(
const syncer::ModelTypeStateMap& expected_states,
syncer::IncomingInvalidationSource expected_source) {
ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
FROM_HERE,
base::Bind(&ChromeSyncNotificationBridgeTest::
VerifyAndDestroyObserverOnSyncThread,
base::Unretained(this),
expected_states,
expected_source)));
BlockForSyncThread();
}
void CreateObserver() {
ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
FROM_HERE,
base::Bind(
&ChromeSyncNotificationBridgeTest::CreateObserverOnSyncThread,
base::Unretained(this))));
BlockForSyncThread();
}
void UpdateEnabledTypes(syncer::ModelTypeSet enabled_types) {
ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
FROM_HERE,
base::Bind(
&ChromeSyncNotificationBridgeTest::
UpdateEnabledTypesOnSyncThread,
base::Unretained(this),
enabled_types)));
BlockForSyncThread();
}
void TriggerRefreshNotification(
int type,
const syncer::ModelTypeStateMap& state_map) {
content::NotificationService::current()->Notify(
type,
content::Source<Profile>(&mock_profile_),
content::Details<const syncer::ModelTypeStateMap>(&state_map));
BlockForSyncThread();
}
private:
void VerifyAndDestroyObserverOnSyncThread(
const syncer::ModelTypeStateMap& expected_states,
syncer::IncomingInvalidationSource expected_source) {
DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
if (sync_handler_.get()) {
sync_handler_notification_success_ =
(sync_handler_->GetInvalidationCount() == 1) &&
ObjectIdStateMapEquals(
sync_handler_->GetLastInvalidationIdStateMap(),
syncer::ModelTypeStateMapToObjectIdStateMap(expected_states)) &&
(sync_handler_->GetLastInvalidationSource() == expected_source);
bridge_->UnregisterHandler(sync_handler_.get());
} else {
sync_handler_notification_success_ = false;
}
sync_handler_.reset();
}
void CreateObserverOnSyncThread() {
DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
sync_handler_.reset(new syncer::FakeInvalidationHandler());
bridge_->RegisterHandler(sync_handler_.get());
}
void UpdateEnabledTypesOnSyncThread(
syncer::ModelTypeSet enabled_types) {
DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
bridge_->UpdateRegisteredIds(
sync_handler_.get(), ModelTypeSetToObjectIdSet(enabled_types));
}
void BlockForSyncThread() {
// Post a task to the sync thread's message loop and block until
// it runs.
base::RunLoop run_loop;
ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTaskAndReply(
FROM_HERE,
base::Bind(&DoNothing),
run_loop.QuitClosure()));
run_loop.Run();
}
MessageLoop ui_loop_;
content::TestBrowserThread ui_thread_;
base::Thread sync_thread_;
NiceMock<ProfileMock> mock_profile_;
// Created/used/destroyed on sync thread.
scoped_ptr<syncer::FakeInvalidationHandler> sync_handler_;
bool sync_handler_notification_success_;
scoped_ptr<ChromeSyncNotificationBridge> bridge_;
};
// Adds an observer on the sync thread, triggers a local refresh
// invalidation, and ensures the bridge posts a LOCAL_INVALIDATION
// with the proper state to it.
TEST_F(ChromeSyncNotificationBridgeTest, LocalNotification) {
syncer::ModelTypeStateMap state_map;
state_map.insert(
std::make_pair(syncer::SESSIONS, syncer::InvalidationState()));
CreateObserver();
UpdateEnabledTypes(syncer::ModelTypeSet(syncer::SESSIONS));
TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
state_map);
VerifyAndDestroyObserver(state_map, syncer::LOCAL_INVALIDATION);
}
// Adds an observer on the sync thread, triggers a remote refresh
// invalidation, and ensures the bridge posts a REMOTE_INVALIDATION
// with the proper state to it.
TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotification) {
syncer::ModelTypeStateMap state_map;
state_map.insert(
std::make_pair(syncer::SESSIONS, syncer::InvalidationState()));
CreateObserver();
UpdateEnabledTypes(syncer::ModelTypeSet(syncer::SESSIONS));
TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
state_map);
VerifyAndDestroyObserver(state_map, syncer::REMOTE_INVALIDATION);
}
// Adds an observer on the sync thread, triggers a local refresh
// notification with empty state map and ensures the bridge posts a
// LOCAL_INVALIDATION with the proper state to it.
TEST_F(ChromeSyncNotificationBridgeTest, LocalNotificationEmptyPayloadMap) {
const syncer::ModelTypeSet enabled_types(
syncer::BOOKMARKS, syncer::PASSWORDS);
const syncer::ModelTypeStateMap enabled_types_state_map =
syncer::ModelTypeSetToStateMap(enabled_types, std::string());
CreateObserver();
UpdateEnabledTypes(enabled_types);
TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
syncer::ModelTypeStateMap());
VerifyAndDestroyObserver(
enabled_types_state_map, syncer::LOCAL_INVALIDATION);
}
// Adds an observer on the sync thread, triggers a remote refresh
// notification with empty state map and ensures the bridge posts a
// REMOTE_INVALIDATION with the proper state to it.
TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotificationEmptyPayloadMap) {
const syncer::ModelTypeSet enabled_types(
syncer::BOOKMARKS, syncer::TYPED_URLS);
const syncer::ModelTypeStateMap enabled_types_state_map =
syncer::ModelTypeSetToStateMap(enabled_types, std::string());
CreateObserver();
UpdateEnabledTypes(enabled_types);
TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
syncer::ModelTypeStateMap());
VerifyAndDestroyObserver(
enabled_types_state_map, syncer::REMOTE_INVALIDATION);
}
} // namespace
} // namespace browser_sync