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

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

DEFINITIONS

This source file includes following definitions.
  1. file_system_
  2. SetUp
  3. TearDown
  4. file_system_context
  5. change_tracker
  6. VerifyAndClearChange
  7. DropChangesInTracker
  8. RestoreChangesFromTrackerDB
  9. GetAllChangedURLs
  10. TEST_F
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. 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 "chrome/browser/sync_file_system/local/local_file_change_tracker.h"

#include <deque>
#include <set>

#include "base/basictypes.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/stl_util.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/sync_file_system_backend.h"
#include "chrome/browser/sync_file_system/sync_status_code.h"
#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
#include "content/public/test/mock_blob_url_request_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"
#include "webkit/browser/fileapi/file_system_context.h"
#include "webkit/browser/quota/quota_manager.h"

using fileapi::FileSystemContext;
using fileapi::FileSystemURL;
using fileapi::FileSystemURLSet;
using content::MockBlobURLRequestContext;
using content::ScopedTextBlob;

namespace sync_file_system {

class LocalFileChangeTrackerTest : public testing::Test {
 public:
  LocalFileChangeTrackerTest()
      : in_memory_env_(leveldb::NewMemEnv(leveldb::Env::Default())),
        file_system_(GURL("http://example.com"),
                     in_memory_env_.get(),
                     base::MessageLoopProxy::current().get(),
                     base::MessageLoopProxy::current().get()) {}

  virtual void SetUp() OVERRIDE {
    file_system_.SetUp(CannedSyncableFileSystem::QUOTA_ENABLED);

    sync_context_ =
        new LocalFileSyncContext(base::FilePath(),
                                 in_memory_env_.get(),
                                 base::MessageLoopProxy::current().get(),
                                 base::MessageLoopProxy::current().get());
    ASSERT_EQ(
        sync_file_system::SYNC_STATUS_OK,
        file_system_.MaybeInitializeFileSystemContext(sync_context_.get()));
  }

  virtual void TearDown() OVERRIDE {
    if (sync_context_.get())
      sync_context_->ShutdownOnUIThread();
    sync_context_ = NULL;

    message_loop_.RunUntilIdle();
    file_system_.TearDown();
    // Make sure we don't leave the external filesystem.
    // (CannedSyncableFileSystem::TearDown does not do this as there may be
    // multiple syncable file systems registered for the name)
    RevokeSyncableFileSystem();
  }

 protected:
  FileSystemURL URL(const std::string& path) {
    return file_system_.URL(path);
  }

  FileSystemContext* file_system_context() {
    return file_system_.file_system_context();
  }

  LocalFileChangeTracker* change_tracker() {
    return file_system_.backend()->change_tracker();
  }

  void VerifyAndClearChange(const FileSystemURL& url,
                            const FileChange& expected_change) {
    SCOPED_TRACE(testing::Message() << url.DebugString() <<
                 " expecting:" << expected_change.DebugString());
    // Get the changes for URL and verify.
    FileChangeList changes;
    change_tracker()->GetChangesForURL(url, &changes);
    ASSERT_EQ(1U, changes.size());
    SCOPED_TRACE(testing::Message() << url.DebugString() <<
                 " actual:" << changes.DebugString());
    EXPECT_EQ(expected_change, changes.list()[0]);

    // Clear the URL from the change tracker.
    change_tracker()->ClearChangesForURL(url);
  }

  void DropChangesInTracker() {
    change_tracker()->DropAllChanges();
  }

  void RestoreChangesFromTrackerDB() {
    change_tracker()->CollectLastDirtyChanges(file_system_context());
  }

  void GetAllChangedURLs(fileapi::FileSystemURLSet* urls) {
    change_tracker()->GetAllChangedURLs(urls);
  }

  ScopedEnableSyncFSDirectoryOperation enable_directory_operation_;
  base::MessageLoopForIO message_loop_;
  scoped_ptr<leveldb::Env> in_memory_env_;
  CannedSyncableFileSystem file_system_;

 private:
  scoped_refptr<LocalFileSyncContext> sync_context_;

  DISALLOW_COPY_AND_ASSIGN(LocalFileChangeTrackerTest);
};

TEST_F(LocalFileChangeTrackerTest, GetChanges) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  // Test URLs (no parent/child relationships, as we test such cases
  // mainly in LocalFileSyncStatusTest).
  const char kPath0[] = "test/dir a/dir";
  const char kPath1[] = "test/dir b";
  const char kPath2[] = "test/foo.txt";
  const char kPath3[] = "test/bar";
  const char kPath4[] = "temporary/dir a";
  const char kPath5[] = "temporary/foo";

  change_tracker()->OnCreateDirectory(URL(kPath0));
  change_tracker()->OnRemoveDirectory(URL(kPath0));  // Offset the create.
  change_tracker()->OnRemoveDirectory(URL(kPath1));
  change_tracker()->OnCreateDirectory(URL(kPath2));
  change_tracker()->OnRemoveFile(URL(kPath3));
  change_tracker()->OnModifyFile(URL(kPath4));
  change_tracker()->OnCreateFile(URL(kPath5));
  change_tracker()->OnRemoveFile(URL(kPath5));  // Recorded as 'delete'.

  FileSystemURLSet urls;
  file_system_.GetChangedURLsInTracker(&urls);

  EXPECT_EQ(5U, urls.size());
  EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath3)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath4)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath5)));

  // Changes for kPath0 must have been offset and removed.
  EXPECT_FALSE(ContainsKey(urls, URL(kPath0)));

  // GetNextChangedURLs only returns up to max_urls (i.e. 3) urls.
  std::deque<FileSystemURL> urls_to_process;
  change_tracker()->GetNextChangedURLs(&urls_to_process, 3);
  ASSERT_EQ(3U, urls_to_process.size());

  // Let it return all.
  urls_to_process.clear();
  change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
  ASSERT_EQ(5U, urls_to_process.size());

  // The changes must be in the last-modified-time order.
  EXPECT_EQ(URL(kPath1), urls_to_process[0]);
  EXPECT_EQ(URL(kPath2), urls_to_process[1]);
  EXPECT_EQ(URL(kPath3), urls_to_process[2]);
  EXPECT_EQ(URL(kPath4), urls_to_process[3]);
  EXPECT_EQ(URL(kPath5), urls_to_process[4]);

  // Modify kPath4 again.
  change_tracker()->OnModifyFile(URL(kPath4));

  // Now the order must be changed.
  urls_to_process.clear();
  change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
  ASSERT_EQ(5U, urls_to_process.size());
  EXPECT_EQ(URL(kPath1), urls_to_process[0]);
  EXPECT_EQ(URL(kPath2), urls_to_process[1]);
  EXPECT_EQ(URL(kPath3), urls_to_process[2]);
  EXPECT_EQ(URL(kPath5), urls_to_process[3]);
  EXPECT_EQ(URL(kPath4), urls_to_process[4]);

  // No changes to promote yet, we've demoted no changes.
  EXPECT_FALSE(change_tracker()->PromoteDemotedChanges());

  // Demote changes for kPath1 and kPath3.
  change_tracker()->DemoteChangesForURL(URL(kPath1));
  change_tracker()->DemoteChangesForURL(URL(kPath3));

  // Now we'll get no changes for kPath1 and kPath3 (it's in a separate queue).
  urls_to_process.clear();
  change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
  ASSERT_EQ(3U, urls_to_process.size());
  EXPECT_EQ(URL(kPath2), urls_to_process[0]);
  EXPECT_EQ(URL(kPath5), urls_to_process[1]);
  EXPECT_EQ(URL(kPath4), urls_to_process[2]);

  // Promote changes.
  EXPECT_TRUE(change_tracker()->PromoteDemotedChanges());

  // Now we should have kPath1 and kPath3.
  urls_to_process.clear();
  change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
  ASSERT_EQ(5U, urls_to_process.size());
  EXPECT_EQ(URL(kPath2), urls_to_process[0]);
  EXPECT_EQ(URL(kPath5), urls_to_process[1]);
  EXPECT_EQ(URL(kPath4), urls_to_process[2]);
  EXPECT_TRUE(URL(kPath1) == urls_to_process[3] ||
              URL(kPath1) == urls_to_process[4]);
  EXPECT_TRUE(URL(kPath3) == urls_to_process[3] ||
              URL(kPath3) == urls_to_process[4]);

  // No changes to promote any more.
  EXPECT_FALSE(change_tracker()->PromoteDemotedChanges());


  VerifyAndClearChange(URL(kPath1),
               FileChange(FileChange::FILE_CHANGE_DELETE,
                          sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath2),
               FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                          sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath3),
               FileChange(FileChange::FILE_CHANGE_DELETE,
                          sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath4),
               FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                          sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath5),
               FileChange(FileChange::FILE_CHANGE_DELETE,
                          sync_file_system::SYNC_FILE_TYPE_FILE));
}

TEST_F(LocalFileChangeTrackerTest, RestoreCreateAndModifyChanges) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  FileSystemURLSet urls;

  const char kPath0[] = "file a";
  const char kPath1[] = "dir a";
  const char kPath2[] = "dir a/dir";
  const char kPath3[] = "dir a/file a";
  const char kPath4[] = "dir a/file b";

  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  const std::string kData("Lorem ipsum.");
  MockBlobURLRequestContext url_request_context(file_system_context());
  ScopedTextBlob blob(url_request_context, "blob_id:test", kData);

  // Create files and nested directories.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath0)));       // Creates a file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath1)));  // Creates a dir.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath2)));  // Creates another dir.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath3)));       // Creates a file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.TruncateFile(URL(kPath3), 1));  // Modifies the file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath4)));    // Creates another file.
  EXPECT_EQ(static_cast<int64>(kData.size()),         // Modifies the file.
            file_system_.Write(&url_request_context,
                               URL(kPath4), blob.GetBlobDataHandle()));

  // Verify the changes.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());

  // Reset the changes in in-memory tracker.
  DropChangesInTracker();

  // Make sure we have no in-memory changes in the tracker.
  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  RestoreChangesFromTrackerDB();

  // Make sure the changes are restored from the DB.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());

  VerifyAndClearChange(URL(kPath0),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath1),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath2),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath3),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath4),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
}

TEST_F(LocalFileChangeTrackerTest, RestoreRemoveChanges) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  FileSystemURLSet urls;

  const char kPath0[] = "file";
  const char kPath1[] = "dir a";
  const char kPath2[] = "dir b";
  const char kPath3[] = "dir b/file";
  const char kPath4[] = "dir b/dir c";
  const char kPath5[] = "dir b/dir c/file";

  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  // Creates and removes a same file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath0)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Remove(URL(kPath0), false /* recursive */));

  // Creates and removes a same directory.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath1)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Remove(URL(kPath1), false /* recursive */));

  // Creates files and nested directories, then removes the parent directory.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath2)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath3)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath4)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath5)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Remove(URL(kPath2), true /* recursive */));

  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(3U, urls.size());

  DropChangesInTracker();

  // Make sure we have no in-memory changes in the tracker.
  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  RestoreChangesFromTrackerDB();

  // Make sure the changes are restored from the DB.
  file_system_.GetChangedURLsInTracker(&urls);
  // Since directories to have been reverted (kPath1, kPath2, kPath4) are
  // treated as FILE_CHANGE_DELETE, the number of changes should be 6.
  EXPECT_EQ(6U, urls.size());

  VerifyAndClearChange(URL(kPath0),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath1),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath2),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath3),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath4),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath5),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
}

TEST_F(LocalFileChangeTrackerTest, RestoreCopyChanges) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  FileSystemURLSet urls;

  const char kPath0[] = "file a";
  const char kPath1[] = "dir a";
  const char kPath2[] = "dir a/dir";
  const char kPath3[] = "dir a/file a";
  const char kPath4[] = "dir a/file b";

  const char kPath0Copy[] = "file b";      // To be copied from kPath0
  const char kPath1Copy[] = "dir b";       // To be copied from kPath1
  const char kPath2Copy[] = "dir b/dir";   // To be copied from kPath2
  const char kPath3Copy[] = "dir b/file a";  // To be copied from kPath3
  const char kPath4Copy[] = "dir b/file b";  // To be copied from kPath4

  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  const std::string kData("Lorem ipsum.");
  MockBlobURLRequestContext url_request_context(file_system_context());
  ScopedTextBlob blob(url_request_context, "blob_id:test", kData);

  // Create files and nested directories.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath0)));       // Creates a file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath1)));  // Creates a dir.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath2)));  // Creates another dir.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath3)));       // Creates a file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.TruncateFile(URL(kPath3), 1));  // Modifies the file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath4)));    // Creates another file.
  EXPECT_EQ(static_cast<int64>(kData.size()),
            file_system_.Write(&url_request_context,   // Modifies the file.
                               URL(kPath4), blob.GetBlobDataHandle()));

  // Verify we have 5 changes for preparation.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());
  change_tracker()->ClearChangesForURL(URL(kPath0));
  change_tracker()->ClearChangesForURL(URL(kPath1));
  change_tracker()->ClearChangesForURL(URL(kPath2));
  change_tracker()->ClearChangesForURL(URL(kPath3));
  change_tracker()->ClearChangesForURL(URL(kPath4));

  // Make sure we have no changes.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_TRUE(urls.empty());

  // Copy the file and the parent directory.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Copy(URL(kPath0), URL(kPath0Copy)));  // Copy the file.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Copy(URL(kPath1), URL(kPath1Copy)));  // Copy the dir.

  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());
  DropChangesInTracker();

  // Make sure we have no in-memory changes in the tracker.
  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  RestoreChangesFromTrackerDB();

  // Make sure the changes are restored from the DB.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());

  VerifyAndClearChange(URL(kPath0Copy),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath1Copy),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath2Copy),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath3Copy),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath4Copy),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
}

TEST_F(LocalFileChangeTrackerTest, RestoreMoveChanges) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  FileSystemURLSet urls;

  const char kPath0[] = "file a";
  const char kPath1[] = "dir a";
  const char kPath2[] = "dir a/file";
  const char kPath3[] = "dir a/dir";
  const char kPath4[] = "dir a/dir/file";

  const char kPath5[] = "file b";          // To be moved from kPath0.
  const char kPath6[] = "dir b";           // To be moved from kPath1.
  const char kPath7[] = "dir b/file";      // To be moved from kPath2.
  const char kPath8[] = "dir b/dir";       // To be moved from kPath3.
  const char kPath9[] = "dir b/dir/file";  // To be moved from kPath4.

  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  // Create files and nested directories.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath0)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath1)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath2)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath3)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath4)));

  // Verify we have 5 changes for preparation.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(5U, urls.size());
  change_tracker()->ClearChangesForURL(URL(kPath0));
  change_tracker()->ClearChangesForURL(URL(kPath1));
  change_tracker()->ClearChangesForURL(URL(kPath2));
  change_tracker()->ClearChangesForURL(URL(kPath3));
  change_tracker()->ClearChangesForURL(URL(kPath4));

  // Make sure we have no changes.
  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_TRUE(urls.empty());

  // Move the file and the parent directory.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Move(URL(kPath0), URL(kPath5)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Move(URL(kPath1), URL(kPath6)));

  file_system_.GetChangedURLsInTracker(&urls);
  EXPECT_EQ(10U, urls.size());

  DropChangesInTracker();

  // Make sure we have no in-memory changes in the tracker.
  file_system_.GetChangedURLsInTracker(&urls);
  ASSERT_EQ(0U, urls.size());

  RestoreChangesFromTrackerDB();

  // Make sure the changes are restored from the DB.
  file_system_.GetChangedURLsInTracker(&urls);
  // Deletion for child files in the deleted directory cannot be restored,
  // so we will only have 8 changes.
  EXPECT_EQ(8U, urls.size());

  VerifyAndClearChange(URL(kPath0),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath1),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath3),
                       FileChange(FileChange::FILE_CHANGE_DELETE,
                                  sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
  VerifyAndClearChange(URL(kPath5),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath6),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath7),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
  VerifyAndClearChange(URL(kPath8),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
  VerifyAndClearChange(URL(kPath9),
                       FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                                  sync_file_system::SYNC_FILE_TYPE_FILE));
}

TEST_F(LocalFileChangeTrackerTest, NextChangedURLsWithRecursiveCopy) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  FileSystemURLSet urls;

  const char kPath0[] = "dir a";
  const char kPath1[] = "dir a/file";
  const char kPath2[] = "dir a/dir";

  const char kPath0Copy[] = "dir b";
  const char kPath1Copy[] = "dir b/file";
  const char kPath2Copy[] = "dir b/dir";

  // Creates kPath0,1,2 and then copies them all.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath0)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath1)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath2)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Copy(URL(kPath0), URL(kPath0Copy)));

  std::deque<FileSystemURL> urls_to_process;
  change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
  ASSERT_EQ(6U, urls_to_process.size());

  // Creation must have occured first.
  EXPECT_EQ(URL(kPath0), urls_to_process[0]);
  EXPECT_EQ(URL(kPath1), urls_to_process[1]);
  EXPECT_EQ(URL(kPath2), urls_to_process[2]);

  // Then recursive copy took place. The exact order cannot be determined
  // but the parent directory must have been created first.
  EXPECT_EQ(URL(kPath0Copy), urls_to_process[3]);
  EXPECT_TRUE(URL(kPath1Copy) == urls_to_process[4] ||
              URL(kPath2Copy) == urls_to_process[4]);
  EXPECT_TRUE(URL(kPath1Copy) == urls_to_process[5] ||
              URL(kPath2Copy) == urls_to_process[5]);
}

TEST_F(LocalFileChangeTrackerTest, NextChangedURLsWithRecursiveRemove) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  const char kPath0[] = "dir a";
  const char kPath1[] = "dir a/file1";
  const char kPath2[] = "dir a/file2";

  // Creates kPath0,1,2 and then removes them all.
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath0)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath1)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath2)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.Remove(URL(kPath0), true /* recursive */));

  FileSystemURLSet urls;
  GetAllChangedURLs(&urls);

  // This is actually not really desirable, but since the directory
  // creation and deletion have been offset now we only have two
  // file deletion changes.
  //
  // NOTE: This will cause 2 local sync for deleting nonexistent files
  // on the remote side.
  //
  // TODO(kinuko): For micro optimization we could probably restore the ADD
  // change type (other than ADD_OR_UPDATE) and offset file ADD+DELETE
  // changes too.
  ASSERT_EQ(2U, urls.size());

  // The exact order of recursive removal cannot be determined.
  EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
}

TEST_F(LocalFileChangeTrackerTest, ResetForFileSystem) {
  EXPECT_EQ(base::File::FILE_OK, file_system_.OpenFileSystem());

  const char kPath0[] = "dir a";
  const char kPath1[] = "dir a/file";
  const char kPath2[] = "dir a/subdir";
  const char kPath3[] = "dir b";

  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath0)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateFile(URL(kPath1)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath2)));
  EXPECT_EQ(base::File::FILE_OK,
            file_system_.CreateDirectory(URL(kPath3)));

  FileSystemURLSet urls;
  GetAllChangedURLs(&urls);
  EXPECT_EQ(4u, urls.size());
  EXPECT_TRUE(ContainsKey(urls, URL(kPath0)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
  EXPECT_TRUE(ContainsKey(urls, URL(kPath3)));

  // Reset all changes for the file system.
  change_tracker()->ResetForFileSystem(
      file_system_.origin(), file_system_.type());

  GetAllChangedURLs(&urls);
  EXPECT_TRUE(urls.empty());

  // Make sure they're gone from the database too.
  DropChangesInTracker();
  RestoreChangesFromTrackerDB();

  GetAllChangedURLs(&urls);
  EXPECT_TRUE(urls.empty());
}

}  // namespace sync_file_system

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