| // 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/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/thread_task_runner_handle.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 "storage/browser/fileapi/file_system_context.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" |
| #include "third_party/leveldatabase/src/include/leveldb/env.h" |
| |
| using content::BrowserThread; |
| using storage::FileSystemURL; |
| using storage::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::File::Error error) { |
| DCHECK(run_loop); |
| EXPECT_EQ(base::File::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_METHOD5(OnFileSynced, |
| void(const storage::FileSystemURL& url, |
| SyncFileType file_type, |
| 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::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(arg1, operation_status)); |
| } |
| |
| ACTION_P(RecordState, states) { |
| states->push_back(arg1); |
| } |
| |
| ACTION_P(MockStatusCallback, status) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(arg4, status)); |
| } |
| |
| ACTION_P2(MockSyncFileCallback, status, url) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(arg0, status, url)); |
| } |
| |
| ACTION(InvokeCompletionClosure) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(arg0)); |
| } |
| |
| class SyncFileSystemServiceTest : public testing::Test { |
| protected: |
| SyncFileSystemServiceTest() |
| : thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD | |
| content::TestBrowserThreadBundle::REAL_IO_THREAD) {} |
| |
| void SetUp() override { |
| in_memory_env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); |
| file_system_.reset(new CannedSyncableFileSystem( |
| GURL(kOrigin), |
| in_memory_env_.get(), |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO), |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE))); |
| |
| scoped_ptr<LocalFileSyncService> local_service = |
| LocalFileSyncService::CreateForTesting(&profile_, in_memory_env_.get()); |
| remote_service_ = new StrictMock<MockRemoteFileSyncService>; |
| sync_service_.reset(new SyncFileSystemService(&profile_)); |
| |
| EXPECT_CALL(*mock_remote_service(), |
| AddServiceObserver(_)).Times(1); |
| EXPECT_CALL(*mock_remote_service(), |
| AddFileStatusObserver(sync_service_.get())).Times(1); |
| EXPECT_CALL(*mock_remote_service(), |
| GetLocalChangeProcessor()) |
| .WillRepeatedly(Return(&local_change_processor_)); |
| EXPECT_CALL(*mock_remote_service(), |
| SetRemoteChangeProcessor(local_service.get())).Times(1); |
| |
| sync_service_->Initialize( |
| local_service.Pass(), |
| 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(CannedSyncableFileSystem::QUOTA_ENABLED); |
| } |
| |
| 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::File::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); |
| } |
| |
| content::TestBrowserThreadBundle thread_bundle_; |
| scoped_ptr<leveldb::Env> in_memory_env_; |
| TestingProfile profile_; |
| scoped_ptr<CannedSyncableFileSystem> file_system_; |
| |
| // Their ownerships are transferred to SyncFileSystemService. |
| 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); |
| } |
| |
| TEST_F(SyncFileSystemServiceTest, 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 as in: |
| // 1. OnWriteEnabled when PrepareForSync(SYNC_SHARED) is finished and |
| // the target file is unlocked for writing |
| // 2. OnSyncEnabled x 3 times; 1) when CreateFile is finished, 2) when |
| // file is unlocked after PrepareForSync, and 3) when the sync is |
| // finished. |
| EXPECT_CALL(status_observer, OnWriteEnabled(kFile)) |
| .Times(AtLeast(1)); |
| |
| { |
| ::testing::InSequence sequence; |
| EXPECT_CALL(status_observer, OnSyncEnabled(kFile)) |
| .Times(AtLeast(2)); |
| EXPECT_CALL(status_observer, OnSyncEnabled(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_CALL(*mock_remote_service(), ProcessRemoteChange(_)) |
| .WillRepeatedly(MockSyncFileCallback(SYNC_STATUS_NO_CHANGE_TO_SYNC, |
| FileSystemURL())); |
| |
| EXPECT_CALL(*mock_remote_service(), PromoteDemotedChanges(_)) |
| .WillRepeatedly(InvokeCompletionClosure()); |
| |
| EXPECT_EQ(base::File::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)); |
| } |
| |
| EXPECT_CALL(*mock_remote_service(), PromoteDemotedChanges(_)) |
| .WillRepeatedly(InvokeCompletionClosure()); |
| |
| // 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 synced state. |
| { |
| base::RunLoop run_loop; |
| 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. The file has pending local changes. |
| { |
| base::RunLoop run_loop; |
| EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile)); |
| |
| 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); |
| } |
| } |
| |
| } // namespace sync_file_system |