| // 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 <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "chrome/browser/sync_file_system/local/canned_syncable_file_system.h" |
| #include "chrome/browser/sync_file_system/local/local_file_sync_context.h" |
| #include "chrome/browser/sync_file_system/local/local_file_sync_service.h" |
| #include "chrome/browser/sync_file_system/local/mock_sync_status_observer.h" |
| #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h" |
| #include "chrome/browser/sync_file_system/mock_remote_file_sync_service.h" |
| #include "chrome/browser/sync_file_system/sync_callbacks.h" |
| #include "chrome/browser/sync_file_system/sync_event_observer.h" |
| #include "chrome/browser/sync_file_system/sync_file_metadata.h" |
| #include "chrome/browser/sync_file_system/sync_file_system_service.h" |
| #include "chrome/browser/sync_file_system/sync_file_system_test_util.h" |
| #include "chrome/browser/sync_file_system/sync_status_code.h" |
| #include "chrome/browser/sync_file_system/syncable_file_system_util.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webkit/browser/fileapi/file_system_context.h" |
| |
| using content::BrowserThread; |
| using fileapi::FileSystemURL; |
| using fileapi::FileSystemURLSet; |
| using ::testing::AnyNumber; |
| using ::testing::AtLeast; |
| using ::testing::InSequence; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| using ::testing::_; |
| |
| namespace sync_file_system { |
| |
| namespace { |
| |
| const char kOrigin[] = "http://example.com"; |
| |
| template <typename R> struct AssignTrait { |
| typedef const R& ArgumentType; |
| }; |
| |
| template <> struct AssignTrait<SyncFileStatus> { |
| typedef SyncFileStatus ArgumentType; |
| }; |
| |
| template <typename R> |
| void AssignValueAndQuit(base::RunLoop* run_loop, |
| SyncStatusCode* status_out, R* value_out, |
| SyncStatusCode status, |
| typename AssignTrait<R>::ArgumentType value) { |
| DCHECK(status_out); |
| DCHECK(value_out); |
| DCHECK(run_loop); |
| *status_out = status; |
| *value_out = value; |
| run_loop->Quit(); |
| } |
| |
| // This is called on IO thread. |
| void VerifyFileError(base::RunLoop* run_loop, |
| base::PlatformFileError error) { |
| DCHECK(run_loop); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, error); |
| run_loop->Quit(); |
| } |
| |
| } // namespace |
| |
| class MockSyncEventObserver : public SyncEventObserver { |
| public: |
| MockSyncEventObserver() {} |
| virtual ~MockSyncEventObserver() {} |
| |
| MOCK_METHOD3(OnSyncStateUpdated, |
| void(const GURL& app_origin, |
| SyncServiceState state, |
| const std::string& description)); |
| MOCK_METHOD4(OnFileSynced, |
| void(const fileapi::FileSystemURL& url, |
| SyncFileStatus status, |
| SyncAction action, |
| SyncDirection direction)); |
| }; |
| |
| ACTION_P3(NotifyStateAndCallback, |
| mock_remote_service, service_state, operation_status) { |
| mock_remote_service->NotifyRemoteServiceStateUpdated( |
| service_state, "Test event."); |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, base::Bind(arg1, operation_status)); |
| } |
| |
| ACTION_P(RecordState, states) { |
| states->push_back(arg1); |
| } |
| |
| ACTION_P(MockStatusCallback, status) { |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, base::Bind(arg4, status)); |
| } |
| |
| ACTION_P2(MockSyncFileCallback, status, url) { |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, base::Bind(arg0, status, url)); |
| } |
| |
| class SyncFileSystemServiceTest : public testing::Test { |
| protected: |
| SyncFileSystemServiceTest() |
| : thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD | |
| content::TestBrowserThreadBundle::REAL_IO_THREAD) {} |
| |
| virtual void SetUp() OVERRIDE { |
| file_system_.reset(new CannedSyncableFileSystem( |
| GURL(kOrigin), |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO), |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE))); |
| |
| local_service_ = new LocalFileSyncService(&profile_); |
| remote_service_ = new StrictMock<MockRemoteFileSyncService>; |
| sync_service_.reset(new SyncFileSystemService(&profile_)); |
| |
| EXPECT_CALL(*mock_remote_service(), |
| AddServiceObserver(sync_service_.get())).Times(1); |
| EXPECT_CALL(*mock_remote_service(), |
| AddFileStatusObserver(sync_service_.get())).Times(1); |
| EXPECT_CALL(*mock_remote_service(), |
| GetLocalChangeProcessor()) |
| .WillOnce(Return(&local_change_processor_)); |
| EXPECT_CALL(*mock_remote_service(), |
| SetRemoteChangeProcessor(local_service_)).Times(1); |
| |
| sync_service_->Initialize( |
| make_scoped_ptr(local_service_), |
| scoped_ptr<RemoteFileSyncService>(remote_service_)); |
| |
| // Disable auto sync by default. |
| EXPECT_CALL(*mock_remote_service(), SetSyncEnabled(false)).Times(1); |
| sync_service_->SetSyncEnabledForTesting(false); |
| |
| file_system_->SetUp(); |
| } |
| |
| virtual void TearDown() OVERRIDE { |
| sync_service_->Shutdown(); |
| file_system_->TearDown(); |
| RevokeSyncableFileSystem(); |
| content::RunAllPendingInMessageLoop(BrowserThread::FILE); |
| } |
| |
| void InitializeApp() { |
| base::RunLoop run_loop; |
| SyncStatusCode status = SYNC_STATUS_UNKNOWN; |
| |
| EXPECT_CALL(*mock_remote_service(), |
| RegisterOrigin(GURL(kOrigin), _)).Times(1); |
| |
| // GetCurrentState may be called when a remote or local sync is scheduled |
| // by change notifications or by a timer. |
| EXPECT_CALL(*mock_remote_service(), GetCurrentState()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(REMOTE_SERVICE_OK)); |
| |
| sync_service_->InitializeForApp( |
| file_system_->file_system_context(), |
| GURL(kOrigin), |
| AssignAndQuitCallback(&run_loop, &status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(SYNC_STATUS_OK, status); |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_->OpenFileSystem()); |
| } |
| |
| // Calls InitializeForApp after setting up the mock remote service to |
| // perform following when RegisterOrigin is called: |
| // 1. Notify RemoteFileSyncService's observers of |state_to_notify| |
| // 2. Run the given callback with |status_to_return|. |
| // |
| // ..and verifies if following conditions are met: |
| // 1. The SyncEventObserver of the service is called with |
| // |expected_states| service state values. |
| // 2. InitializeForApp's callback is called with |expected_status| |
| void InitializeAppForObserverTest( |
| RemoteServiceState state_to_notify, |
| SyncStatusCode status_to_return, |
| const std::vector<SyncServiceState>& expected_states, |
| SyncStatusCode expected_status) { |
| StrictMock<MockSyncEventObserver> event_observer; |
| sync_service_->AddSyncEventObserver(&event_observer); |
| |
| EnableSync(); |
| |
| EXPECT_CALL(*mock_remote_service(), GetCurrentState()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(state_to_notify)); |
| |
| EXPECT_CALL(*mock_remote_service(), |
| RegisterOrigin(GURL(kOrigin), _)) |
| .WillOnce(NotifyStateAndCallback(mock_remote_service(), |
| state_to_notify, |
| status_to_return)); |
| |
| std::vector<SyncServiceState> actual_states; |
| EXPECT_CALL(event_observer, OnSyncStateUpdated(GURL(), _, _)) |
| .WillRepeatedly(RecordState(&actual_states)); |
| |
| SyncStatusCode actual_status = SYNC_STATUS_UNKNOWN; |
| base::RunLoop run_loop; |
| sync_service_->InitializeForApp( |
| file_system_->file_system_context(), |
| GURL(kOrigin), |
| AssignAndQuitCallback(&run_loop, &actual_status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(expected_status, actual_status); |
| ASSERT_EQ(expected_states.size(), actual_states.size()); |
| for (size_t i = 0; i < actual_states.size(); ++i) |
| EXPECT_EQ(expected_states[i], actual_states[i]); |
| |
| sync_service_->RemoveSyncEventObserver(&event_observer); |
| } |
| |
| FileSystemURL URL(const std::string& path) const { |
| return file_system_->URL(path); |
| } |
| |
| StrictMock<MockRemoteFileSyncService>* mock_remote_service() { |
| return remote_service_; |
| } |
| |
| StrictMock<MockLocalChangeProcessor>* mock_local_change_processor() { |
| return &local_change_processor_; |
| } |
| |
| void EnableSync() { |
| EXPECT_CALL(*mock_remote_service(), SetSyncEnabled(true)).Times(1); |
| sync_service_->SetSyncEnabledForTesting(true); |
| } |
| |
| ScopedEnableSyncFSDirectoryOperation enable_directory_operation_; |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| TestingProfile profile_; |
| scoped_ptr<CannedSyncableFileSystem> file_system_; |
| |
| // Their ownerships are transferred to SyncFileSystemService. |
| LocalFileSyncService* local_service_; |
| StrictMock<MockRemoteFileSyncService>* remote_service_; |
| StrictMock<MockLocalChangeProcessor> local_change_processor_; |
| |
| scoped_ptr<SyncFileSystemService> sync_service_; |
| }; |
| |
| TEST_F(SyncFileSystemServiceTest, InitializeForApp) { |
| InitializeApp(); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, InitializeForAppSuccess) { |
| std::vector<SyncServiceState> expected_states; |
| expected_states.push_back(SYNC_SERVICE_RUNNING); |
| |
| InitializeAppForObserverTest( |
| REMOTE_SERVICE_OK, |
| SYNC_STATUS_OK, |
| expected_states, |
| SYNC_STATUS_OK); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, InitializeForAppWithNetworkFailure) { |
| std::vector<SyncServiceState> expected_states; |
| expected_states.push_back(SYNC_SERVICE_TEMPORARY_UNAVAILABLE); |
| |
| // Notify REMOTE_SERVICE_TEMPORARY_UNAVAILABLE and callback with |
| // SYNC_STATUS_NETWORK_ERROR. This should let the |
| // InitializeApp fail. |
| InitializeAppForObserverTest( |
| REMOTE_SERVICE_TEMPORARY_UNAVAILABLE, |
| SYNC_STATUS_NETWORK_ERROR, |
| expected_states, |
| SYNC_STATUS_NETWORK_ERROR); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, InitializeForAppWithError) { |
| std::vector<SyncServiceState> expected_states; |
| expected_states.push_back(SYNC_SERVICE_DISABLED); |
| |
| // Notify REMOTE_SERVICE_DISABLED and callback with |
| // SYNC_STATUS_FAILED. This should let the InitializeApp fail. |
| InitializeAppForObserverTest( |
| REMOTE_SERVICE_DISABLED, |
| SYNC_STATUS_FAILED, |
| expected_states, |
| SYNC_STATUS_FAILED); |
| } |
| |
| // Flaky. http://crbug.com/237710 |
| TEST_F(SyncFileSystemServiceTest, DISABLED_SimpleLocalSyncFlow) { |
| InitializeApp(); |
| |
| StrictMock<MockSyncStatusObserver> status_observer; |
| |
| EnableSync(); |
| file_system_->backend()->sync_context()-> |
| set_mock_notify_changes_duration_in_sec(0); |
| file_system_->AddSyncStatusObserver(&status_observer); |
| |
| // We'll test one local sync for this file. |
| const FileSystemURL kFile(file_system_->URL("foo")); |
| |
| base::RunLoop run_loop; |
| |
| // We should get called OnSyncEnabled and OnWriteEnabled on kFile. |
| // (We quit the run loop when OnWriteEnabled is called on kFile) |
| EXPECT_CALL(status_observer, OnSyncEnabled(kFile)) |
| .Times(AtLeast(1)); |
| EXPECT_CALL(status_observer, OnWriteEnabled(kFile)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| |
| // The local_change_processor's ApplyLocalChange should be called once |
| // with ADD_OR_UPDATE change for TYPE_FILE. |
| const FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| SYNC_FILE_TYPE_FILE); |
| EXPECT_CALL(*mock_local_change_processor(), |
| ApplyLocalChange(change, _, _, kFile, _)) |
| .WillOnce(MockStatusCallback(SYNC_STATUS_OK)); |
| |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_->CreateFile(kFile)); |
| |
| run_loop.Run(); |
| |
| file_system_->RemoveSyncStatusObserver(&status_observer); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, SimpleRemoteSyncFlow) { |
| InitializeApp(); |
| |
| EnableSync(); |
| |
| base::RunLoop run_loop; |
| |
| // We expect a set of method calls for starting a remote sync. |
| EXPECT_CALL(*mock_remote_service(), ProcessRemoteChange(_)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| |
| // This should trigger a remote sync. |
| mock_remote_service()->NotifyRemoteChangeQueueUpdated(1); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, SimpleSyncFlowWithFileBusy) { |
| InitializeApp(); |
| |
| EnableSync(); |
| file_system_->backend()->sync_context()-> |
| set_mock_notify_changes_duration_in_sec(0); |
| |
| const FileSystemURL kFile(file_system_->URL("foo")); |
| |
| base::RunLoop run_loop; |
| |
| { |
| InSequence sequence; |
| |
| // Return with SYNC_STATUS_FILE_BUSY once. |
| EXPECT_CALL(*mock_remote_service(), ProcessRemoteChange(_)) |
| .WillOnce(MockSyncFileCallback(SYNC_STATUS_FILE_BUSY, |
| kFile)); |
| |
| // ProcessRemoteChange should be called again when the becomes |
| // not busy. |
| EXPECT_CALL(*mock_remote_service(), ProcessRemoteChange(_)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| } |
| |
| // We might also see an activity for local sync as we're going to make |
| // a local write operation on kFile. |
| EXPECT_CALL(*mock_local_change_processor(), |
| ApplyLocalChange(_, _, _, kFile, _)) |
| .Times(AnyNumber()); |
| |
| // This should trigger a remote sync. |
| mock_remote_service()->NotifyRemoteChangeQueueUpdated(1); |
| |
| // Start a local operation on the same file (to make it BUSY). |
| base::RunLoop verify_file_error_run_loop; |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&CannedSyncableFileSystem::DoCreateFile, |
| base::Unretained(file_system_.get()), |
| kFile, base::Bind(&VerifyFileError, |
| &verify_file_error_run_loop))); |
| |
| run_loop.Run(); |
| |
| mock_remote_service()->NotifyRemoteChangeQueueUpdated(0); |
| |
| verify_file_error_run_loop.Run(); |
| } |
| |
| #if defined(THREAD_SANITIZER) |
| // SyncFileSystemServiceTest.GetFileSyncStatus fails under ThreadSanitizer, |
| // see http://crbug.com/294904. |
| #define MAYBE_GetFileSyncStatus DISABLED_GetFileSyncStatus |
| #else |
| #define MAYBE_GetFileSyncStatus GetFileSyncStatus |
| #endif |
| TEST_F(SyncFileSystemServiceTest, MAYBE_GetFileSyncStatus) { |
| InitializeApp(); |
| |
| const FileSystemURL kFile(file_system_->URL("foo")); |
| |
| SyncStatusCode status; |
| SyncFileStatus sync_file_status; |
| |
| // 1. The file is not in conflicting nor in pending change state. |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_remote_service(), IsConflicting(kFile)) |
| .WillOnce(Return(false)); |
| |
| status = SYNC_STATUS_UNKNOWN; |
| sync_file_status = SYNC_FILE_STATUS_UNKNOWN; |
| sync_service_->GetFileSyncStatus( |
| kFile, |
| base::Bind(&AssignValueAndQuit<SyncFileStatus>, |
| &run_loop, &status, &sync_file_status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(SYNC_STATUS_OK, status); |
| EXPECT_EQ(SYNC_FILE_STATUS_SYNCED, sync_file_status); |
| } |
| |
| // 2. Conflicting case. |
| { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_remote_service(), IsConflicting(kFile)) |
| .WillOnce(Return(true)); |
| |
| status = SYNC_STATUS_UNKNOWN; |
| sync_file_status = SYNC_FILE_STATUS_UNKNOWN; |
| sync_service_->GetFileSyncStatus( |
| kFile, |
| base::Bind(&AssignValueAndQuit<SyncFileStatus>, |
| &run_loop, &status, &sync_file_status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(SYNC_STATUS_OK, status); |
| EXPECT_EQ(SYNC_FILE_STATUS_CONFLICTING, sync_file_status); |
| } |
| |
| // 3. The file has pending local changes. |
| { |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_->CreateFile(kFile)); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_remote_service(), IsConflicting(kFile)) |
| .WillOnce(Return(false)); |
| |
| status = SYNC_STATUS_UNKNOWN; |
| sync_file_status = SYNC_FILE_STATUS_UNKNOWN; |
| sync_service_->GetFileSyncStatus( |
| kFile, |
| base::Bind(&AssignValueAndQuit<SyncFileStatus>, |
| &run_loop, &status, &sync_file_status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(SYNC_STATUS_OK, status); |
| EXPECT_EQ(SYNC_FILE_STATUS_HAS_PENDING_CHANGES, sync_file_status); |
| } |
| |
| // 4. The file has a conflict and pending local changes. In this case |
| // we return SYNC_FILE_STATUS_CONFLICTING. |
| { |
| EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_->TruncateFile(kFile, 1U)); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_remote_service(), IsConflicting(kFile)) |
| .WillOnce(Return(true)); |
| |
| status = SYNC_STATUS_UNKNOWN; |
| sync_file_status = SYNC_FILE_STATUS_UNKNOWN; |
| sync_service_->GetFileSyncStatus( |
| kFile, |
| base::Bind(&AssignValueAndQuit<SyncFileStatus>, |
| &run_loop, &status, &sync_file_status)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(SYNC_STATUS_OK, status); |
| EXPECT_EQ(SYNC_FILE_STATUS_CONFLICTING, sync_file_status); |
| } |
| } |
| |
| } // namespace sync_file_system |