root/chrome/browser/sync_file_system/local/local_file_sync_service_unittest.cc

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

DEFINITIONS

This source file includes following definitions.
  1. DidPrepareForProcessRemoteChange
  2. OnSyncCompleted
  3. OnGetFileMetadata
  4. ACTION_P
  5. ACTION_P2
  6. num_changes_
  7. SetUp
  8. TearDown
  9. OnLocalChangeAvailable
  10. PrepareForProcessRemoteChange
  11. ApplyRemoteChange
  12. GetNumChangesInTracker
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. NextOriginToProcess
  23. GetTotalChangeCount
  24. SetOriginChangeCount
  25. SetOriginEnabled
  26. TEST_F
  27. TEST_F

// Copyright 2013 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/file_util.h"
#include "base/location.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/threading/thread.h"
#include "chrome/browser/sync_file_system/file_change.h"
#include "chrome/browser/sync_file_system/local/canned_syncable_file_system.h"
#include "chrome/browser/sync_file_system/local/local_file_change_tracker.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/local_file_sync_status.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_local_change_processor.h"
#include "chrome/browser/sync_file_system/sync_file_metadata.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/gmock/include/gmock/gmock.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 ::testing::_;
using ::testing::AtLeast;
using ::testing::InvokeWithoutArgs;
using ::testing::StrictMock;

namespace sync_file_system {

namespace {

const char kOrigin[] = "http://example.com";

void DidPrepareForProcessRemoteChange(const tracked_objects::Location& where,
                                      const base::Closure& oncompleted,
                                      SyncStatusCode expected_status,
                                      const SyncFileMetadata& expected_metadata,
                                      SyncStatusCode status,
                                      const SyncFileMetadata& metadata,
                                      const FileChangeList& changes) {
  SCOPED_TRACE(testing::Message() << where.ToString());
  ASSERT_EQ(expected_status, status);
  ASSERT_EQ(expected_metadata.file_type, metadata.file_type);
  ASSERT_EQ(expected_metadata.size, metadata.size);
  ASSERT_TRUE(changes.empty());
  oncompleted.Run();
}

void OnSyncCompleted(const tracked_objects::Location& where,
                     const base::Closure& oncompleted,
                     SyncStatusCode expected_status,
                     const FileSystemURL& expected_url,
                     SyncStatusCode status,
                     const FileSystemURL& url) {
  SCOPED_TRACE(testing::Message() << where.ToString());
  ASSERT_EQ(expected_status, status);
  ASSERT_EQ(expected_url, url);
  oncompleted.Run();
}

void OnGetFileMetadata(const tracked_objects::Location& where,
                       const base::Closure& oncompleted,
                       SyncStatusCode* status_out,
                       SyncFileMetadata* metadata_out,
                       SyncStatusCode status,
                       const SyncFileMetadata& metadata) {
  SCOPED_TRACE(testing::Message() << where.ToString());
  *status_out = status;
  *metadata_out = metadata;
  oncompleted.Run();
}

ACTION_P(MockStatusCallback, status) {
  base::MessageLoopProxy::current()->PostTask(
      FROM_HERE, base::Bind(arg4, status));
}

ACTION_P2(MockStatusCallbackAndRecordChange, status, changes) {
  base::MessageLoopProxy::current()->PostTask(
      FROM_HERE, base::Bind(arg4, status));
  changes->push_back(arg0);
}

}  // namespace

class LocalFileSyncServiceTest
    : public testing::Test,
      public LocalFileSyncService::Observer {
 protected:
  LocalFileSyncServiceTest()
      : thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD |
                       content::TestBrowserThreadBundle::REAL_IO_THREAD),
        num_changes_(0) {}

  virtual void SetUp() OVERRIDE {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    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)));

    local_service_ = LocalFileSyncService::CreateForTesting(
        &profile_, in_memory_env_.get());

    file_system_->SetUp(CannedSyncableFileSystem::QUOTA_ENABLED);

    base::RunLoop run_loop;
    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
    local_service_->MaybeInitializeFileSystemContext(
        GURL(kOrigin), file_system_->file_system_context(),
        AssignAndQuitCallback(&run_loop, &status));
    run_loop.Run();

    local_service_->AddChangeObserver(this);

    EXPECT_EQ(base::File::FILE_OK, file_system_->OpenFileSystem());

    file_system_->backend()->sync_context()->
        set_mock_notify_changes_duration_in_sec(0);
  }

  virtual void TearDown() OVERRIDE {
    local_service_->Shutdown();
    file_system_->TearDown();
    RevokeSyncableFileSystem();
    content::RunAllPendingInMessageLoop(BrowserThread::FILE);
    content::RunAllPendingInMessageLoop(BrowserThread::IO);
  }

  // LocalChangeObserver overrides.
  virtual void OnLocalChangeAvailable(int64 num_changes) OVERRIDE {
    num_changes_ = num_changes;
  }

  void PrepareForProcessRemoteChange(
      const FileSystemURL& url,
      const tracked_objects::Location& where,
      SyncStatusCode expected_status,
      const SyncFileMetadata& expected_metadata) {
    base::RunLoop run_loop;
    local_service_->PrepareForProcessRemoteChange(
        url,
        base::Bind(&DidPrepareForProcessRemoteChange,
                   where,
                   run_loop.QuitClosure(),
                   expected_status,
                   expected_metadata));
    run_loop.Run();
  }

  SyncStatusCode ApplyRemoteChange(const FileChange& change,
                                   const base::FilePath& local_path,
                                   const FileSystemURL& url) {
    SyncStatusCode sync_status = SYNC_STATUS_UNKNOWN;
    {
      base::RunLoop run_loop;
      local_service_->ApplyRemoteChange(
          change, local_path, url,
          AssignAndQuitCallback(&run_loop, &sync_status));
      run_loop.Run();
    }
    {
      base::RunLoop run_loop;
      local_service_->FinalizeRemoteSync(
          url,
          sync_status == SYNC_STATUS_OK,
          run_loop.QuitClosure());
      run_loop.Run();
    }
    return sync_status;
  }

  int64 GetNumChangesInTracker() const {
    return file_system_->backend()->change_tracker()->num_changes();
  }

  content::TestBrowserThreadBundle thread_bundle_;

  ScopedEnableSyncFSDirectoryOperation enable_directory_operation_;
  base::ScopedTempDir temp_dir_;
  scoped_ptr<leveldb::Env> in_memory_env_;
  TestingProfile profile_;

  scoped_ptr<CannedSyncableFileSystem> file_system_;
  scoped_ptr<LocalFileSyncService> local_service_;

  int64 num_changes_;
};

// More complete tests for PrepareForProcessRemoteChange and ApplyRemoteChange
// are also in content_unittest:LocalFileSyncContextTest.
TEST_F(LocalFileSyncServiceTest, RemoteSyncStepsSimple) {
  const FileSystemURL kFile(file_system_->URL("file"));
  const FileSystemURL kDir(file_system_->URL("dir"));
  const char kTestFileData[] = "0123456789";
  const int kTestFileDataSize = static_cast<int>(arraysize(kTestFileData) - 1);

  base::FilePath local_path;
  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &local_path));
  ASSERT_EQ(kTestFileDataSize,
            base::WriteFile(local_path, kTestFileData, kTestFileDataSize));

  // Run PrepareForProcessRemoteChange for kFile.
  SyncFileMetadata expected_metadata;
  expected_metadata.file_type = SYNC_FILE_TYPE_UNKNOWN;
  expected_metadata.size = 0;
  PrepareForProcessRemoteChange(kFile, FROM_HERE,
                                SYNC_STATUS_OK,
                                expected_metadata);

  // Run ApplyRemoteChange for kFile.
  FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                    SYNC_FILE_TYPE_FILE);
  EXPECT_EQ(SYNC_STATUS_OK,
            ApplyRemoteChange(change, local_path, kFile));

  // Verify the file is synced.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_->VerifyFile(kFile, kTestFileData));

  // Run PrepareForProcessRemoteChange for kDir.
  PrepareForProcessRemoteChange(kDir, FROM_HERE,
                                SYNC_STATUS_OK,
                                expected_metadata);

  // Run ApplyRemoteChange for kDir.
  change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                      SYNC_FILE_TYPE_DIRECTORY);
  EXPECT_EQ(SYNC_STATUS_OK,
            ApplyRemoteChange(change, base::FilePath(), kDir));

  // Verify the directory.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_->DirectoryExists(kDir));

  // Run PrepareForProcessRemoteChange and ApplyRemoteChange for
  // kDir once again for deletion.
  expected_metadata.file_type = SYNC_FILE_TYPE_DIRECTORY;
  expected_metadata.size = 0;
  PrepareForProcessRemoteChange(kDir, FROM_HERE,
                                SYNC_STATUS_OK,
                                expected_metadata);

  change = FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_UNKNOWN);
  EXPECT_EQ(SYNC_STATUS_OK, ApplyRemoteChange(change, base::FilePath(), kDir));

  // Now the directory must have deleted.
  EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
            file_system_->DirectoryExists(kDir));
}

TEST_F(LocalFileSyncServiceTest, LocalChangeObserver) {
  const FileSystemURL kFile(file_system_->URL("file"));
  const FileSystemURL kDir(file_system_->URL("dir"));
  const char kTestFileData[] = "0123456789";
  const int kTestFileDataSize = static_cast<int>(arraysize(kTestFileData) - 1);

  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile));

  EXPECT_EQ(1, num_changes_);

  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateDirectory(kDir));
  EXPECT_EQ(kTestFileDataSize,
            file_system_->WriteString(kFile, kTestFileData));

  EXPECT_EQ(2, num_changes_);
}

#if defined(OS_WIN)
// Flaky: http://crbug.com/171487
#define MAYBE_LocalChangeObserverMultipleContexts\
    DISABLED_LocalChangeObserverMultipleContexts
#else
#define MAYBE_LocalChangeObserverMultipleContexts\
    LocalChangeObserverMultipleContexts
#endif

TEST_F(LocalFileSyncServiceTest, MAYBE_LocalChangeObserverMultipleContexts) {
  const char kOrigin2[] = "http://foo";
  CannedSyncableFileSystem file_system2(
      GURL(kOrigin2),
      in_memory_env_.get(),
      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO),
      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
  file_system2.SetUp(CannedSyncableFileSystem::QUOTA_ENABLED);

  base::RunLoop run_loop;
  SyncStatusCode status = SYNC_STATUS_UNKNOWN;
  local_service_->MaybeInitializeFileSystemContext(
      GURL(kOrigin2), file_system2.file_system_context(),
      AssignAndQuitCallback(&run_loop, &status));
  run_loop.Run();

  EXPECT_EQ(base::File::FILE_OK, file_system2.OpenFileSystem());
  file_system2.backend()->sync_context()->
      set_mock_notify_changes_duration_in_sec(0);

  const FileSystemURL kFile1(file_system_->URL("file1"));
  const FileSystemURL kFile2(file_system_->URL("file2"));
  const FileSystemURL kFile3(file_system2.URL("file3"));
  const FileSystemURL kFile4(file_system2.URL("file4"));

  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile1));
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile2));
  EXPECT_EQ(base::File::FILE_OK, file_system2.CreateFile(kFile3));
  EXPECT_EQ(base::File::FILE_OK, file_system2.CreateFile(kFile4));

  EXPECT_EQ(4, num_changes_);

  file_system2.TearDown();
}

TEST_F(LocalFileSyncServiceTest, ProcessLocalChange_CreateFile) {
  const FileSystemURL kFile(file_system_->URL("foo"));
  const char kTestFileData[] = "0123456789";
  const int kTestFileDataSize = static_cast<int>(arraysize(kTestFileData) - 1);

  base::RunLoop run_loop;

  // We should get called OnSyncEnabled and OnWriteEnabled on kFile.
  // (OnWriteEnabled is called because we release lock before returning
  // from ApplyLocalChange)
  StrictMock<MockSyncStatusObserver> status_observer;
  EXPECT_CALL(status_observer, OnSyncEnabled(kFile)).Times(AtLeast(1));
  EXPECT_CALL(status_observer, OnWriteEnabled(kFile)).Times(AtLeast(0));
  file_system_->AddSyncStatusObserver(&status_observer);

  // Creates and writes into a file.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile));
  EXPECT_EQ(kTestFileDataSize,
            file_system_->WriteString(kFile, std::string(kTestFileData)));

  // Retrieve the expected file info.
  base::File::Info info;
  base::FilePath platform_path;
  EXPECT_EQ(base::File::FILE_OK,
            file_system_->GetMetadataAndPlatformPath(
                kFile, &info, &platform_path));

  ASSERT_FALSE(info.is_directory);
  ASSERT_EQ(kTestFileDataSize, info.size);

  SyncFileMetadata metadata;
  metadata.file_type = SYNC_FILE_TYPE_FILE;
  metadata.size = info.size;
  metadata.last_modified = info.last_modified;

  // The local_change_processor's ApplyLocalChange should be called once
  // with ADD_OR_UPDATE change for TYPE_FILE.
  StrictMock<MockLocalChangeProcessor> local_change_processor;
  const FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                          SYNC_FILE_TYPE_FILE);
  EXPECT_CALL(local_change_processor,
              ApplyLocalChange(change, _, metadata, kFile, _))
      .WillOnce(MockStatusCallback(SYNC_STATUS_OK));

  local_service_->SetLocalChangeProcessor(&local_change_processor);
  local_service_->ProcessLocalChange(
      base::Bind(&OnSyncCompleted, FROM_HERE, run_loop.QuitClosure(),
                 SYNC_STATUS_OK, kFile));

  run_loop.Run();

  file_system_->RemoveSyncStatusObserver(&status_observer);

  EXPECT_EQ(0, GetNumChangesInTracker());
}

TEST_F(LocalFileSyncServiceTest, ProcessLocalChange_CreateAndRemoveFile) {
  const FileSystemURL kFile(file_system_->URL("foo"));

  base::RunLoop run_loop;

  // We should get called OnSyncEnabled and possibly OnWriteEnabled (depends
  // on timing) on kFile.
  StrictMock<MockSyncStatusObserver> status_observer;
  EXPECT_CALL(status_observer, OnSyncEnabled(kFile)).Times(AtLeast(1));
  EXPECT_CALL(status_observer, OnWriteEnabled(kFile)).Times(AtLeast(0));
  file_system_->AddSyncStatusObserver(&status_observer);

  // Creates and then deletes a file.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kFile));
  EXPECT_EQ(base::File::FILE_OK, file_system_->Remove(kFile, false));

  // The local_change_processor's ApplyLocalChange should be called once
  // with DELETE change for TYPE_FILE.
  // The file will NOT exist in the remote side and the processor might
  // return SYNC_FILE_ERROR_NOT_FOUND (as mocked).
  StrictMock<MockLocalChangeProcessor> local_change_processor;
  const FileChange change(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_FILE);
  EXPECT_CALL(local_change_processor, ApplyLocalChange(change, _, _, kFile, _))
      .WillOnce(MockStatusCallback(SYNC_FILE_ERROR_NOT_FOUND));

  // The sync should succeed anyway.
  local_service_->SetLocalChangeProcessor(&local_change_processor);
  local_service_->ProcessLocalChange(
      base::Bind(&OnSyncCompleted, FROM_HERE, run_loop.QuitClosure(),
                 SYNC_STATUS_OK, kFile));

  run_loop.Run();

  file_system_->RemoveSyncStatusObserver(&status_observer);

  EXPECT_EQ(0, GetNumChangesInTracker());
}

TEST_F(LocalFileSyncServiceTest, ProcessLocalChange_CreateAndRemoveDirectory) {
  const FileSystemURL kDir(file_system_->URL("foo"));

  base::RunLoop run_loop;

  // OnSyncEnabled is expected to be called at least or more than once.
  StrictMock<MockSyncStatusObserver> status_observer;
  EXPECT_CALL(status_observer, OnSyncEnabled(kDir)).Times(AtLeast(1));
  file_system_->AddSyncStatusObserver(&status_observer);

  // Creates and then deletes a directory.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateDirectory(kDir));
  EXPECT_EQ(base::File::FILE_OK, file_system_->Remove(kDir, false));

  // The local_change_processor's ApplyLocalChange should never be called.
  StrictMock<MockLocalChangeProcessor> local_change_processor;

  local_service_->SetLocalChangeProcessor(&local_change_processor);
  local_service_->ProcessLocalChange(
      base::Bind(&OnSyncCompleted, FROM_HERE, run_loop.QuitClosure(),
                 SYNC_STATUS_NO_CHANGE_TO_SYNC, FileSystemURL()));

  run_loop.Run();

  file_system_->RemoveSyncStatusObserver(&status_observer);

  EXPECT_EQ(0, GetNumChangesInTracker());
}

TEST_F(LocalFileSyncServiceTest, ProcessLocalChange_MultipleChanges) {
  const FileSystemURL kPath(file_system_->URL("foo"));
  const FileSystemURL kOther(file_system_->URL("bar"));

  base::RunLoop run_loop;

  // We should get called OnSyncEnabled and OnWriteEnabled on kPath and
  // OnSyncEnabled on kOther.
  StrictMock<MockSyncStatusObserver> status_observer;
  EXPECT_CALL(status_observer, OnSyncEnabled(kPath)).Times(AtLeast(1));
  EXPECT_CALL(status_observer, OnSyncEnabled(kOther)).Times(AtLeast(1));
  file_system_->AddSyncStatusObserver(&status_observer);

  // Creates a file, delete the file and creates a directory with the same
  // name.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kPath));
  EXPECT_EQ(base::File::FILE_OK, file_system_->Remove(kPath, false));
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateDirectory(kPath));

  // Creates one more file.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kOther));

  // The local_change_processor's ApplyLocalChange will be called
  // twice for FILE_TYPE and FILE_DIRECTORY.
  StrictMock<MockLocalChangeProcessor> local_change_processor;
  std::vector<FileChange> changes;
  EXPECT_CALL(local_change_processor, ApplyLocalChange(_, _, _, kPath, _))
      .Times(2)
      .WillOnce(MockStatusCallbackAndRecordChange(SYNC_STATUS_OK, &changes))
      .WillOnce(MockStatusCallbackAndRecordChange(SYNC_STATUS_OK, &changes));
  local_service_->SetLocalChangeProcessor(&local_change_processor);

  // OnWriteEnabled will be notified on kPath (in multi-threaded this
  // could be delayed, so AtLeast(0)).
  EXPECT_CALL(status_observer, OnWriteEnabled(kPath)).Times(AtLeast(0));

  local_service_->ProcessLocalChange(
      base::Bind(&OnSyncCompleted, FROM_HERE, run_loop.QuitClosure(),
                 SYNC_STATUS_OK, kPath));

  run_loop.Run();

  EXPECT_EQ(2U, changes.size());
  EXPECT_EQ(FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_FILE),
            changes[0]);
  EXPECT_EQ(FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                       SYNC_FILE_TYPE_DIRECTORY),
            changes[1]);

  file_system_->RemoveSyncStatusObserver(&status_observer);

  // We have one more change for kOther.
  EXPECT_EQ(1, GetNumChangesInTracker());
}

TEST_F(LocalFileSyncServiceTest, ProcessLocalChange_GetLocalMetadata) {
  const FileSystemURL kURL(file_system_->URL("foo"));
  const base::Time kTime = base::Time::FromDoubleT(333);
  const int kSize = 555;

  base::RunLoop run_loop;

  // Creates a file.
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kURL));
  EXPECT_EQ(base::File::FILE_OK, file_system_->TruncateFile(kURL, kSize));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_->TouchFile(kURL, base::Time(), kTime));

  SyncStatusCode status = SYNC_STATUS_UNKNOWN;
  SyncFileMetadata metadata;
  local_service_->GetLocalFileMetadata(
      kURL,
      base::Bind(&OnGetFileMetadata, FROM_HERE, run_loop.QuitClosure(),
                 &status, &metadata));

  run_loop.Run();

  EXPECT_EQ(SYNC_STATUS_OK, status);
  EXPECT_EQ(kTime, metadata.last_modified);
  EXPECT_EQ(kSize, metadata.size);
}

TEST_F(LocalFileSyncServiceTest, RecordFakeChange) {
  const FileSystemURL kURL(file_system_->URL("foo"));

  // Create a file and reset the changes (as preparation).
  EXPECT_EQ(base::File::FILE_OK, file_system_->CreateFile(kURL));
  file_system_->ClearChangeForURLInTracker(kURL);

  EXPECT_EQ(0, GetNumChangesInTracker());

  fileapi::FileSystemURLSet urlset;
  file_system_->GetChangedURLsInTracker(&urlset);
  EXPECT_TRUE(urlset.empty());

  const FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                          SYNC_FILE_TYPE_FILE);

  // Call RecordFakeLocalChange to add an ADD_OR_UPDATE change.
  {
    base::RunLoop run_loop;
    SyncStatusCode status = SYNC_STATUS_UNKNOWN;
    local_service_->RecordFakeLocalChange(
        kURL, change, AssignAndQuitCallback(&run_loop, &status));
    run_loop.Run();
    EXPECT_EQ(SYNC_STATUS_OK, status);
  }

  EXPECT_EQ(1, GetNumChangesInTracker());
  file_system_->GetChangedURLsInTracker(&urlset);
  EXPECT_EQ(1U, urlset.size());
  EXPECT_TRUE(urlset.find(kURL) != urlset.end());

  // Next local sync should pick up the recorded change.
  StrictMock<MockLocalChangeProcessor> local_change_processor;
  std::vector<FileChange> changes;
  EXPECT_CALL(local_change_processor, ApplyLocalChange(_, _, _, kURL, _))
      .WillOnce(MockStatusCallbackAndRecordChange(SYNC_STATUS_OK, &changes));
  {
    base::RunLoop run_loop;
    local_service_->SetLocalChangeProcessor(&local_change_processor);
    local_service_->ProcessLocalChange(
        base::Bind(&OnSyncCompleted, FROM_HERE, run_loop.QuitClosure(),
                   SYNC_STATUS_OK, kURL));
    run_loop.Run();
  }

  EXPECT_EQ(1U, changes.size());
  EXPECT_EQ(change, changes[0]);
}

// TODO(kinuko): Add tests for multiple file changes and multiple
// FileSystemContexts.

// Unit test for OriginChangeMap ---------------------------------------------

class OriginChangeMapTest : public testing::Test {
 protected:
  OriginChangeMapTest() {}
  virtual ~OriginChangeMapTest() {}

  bool NextOriginToProcess(GURL* origin) {
    return map_.NextOriginToProcess(origin);
  }

  int64 GetTotalChangeCount() const {
    return map_.GetTotalChangeCount();
  }

  void SetOriginChangeCount(const GURL& origin, int64 changes) {
    map_.SetOriginChangeCount(origin, changes);
  }

  void SetOriginEnabled(const GURL& origin, bool enabled) {
    map_.SetOriginEnabled(origin, enabled);
  }

  LocalFileSyncService::OriginChangeMap map_;
};

TEST_F(OriginChangeMapTest, Basic) {
  const GURL kOrigin1("chrome-extension://foo");
  const GURL kOrigin2("chrome-extension://bar");
  const GURL kOrigin3("chrome-extension://baz");

  ASSERT_EQ(0, GetTotalChangeCount());

  SetOriginChangeCount(kOrigin1, 1);
  SetOriginChangeCount(kOrigin2, 2);

  ASSERT_EQ(1 + 2, GetTotalChangeCount());

  SetOriginChangeCount(kOrigin3, 4);

  ASSERT_EQ(1 + 2 + 4, GetTotalChangeCount());

  const GURL kOrigins[] = { kOrigin1, kOrigin2, kOrigin3 };
  std::set<GURL> all_origins;
  all_origins.insert(kOrigins, kOrigins + ARRAYSIZE_UNSAFE(kOrigins));

  GURL origin;
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }

  // Set kOrigin2's change count 0.
  SetOriginChangeCount(kOrigin2, 0);
  ASSERT_EQ(1 + 4, GetTotalChangeCount());

  // kOrigin2 won't return this time.
  all_origins.insert(kOrigin1);
  all_origins.insert(kOrigin3);
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }

  // Calling NextOriginToProcess() again will just return
  // the same set of origins (as far as we don't change the
  // change count).
  all_origins.insert(kOrigin1);
  all_origins.insert(kOrigin3);
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }

  // Set kOrigin2's change count 8.
  SetOriginChangeCount(kOrigin2, 8);
  ASSERT_EQ(1 + 4 + 8, GetTotalChangeCount());

  all_origins.insert(kOrigins, kOrigins + ARRAYSIZE_UNSAFE(kOrigins));
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }
}

TEST_F(OriginChangeMapTest, WithDisabled) {
  const GURL kOrigin1("chrome-extension://foo");
  const GURL kOrigin2("chrome-extension://bar");
  const GURL kOrigin3("chrome-extension://baz");
  const GURL kOrigins[] = { kOrigin1, kOrigin2, kOrigin3 };

  ASSERT_EQ(0, GetTotalChangeCount());

  SetOriginChangeCount(kOrigin1, 1);
  SetOriginChangeCount(kOrigin2, 2);
  SetOriginChangeCount(kOrigin3, 4);

  ASSERT_EQ(1 + 2 + 4, GetTotalChangeCount());

  std::set<GURL> all_origins;
  all_origins.insert(kOrigins, kOrigins + ARRAYSIZE_UNSAFE(kOrigins));

  GURL origin;
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }

  SetOriginEnabled(kOrigin2, false);
  ASSERT_EQ(1 + 4, GetTotalChangeCount());

  // kOrigin2 won't return this time.
  all_origins.insert(kOrigin1);
  all_origins.insert(kOrigin3);
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }

  // kOrigin1 and kOrigin2 are now disabled.
  SetOriginEnabled(kOrigin1, false);
  ASSERT_EQ(4, GetTotalChangeCount());

  ASSERT_TRUE(NextOriginToProcess(&origin));
  ASSERT_EQ(kOrigin3, origin);

  // Re-enable kOrigin2.
  SetOriginEnabled(kOrigin2, true);
  ASSERT_EQ(2 + 4, GetTotalChangeCount());

  // kOrigin1 won't return this time.
  all_origins.insert(kOrigin2);
  all_origins.insert(kOrigin3);
  while (!all_origins.empty()) {
    ASSERT_TRUE(NextOriginToProcess(&origin));
    ASSERT_TRUE(ContainsKey(all_origins, origin));
    all_origins.erase(origin);
  }
}

}  // namespace sync_file_system

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