// 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 #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(&mock_profile_), content::Details(&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 mock_profile_; // Created/used/destroyed on sync thread. scoped_ptr sync_handler_; bool sync_handler_notification_success_; scoped_ptr 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