blob: b209b194e9ff60686310ac691c718c1580d52a8e [file] [log] [blame]
// 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 "chrome/browser/chromeos/drive/sync_client.h"
#include <vector>
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/test/test_timeouts.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/file_cache.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/mock_drive_file_system.h"
#include "chrome/browser/chromeos/drive/test_util.h"
#include "chrome/browser/google_apis/test_util.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::StrictMock;
using ::testing::_;
namespace drive {
namespace {
// Action used to set mock expectations for GetFileByResourceId().
ACTION_P4(MockGetFileByResourceId, error, local_path, mime_type, file_type) {
arg2.Run(error, local_path, mime_type, file_type);
}
// Action used to set mock expectations for UpdateFileByResourceId().
ACTION_P(MockUpdateFileByResourceId, error) {
arg2.Run(error);
}
// Action used to set mock expectations for GetFileInfoByResourceId().
ACTION_P2(MockUpdateFileByResourceId, error, md5) {
scoped_ptr<DriveEntryProto> entry_proto(new DriveEntryProto);
entry_proto->mutable_file_specific_info()->set_file_md5(md5);
arg1.Run(error, base::FilePath(), entry_proto.Pass());
}
} // namespace
class SyncClientTest : public testing::Test {
public:
SyncClientTest()
: ui_thread_(content::BrowserThread::UI, &message_loop_),
mock_file_system_(new StrictMock<MockDriveFileSystem>) {
}
virtual void SetUp() OVERRIDE {
// Create a temporary directory.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Initialize the cache.
scoped_refptr<base::SequencedWorkerPool> pool =
content::BrowserThread::GetBlockingPool();
cache_.reset(new FileCache(
temp_dir_.path(),
pool->GetSequencedTaskRunner(pool->GetSequenceToken()),
NULL /* free_disk_space_getter */));
bool success = false;
cache_->RequestInitialize(
google_apis::test_util::CreateCopyResultCallback(&success));
google_apis::test_util::RunBlockingPoolTask();
ASSERT_TRUE(success);
SetUpCache();
// Initialize the sync client.
EXPECT_CALL(*mock_file_system_, AddObserver(_)).Times(1);
EXPECT_CALL(*mock_file_system_, RemoveObserver(_)).Times(1);
sync_client_.reset(new SyncClient(mock_file_system_.get(), cache_.get()));
// Disable delaying so that DoSyncLoop() starts immediately.
sync_client_->set_delay_for_testing(base::TimeDelta::FromSeconds(0));
}
virtual void TearDown() OVERRIDE {
sync_client_.reset();
cache_.reset();
}
// Sets up cache for tests.
void SetUpCache() {
// Prepare a temp file.
base::FilePath temp_file;
EXPECT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
&temp_file));
const std::string content = "hello";
EXPECT_EQ(static_cast<int>(content.size()),
file_util::WriteFile(temp_file, content.data(), content.size()));
// Prepare 3 pinned-but-not-present files.
FileError error = FILE_ERROR_OK;
cache_->Pin("resource_id_not_fetched_foo", "",
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->Pin("resource_id_not_fetched_bar", "",
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->Pin("resource_id_not_fetched_baz", "",
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
// Prepare a pinned-and-fetched file.
const std::string resource_id_fetched = "resource_id_fetched";
const std::string md5_fetched = "md5";
cache_->Store(resource_id_fetched, md5_fetched, temp_file,
FileCache::FILE_OPERATION_COPY,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->Pin(resource_id_fetched, md5_fetched,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
// Prepare a pinned-and-fetched-and-dirty file.
const std::string resource_id_dirty = "resource_id_dirty";
const std::string md5_dirty = ""; // Don't care.
cache_->Store(resource_id_dirty, md5_dirty, temp_file,
FileCache::FILE_OPERATION_COPY,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->Pin(resource_id_dirty, md5_dirty,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->MarkDirty(
resource_id_dirty, md5_dirty,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
cache_->CommitDirty(
resource_id_dirty, md5_dirty,
google_apis::test_util::CreateCopyResultCallback(&error));
google_apis::test_util::RunBlockingPoolTask();
EXPECT_EQ(FILE_ERROR_OK, error);
}
// Sets the expectation for MockDriveFileSystem::GetFileByResourceId(),
// that simulates successful retrieval of a file for the given resource ID.
void SetExpectationForGetFileByResourceId(const std::string& resource_id) {
EXPECT_CALL(*mock_file_system_,
GetFileByResourceId(resource_id, _, _, _))
.WillOnce(
MockGetFileByResourceId(
FILE_ERROR_OK,
base::FilePath::FromUTF8Unsafe("local_path_does_not_matter"),
std::string("mime_type_does_not_matter"),
REGULAR_FILE));
}
// Sets the expectation for MockDriveFileSystem::UpdateFileByResourceId(),
// that simulates successful uploading of a file for the given resource ID.
void SetExpectationForUpdateFileByResourceId(
const std::string& resource_id) {
EXPECT_CALL(*mock_file_system_,
UpdateFileByResourceId(resource_id, _, _))
.WillOnce(MockUpdateFileByResourceId(FILE_ERROR_OK));
}
// Sets the expectation for MockDriveFileSystem::GetFileInfoByResourceId(),
// that simulates successful retrieval of file info for the given resource
// ID.
//
// This is used for testing StartCheckingExistingPinnedFiles(), hence we
// are only interested in the MD5 value in DriveEntryProto.
void SetExpectationForGetFileInfoByResourceId(
const std::string& resource_id,
const std::string& new_md5) {
EXPECT_CALL(*mock_file_system_,
GetEntryInfoByResourceId(resource_id, _))
.WillOnce(MockUpdateFileByResourceId(
FILE_ERROR_OK,
new_md5));
}
// Returns the resource IDs in the queue to be fetched.
std::vector<std::string> GetResourceIdsToBeFetched() {
return sync_client_->GetResourceIdsForTesting(SyncClient::FETCH);
}
// Returns the resource IDs in the queue to be uploaded.
std::vector<std::string> GetResourceIdsToBeUploaded() {
return sync_client_->GetResourceIdsForTesting(SyncClient::UPLOAD);
}
// Adds a resource ID of a file to fetch.
void AddResourceIdToFetch(const std::string& resource_id) {
sync_client_->AddResourceIdForTesting(SyncClient::FETCH, resource_id);
}
// Adds a resource ID of a file to upload.
void AddResourceIdToUpload(const std::string& resource_id) {
sync_client_->AddResourceIdForTesting(SyncClient::UPLOAD, resource_id);
}
protected:
MessageLoopForUI message_loop_;
content::TestBrowserThread ui_thread_;
base::ScopedTempDir temp_dir_;
scoped_ptr<StrictMock<MockDriveFileSystem> > mock_file_system_;
scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
scoped_ptr<SyncClient> sync_client_;
};
TEST_F(SyncClientTest, StartInitialScan) {
// Start processing the files in the backlog. This will collect the
// resource IDs of these files.
sync_client_->StartProcessingBacklog();
// Check the contents of the queue for fetching.
SetExpectationForGetFileByResourceId("resource_id_not_fetched_bar");
SetExpectationForGetFileByResourceId("resource_id_not_fetched_baz");
SetExpectationForGetFileByResourceId("resource_id_not_fetched_foo");
// Check the contents of the queue for uploading.
SetExpectationForUpdateFileByResourceId("resource_id_dirty");
google_apis::test_util::RunBlockingPoolTask();
}
TEST_F(SyncClientTest, OnCachePinned) {
// This file will be fetched by GetFileByResourceId() as OnCachePinned()
// will kick off the sync loop.
SetExpectationForGetFileByResourceId("resource_id_not_fetched_foo");
sync_client_->OnCachePinned("resource_id_not_fetched_foo", "md5");
google_apis::test_util::RunBlockingPoolTask();
}
TEST_F(SyncClientTest, OnCacheUnpinned) {
AddResourceIdToFetch("resource_id_not_fetched_foo");
AddResourceIdToFetch("resource_id_not_fetched_bar");
AddResourceIdToFetch("resource_id_not_fetched_baz");
ASSERT_EQ(3U, GetResourceIdsToBeFetched().size());
sync_client_->OnCacheUnpinned("resource_id_not_fetched_foo", "md5");
sync_client_->OnCacheUnpinned("resource_id_not_fetched_baz", "md5");
// Only resource_id_not_fetched_foo should be fetched.
SetExpectationForGetFileByResourceId("resource_id_not_fetched_bar");
google_apis::test_util::RunBlockingPoolTask();
}
TEST_F(SyncClientTest, Deduplication) {
AddResourceIdToFetch("resource_id_not_fetched_foo");
// Set the delay so that DoSyncLoop() is delayed.
sync_client_->set_delay_for_testing(TestTimeouts::action_max_timeout());
// Raise OnCachePinned() event. This shouldn't result in adding the second
// task, as tasks are de-duplicated.
sync_client_->OnCachePinned("resource_id_not_fetched_foo", "md5");
ASSERT_EQ(1U, GetResourceIdsToBeFetched().size());
}
TEST_F(SyncClientTest, ExistingPinnedFiles) {
// Set the expectation so that the MockDriveFileSystem returns "new_md5"
// for "resource_id_fetched". This simulates that the file is updated on
// the server side, and the new MD5 is obtained from the server (i.e. the
// local cache file is stale).
SetExpectationForGetFileInfoByResourceId("resource_id_fetched",
"new_md5");
// Set the expectation so that the MockDriveFileSystem returns "some_md5"
// for "resource_id_dirty". The MD5 on the server is always different from
// the MD5 of a dirty file, which is set to "local". We should not collect
// this by StartCheckingExistingPinnedFiles().
SetExpectationForGetFileInfoByResourceId("resource_id_dirty",
"some_md5");
// Start checking the existing pinned files. This will collect the resource
// IDs of pinned files, with stale local cache files.
sync_client_->StartCheckingExistingPinnedFiles();
SetExpectationForGetFileByResourceId("resource_id_fetched");
google_apis::test_util::RunBlockingPoolTask();
}
} // namespace drive