blob: 4c5bbe4019bf53c1fa86146bb47b6933819d00c2 [file] [log] [blame]
// 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/syncable_file_operation_runner.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/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_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_status.h"
#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
#include "chrome/browser/sync_file_system/local/syncable_file_system_operation.h"
#include "chrome/browser/sync_file_system/syncable_file_system_util.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 "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/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"
using storage::FileSystemOperation;
using storage::FileSystemURL;
using content::MockBlobURLRequestContext;
using content::ScopedTextBlob;
using base::File;
namespace sync_file_system {
namespace {
const std::string kParent = "foo";
const std::string kFile = "foo/file";
const std::string kDir = "foo/dir";
const std::string kChild = "foo/dir/bar";
const std::string kOther = "bar";
} // namespace
class SyncableFileOperationRunnerTest : public testing::Test {
protected:
typedef FileSystemOperation::StatusCallback StatusCallback;
// Use the current thread as IO thread so that we can directly call
// operations in the tests.
SyncableFileOperationRunnerTest()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
in_memory_env_(leveldb::NewMemEnv(leveldb::Env::Default())),
file_system_(GURL("http://example.com"),
in_memory_env_.get(),
base::ThreadTaskRunnerHandle::Get().get(),
base::ThreadTaskRunnerHandle::Get().get()),
callback_count_(0),
write_status_(File::FILE_ERROR_FAILED),
write_bytes_(0),
write_complete_(false),
url_request_context_(file_system_.file_system_context()),
weak_factory_(this) {}
void SetUp() override {
ASSERT_TRUE(dir_.CreateUniqueTempDir());
file_system_.SetUp(CannedSyncableFileSystem::QUOTA_ENABLED);
sync_context_ =
new LocalFileSyncContext(dir_.GetPath(), in_memory_env_.get(),
base::ThreadTaskRunnerHandle::Get().get(),
base::ThreadTaskRunnerHandle::Get().get());
ASSERT_EQ(
SYNC_STATUS_OK,
file_system_.MaybeInitializeFileSystemContext(sync_context_.get()));
ASSERT_EQ(File::FILE_OK, file_system_.OpenFileSystem());
ASSERT_EQ(File::FILE_OK,
file_system_.CreateDirectory(URL(kParent)));
}
void TearDown() override {
if (sync_context_.get())
sync_context_->ShutdownOnUIThread();
sync_context_ = nullptr;
file_system_.TearDown();
RevokeSyncableFileSystem();
}
FileSystemURL URL(const std::string& path) {
return file_system_.URL(path);
}
LocalFileSyncStatus* sync_status() {
return file_system_.backend()->sync_context()->sync_status();
}
void ResetCallbackStatus() {
write_status_ = File::FILE_ERROR_FAILED;
write_bytes_ = 0;
write_complete_ = false;
callback_count_ = 0;
}
StatusCallback ExpectStatus(const base::Location& location,
File::Error expect) {
return base::Bind(&SyncableFileOperationRunnerTest::DidFinish,
weak_factory_.GetWeakPtr(), location, expect);
}
FileSystemOperation::WriteCallback GetWriteCallback(
const base::Location& location) {
return base::Bind(&SyncableFileOperationRunnerTest::DidWrite,
weak_factory_.GetWeakPtr(), location);
}
void DidWrite(const base::Location& location,
File::Error status,
int64_t bytes,
bool complete) {
SCOPED_TRACE(testing::Message() << location.ToString());
write_status_ = status;
write_bytes_ += bytes;
write_complete_ = complete;
++callback_count_;
}
void DidFinish(const base::Location& location,
File::Error expect,
File::Error status) {
SCOPED_TRACE(testing::Message() << location.ToString());
EXPECT_EQ(expect, status);
++callback_count_;
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
bool CreateTempFile(base::FilePath* path) {
return base::CreateTemporaryFileInDir(dir_.GetPath(), path);
}
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir dir_;
std::unique_ptr<leveldb::Env> in_memory_env_;
CannedSyncableFileSystem file_system_;
scoped_refptr<LocalFileSyncContext> sync_context_;
int callback_count_;
File::Error write_status_;
size_t write_bytes_;
bool write_complete_;
MockBlobURLRequestContext url_request_context_;
private:
base::WeakPtrFactory<SyncableFileOperationRunnerTest> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(SyncableFileOperationRunnerTest);
};
TEST_F(SyncableFileOperationRunnerTest, SimpleQueue) {
sync_status()->StartSyncing(URL(kFile));
ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
// The URL is in syncing so the write operations won't run.
ResetCallbackStatus();
file_system_.operation_runner()->CreateFile(
URL(kFile), false /* exclusive */,
ExpectStatus(FROM_HERE, File::FILE_OK));
file_system_.operation_runner()->Truncate(
URL(kFile), 1,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
// Read operations are not blocked (and are executed before queued ones).
file_system_.operation_runner()->FileExists(
URL(kFile), ExpectStatus(FROM_HERE, File::FILE_ERROR_NOT_FOUND));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// End syncing (to enable write).
sync_status()->EndSyncing(URL(kFile));
ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
ResetCallbackStatus();
content::RunAllTasksUntilIdle();
EXPECT_EQ(2, callback_count_);
// Now the file must have been created and updated.
ResetCallbackStatus();
file_system_.operation_runner()->FileExists(
URL(kFile), ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
}
TEST_F(SyncableFileOperationRunnerTest, WriteToParentAndChild) {
// First create the kDir directory and kChild in the dir.
EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir)));
EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild)));
// Start syncing the kDir directory.
sync_status()->StartSyncing(URL(kDir));
ASSERT_FALSE(sync_status()->IsWritable(URL(kDir)));
// Writes to kParent and kChild should be all queued up.
ResetCallbackStatus();
file_system_.operation_runner()->Truncate(
URL(kChild), 1, ExpectStatus(FROM_HERE, File::FILE_OK));
file_system_.operation_runner()->Remove(
URL(kParent), true /* recursive */,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
// Read operations are not blocked (and are executed before queued ones).
file_system_.operation_runner()->DirectoryExists(
URL(kDir), ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Writes to unrelated files must succeed as well.
ResetCallbackStatus();
file_system_.operation_runner()->CreateDirectory(
URL(kOther), false /* exclusive */, false /* recursive */,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// End syncing (to enable write).
sync_status()->EndSyncing(URL(kDir));
ASSERT_TRUE(sync_status()->IsWritable(URL(kDir)));
ResetCallbackStatus();
content::RunAllTasksUntilIdle();
EXPECT_EQ(2, callback_count_);
}
TEST_F(SyncableFileOperationRunnerTest, CopyAndMove) {
// First create the kDir directory and kChild in the dir.
EXPECT_EQ(File::FILE_OK, file_system_.CreateDirectory(URL(kDir)));
EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kChild)));
// Start syncing the kParent directory.
sync_status()->StartSyncing(URL(kParent));
// Copying kDir to other directory should succeed, while moving would fail
// (since the source directory is in syncing).
ResetCallbackStatus();
file_system_.operation_runner()->Copy(
URL(kDir), URL("dest-copy"), storage::FileSystemOperation::OPTION_NONE,
storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
storage::FileSystemOperationRunner::CopyProgressCallback(),
ExpectStatus(FROM_HERE, File::FILE_OK));
file_system_.operation_runner()->Move(
URL(kDir),
URL("dest-move"),
storage::FileSystemOperation::OPTION_NONE,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Only "dest-copy1" should exist.
EXPECT_EQ(File::FILE_OK,
file_system_.DirectoryExists(URL("dest-copy")));
EXPECT_EQ(File::FILE_ERROR_NOT_FOUND,
file_system_.DirectoryExists(URL("dest-move")));
// Start syncing the "dest-copy2" directory.
sync_status()->StartSyncing(URL("dest-copy2"));
// Now the destination is also locked copying kDir should be queued.
ResetCallbackStatus();
file_system_.operation_runner()->Copy(
URL(kDir), URL("dest-copy2"), storage::FileSystemOperation::OPTION_NONE,
storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT,
storage::FileSystemOperationRunner::CopyProgressCallback(),
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
// Finish syncing the "dest-copy2" directory to unlock Copy.
sync_status()->EndSyncing(URL("dest-copy2"));
ResetCallbackStatus();
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Now we should have "dest-copy2".
EXPECT_EQ(File::FILE_OK,
file_system_.DirectoryExists(URL("dest-copy2")));
// Finish syncing the kParent to unlock Move.
sync_status()->EndSyncing(URL(kParent));
ResetCallbackStatus();
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Now we should have "dest-move".
EXPECT_EQ(File::FILE_OK,
file_system_.DirectoryExists(URL("dest-move")));
}
TEST_F(SyncableFileOperationRunnerTest, Write) {
EXPECT_EQ(File::FILE_OK, file_system_.CreateFile(URL(kFile)));
const std::string kData("Lorem ipsum.");
ScopedTextBlob blob(url_request_context_, "blob:foo", kData);
sync_status()->StartSyncing(URL(kFile));
ResetCallbackStatus();
file_system_.operation_runner()->Write(
&url_request_context_,
URL(kFile), blob.GetBlobDataHandle(), 0, GetWriteCallback(FROM_HERE));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
sync_status()->EndSyncing(URL(kFile));
ResetCallbackStatus();
while (!write_complete_)
content::RunAllTasksUntilIdle();
EXPECT_EQ(File::FILE_OK, write_status_);
EXPECT_EQ(kData.size(), write_bytes_);
EXPECT_TRUE(write_complete_);
}
TEST_F(SyncableFileOperationRunnerTest, QueueAndCancel) {
sync_status()->StartSyncing(URL(kFile));
ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
ResetCallbackStatus();
file_system_.operation_runner()->CreateFile(
URL(kFile), false /* exclusive */,
ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT));
file_system_.operation_runner()->Truncate(
URL(kFile), 1,
ExpectStatus(FROM_HERE, File::FILE_ERROR_ABORT));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
ResetCallbackStatus();
// This shouldn't crash nor leak memory.
sync_context_->ShutdownOnUIThread();
sync_context_ = nullptr;
content::RunAllTasksUntilIdle();
EXPECT_EQ(2, callback_count_);
}
// Test if CopyInForeignFile runs cooperatively with other Sync operations.
TEST_F(SyncableFileOperationRunnerTest, CopyInForeignFile) {
const std::string kTestData("test data");
base::FilePath temp_path;
ASSERT_TRUE(CreateTempFile(&temp_path));
ASSERT_EQ(static_cast<int>(kTestData.size()),
base::WriteFile(
temp_path, kTestData.data(), kTestData.size()));
sync_status()->StartSyncing(URL(kFile));
ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
// The URL is in syncing so CopyIn (which is a write operation) won't run.
ResetCallbackStatus();
file_system_.operation_runner()->CopyInForeignFile(
temp_path, URL(kFile),
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(0, callback_count_);
// End syncing (to enable write).
sync_status()->EndSyncing(URL(kFile));
ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
ResetCallbackStatus();
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Now the file must have been created and have the same content as temp_path.
ResetCallbackStatus();
file_system_.DoVerifyFile(
URL(kFile), kTestData,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
}
TEST_F(SyncableFileOperationRunnerTest, Cancel) {
// Prepare a file.
file_system_.operation_runner()->CreateFile(
URL(kFile), false /* exclusive */,
ExpectStatus(FROM_HERE, File::FILE_OK));
content::RunAllTasksUntilIdle();
EXPECT_EQ(1, callback_count_);
// Run Truncate and immediately cancel. This shouldn't crash.
ResetCallbackStatus();
storage::FileSystemOperationRunner::OperationID id =
file_system_.operation_runner()->Truncate(
URL(kFile), 10, ExpectStatus(FROM_HERE, File::FILE_OK));
file_system_.operation_runner()->Cancel(
id, ExpectStatus(FROM_HERE, File::FILE_ERROR_INVALID_OPERATION));
base::RunLoop().Run();
EXPECT_EQ(2, callback_count_);
}
} // namespace sync_file_system