root/chrome/browser/sync_file_system/sync_file_system_service_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. AssignValueAndQuit
  2. VerifyFileError
  3. ACTION_P3
  4. ACTION_P
  5. ACTION_P
  6. ACTION_P2
  7. SetUp
  8. TearDown
  9. InitializeApp
  10. InitializeAppForObserverTest
  11. mock_remote_service
  12. mock_local_change_processor
  13. EnableSync
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F

// 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 "third_party/leveldatabase/src/helpers/memenv/memenv.h"
#include "third_party/leveldatabase/src/include/leveldb/env.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::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_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 {
    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);
  }

  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::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);
  }

  ScopedEnableSyncFSDirectoryOperation enable_directory_operation_;

  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_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));
  }

  // 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::File::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::File::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

/* [<][>][^][v][top][bottom][index][help] */