// 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 "storage/browser/fileapi/recursive_operation_delegate.h" #include #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/files/scoped_temp_dir.h" #include "base/location.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" #include "content/public/test/sandbox_file_system_test_helper.h" #include "storage/browser/fileapi/file_system_file_util.h" #include "storage/browser/fileapi/file_system_operation.h" #include "storage/browser/fileapi/file_system_operation_runner.h" #include "testing/gtest/include/gtest/gtest.h" using storage::FileSystemContext; using storage::FileSystemOperationContext; using storage::FileSystemURL; namespace content { namespace { class LoggingRecursiveOperation : public storage::RecursiveOperationDelegate { public: struct LogEntry { enum Type { PROCESS_FILE, PROCESS_DIRECTORY, POST_PROCESS_DIRECTORY }; Type type; FileSystemURL url; }; LoggingRecursiveOperation(FileSystemContext* file_system_context, const FileSystemURL& root, const StatusCallback& callback) : storage::RecursiveOperationDelegate(file_system_context), root_(root), callback_(callback), weak_factory_(this) {} ~LoggingRecursiveOperation() override {} const std::vector& log_entries() const { return log_entries_; } // RecursiveOperationDelegate overrides. void Run() override { NOTREACHED(); } void RunRecursively() override { StartRecursiveOperation( root_, storage::FileSystemOperation::ERROR_BEHAVIOR_ABORT, callback_); } void RunRecursivelyWithIgnoringError() { StartRecursiveOperation( root_, storage::FileSystemOperation::ERROR_BEHAVIOR_SKIP, callback_); } void ProcessFile(const FileSystemURL& url, const StatusCallback& callback) override { RecordLogEntry(LogEntry::PROCESS_FILE, url); if (error_url_.is_valid() && error_url_ == url) { callback.Run(base::File::FILE_ERROR_FAILED); return; } operation_runner()->GetMetadata( url, base::Bind(&LoggingRecursiveOperation::DidGetMetadata, weak_factory_.GetWeakPtr(), callback)); } void ProcessDirectory(const FileSystemURL& url, const StatusCallback& callback) override { RecordLogEntry(LogEntry::PROCESS_DIRECTORY, url); callback.Run(base::File::FILE_OK); } void PostProcessDirectory(const FileSystemURL& url, const StatusCallback& callback) override { RecordLogEntry(LogEntry::POST_PROCESS_DIRECTORY, url); callback.Run(base::File::FILE_OK); } void SetEntryToFail(const FileSystemURL& url) { error_url_ = url; } private: void RecordLogEntry(LogEntry::Type type, const FileSystemURL& url) { LogEntry entry; entry.type = type; entry.url = url; log_entries_.push_back(entry); } void DidGetMetadata(const StatusCallback& callback, base::File::Error result, const base::File::Info& file_info) { if (result != base::File::FILE_OK) { callback.Run(result); return; } callback.Run(file_info.is_directory ? base::File::FILE_ERROR_NOT_A_FILE : base::File::FILE_OK); } FileSystemURL root_; StatusCallback callback_; std::vector log_entries_; FileSystemURL error_url_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(LoggingRecursiveOperation); }; void ReportStatus(base::File::Error* out_error, base::File::Error error) { DCHECK(out_error); *out_error = error; } // To test the Cancel() during operation, calls Cancel() of |operation| // after |counter| times message posting. void CallCancelLater(storage::RecursiveOperationDelegate* operation, int counter) { if (counter > 0) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&CallCancelLater, base::Unretained(operation), counter - 1)); return; } operation->Cancel(); } } // namespace class RecursiveOperationDelegateTest : public testing::Test { protected: void SetUp() override { EXPECT_TRUE(base_.CreateUniqueTempDir()); sandbox_file_system_.SetUp(base_.path().AppendASCII("filesystem")); } void TearDown() override { sandbox_file_system_.TearDown(); } scoped_ptr NewContext() { FileSystemOperationContext* context = sandbox_file_system_.NewOperationContext(); // Grant enough quota for all test cases. context->set_allowed_bytes_growth(1000000); return make_scoped_ptr(context); } storage::FileSystemFileUtil* file_util() { return sandbox_file_system_.file_util(); } FileSystemURL URLForPath(const std::string& path) const { return sandbox_file_system_.CreateURLFromUTF8(path); } FileSystemURL CreateFile(const std::string& path) { FileSystemURL url = URLForPath(path); bool created = false; EXPECT_EQ(base::File::FILE_OK, file_util()->EnsureFileExists(NewContext().get(), url, &created)); EXPECT_TRUE(created); return url; } FileSystemURL CreateDirectory(const std::string& path) { FileSystemURL url = URLForPath(path); EXPECT_EQ(base::File::FILE_OK, file_util()->CreateDirectory(NewContext().get(), url, false /* exclusive */, true)); return url; } private: base::MessageLoop message_loop_; // Common temp base for nondestructive uses. base::ScopedTempDir base_; SandboxFileSystemTestHelper sandbox_file_system_; }; TEST_F(RecursiveOperationDelegateTest, RootIsFile) { FileSystemURL src_file(CreateFile("src")); base::File::Error error = base::File::FILE_ERROR_FAILED; scoped_ptr context = NewContext(); scoped_ptr operation( new LoggingRecursiveOperation( context->file_system_context(), src_file, base::Bind(&ReportStatus, &error))); operation->RunRecursively(); base::RunLoop().RunUntilIdle(); ASSERT_EQ(base::File::FILE_OK, error); const std::vector& log_entries = operation->log_entries(); ASSERT_EQ(1U, log_entries.size()); const LoggingRecursiveOperation::LogEntry& entry = log_entries[0]; EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, entry.type); EXPECT_EQ(src_file, entry.url); } TEST_F(RecursiveOperationDelegateTest, RootIsDirectory) { FileSystemURL src_root(CreateDirectory("src")); FileSystemURL src_dir1(CreateDirectory("src/dir1")); FileSystemURL src_file1(CreateFile("src/file1")); FileSystemURL src_file2(CreateFile("src/dir1/file2")); FileSystemURL src_file3(CreateFile("src/dir1/file3")); base::File::Error error = base::File::FILE_ERROR_FAILED; scoped_ptr context = NewContext(); scoped_ptr operation( new LoggingRecursiveOperation( context->file_system_context(), src_root, base::Bind(&ReportStatus, &error))); operation->RunRecursively(); base::RunLoop().RunUntilIdle(); ASSERT_EQ(base::File::FILE_OK, error); const std::vector& log_entries = operation->log_entries(); ASSERT_EQ(8U, log_entries.size()); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[0].type); EXPECT_EQ(src_root, log_entries[0].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY, log_entries[1].type); EXPECT_EQ(src_root, log_entries[1].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[2].type); EXPECT_EQ(src_file1, log_entries[2].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY, log_entries[3].type); EXPECT_EQ(src_dir1, log_entries[3].url); // The order of src/dir1/file2 and src/dir1/file3 depends on the file system // implementation (can be swapped). EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[4].type); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[5].type); EXPECT_TRUE((src_file2 == log_entries[4].url && src_file3 == log_entries[5].url) || (src_file3 == log_entries[4].url && src_file2 == log_entries[5].url)); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY, log_entries[6].type); EXPECT_EQ(src_dir1, log_entries[6].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY, log_entries[7].type); EXPECT_EQ(src_root, log_entries[7].url); } TEST_F(RecursiveOperationDelegateTest, Cancel) { FileSystemURL src_root(CreateDirectory("src")); FileSystemURL src_dir1(CreateDirectory("src/dir1")); FileSystemURL src_file1(CreateFile("src/file1")); FileSystemURL src_file2(CreateFile("src/dir1/file2")); base::File::Error error = base::File::FILE_ERROR_FAILED; scoped_ptr context = NewContext(); scoped_ptr operation( new LoggingRecursiveOperation( context->file_system_context(), src_root, base::Bind(&ReportStatus, &error))); operation->RunRecursively(); // Invoke Cancel(), after 5 times message posting. CallCancelLater(operation.get(), 5); base::RunLoop().RunUntilIdle(); ASSERT_EQ(base::File::FILE_ERROR_ABORT, error); } TEST_F(RecursiveOperationDelegateTest, AbortWithError) { FileSystemURL src_root(CreateDirectory("src")); FileSystemURL src_dir1(CreateDirectory("src/dir1")); FileSystemURL src_file1(CreateFile("src/file1")); FileSystemURL src_file2(CreateFile("src/dir1/file2")); FileSystemURL src_file3(CreateFile("src/dir1/file3")); base::File::Error error = base::File::FILE_ERROR_FAILED; scoped_ptr context = NewContext(); scoped_ptr operation( new LoggingRecursiveOperation(context->file_system_context(), src_root, base::Bind(&ReportStatus, &error))); operation->SetEntryToFail(src_file1); operation->RunRecursively(); base::RunLoop().RunUntilIdle(); ASSERT_EQ(base::File::FILE_ERROR_FAILED, error); // Confirm that operation has been aborted in the middle. const std::vector& log_entries = operation->log_entries(); ASSERT_EQ(3U, log_entries.size()); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[0].type); EXPECT_EQ(src_root, log_entries[0].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY, log_entries[1].type); EXPECT_EQ(src_root, log_entries[1].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[2].type); EXPECT_EQ(src_file1, log_entries[2].url); } TEST_F(RecursiveOperationDelegateTest, ContinueWithError) { FileSystemURL src_root(CreateDirectory("src")); FileSystemURL src_dir1(CreateDirectory("src/dir1")); FileSystemURL src_file1(CreateFile("src/file1")); FileSystemURL src_file2(CreateFile("src/dir1/file2")); FileSystemURL src_file3(CreateFile("src/dir1/file3")); base::File::Error error = base::File::FILE_ERROR_FAILED; scoped_ptr context = NewContext(); scoped_ptr operation( new LoggingRecursiveOperation(context->file_system_context(), src_root, base::Bind(&ReportStatus, &error))); operation->SetEntryToFail(src_file1); operation->RunRecursivelyWithIgnoringError(); base::RunLoop().RunUntilIdle(); // Error code should be base::File::FILE_ERROR_FAILED. ASSERT_EQ(base::File::FILE_ERROR_FAILED, error); // Confirm that operation continues after the error. const std::vector& log_entries = operation->log_entries(); ASSERT_EQ(8U, log_entries.size()); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[0].type); EXPECT_EQ(src_root, log_entries[0].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY, log_entries[1].type); EXPECT_EQ(src_root, log_entries[1].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[2].type); EXPECT_EQ(src_file1, log_entries[2].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_DIRECTORY, log_entries[3].type); EXPECT_EQ(src_dir1, log_entries[3].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[4].type); EXPECT_EQ(src_file3, log_entries[4].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::PROCESS_FILE, log_entries[5].type); EXPECT_EQ(src_file2, log_entries[5].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY, log_entries[6].type); EXPECT_EQ(src_dir1, log_entries[6].url); EXPECT_EQ(LoggingRecursiveOperation::LogEntry::POST_PROCESS_DIRECTORY, log_entries[7].type); EXPECT_EQ(src_root, log_entries[7].url); } } // namespace content