diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/content/browser/fileapi | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/content/browser/fileapi')
25 files changed, 10400 insertions, 242 deletions
diff --git a/chromium/content/browser/fileapi/blob_url_request_job_unittest.cc b/chromium/content/browser/fileapi/blob_url_request_job_unittest.cc new file mode 100644 index 00000000000..a09456b607f --- /dev/null +++ b/chromium/content/browser/fileapi/blob_url_request_job_unittest.cc @@ -0,0 +1,438 @@ +// 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 "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/time/time.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/io_buffer.h" +#include "net/base/request_priority.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/blob/blob_url_request_job.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/common/blob/blob_data.h" + +namespace webkit_blob { + +namespace { + +const int kBufferSize = 1024; +const char kTestData1[] = "Hello"; +const char kTestData2[] = "Here it is data."; +const char kTestFileData1[] = "0123456789"; +const char kTestFileData2[] = "This is sample file."; +const char kTestFileSystemFileData1[] = "abcdefghijklmnop"; +const char kTestFileSystemFileData2[] = "File system file test data."; +const char kTestContentType[] = "foo/bar"; +const char kTestContentDisposition[] = "attachment; filename=foo.txt"; + +const char kFileSystemURLOrigin[] = "http://remote"; +const fileapi::FileSystemType kFileSystemType = + fileapi::kFileSystemTypeTemporary; + +} // namespace + +class BlobURLRequestJobTest : public testing::Test { + public: + + // Test Harness ------------------------------------------------------------- + // TODO(jianli): share this test harness with AppCacheURLRequestJobTest + + class MockURLRequestDelegate : public net::URLRequest::Delegate { + public: + MockURLRequestDelegate() + : received_data_(new net::IOBuffer(kBufferSize)) {} + + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE { + if (request->status().is_success()) { + EXPECT_TRUE(request->response_headers()); + ReadSome(request); + } else { + RequestComplete(); + } + } + + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE { + if (bytes_read > 0) + ReceiveData(request, bytes_read); + else + RequestComplete(); + } + + const std::string& response_data() const { return response_data_; } + + private: + void ReadSome(net::URLRequest* request) { + if (!request->is_pending()) { + RequestComplete(); + return; + } + + int bytes_read = 0; + if (!request->Read(received_data_.get(), kBufferSize, &bytes_read)) { + if (!request->status().is_io_pending()) { + RequestComplete(); + } + return; + } + + ReceiveData(request, bytes_read); + } + + void ReceiveData(net::URLRequest* request, int bytes_read) { + if (bytes_read) { + response_data_.append(received_data_->data(), + static_cast<size_t>(bytes_read)); + ReadSome(request); + } else { + RequestComplete(); + } + } + + void RequestComplete() { + base::MessageLoop::current()->Quit(); + } + + scoped_refptr<net::IOBuffer> received_data_; + std::string response_data_; + }; + + // A simple ProtocolHandler implementation to create BlobURLRequestJob. + class MockProtocolHandler : + public net::URLRequestJobFactory::ProtocolHandler { + public: + MockProtocolHandler(BlobURLRequestJobTest* test) : test_(test) {} + + // net::URLRequestJobFactory::ProtocolHandler override. + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE { + return new BlobURLRequestJob(request, + network_delegate, + test_->blob_data_.get(), + test_->file_system_context_.get(), + base::MessageLoopProxy::current().get()); + } + + private: + BlobURLRequestJobTest* test_; + }; + + BlobURLRequestJobTest() + : blob_data_(new BlobData()), + expected_status_code_(0) {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + temp_file1_ = temp_dir_.path().AppendASCII("BlobFile1.dat"); + ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1), + file_util::WriteFile(temp_file1_, kTestFileData1, + arraysize(kTestFileData1) - 1)); + base::PlatformFileInfo file_info1; + base::GetFileInfo(temp_file1_, &file_info1); + temp_file_modification_time1_ = file_info1.last_modified; + + temp_file2_ = temp_dir_.path().AppendASCII("BlobFile2.dat"); + ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1), + file_util::WriteFile(temp_file2_, kTestFileData2, + arraysize(kTestFileData2) - 1)); + base::PlatformFileInfo file_info2; + base::GetFileInfo(temp_file2_, &file_info2); + temp_file_modification_time2_ = file_info2.last_modified; + + url_request_job_factory_.SetProtocolHandler("blob", + new MockProtocolHandler(this)); + url_request_context_.set_job_factory(&url_request_job_factory_); + } + + virtual void TearDown() { + } + + void SetUpFileSystem() { + // Prepare file system. + file_system_context_ = fileapi::CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL(kFileSystemURLOrigin), + kFileSystemType, + fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&BlobURLRequestJobTest::OnValidateFileSystem, + base::Unretained(this))); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(file_system_root_url_.is_valid()); + + // Prepare files on file system. + const char kFilename1[] = "FileSystemFile1.dat"; + temp_file_system_file1_ = GetFileSystemURL(kFilename1); + WriteFileSystemFile(kFilename1, kTestFileSystemFileData1, + arraysize(kTestFileSystemFileData1) - 1, + &temp_file_system_file_modification_time1_); + const char kFilename2[] = "FileSystemFile2.dat"; + temp_file_system_file2_ = GetFileSystemURL(kFilename2); + WriteFileSystemFile(kFilename2, kTestFileSystemFileData2, + arraysize(kTestFileSystemFileData2) - 1, + &temp_file_system_file_modification_time2_); + } + + GURL GetFileSystemURL(const std::string& filename) { + return GURL(file_system_root_url_.spec() + filename); + } + + void WriteFileSystemFile(const std::string& filename, + const char* buf, int buf_size, + base::Time* modification_time) { + fileapi::FileSystemURL url = + file_system_context_->CreateCrackedFileSystemURL( + GURL(kFileSystemURLOrigin), + kFileSystemType, + base::FilePath().AppendASCII(filename)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + fileapi::AsyncFileTestHelper::CreateFileWithData( + file_system_context_, url, buf, buf_size)); + + base::PlatformFileInfo file_info; + ASSERT_EQ(base::PLATFORM_FILE_OK, + fileapi::AsyncFileTestHelper::GetMetadata( + file_system_context_, url, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; + } + + void OnValidateFileSystem(const GURL& root, + const std::string& name, + base::PlatformFileError result) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + ASSERT_TRUE(root.is_valid()); + file_system_root_url_ = root; + } + + void TestSuccessRequest(const std::string& expected_response) { + expected_status_code_ = 200; + expected_response_ = expected_response; + TestRequest("GET", net::HttpRequestHeaders()); + } + + void TestErrorRequest(int expected_status_code) { + expected_status_code_ = expected_status_code; + expected_response_ = ""; + TestRequest("GET", net::HttpRequestHeaders()); + } + + void TestRequest(const std::string& method, + const net::HttpRequestHeaders& extra_headers) { + request_ = url_request_context_.CreateRequest( + GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_); + request_->set_method(method); + if (!extra_headers.IsEmpty()) + request_->SetExtraRequestHeaders(extra_headers); + request_->Start(); + + base::MessageLoop::current()->Run(); + + // Verify response. + EXPECT_TRUE(request_->status().is_success()); + EXPECT_EQ(expected_status_code_, + request_->response_headers()->response_code()); + EXPECT_EQ(expected_response_, url_request_delegate_.response_data()); + } + + void BuildComplicatedData(std::string* expected_result) { + blob_data_->AppendData(kTestData1 + 1, 2); + blob_data_->AppendFile(temp_file1_, 2, 3, temp_file_modification_time1_); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 3, 4, + temp_file_system_file_modification_time1_); + blob_data_->AppendData(kTestData2 + 4, 5); + blob_data_->AppendFile(temp_file2_, 5, 6, temp_file_modification_time2_); + blob_data_->AppendFileSystemFile(temp_file_system_file2_, 6, 7, + temp_file_system_file_modification_time2_); + *expected_result = std::string(kTestData1 + 1, 2); + *expected_result += std::string(kTestFileData1 + 2, 3); + *expected_result += std::string(kTestFileSystemFileData1 + 3, 4); + *expected_result += std::string(kTestData2 + 4, 5); + *expected_result += std::string(kTestFileData2 + 5, 6); + *expected_result += std::string(kTestFileSystemFileData2 + 6, 7); + } + + protected: + base::ScopedTempDir temp_dir_; + base::FilePath temp_file1_; + base::FilePath temp_file2_; + base::Time temp_file_modification_time1_; + base::Time temp_file_modification_time2_; + GURL file_system_root_url_; + GURL temp_file_system_file1_; + GURL temp_file_system_file2_; + base::Time temp_file_system_file_modification_time1_; + base::Time temp_file_system_file_modification_time2_; + + base::MessageLoopForIO message_loop_; + scoped_refptr<fileapi::FileSystemContext> file_system_context_; + scoped_refptr<BlobData> blob_data_; + net::URLRequestJobFactoryImpl url_request_job_factory_; + net::URLRequestContext url_request_context_; + MockURLRequestDelegate url_request_delegate_; + scoped_ptr<net::URLRequest> request_; + + int expected_status_code_; + std::string expected_response_; +}; + +TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) { + blob_data_->AppendData(kTestData1); + TestSuccessRequest(kTestData1); +} + +TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) { + blob_data_->AppendFile(temp_file1_, 0, -1, base::Time()); + TestSuccessRequest(kTestFileData1); +} + +TEST_F(BlobURLRequestJobTest, TestGetLargeFileRequest) { + base::FilePath large_temp_file = + temp_dir_.path().AppendASCII("LargeBlob.dat"); + std::string large_data; + large_data.reserve(kBufferSize * 5); + for (int i = 0; i < kBufferSize * 5; ++i) + large_data.append(1, static_cast<char>(i % 256)); + ASSERT_EQ(static_cast<int>(large_data.size()), + file_util::WriteFile(large_temp_file, large_data.data(), + large_data.size())); + blob_data_->AppendFile(large_temp_file, 0, -1, base::Time()); + TestSuccessRequest(large_data); +} + +TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileRequest) { + base::FilePath non_existent_file = + temp_file1_.InsertBeforeExtension(FILE_PATH_LITERAL("-na")); + blob_data_->AppendFile(non_existent_file, 0, -1, base::Time()); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetChangedFileRequest) { + base::Time old_time = + temp_file_modification_time1_ - base::TimeDelta::FromSeconds(10); + blob_data_->AppendFile(temp_file1_, 0, 3, old_time); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetSlicedFileRequest) { + blob_data_->AppendFile(temp_file1_, 2, 4, temp_file_modification_time1_); + std::string result(kTestFileData1 + 2, 4); + TestSuccessRequest(result); +} + +TEST_F(BlobURLRequestJobTest, TestGetSimpleFileSystemFileRequest) { + SetUpFileSystem(); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, -1, + base::Time()); + TestSuccessRequest(kTestFileSystemFileData1); +} + +TEST_F(BlobURLRequestJobTest, TestGetLargeFileSystemFileRequest) { + SetUpFileSystem(); + std::string large_data; + large_data.reserve(kBufferSize * 5); + for (int i = 0; i < kBufferSize * 5; ++i) + large_data.append(1, static_cast<char>(i % 256)); + + const char kFilename[] = "LargeBlob.dat"; + WriteFileSystemFile(kFilename, large_data.data(), large_data.size(), NULL); + + blob_data_->AppendFileSystemFile(GetFileSystemURL(kFilename), + 0, -1, base::Time()); + TestSuccessRequest(large_data); +} + +TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileSystemFileRequest) { + SetUpFileSystem(); + GURL non_existent_file = GetFileSystemURL("non-existent.dat"); + blob_data_->AppendFileSystemFile(non_existent_file, 0, -1, base::Time()); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetChangedFileSystemFileRequest) { + SetUpFileSystem(); + base::Time old_time = + temp_file_system_file_modification_time1_ - + base::TimeDelta::FromSeconds(10); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, 3, old_time); + TestErrorRequest(404); +} + +TEST_F(BlobURLRequestJobTest, TestGetSlicedFileSystemFileRequest) { + SetUpFileSystem(); + blob_data_->AppendFileSystemFile(temp_file_system_file1_, 2, 4, + temp_file_system_file_modification_time1_); + std::string result(kTestFileSystemFileData1 + 2, 4); + TestSuccessRequest(result); +} + +TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataAndFileRequest) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + TestSuccessRequest(result); +} + +TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + net::HttpRequestHeaders extra_headers; + extra_headers.SetHeader(net::HttpRequestHeaders::kRange, + net::HttpByteRange::Bounded(5, 10).GetHeaderValue()); + expected_status_code_ = 206; + expected_response_ = result.substr(5, 10 - 5 + 1); + TestRequest("GET", extra_headers); +} + +TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) { + SetUpFileSystem(); + std::string result; + BuildComplicatedData(&result); + net::HttpRequestHeaders extra_headers; + extra_headers.SetHeader(net::HttpRequestHeaders::kRange, + net::HttpByteRange::Suffix(10).GetHeaderValue()); + expected_status_code_ = 206; + expected_response_ = result.substr(result.length() - 10); + TestRequest("GET", extra_headers); +} + +TEST_F(BlobURLRequestJobTest, TestExtraHeaders) { + blob_data_->set_content_type(kTestContentType); + blob_data_->set_content_disposition(kTestContentDisposition); + blob_data_->AppendData(kTestData1); + expected_status_code_ = 200; + expected_response_ = kTestData1; + TestRequest("GET", net::HttpRequestHeaders()); + + std::string content_type; + EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type)); + EXPECT_EQ(kTestContentType, content_type); + void* iter = NULL; + std::string content_disposition; + EXPECT_TRUE(request_->response_headers()->EnumerateHeader( + &iter, "Content-Disposition", &content_disposition)); + EXPECT_EQ(kTestContentDisposition, content_disposition); +} + +} // namespace webkit_blob diff --git a/chromium/content/browser/fileapi/copy_or_move_file_validator_unittest.cc b/chromium/content/browser/fileapi/copy_or_move_file_validator_unittest.cc new file mode 100644 index 00000000000..1dd3f986552 --- /dev/null +++ b/chromium/content/browser/fileapi/copy_or_move_file_validator_unittest.cc @@ -0,0 +1,326 @@ +// 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 "base/basictypes.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_backend.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +const FileSystemType kNoValidatorType = kFileSystemTypeTemporary; +const FileSystemType kWithValidatorType = kFileSystemTypeTest; + +void ExpectOk(const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + ASSERT_EQ(base::PLATFORM_FILE_OK, error); +} + +class CopyOrMoveFileValidatorTestHelper { + public: + CopyOrMoveFileValidatorTestHelper( + const GURL& origin, + FileSystemType src_type, + FileSystemType dest_type) + : origin_(origin), + src_type_(src_type), + dest_type_(dest_type) {} + + ~CopyOrMoveFileValidatorTestHelper() { + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + void SetUp() { + ASSERT_TRUE(base_.CreateUniqueTempDir()); + base::FilePath base_dir = base_.path(); + + file_system_context_ = CreateFileSystemContextForTesting(NULL, base_dir); + + // Set up TestFileSystemBackend to require CopyOrMoveFileValidator. + FileSystemBackend* test_file_system_backend = + file_system_context_->GetFileSystemBackend(kWithValidatorType); + static_cast<TestFileSystemBackend*>(test_file_system_backend)-> + set_require_copy_or_move_validator(true); + + // Sets up source. + FileSystemBackend* src_file_system_backend = + file_system_context_->GetFileSystemBackend(src_type_); + src_file_system_backend->OpenFileSystem( + origin_, src_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(SourceURL(""))); + + // Sets up dest. + DCHECK_EQ(kWithValidatorType, dest_type_); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(DestURL(""))); + + copy_src_ = SourceURL("copy_src.jpg"); + move_src_ = SourceURL("move_src.jpg"); + copy_dest_ = DestURL("copy_dest.jpg"); + move_dest_ = DestURL("move_dest.jpg"); + + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(copy_src_, 10)); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(move_src_, 10)); + + ASSERT_TRUE(FileExists(copy_src_, 10)); + ASSERT_TRUE(FileExists(move_src_, 10)); + ASSERT_FALSE(FileExists(copy_dest_, 10)); + ASSERT_FALSE(FileExists(move_dest_, 10)); + } + + void SetMediaCopyOrMoveFileValidatorFactory( + scoped_ptr<CopyOrMoveFileValidatorFactory> factory) { + TestFileSystemBackend* backend = static_cast<TestFileSystemBackend*>( + file_system_context_->GetFileSystemBackend(kWithValidatorType)); + backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass()); + } + + void CopyTest(base::PlatformFileError expected) { + ASSERT_TRUE(FileExists(copy_src_, 10)); + ASSERT_FALSE(FileExists(copy_dest_, 10)); + + EXPECT_EQ(expected, + AsyncFileTestHelper::Copy( + file_system_context_.get(), copy_src_, copy_dest_)); + + EXPECT_TRUE(FileExists(copy_src_, 10)); + if (expected == base::PLATFORM_FILE_OK) + EXPECT_TRUE(FileExists(copy_dest_, 10)); + else + EXPECT_FALSE(FileExists(copy_dest_, 10)); + }; + + void MoveTest(base::PlatformFileError expected) { + ASSERT_TRUE(FileExists(move_src_, 10)); + ASSERT_FALSE(FileExists(move_dest_, 10)); + + EXPECT_EQ(expected, + AsyncFileTestHelper::Move( + file_system_context_.get(), move_src_, move_dest_)); + + if (expected == base::PLATFORM_FILE_OK) { + EXPECT_FALSE(FileExists(move_src_, 10)); + EXPECT_TRUE(FileExists(move_dest_, 10)); + } else { + EXPECT_TRUE(FileExists(move_src_, 10)); + EXPECT_FALSE(FileExists(move_dest_, 10)); + } + }; + + private: + FileSystemURL SourceURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, src_type_, + base::FilePath().AppendASCII("src").AppendASCII(path)); + } + + FileSystemURL DestURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, dest_type_, + base::FilePath().AppendASCII("dest").AppendASCII(path)); + } + + base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) { + base::PlatformFileError result = + AsyncFileTestHelper::CreateFile(file_system_context_.get(), url); + if (result != base::PLATFORM_FILE_OK) + return result; + return AsyncFileTestHelper::TruncateFile( + file_system_context_.get(), url, size); + } + + base::PlatformFileError CreateDirectory(const FileSystemURL& url) { + return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), + url); + } + + bool FileExists(const FileSystemURL& url, int64 expected_size) { + return AsyncFileTestHelper::FileExists( + file_system_context_.get(), url, expected_size); + } + + base::ScopedTempDir base_; + + const GURL origin_; + + const FileSystemType src_type_; + const FileSystemType dest_type_; + std::string src_fsid_; + std::string dest_fsid_; + + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + + FileSystemURL copy_src_; + FileSystemURL copy_dest_; + FileSystemURL move_src_; + FileSystemURL move_dest_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveFileValidatorTestHelper); +}; + +// For TestCopyOrMoveFileValidatorFactory +enum Validity { + VALID, + PRE_WRITE_INVALID, + POST_WRITE_INVALID +}; + +class TestCopyOrMoveFileValidatorFactory + : public CopyOrMoveFileValidatorFactory { + public: + // A factory that creates validators that accept everything or nothing. + // TODO(gbillock): switch args to enum or something + explicit TestCopyOrMoveFileValidatorFactory(Validity validity) + : validity_(validity) {} + virtual ~TestCopyOrMoveFileValidatorFactory() {} + + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& /*src_url*/, + const base::FilePath& /*platform_path*/) OVERRIDE { + return new TestCopyOrMoveFileValidator(validity_); + } + + private: + class TestCopyOrMoveFileValidator : public CopyOrMoveFileValidator { + public: + explicit TestCopyOrMoveFileValidator(Validity validity) + : result_(validity == VALID || validity == POST_WRITE_INVALID + ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + write_result_(validity == VALID || validity == PRE_WRITE_INVALID + ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY) { + } + virtual ~TestCopyOrMoveFileValidator() {} + + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result_)); + } + + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, write_result_)); + } + + private: + base::PlatformFileError result_; + base::PlatformFileError write_result_; + + DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidator); + }; + + Validity validity_; + + DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidatorFactory); +}; + +} // namespace + +TEST(CopyOrMoveFileValidatorTest, NoValidatorWithinSameFSType) { + // Within a file system type, validation is not expected, so it should + // work for kWithValidatorType without a validator set. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kWithValidatorType, + kWithValidatorType); + helper.SetUp(); + helper.CopyTest(base::PLATFORM_FILE_OK); + helper.MoveTest(base::PLATFORM_FILE_OK); +} + +TEST(CopyOrMoveFileValidatorTest, MissingValidator) { + // Copying or moving into a kWithValidatorType requires a file + // validator. An error is expected if copy is attempted without a validator. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, AcceptAll) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(VALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_OK); + helper.MoveTest(base::PLATFORM_FILE_OK); +} + +TEST(CopyOrMoveFileValidatorTest, AcceptNone) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, OverrideValidator) { + // Once set, you can not override the validator. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> reject_factory( + new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(reject_factory.Pass()); + + scoped_ptr<CopyOrMoveFileValidatorFactory> accept_factory( + new TestCopyOrMoveFileValidatorFactory(VALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(accept_factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, RejectPostWrite) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(POST_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/chromium/content/browser/fileapi/copy_or_move_operation_delegate_unittest.cc new file mode 100644 index 00000000000..eb002ab0a0c --- /dev/null +++ b/chromium/content/browser/fileapi/copy_or_move_operation_delegate_unittest.cc @@ -0,0 +1,866 @@ +// 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 <map> +#include <queue> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "content/public/test/test_file_system_backend.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/test_file_set.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +typedef FileSystemOperation::FileEntryList FileEntryList; + +namespace { + +void ExpectOk(const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + ASSERT_EQ(base::PLATFORM_FILE_OK, error); +} + +class TestValidatorFactory : public CopyOrMoveFileValidatorFactory { + public: + // A factory that creates validators that accept everything or nothing. + TestValidatorFactory() {} + virtual ~TestValidatorFactory() {} + + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& /*src_url*/, + const base::FilePath& /*platform_path*/) OVERRIDE { + // Move arg management to TestValidator? + return new TestValidator(true, true, std::string("2")); + } + + private: + class TestValidator : public CopyOrMoveFileValidator { + public: + explicit TestValidator(bool pre_copy_valid, + bool post_copy_valid, + const std::string& reject_string) + : result_(pre_copy_valid ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + write_result_(post_copy_valid ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + reject_string_(reject_string) { + } + virtual ~TestValidator() {} + + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result_)); + } + + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) OVERRIDE { + base::PlatformFileError result = write_result_; + std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe(); + if (unsafe.find(reject_string_) != std::string::npos) { + result = base::PLATFORM_FILE_ERROR_SECURITY; + } + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result)); + } + + private: + base::PlatformFileError result_; + base::PlatformFileError write_result_; + std::string reject_string_; + + DISALLOW_COPY_AND_ASSIGN(TestValidator); + }; +}; + +// Records CopyProgressCallback invocations. +struct ProgressRecord { + FileSystemOperation::CopyProgressType type; + FileSystemURL source_url; + FileSystemURL dest_url; + int64 size; +}; + +void RecordProgressCallback(std::vector<ProgressRecord>* records, + FileSystemOperation::CopyProgressType type, + const FileSystemURL& source_url, + const FileSystemURL& dest_url, + int64 size) { + ProgressRecord record; + record.type = type; + record.source_url = source_url; + record.dest_url = dest_url; + record.size = size; + records->push_back(record); +} + +void RecordFileProgressCallback(std::vector<int64>* records, + int64 progress) { + records->push_back(progress); +} + +void AssignAndQuit(base::RunLoop* run_loop, + base::PlatformFileError* result_out, + base::PlatformFileError result) { + *result_out = result; + run_loop->Quit(); +} + +class ScopedThreadStopper { + public: + ScopedThreadStopper(base::Thread* thread) : thread_(thread) { + } + + ~ScopedThreadStopper() { + if (thread_) { + // Give another chance for deleted streams to perform Close. + base::RunLoop run_loop; + thread_->message_loop_proxy()->PostTaskAndReply( + FROM_HERE, base::Bind(&base::DoNothing), run_loop.QuitClosure()); + run_loop.Run(); + thread_->Stop(); + } + } + + bool is_valid() const { return thread_; } + + private: + base::Thread* thread_; + DISALLOW_COPY_AND_ASSIGN(ScopedThreadStopper); +}; + +} // namespace + +class CopyOrMoveOperationTestHelper { + public: + CopyOrMoveOperationTestHelper( + const GURL& origin, + FileSystemType src_type, + FileSystemType dest_type) + : origin_(origin), + src_type_(src_type), + dest_type_(dest_type) {} + + ~CopyOrMoveOperationTestHelper() { + file_system_context_ = NULL; + quota_manager_proxy_->SimulateQuotaManagerDestroyed(); + quota_manager_ = NULL; + quota_manager_proxy_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + void SetUp() { + SetUp(true, true); + } + + void SetUpNoValidator() { + SetUp(true, false); + } + + void SetUp(bool require_copy_or_move_validator, + bool init_copy_or_move_validator) { + ASSERT_TRUE(base_.CreateUniqueTempDir()); + base::FilePath base_dir = base_.path(); + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + base_dir, + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + quota_manager_proxy_ = new quota::MockQuotaManagerProxy( + quota_manager_.get(), base::MessageLoopProxy::current().get()); + file_system_context_ = + CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir); + + // Prepare the origin's root directory. + FileSystemBackend* backend = + file_system_context_->GetFileSystemBackend(src_type_); + backend->OpenFileSystem(origin_, src_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + backend = file_system_context_->GetFileSystemBackend(dest_type_); + if (dest_type_ == kFileSystemTypeTest) { + TestFileSystemBackend* test_backend = + static_cast<TestFileSystemBackend*>(backend); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestValidatorFactory); + test_backend->set_require_copy_or_move_validator( + require_copy_or_move_validator); + if (init_copy_or_move_validator) + test_backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass()); + } + backend->OpenFileSystem(origin_, dest_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + base::RunLoop().RunUntilIdle(); + + // Grant relatively big quota initially. + quota_manager_->SetQuota(origin_, + FileSystemTypeToQuotaStorageType(src_type_), + 1024 * 1024); + quota_manager_->SetQuota(origin_, + FileSystemTypeToQuotaStorageType(dest_type_), + 1024 * 1024); + } + + int64 GetSourceUsage() { + int64 usage = 0; + GetUsageAndQuota(src_type_, &usage, NULL); + return usage; + } + + int64 GetDestUsage() { + int64 usage = 0; + GetUsageAndQuota(dest_type_, &usage, NULL); + return usage; + } + + FileSystemURL SourceURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, src_type_, base::FilePath::FromUTF8Unsafe(path)); + } + + FileSystemURL DestURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, dest_type_, base::FilePath::FromUTF8Unsafe(path)); + } + + base::PlatformFileError Copy(const FileSystemURL& src, + const FileSystemURL& dest) { + return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest); + } + + base::PlatformFileError CopyWithProgress( + const FileSystemURL& src, + const FileSystemURL& dest, + const AsyncFileTestHelper::CopyProgressCallback& progress_callback) { + return AsyncFileTestHelper::CopyWithProgress( + file_system_context_.get(), src, dest, progress_callback); + } + + base::PlatformFileError Move(const FileSystemURL& src, + const FileSystemURL& dest) { + return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest); + } + + base::PlatformFileError SetUpTestCaseFiles( + const FileSystemURL& root, + const test::TestCaseRecord* const test_cases, + size_t test_case_size) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + for (size_t i = 0; i < test_case_size; ++i) { + const test::TestCaseRecord& test_case = test_cases[i]; + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + root.origin(), + root.mount_type(), + root.virtual_path().Append(test_case.path)); + if (test_case.is_directory) + result = CreateDirectory(url); + else + result = CreateFile(url, test_case.data_file_size); + EXPECT_EQ(base::PLATFORM_FILE_OK, result) << url.DebugString(); + if (result != base::PLATFORM_FILE_OK) + return result; + } + return result; + } + + void VerifyTestCaseFiles( + const FileSystemURL& root, + const test::TestCaseRecord* const test_cases, + size_t test_case_size) { + std::map<base::FilePath, const test::TestCaseRecord*> test_case_map; + for (size_t i = 0; i < test_case_size; ++i) { + test_case_map[ + base::FilePath(test_cases[i].path).NormalizePathSeparators()] = + &test_cases[i]; + } + + std::queue<FileSystemURL> directories; + FileEntryList entries; + directories.push(root); + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + ASSERT_EQ(base::PLATFORM_FILE_OK, ReadDirectory(dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + dir.origin(), + dir.mount_type(), + dir.virtual_path().Append(entries[i].name)); + base::FilePath relative; + root.virtual_path().AppendRelativePath(url.virtual_path(), &relative); + relative = relative.NormalizePathSeparators(); + ASSERT_TRUE(ContainsKey(test_case_map, relative)); + if (entries[i].is_directory) { + EXPECT_TRUE(test_case_map[relative]->is_directory); + directories.push(url); + } else { + EXPECT_FALSE(test_case_map[relative]->is_directory); + EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size)); + } + test_case_map.erase(relative); + } + } + EXPECT_TRUE(test_case_map.empty()); + std::map<base::FilePath, const test::TestCaseRecord*>::const_iterator it; + for (it = test_case_map.begin(); it != test_case_map.end(); ++it) { + LOG(ERROR) << "Extra entry: " << it->first.LossyDisplayName(); + } + } + + base::PlatformFileError ReadDirectory(const FileSystemURL& url, + FileEntryList* entries) { + return AsyncFileTestHelper::ReadDirectory( + file_system_context_.get(), url, entries); + } + + base::PlatformFileError CreateDirectory(const FileSystemURL& url) { + return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), + url); + } + + base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) { + base::PlatformFileError result = + AsyncFileTestHelper::CreateFile(file_system_context_.get(), url); + if (result != base::PLATFORM_FILE_OK) + return result; + return AsyncFileTestHelper::TruncateFile( + file_system_context_.get(), url, size); + } + + bool FileExists(const FileSystemURL& url, int64 expected_size) { + return AsyncFileTestHelper::FileExists( + file_system_context_.get(), url, expected_size); + } + + bool DirectoryExists(const FileSystemURL& url) { + return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(), + url); + } + + private: + void GetUsageAndQuota(FileSystemType type, int64* usage, int64* quota) { + quota::QuotaStatusCode status = AsyncFileTestHelper::GetUsageAndQuota( + quota_manager_.get(), origin_, type, usage, quota); + ASSERT_EQ(quota::kQuotaStatusOk, status); + } + + private: + base::ScopedTempDir base_; + + const GURL origin_; + const FileSystemType src_type_; + const FileSystemType dest_type_; + + base::MessageLoopForIO message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<quota::MockQuotaManagerProxy> quota_manager_proxy_; + scoped_refptr<quota::MockQuotaManager> quota_manager_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationTestHelper); +}; + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFile) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest)); + + // Verify. + ASSERT_TRUE(helper.FileExists(src, 10)); + ASSERT_TRUE(helper.FileExists(dest, 10)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleFile) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.FileExists(src, AsyncFileTestHelper::kDontCheckSize)); + ASSERT_TRUE(helper.FileExists(dest, 10)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest)); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.CopyWithProgress( + src, dest, + AsyncFileTestHelper::CopyProgressCallback())); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + helper.VerifyTestCaseFiles(dest, + test::kRegularTestCases, + test::kRegularTestCaseSize); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + helper.VerifyTestCaseFiles(dest, + test::kRegularTestCases, + test::kRegularTestCaseSize); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, + MoveDirectoryFailPostWriteValidation) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypeTest); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + + // Move it. + helper.Move(src, dest); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + test::TestCaseRecord kMoveDirResultCases[] = { + {false, FILE_PATH_LITERAL("file 0"), 38}, + {false, FILE_PATH_LITERAL("file 3"), 0}, + }; + + helper.VerifyTestCaseFiles(dest, + kMoveDirResultCases, + arraysize(kMoveDirResultCases)); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypeTest); + helper.SetUpNoValidator(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + + // The copy attempt should fail with a security error -- getting + // the factory returns a security error, and the copy operation must + // respect that. + ASSERT_EQ(base::PLATFORM_FILE_ERROR_SECURITY, helper.Copy(src, dest)); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, ProgressCallback) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + + std::vector<ProgressRecord> records; + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.CopyWithProgress(src, dest, + base::Bind(&RecordProgressCallback, + base::Unretained(&records)))); + + // Verify progress callback. + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + + FileSystemURL src_url = helper.SourceURL( + std::string("a/") + base::FilePath(test_case.path).AsUTF8Unsafe()); + FileSystemURL dest_url = helper.DestURL( + std::string("b/") + base::FilePath(test_case.path).AsUTF8Unsafe()); + + // Find the first and last progress record. + size_t begin_index = records.size(); + size_t end_index = records.size(); + for (size_t j = 0; j < records.size(); ++j) { + if (records[j].source_url == src_url) { + if (begin_index == records.size()) + begin_index = j; + end_index = j; + } + } + + // The record should be found. + ASSERT_NE(begin_index, records.size()); + ASSERT_NE(end_index, records.size()); + ASSERT_NE(begin_index, end_index); + + EXPECT_EQ(FileSystemOperation::BEGIN_COPY_ENTRY, + records[begin_index].type); + EXPECT_FALSE(records[begin_index].dest_url.is_valid()); + EXPECT_EQ(FileSystemOperation::END_COPY_ENTRY, records[end_index].type); + EXPECT_EQ(dest_url, records[end_index].dest_url); + + if (test_case.is_directory) { + // For directory copy, the progress shouldn't be interlaced. + EXPECT_EQ(begin_index + 1, end_index); + } else { + // PROGRESS event's size should be assending order. + int64 current_size = 0; + for (size_t j = begin_index + 1; j < end_index; ++j) { + if (records[j].source_url == src_url) { + EXPECT_EQ(FileSystemOperation::PROGRESS, records[j].type); + EXPECT_FALSE(records[j].dest_url.is_valid()); + EXPECT_GE(records[j].size, current_size); + current_size = records[j].size; + } + } + } + } +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath source_path = temp_dir.path().AppendASCII("source"); + const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + file_util::WriteFile(source_path, kTestData, + arraysize(kTestData) - 1); // Exclude trailing '\0'. + + base::FilePath dest_path = temp_dir.path().AppendASCII("dest"); + // LocalFileWriter requires the file exists. So create an empty file here. + file_util::WriteFile(dest_path, "", 0); + + base::MessageLoopForIO message_loop; + base::Thread file_thread("file_thread"); + ASSERT_TRUE(file_thread.Start()); + ScopedThreadStopper thread_stopper(&file_thread); + ASSERT_TRUE(thread_stopper.is_valid()); + + scoped_refptr<base::MessageLoopProxy> task_runner = + file_thread.message_loop_proxy(); + + scoped_ptr<webkit_blob::FileStreamReader> reader( + webkit_blob::FileStreamReader::CreateForLocalFile( + task_runner.get(), source_path, 0, base::Time())); + + scoped_ptr<FileStreamWriter> writer( + FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0)); + + std::vector<int64> progress; + CopyOrMoveOperationDelegate::StreamCopyHelper helper( + reader.Pass(), writer.Pass(), + false, // don't need flush + 10, // buffer size + base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)), + base::TimeDelta()); // For testing, we need all the progress. + + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error)); + run_loop.Run(); + + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_EQ(5U, progress.size()); + EXPECT_EQ(0, progress[0]); + EXPECT_EQ(10, progress[1]); + EXPECT_EQ(20, progress[2]); + EXPECT_EQ(30, progress[3]); + EXPECT_EQ(36, progress[4]); + + std::string content; + ASSERT_TRUE(base::ReadFileToString(dest_path, &content)); + EXPECT_EQ(kTestData, content); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelperWithFlush) { + // Testing the same configuration as StreamCopyHelper, but with |need_flush| + // parameter set to true. Since it is hard to test that the flush is indeed + // taking place, this test just only verifies that the file is correctly + // written with or without the flag. + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath source_path = temp_dir.path().AppendASCII("source"); + const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + file_util::WriteFile(source_path, kTestData, + arraysize(kTestData) - 1); // Exclude trailing '\0'. + + base::FilePath dest_path = temp_dir.path().AppendASCII("dest"); + // LocalFileWriter requires the file exists. So create an empty file here. + file_util::WriteFile(dest_path, "", 0); + + base::MessageLoopForIO message_loop; + base::Thread file_thread("file_thread"); + ASSERT_TRUE(file_thread.Start()); + ScopedThreadStopper thread_stopper(&file_thread); + ASSERT_TRUE(thread_stopper.is_valid()); + + scoped_refptr<base::MessageLoopProxy> task_runner = + file_thread.message_loop_proxy(); + + scoped_ptr<webkit_blob::FileStreamReader> reader( + webkit_blob::FileStreamReader::CreateForLocalFile( + task_runner.get(), source_path, 0, base::Time())); + + scoped_ptr<FileStreamWriter> writer( + FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0)); + + std::vector<int64> progress; + CopyOrMoveOperationDelegate::StreamCopyHelper helper( + reader.Pass(), writer.Pass(), + true, // need flush + 10, // buffer size + base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)), + base::TimeDelta()); // For testing, we need all the progress. + + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error)); + run_loop.Run(); + + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_EQ(5U, progress.size()); + EXPECT_EQ(0, progress[0]); + EXPECT_EQ(10, progress[1]); + EXPECT_EQ(20, progress[2]); + EXPECT_EQ(30, progress[3]); + EXPECT_EQ(36, progress[4]); + + std::string content; + ASSERT_TRUE(base::ReadFileToString(dest_path, &content)); + EXPECT_EQ(kTestData, content); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, StreamCopyHelper_Cancel) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath source_path = temp_dir.path().AppendASCII("source"); + const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + file_util::WriteFile(source_path, kTestData, + arraysize(kTestData) - 1); // Exclude trailing '\0'. + + base::FilePath dest_path = temp_dir.path().AppendASCII("dest"); + // LocalFileWriter requires the file exists. So create an empty file here. + file_util::WriteFile(dest_path, "", 0); + + base::MessageLoopForIO message_loop; + base::Thread file_thread("file_thread"); + ASSERT_TRUE(file_thread.Start()); + ScopedThreadStopper thread_stopper(&file_thread); + ASSERT_TRUE(thread_stopper.is_valid()); + + scoped_refptr<base::MessageLoopProxy> task_runner = + file_thread.message_loop_proxy(); + + scoped_ptr<webkit_blob::FileStreamReader> reader( + webkit_blob::FileStreamReader::CreateForLocalFile( + task_runner.get(), source_path, 0, base::Time())); + + scoped_ptr<FileStreamWriter> writer( + FileStreamWriter::CreateForLocalFile(task_runner.get(), dest_path, 0)); + + std::vector<int64> progress; + CopyOrMoveOperationDelegate::StreamCopyHelper helper( + reader.Pass(), writer.Pass(), + false, // need_flush + 10, // buffer size + base::Bind(&RecordFileProgressCallback, base::Unretained(&progress)), + base::TimeDelta()); // For testing, we need all the progress. + + // Call Cancel() later. + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel, + base::Unretained(&helper))); + + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + helper.Run(base::Bind(&AssignAndQuit, &run_loop, &error)); + run_loop.Run(); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, error); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/dragged_file_util_unittest.cc b/chromium/content/browser/fileapi/dragged_file_util_unittest.cc new file mode 100644 index 00000000000..17b29cc6be5 --- /dev/null +++ b/chromium/content/browser/fileapi/dragged_file_util_unittest.cc @@ -0,0 +1,544 @@ +// 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 <map> +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/dragged_file_util.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/browser/fileapi/test_file_set.h" + +namespace fileapi { + +namespace { + +typedef AsyncFileTestHelper::FileEntryList FileEntryList; + +// Used in DraggedFileUtilTest::SimulateDropFiles(). +// Random root paths in which we create each file/directory of the +// RegularTestCases (so that we can simulate a drop with files/directories +// from multiple directories). +static const base::FilePath::CharType* kRootPaths[] = { + FILE_PATH_LITERAL("a"), + FILE_PATH_LITERAL("b/c"), + FILE_PATH_LITERAL("etc"), +}; + +base::FilePath GetTopLevelPath(const base::FilePath& path) { + std::vector<base::FilePath::StringType> components; + path.GetComponents(&components); + return base::FilePath(components[0]); +} + +bool IsDirectoryEmpty(FileSystemContext* context, const FileSystemURL& url) { + FileEntryList entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(context, url, &entries)); + return entries.empty(); +} + +FileSystemURL GetEntryURL(FileSystemContext* file_system_context, + const FileSystemURL& dir, + const base::FilePath::StringType& name) { + return file_system_context->CreateCrackedFileSystemURL( + dir.origin(), + dir.mount_type(), + dir.virtual_path().Append(name)); +} + +base::FilePath GetRelativeVirtualPath(const FileSystemURL& root, + const FileSystemURL& url) { + if (root.virtual_path().empty()) + return url.virtual_path(); + base::FilePath relative; + const bool success = root.virtual_path().AppendRelativePath( + url.virtual_path(), &relative); + DCHECK(success); + return relative; +} + +FileSystemURL GetOtherURL(FileSystemContext* file_system_context, + const FileSystemURL& root, + const FileSystemURL& other_root, + const FileSystemURL& url) { + return file_system_context->CreateCrackedFileSystemURL( + other_root.origin(), + other_root.mount_type(), + other_root.virtual_path().Append(GetRelativeVirtualPath(root, url))); +} + +} // namespace + +class DraggedFileUtilTest : public testing::Test { + public: + DraggedFileUtilTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(partition_dir_.CreateUniqueTempDir()); + file_util_.reset(new DraggedFileUtil()); + + // Register the files/directories of RegularTestCases (with random + // root paths) as dropped files. + SimulateDropFiles(); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL /* quota_manager */, + partition_dir_.path()); + + isolated_context()->AddReference(filesystem_id_); + } + + virtual void TearDown() { + isolated_context()->RemoveReference(filesystem_id_); + } + + protected: + IsolatedContext* isolated_context() const { + return IsolatedContext::GetInstance(); + } + const base::FilePath& root_path() const { + return data_dir_.path(); + } + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + FileSystemFileUtil* file_util() const { return file_util_.get(); } + std::string filesystem_id() const { return filesystem_id_; } + + base::FilePath GetTestCasePlatformPath( + const base::FilePath::StringType& path) { + return toplevel_root_map_[GetTopLevelPath(base::FilePath(path))] + .Append(path).NormalizePathSeparators(); + } + + base::FilePath GetTestCaseLocalPath(const base::FilePath& path) { + base::FilePath relative; + if (data_dir_.path().AppendRelativePath(path, &relative)) + return relative; + return path; + } + + FileSystemURL GetFileSystemURL(const base::FilePath& path) const { + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath( + filesystem_id()).Append(path); + return file_system_context_->CreateCrackedFileSystemURL( + GURL("http://example.com"), + kFileSystemTypeIsolated, + virtual_path); + } + + FileSystemURL GetOtherFileSystemURL(const base::FilePath& path) const { + return file_system_context()->CreateCrackedFileSystemURL( + GURL("http://example.com"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII("dest").Append(path)); + } + + void VerifyFilesHaveSameContent(const FileSystemURL& url1, + const FileSystemURL& url2) { + // Get the file info and the platform path for url1. + base::PlatformFileInfo info1; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context(), url1, &info1)); + base::FilePath platform_path1; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetPlatformPath( + file_system_context(), url1, &platform_path1)); + + // Get the file info and the platform path for url2. + base::PlatformFileInfo info2; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context(), url2, &info2)); + base::FilePath platform_path2; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetPlatformPath( + file_system_context(), url2, &platform_path2)); + + // See if file info matches with the other one. + EXPECT_EQ(info1.is_directory, info2.is_directory); + EXPECT_EQ(info1.size, info2.size); + EXPECT_EQ(info1.is_symbolic_link, info2.is_symbolic_link); + EXPECT_NE(platform_path1, platform_path2); + + std::string content1, content2; + EXPECT_TRUE(base::ReadFileToString(platform_path1, &content1)); + EXPECT_TRUE(base::ReadFileToString(platform_path2, &content2)); + EXPECT_EQ(content1, content2); + } + + void VerifyDirectoriesHaveSameContent(const FileSystemURL& root1, + const FileSystemURL& root2) { + base::FilePath root_path1 = root1.path(); + base::FilePath root_path2 = root2.path(); + + FileEntryList entries; + std::queue<FileSystemURL> directories; + + directories.push(root1); + std::set<base::FilePath> file_set1; + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url = GetEntryURL(file_system_context(), + dir, entries[i].name); + if (entries[i].is_directory) { + directories.push(url); + continue; + } + file_set1.insert(GetRelativeVirtualPath(root1, url)); + } + } + + directories.push(root2); + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url2 = GetEntryURL(file_system_context(), + dir, entries[i].name); + FileSystemURL url1 = GetOtherURL(file_system_context(), + root2, root1, url2); + if (entries[i].is_directory) { + directories.push(url2); + EXPECT_EQ(IsDirectoryEmpty(file_system_context(), url1), + IsDirectoryEmpty(file_system_context(), url2)); + continue; + } + base::FilePath relative = GetRelativeVirtualPath(root2, url2); + EXPECT_TRUE(file_set1.find(relative) != file_set1.end()); + VerifyFilesHaveSameContent(url1, url2); + } + } + } + + scoped_ptr<FileSystemOperationContext> GetOperationContext() { + return make_scoped_ptr( + new FileSystemOperationContext(file_system_context())).Pass(); + } + + + private: + void SimulateDropFiles() { + size_t root_path_index = 0; + + IsolatedContext::FileInfoSet toplevels; + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + base::FilePath path(test_case.path); + base::FilePath toplevel = GetTopLevelPath(path); + + // We create the test case files under one of the kRootPaths + // to simulate a drop with multiple directories. + if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) { + base::FilePath root = root_path().Append( + kRootPaths[(root_path_index++) % arraysize(kRootPaths)]); + toplevel_root_map_[toplevel] = root; + toplevels.AddPath(root.Append(path), NULL); + } + + test::SetUpOneTestCase(toplevel_root_map_[toplevel], test_case); + } + + // Register the toplevel entries. + filesystem_id_ = isolated_context()->RegisterDraggedFileSystem(toplevels); + } + + base::ScopedTempDir data_dir_; + base::ScopedTempDir partition_dir_; + base::MessageLoopForIO message_loop_; + std::string filesystem_id_; + scoped_refptr<FileSystemContext> file_system_context_; + std::map<base::FilePath, base::FilePath> toplevel_root_map_; + scoped_ptr<DraggedFileUtil> file_util_; + DISALLOW_COPY_AND_ASSIGN(DraggedFileUtilTest); +}; + +TEST_F(DraggedFileUtilTest, BasicTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i); + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // See if we can query the file info via the isolated FileUtil. + // (This should succeed since we have registered all the top-level + // entries of the test cases in SetUp()) + base::PlatformFileInfo info; + base::FilePath platform_path; + FileSystemOperationContext context(file_system_context()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(&context, url, &info, &platform_path)); + + // See if the obtained file info is correct. + if (!test_case.is_directory) + ASSERT_EQ(test_case.data_file_size, info.size); + ASSERT_EQ(test_case.is_directory, info.is_directory); + ASSERT_EQ(GetTestCasePlatformPath(test_case.path), + platform_path.NormalizePathSeparators()); + } +} + +TEST_F(DraggedFileUtilTest, UnregisteredPathsTest) { + static const fileapi::test::TestCaseRecord kUnregisteredCases[] = { + {true, FILE_PATH_LITERAL("nonexistent"), 0}, + {true, FILE_PATH_LITERAL("nonexistent/dir foo"), 0}, + {false, FILE_PATH_LITERAL("nonexistent/false"), 0}, + {false, FILE_PATH_LITERAL("foo"), 30}, + {false, FILE_PATH_LITERAL("bar"), 20}, + }; + + for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { + SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); + const test::TestCaseRecord& test_case = kUnregisteredCases[i]; + + // Prepare the test file/directory. + SetUpOneTestCase(root_path(), test_case); + + // Make sure regular GetFileInfo succeeds. + base::PlatformFileInfo info; + ASSERT_TRUE(base::GetFileInfo(root_path().Append(test_case.path), &info)); + if (!test_case.is_directory) + ASSERT_EQ(test_case.data_file_size, info.size); + ASSERT_EQ(test_case.is_directory, info.is_directory); + } + + for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { + SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); + const test::TestCaseRecord& test_case = kUnregisteredCases[i]; + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // We should not be able to get the valid URL for unregistered files. + ASSERT_FALSE(url.is_valid()); + } +} + +TEST_F(DraggedFileUtilTest, ReadDirectoryTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (!test_case.is_directory) + continue; + + SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i + << ": " << test_case.path); + + // Read entries in the directory to construct the expected results map. + typedef std::map<base::FilePath::StringType, DirectoryEntry> EntryMap; + EntryMap expected_entry_map; + + base::FilePath dir_path = GetTestCasePlatformPath(test_case.path); + base::FileEnumerator file_enum( + dir_path, false /* not recursive */, + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); + base::FilePath current; + while (!(current = file_enum.Next()).empty()) { + base::FileEnumerator::FileInfo file_info = file_enum.GetInfo(); + DirectoryEntry entry; + entry.is_directory = file_info.IsDirectory(); + entry.name = current.BaseName().value(); + entry.size = file_info.GetSize(); + entry.last_modified_time = file_info.GetLastModifiedTime(); + expected_entry_map[entry.name] = entry; + +#if defined(OS_POSIX) + // Creates a symlink for each file/directory. + // They should be ignored by ReadDirectory, so we don't add them + // to expected_entry_map. + base::CreateSymbolicLink( + current, + dir_path.Append(current.BaseName().AddExtension( + FILE_PATH_LITERAL("link")))); +#endif + } + + // Perform ReadDirectory in the isolated filesystem. + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + FileEntryList entries; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), url, &entries)); + + EXPECT_EQ(expected_entry_map.size(), entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + const DirectoryEntry& entry = entries[i]; + EntryMap::iterator found = expected_entry_map.find(entry.name); + EXPECT_TRUE(found != expected_entry_map.end()); + EXPECT_EQ(found->second.name, entry.name); + EXPECT_EQ(found->second.is_directory, entry.is_directory); + EXPECT_EQ(found->second.size, entry.size); + EXPECT_EQ(found->second.last_modified_time.ToDoubleT(), + entry.last_modified_time.ToDoubleT()); + } + } +} + +TEST_F(DraggedFileUtilTest, GetLocalFilePathTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + FileSystemOperationContext context(file_system_context()); + + base::FilePath local_file_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetLocalFilePath(&context, url, &local_file_path)); + EXPECT_EQ(GetTestCasePlatformPath(test_case.path).value(), + local_file_path.value()); + } +} + +TEST_F(DraggedFileUtilTest, CopyOutFileTest) { + FileSystemURL src_root = GetFileSystemURL(base::FilePath()); + FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); + + FileEntryList entries; + std::queue<FileSystemURL> directories; + directories.push(src_root); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_root)); + + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(file_system_context(), + dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL src_url = GetEntryURL(file_system_context(), + dir, entries[i].name); + FileSystemURL dest_url = GetOtherURL(file_system_context(), + src_root, dest_root, src_url); + + if (entries[i].is_directory) { + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_url)); + directories.push(src_url); + continue; + } + SCOPED_TRACE(testing::Message() << "Testing file copy " + << src_url.path().value()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + src_url, dest_url)); + VerifyFilesHaveSameContent(src_url, dest_url); + } + } +} + +TEST_F(DraggedFileUtilTest, CopyOutDirectoryTest) { + FileSystemURL src_root = GetFileSystemURL(base::FilePath()); + FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_root)); + + FileEntryList entries; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(file_system_context(), + src_root, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + if (!entries[i].is_directory) + continue; + FileSystemURL src_url = GetEntryURL(file_system_context(), + src_root, entries[i].name); + FileSystemURL dest_url = GetOtherURL(file_system_context(), + src_root, dest_root, src_url); + SCOPED_TRACE(testing::Message() << "Testing file copy " + << src_url.path().value()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + src_url, dest_url)); + VerifyDirectoriesHaveSameContent(src_url, dest_url); + } +} + +TEST_F(DraggedFileUtilTest, TouchTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (test_case.is_directory) + continue; + SCOPED_TRACE(testing::Message() << test_case.path); + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + base::Time last_access_time = base::Time::FromTimeT(1000); + base::Time last_modified_time = base::Time::FromTimeT(2000); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(GetOperationContext().get(), url, + last_access_time, + last_modified_time)); + + // Verification. + base::PlatformFileInfo info; + base::FilePath platform_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(last_access_time.ToTimeT(), info.last_accessed.ToTimeT()); + EXPECT_EQ(last_modified_time.ToTimeT(), info.last_modified.ToTimeT()); + } +} + +TEST_F(DraggedFileUtilTest, TruncateTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (test_case.is_directory) + continue; + + SCOPED_TRACE(testing::Message() << test_case.path); + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // Truncate to 0. + base::PlatformFileInfo info; + base::FilePath platform_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(GetOperationContext().get(), url, 0)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(0, info.size); + + // Truncate (extend) to 999. + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(GetOperationContext().get(), url, 999)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(999, info.size); + } +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_browsertest.cc b/chromium/content/browser/fileapi/file_system_browsertest.cc index eafb1b1cfa4..a3a83efd483 100644 --- a/chromium/content/browser/fileapi/file_system_browsertest.cc +++ b/chromium/content/browser/fileapi/file_system_browsertest.cc @@ -33,9 +33,9 @@ class FileSystemBrowserTest : public ContentBrowserTest { // a #pass or #fail ref. Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell(); - LOG(INFO) << "Navigating to URL and blocking."; + VLOG(0) << "Navigating to URL and blocking."; NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2); - LOG(INFO) << "Navigation done."; + VLOG(0) << "Navigation done."; std::string result = the_browser->web_contents()->GetLastCommittedURL().ref(); if (result != "pass") { diff --git a/chromium/content/browser/fileapi/file_system_context_unittest.cc b/chromium/content/browser/fileapi/file_system_context_unittest.cc new file mode 100644 index 00000000000..43003ad0dc4 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_context_unittest.cc @@ -0,0 +1,374 @@ +// 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 "webkit/browser/fileapi/file_system_context.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "content/public/test/test_file_system_options.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +namespace fileapi { + +namespace { + +const char kTestOrigin[] = "http://chromium.org/"; + +GURL CreateRawFileSystemURL(const std::string& type_str, + const std::string& fs_id) { + std::string url_str = base::StringPrintf( + "filesystem:http://chromium.org/%s/%s/root/file", + type_str.c_str(), + fs_id.c_str()); + return GURL(url_str); +} + +class FileSystemContextTest : public testing::Test { + public: + FileSystemContextTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + + storage_policy_ = new quota::MockSpecialStoragePolicy(); + + mock_quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + data_dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + storage_policy_.get()); + } + + protected: + FileSystemContext* CreateFileSystemContextForTest( + ExternalMountPoints* external_mount_points) { + return new FileSystemContext(base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + external_mount_points, + storage_policy_.get(), + mock_quota_manager_->proxy(), + ScopedVector<FileSystemBackend>(), + data_dir_.path(), + CreateAllowFileAccessOptions()); + } + + // Verifies a *valid* filesystem url has expected values. + void ExpectFileSystemURLMatches(const FileSystemURL& url, + const GURL& expect_origin, + FileSystemType expect_mount_type, + FileSystemType expect_type, + const base::FilePath& expect_path, + const base::FilePath& expect_virtual_path, + const std::string& expect_filesystem_id) { + EXPECT_TRUE(url.is_valid()); + + EXPECT_EQ(expect_origin, url.origin()); + EXPECT_EQ(expect_mount_type, url.mount_type()); + EXPECT_EQ(expect_type, url.type()); + EXPECT_EQ(expect_path, url.path()); + EXPECT_EQ(expect_virtual_path, url.virtual_path()); + EXPECT_EQ(expect_filesystem_id, url.filesystem_id()); + } + + private: + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<quota::SpecialStoragePolicy> storage_policy_; + scoped_refptr<quota::MockQuotaManager> mock_quota_manager_; +}; + +// It is not valid to pass NULL ExternalMountPoints to FileSystemContext on +// ChromeOS. +#if !defined(OS_CHROMEOS) +TEST_F(FileSystemContextTest, NullExternalMountPoints) { + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(NULL)); + + // Cracking system external mount and isolated mount points should work. + std::string isolated_name = "root"; + std::string isolated_id = + IsolatedContext::GetInstance()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root")), + &isolated_name); + // Register system external mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "system", + kFileSystemTypeNativeLocal, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/sys/")))); + + FileSystemURL cracked_isolated = file_system_context->CrackURL( + CreateRawFileSystemURL("isolated", isolated_id)); + + ExpectFileSystemURLMatches( + cracked_isolated, + GURL(kTestOrigin), + kFileSystemTypeIsolated, + kFileSystemTypeNativeLocal, + base::FilePath( + DRIVE FPL("/test/isolated/root/file")).NormalizePathSeparators(), + base::FilePath::FromUTF8Unsafe(isolated_id).Append(FPL("root/file")). + NormalizePathSeparators(), + isolated_id); + + FileSystemURL cracked_external = file_system_context->CrackURL( + CreateRawFileSystemURL("external", "system")); + + ExpectFileSystemURLMatches( + cracked_external, + GURL(kTestOrigin), + kFileSystemTypeExternal, + kFileSystemTypeNativeLocal, + base::FilePath( + DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(), + base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), + "system"); + + + IsolatedContext::GetInstance()->RevokeFileSystem(isolated_id); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system"); +} +#endif // !defiend(OS_CHROMEOS) + +TEST_F(FileSystemContextTest, FileSystemContextKeepsMountPointsAlive) { + scoped_refptr<ExternalMountPoints> mount_points = + ExternalMountPoints::CreateRefCounted(); + + // Register system external mount point. + ASSERT_TRUE(mount_points->RegisterFileSystem( + "system", + kFileSystemTypeNativeLocal, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/sys/")))); + + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(mount_points.get())); + + // Release a MountPoints reference created in the test. + mount_points = NULL; + + // FileSystemContext should keep a reference to the |mount_points|, so it + // should be able to resolve the URL. + FileSystemURL cracked_external = file_system_context->CrackURL( + CreateRawFileSystemURL("external", "system")); + + ExpectFileSystemURLMatches( + cracked_external, + GURL(kTestOrigin), + kFileSystemTypeExternal, + kFileSystemTypeNativeLocal, + base::FilePath( + DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(), + base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), + "system"); + + // No need to revoke the registered filesystem since |mount_points| lifetime + // is bound to this test. +} + +TEST_F(FileSystemContextTest, CrackFileSystemURL) { + scoped_refptr<ExternalMountPoints> external_mount_points( + ExternalMountPoints::CreateRefCounted()); + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(external_mount_points.get())); + + // Register an isolated mount point. + std::string isolated_file_system_name = "root"; + const std::string kIsolatedFileSystemID = + IsolatedContext::GetInstance()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root")), + &isolated_file_system_name); + // Register system external mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "system", + kFileSystemTypeDrive, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/sys/")))); + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "ext", + kFileSystemTypeNativeLocal, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/ext")))); + // Register a system external mount point with the same name/id as the + // registered isolated mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + kIsolatedFileSystemID, + kFileSystemTypeRestrictedNativeLocal, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/system/isolated")))); + // Add a mount points with the same name as a system mount point to + // FileSystemContext's external mount points. + ASSERT_TRUE(external_mount_points->RegisterFileSystem( + "ext", + kFileSystemTypeNativeLocal, + FileSystemMountOption(), + base::FilePath(DRIVE FPL("/test/local/ext/")))); + + const GURL kTestOrigin = GURL("http://chromium.org/"); + const base::FilePath kVirtualPathNoRoot = base::FilePath(FPL("root/file")); + + struct TestCase { + // Test case values. + std::string root; + std::string type_str; + + // Expected test results. + bool expect_is_valid; + FileSystemType expect_mount_type; + FileSystemType expect_type; + const base::FilePath::CharType* expect_path; + std::string expect_filesystem_id; + }; + + const TestCase kTestCases[] = { + // Following should not be handled by the url crackers: + { + "pers_mount", "persistent", true /* is_valid */, + kFileSystemTypePersistent, kFileSystemTypePersistent, + FPL("pers_mount/root/file"), + std::string() /* filesystem id */ + }, + { + "temp_mount", "temporary", true /* is_valid */, + kFileSystemTypeTemporary, kFileSystemTypeTemporary, + FPL("temp_mount/root/file"), + std::string() /* filesystem id */ + }, + // Should be cracked by isolated mount points: + { + kIsolatedFileSystemID, "isolated", true /* is_valid */, + kFileSystemTypeIsolated, kFileSystemTypeNativeLocal, + DRIVE FPL("/test/isolated/root/file"), + kIsolatedFileSystemID + }, + // Should be cracked by system mount points: + { + "system", "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeDrive, + DRIVE FPL("/test/sys/root/file"), + "system" + }, + { + kIsolatedFileSystemID, "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeRestrictedNativeLocal, + DRIVE FPL("/test/system/isolated/root/file"), + kIsolatedFileSystemID + }, + // Should be cracked by FileSystemContext's ExternalMountPoints. + { + "ext", "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeNativeLocal, + DRIVE FPL("/test/local/ext/root/file"), + "ext" + }, + // Test for invalid filesystem url (made invalid by adding invalid + // filesystem type). + { + "sytem", "external", false /* is_valid */, + // The rest of values will be ignored. + kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), + std::string() + }, + // Test for URL with non-existing filesystem id. + { + "invalid", "external", false /* is_valid */, + // The rest of values will be ignored. + kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), + std::string() + }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + const base::FilePath virtual_path = + base::FilePath::FromUTF8Unsafe( + kTestCases[i].root).Append(kVirtualPathNoRoot); + + GURL raw_url = + CreateRawFileSystemURL(kTestCases[i].type_str, kTestCases[i].root); + FileSystemURL cracked_url = file_system_context->CrackURL(raw_url); + + SCOPED_TRACE(testing::Message() << "Test case " << i << ": " + << "Cracking URL: " << raw_url); + + EXPECT_EQ(kTestCases[i].expect_is_valid, cracked_url.is_valid()); + if (!kTestCases[i].expect_is_valid) + continue; + + ExpectFileSystemURLMatches( + cracked_url, + GURL(kTestOrigin), + kTestCases[i].expect_mount_type, + kTestCases[i].expect_type, + base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(), + virtual_path.NormalizePathSeparators(), + kTestCases[i].expect_filesystem_id); + } + + IsolatedContext::GetInstance()->RevokeFileSystemByPath( + base::FilePath(DRIVE FPL("/test/isolated/root"))); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system"); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("ext"); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( + kIsolatedFileSystemID); +} + +TEST_F(FileSystemContextTest, CanServeURLRequest) { + scoped_refptr<ExternalMountPoints> external_mount_points( + ExternalMountPoints::CreateRefCounted()); + scoped_refptr<FileSystemContext> context( + CreateFileSystemContextForTest(external_mount_points.get())); + + // A request for a sandbox mount point should be served. + FileSystemURL cracked_url = + context->CrackURL(CreateRawFileSystemURL("persistent", "pers_mount")); + EXPECT_EQ(kFileSystemTypePersistent, cracked_url.mount_type()); + EXPECT_TRUE(context->CanServeURLRequest(cracked_url)); + + // A request for an isolated mount point should NOT be served. + std::string isolated_fs_name = "root"; + std::string isolated_fs_id = + IsolatedContext::GetInstance()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root")), + &isolated_fs_name); + cracked_url = context->CrackURL( + CreateRawFileSystemURL("isolated", isolated_fs_id)); + EXPECT_EQ(kFileSystemTypeIsolated, cracked_url.mount_type()); + EXPECT_FALSE(context->CanServeURLRequest(cracked_url)); + + // A request for an external mount point should be served. + const std::string kExternalMountName = "ext_mount"; + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + kExternalMountName, kFileSystemTypeDrive, FileSystemMountOption(), + base::FilePath())); + cracked_url = context->CrackURL( + CreateRawFileSystemURL("external", kExternalMountName)); + EXPECT_EQ(kFileSystemTypeExternal, cracked_url.mount_type()); + EXPECT_TRUE(context->CanServeURLRequest(cracked_url)); + + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( + kExternalMountName); + IsolatedContext::GetInstance()->RevokeFileSystem(isolated_fs_id); +} + +} // namespace + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_dir_url_request_job_unittest.cc b/chromium/content/browser/fileapi/file_system_dir_url_request_job_unittest.cc new file mode 100644 index 00000000000..874e61b0203 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_dir_url_request_job_unittest.cc @@ -0,0 +1,321 @@ +// 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 "webkit/browser/fileapi/file_system_dir_url_request_job.h" + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/base/request_priority.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/i18n/unicode/regex.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" + +namespace fileapi { +namespace { + +// We always use the TEMPORARY FileSystem in this test. +static const char kFileSystemURLPrefix[] = + "filesystem:http://remote/temporary/"; + +} // namespace + +class FileSystemDirURLRequestJobTest : public testing::Test { + protected: + FileSystemDirURLRequestJobTest() + : weak_factory_(this) { + } + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + special_storage_policy_ = new quota::MockSpecialStoragePolicy; + file_system_context_ = CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL("http://remote/"), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + + net::URLRequest::Deprecated::RegisterProtocolFactory( + "filesystem", &FileSystemDirURLRequestJobFactory); + } + + virtual void TearDown() OVERRIDE { + // NOTE: order matters, request must die before delegate + request_.reset(NULL); + delegate_.reset(NULL); + + net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL); + ClearUnusedJob(); + } + + void OnOpenFileSystem(const GURL& root_url, + const std::string& name, + base::PlatformFileError result) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + void TestRequestHelper(const GURL& url, bool run_to_completion, + FileSystemContext* file_system_context) { + delegate_.reset(new net::TestDelegate()); + delegate_->set_quit_on_redirect(true); + request_ = empty_context_.CreateRequest( + url, net::DEFAULT_PRIORITY, delegate_.get()); + job_ = new FileSystemDirURLRequestJob( + request_.get(), NULL, file_system_context); + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + if (run_to_completion) + base::MessageLoop::current()->Run(); + } + + void TestRequest(const GURL& url) { + TestRequestHelper(url, true, file_system_context_.get()); + } + + void TestRequestWithContext(const GURL& url, + FileSystemContext* file_system_context) { + TestRequestHelper(url, true, file_system_context); + } + + void TestRequestNoRun(const GURL& url) { + TestRequestHelper(url, false, file_system_context_.get()); + } + + FileSystemURL CreateURL(const base::FilePath& file_path) { + return file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + fileapi::kFileSystemTypeTemporary, + file_path); + } + + FileSystemOperationContext* NewOperationContext() { + FileSystemOperationContext* context( + new FileSystemOperationContext(file_system_context_.get())); + context->set_allowed_bytes_growth(1024); + return context; + } + + void CreateDirectory(const base::StringPiece& dir_name) { + base::FilePath path = base::FilePath().AppendASCII(dir_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->CreateDirectory( + context.get(), + CreateURL(path), + false /* exclusive */, + false /* recursive */)); + } + + void EnsureFileExists(const base::StringPiece file_name) { + base::FilePath path = base::FilePath().AppendASCII(file_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->EnsureFileExists( + context.get(), CreateURL(path), NULL)); + } + + void TruncateFile(const base::StringPiece file_name, int64 length) { + base::FilePath path = base::FilePath().AppendASCII(file_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->Truncate( + context.get(), CreateURL(path), length)); + } + + base::PlatformFileError GetFileInfo(const base::FilePath& path, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path) { + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + return file_util()->GetFileInfo(context.get(), + CreateURL(path), + file_info, platform_file_path); + } + + void VerifyListingEntry(const std::string& entry_line, + const std::string& name, + const std::string& url, + bool is_directory, + int64 size) { +#define STR "([^\"]*)" + icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR + "\",(0|1),\"" STR "\",\"" STR "\"\\);</script>"); +#undef STR + icu::UnicodeString input(entry_line.c_str()); + + UErrorCode status = U_ZERO_ERROR; + icu::RegexMatcher match(pattern, input, 0, status); + + EXPECT_TRUE(match.find()); + EXPECT_EQ(5, match.groupCount()); + EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status)); + EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status)); + EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"), + match.group(3, status)); + icu::UnicodeString size_string(FormatBytesUnlocalized(size).c_str()); + EXPECT_EQ(size_string, match.group(4, status)); + + base::Time date; + icu::UnicodeString date_ustr(match.group(5, status)); + std::string date_str; + UTF16ToUTF8(date_ustr.getBuffer(), date_ustr.length(), &date_str); + EXPECT_TRUE(base::Time::FromString(date_str.c_str(), &date)); + EXPECT_FALSE(date.is_null()); + } + + GURL CreateFileSystemURL(const std::string path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemDirURLRequestJobFactory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + static void ClearUnusedJob() { + if (job_) { + scoped_refptr<net::URLRequestJob> deleter = job_; + job_ = NULL; + } + } + + FileSystemFileUtil* file_util() { + return file_system_context_->sandbox_delegate()->sync_file_util(); + } + + // Put the message loop at the top, so that it's the last thing deleted. + // Delete all MessageLoopProxy objects before the MessageLoop, to help prevent + // leaks caused by tasks posted during shutdown. + base::MessageLoopForIO message_loop_; + + base::ScopedTempDir temp_dir_; + net::URLRequestContext empty_context_; + scoped_ptr<net::TestDelegate> delegate_; + scoped_ptr<net::URLRequest> request_; + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_; + + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemDirURLRequestJobTest::job_ = NULL; + +namespace { + +TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) { + CreateDirectory("foo"); + CreateDirectory("foo/bar"); + CreateDirectory("foo/bar/baz"); + + EnsureFileExists("foo/bar/hoge"); + TruncateFile("foo/bar/hoge", 10); + + TestRequest(CreateFileSystemURL("foo/bar/")); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_GT(delegate_->bytes_received(), 0); + + std::istringstream in(delegate_->data_received()); + std::string line; + EXPECT_TRUE(std::getline(in, line)); + +#if defined(OS_WIN) + EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line); +#elif defined(OS_POSIX) + EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line); +#endif + + EXPECT_TRUE(std::getline(in, line)); + VerifyListingEntry(line, "hoge", "hoge", false, 10); + + EXPECT_TRUE(std::getline(in, line)); + VerifyListingEntry(line, "baz", "baz", true, 0); +} + +TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) { + TestRequest(GURL("filesystem:/foo/bar/baz")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) { + TestRequest(GURL("filesystem:http://remote/persistent/somedir/")); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) { + TestRequest(CreateFileSystemURL("somedir/")); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, Cancel) { + CreateDirectory("foo"); + TestRequestNoRun(CreateFileSystemURL("foo/")); + // Run StartAsync() and only StartAsync(). + base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); + base::RunLoop().RunUntilIdle(); + // If we get here, success! we didn't crash! +} + +TEST_F(FileSystemDirURLRequestJobTest, Incognito) { + CreateDirectory("foo"); + + scoped_refptr<FileSystemContext> file_system_context = + CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path()); + + TestRequestWithContext(CreateFileSystemURL("/"), + file_system_context.get()); + ASSERT_FALSE(request_->is_pending()); + ASSERT_TRUE(request_->status().is_success()); + + std::istringstream in(delegate_->data_received()); + std::string line; + EXPECT_TRUE(std::getline(in, line)); + EXPECT_FALSE(std::getline(in, line)); + + TestRequestWithContext(CreateFileSystemURL("foo"), + file_system_context.get()); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +} // namespace (anonymous) +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_file_stream_reader_unittest.cc b/chromium/content/browser/fileapi/file_system_file_stream_reader_unittest.cc new file mode 100644 index 00000000000..3540d5b8c05 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_file_stream_reader_unittest.cc @@ -0,0 +1,266 @@ +// 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 "webkit/browser/fileapi/file_system_file_stream_reader.h" + +#include <limits> +#include <string> + +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" + +namespace fileapi { + +namespace { + +const char kURLOrigin[] = "http://remote/"; +const char kTestFileName[] = "test.dat"; +const char kTestData[] = "0123456789"; +const int kTestDataSize = arraysize(kTestData) - 1; + +void ReadFromReader(FileSystemFileStreamReader* reader, + std::string* data, + size_t size, + int* result) { + ASSERT_TRUE(reader != NULL); + ASSERT_TRUE(result != NULL); + *result = net::OK; + net::TestCompletionCallback callback; + size_t total_bytes_read = 0; + while (total_bytes_read < size) { + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(size - total_bytes_read)); + int rv = reader->Read(buf.get(), buf->size(), callback.callback()); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + if (rv < 0) + *result = rv; + if (rv <= 0) + break; + total_bytes_read += rv; + data->append(buf->data(), rv); + } +} + +void NeverCalled(int unused) { ADD_FAILURE(); } + +} // namespace + +class FileSystemFileStreamReaderTest : public testing::Test { + public: + FileSystemFileStreamReaderTest() {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL(kURLOrigin), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&OnOpenFileSystem)); + base::RunLoop().RunUntilIdle(); + + WriteFile(kTestFileName, kTestData, kTestDataSize, + &test_file_modification_time_); + } + + virtual void TearDown() OVERRIDE { + base::RunLoop().RunUntilIdle(); + } + + protected: + FileSystemFileStreamReader* CreateFileReader( + const std::string& file_name, + int64 initial_offset, + const base::Time& expected_modification_time) { + return new FileSystemFileStreamReader(file_system_context_.get(), + GetFileSystemURL(file_name), + initial_offset, + expected_modification_time); + } + + base::Time test_file_modification_time() const { + return test_file_modification_time_; + } + + void WriteFile(const std::string& file_name, + const char* buf, + int buf_size, + base::Time* modification_time) { + FileSystemURL url = GetFileSystemURL(file_name); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + fileapi::AsyncFileTestHelper::CreateFileWithData( + file_system_context_, url, buf, buf_size)); + + base::PlatformFileInfo file_info; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context_, url, &file_info)); + if (modification_time) + *modification_time = file_info.last_modified; + } + + private: + static void OnOpenFileSystem(const GURL& root_url, + const std::string& name, + base::PlatformFileError result) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + FileSystemURL GetFileSystemURL(const std::string& file_name) { + return file_system_context_->CreateCrackedFileSystemURL( + GURL(kURLOrigin), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(file_name)); + } + + base::MessageLoopForIO message_loop_; + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + base::Time test_file_modification_time_; +}; + +TEST_F(FileSystemFileStreamReaderTest, NonExistent) { + const char kFileName[] = "nonexistent"; + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); + ASSERT_EQ(0U, data.size()); +} + +TEST_F(FileSystemFileStreamReaderTest, Empty) { + const char kFileName[] = "empty"; + WriteFile(kFileName, NULL, 0, NULL); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(0U, data.size()); + + net::TestInt64CompletionCallback callback; + int64 length_result = reader->GetLength(callback.callback()); + if (length_result == net::ERR_IO_PENDING) + length_result = callback.WaitForResult(); + ASSERT_EQ(0, length_result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, test_file_modification_time())); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) { + // Pass a fake expected modifictaion time so that the expectation fails. + base::Time fake_expected_modification_time = + test_file_modification_time() - base::TimeDelta::FromSeconds(10); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + + // With NULL expected modification time this should work. + reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); + result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 3, base::Time())); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + // Initial offset does not affect the result of GetLength. + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadNormal) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(kTestData, data); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) { + // Pass a fake expected modifictaion time so that the expectation fails. + base::Time fake_expected_modification_time = + test_file_modification_time() - base::TimeDelta::FromSeconds(10); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + ASSERT_EQ(0U, data.size()); + + // With NULL expected modification time this should work. + data.clear(); + reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(kTestData, data); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 3, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(&kTestData[3], data); +} + +TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, base::Time())); + + net::TestCompletionCallback callback; + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(kTestDataSize)); + int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled)); + ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0); + + // Delete immediately. + // Should not crash; nor should NeverCalled be callback. + reader.reset(); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_operation_impl_unittest.cc b/chromium/content/browser/fileapi/file_system_operation_impl_unittest.cc new file mode 100644 index 00000000000..445657d9a01 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_operation_impl_unittest.cc @@ -0,0 +1,1241 @@ +// 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 "webkit/browser/fileapi/file_system_operation_impl.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "content/public/test/sandbox_file_system_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +using quota::QuotaManager; +using quota::QuotaManagerProxy; +using webkit_blob::ShareableFileReference; + +namespace fileapi { + +namespace { + +const int kFileOperationStatusNotSet = 1; + +void AssertFileErrorEq(const tracked_objects::Location& from_here, + base::PlatformFileError expected, + base::PlatformFileError actual) { + ASSERT_EQ(expected, actual) << from_here.ToString(); +} + +} // namespace (anonymous) + +// Test class for FileSystemOperationImpl. +class FileSystemOperationImplTest + : public testing::Test { + public: + FileSystemOperationImplTest() + : status_(kFileOperationStatusNotSet), + weak_factory_(this) {} + + protected: + virtual void SetUp() OVERRIDE { + EXPECT_TRUE(base_.CreateUniqueTempDir()); + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + + base::FilePath base_dir = base_.path().AppendASCII("filesystem"); + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + base_dir, + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + quota_manager_proxy_ = new quota::MockQuotaManagerProxy( + quota_manager(), base::MessageLoopProxy::current().get()); + sandbox_file_system_.SetUp(base_dir, quota_manager_proxy_.get()); + sandbox_file_system_.AddFileChangeObserver(&change_observer_); + } + + virtual void TearDown() OVERRIDE { + // Let the client go away before dropping a ref of the quota manager proxy. + quota_manager_proxy()->SimulateQuotaManagerDestroyed(); + quota_manager_ = NULL; + quota_manager_proxy_ = NULL; + sandbox_file_system_.TearDown(); + } + + FileSystemOperationRunner* operation_runner() { + return sandbox_file_system_.operation_runner(); + } + + int status() const { return status_; } + const base::PlatformFileInfo& info() const { return info_; } + const base::FilePath& path() const { return path_; } + const std::vector<DirectoryEntry>& entries() const { + return entries_; + } + + const ShareableFileReference* shareable_file_ref() const { + return shareable_file_ref_.get(); + } + + quota::MockQuotaManager* quota_manager() { + return static_cast<quota::MockQuotaManager*>(quota_manager_.get()); + } + + quota::MockQuotaManagerProxy* quota_manager_proxy() { + return static_cast<quota::MockQuotaManagerProxy*>( + quota_manager_proxy_.get()); + } + + FileSystemFileUtil* file_util() { + return sandbox_file_system_.file_util(); + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + scoped_ptr<FileSystemOperationContext> 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); + } + + FileSystemURL URLForPath(const std::string& path) const { + return sandbox_file_system_.CreateURLFromUTF8(path); + } + + base::FilePath PlatformPath(const std::string& path) { + return sandbox_file_system_.GetLocalPath( + base::FilePath::FromUTF8Unsafe(path)); + } + + bool FileExists(const std::string& path) { + return AsyncFileTestHelper::FileExists( + sandbox_file_system_.file_system_context(), URLForPath(path), + AsyncFileTestHelper::kDontCheckSize); + } + + bool DirectoryExists(const std::string& path) { + return AsyncFileTestHelper::DirectoryExists( + sandbox_file_system_.file_system_context(), URLForPath(path)); + } + + FileSystemURL CreateFile(const std::string& path) { + FileSystemURL url = URLForPath(path); + bool created = false; + EXPECT_EQ(base::PLATFORM_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::PLATFORM_FILE_OK, + file_util()->CreateDirectory(NewContext().get(), url, + false /* exclusive */, true)); + return url; + } + + int64 GetFileSize(const std::string& path) { + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetFileInfo(PlatformPath(path), &info)); + return info.size; + } + + // Callbacks for recording test results. + FileSystemOperation::StatusCallback RecordStatusCallback() { + return base::Bind(&FileSystemOperationImplTest::DidFinish, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::ReadDirectoryCallback + RecordReadDirectoryCallback() { + return base::Bind(&FileSystemOperationImplTest::DidReadDirectory, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::GetMetadataCallback RecordMetadataCallback() { + return base::Bind(&FileSystemOperationImplTest::DidGetMetadata, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::SnapshotFileCallback RecordSnapshotFileCallback() { + return base::Bind(&FileSystemOperationImplTest::DidCreateSnapshotFile, + weak_factory_.GetWeakPtr()); + } + + void DidFinish(base::PlatformFileError status) { + status_ = status; + } + + void DidReadDirectory( + base::PlatformFileError status, + const std::vector<DirectoryEntry>& entries, + bool /* has_more */) { + entries_ = entries; + status_ = status; + } + + void DidGetMetadata(base::PlatformFileError status, + const base::PlatformFileInfo& info) { + info_ = info; + status_ = status; + } + + void DidCreateSnapshotFile( + base::PlatformFileError status, + const base::PlatformFileInfo& info, + const base::FilePath& platform_path, + const scoped_refptr<ShareableFileReference>& shareable_file_ref) { + info_ = info; + path_ = platform_path; + status_ = status; + shareable_file_ref_ = shareable_file_ref; + } + + int64 GetDataSizeOnDisk() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + void GetUsageAndQuota(int64* usage, int64* quota) { + quota::QuotaStatusCode status = + AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(), + sandbox_file_system_.origin(), + sandbox_file_system_.type(), + usage, + quota); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(quota::kQuotaStatusOk, status); + } + + int64 ComputePathCost(const FileSystemURL& url) { + int64 base_usage; + GetUsageAndQuota(&base_usage, NULL); + + AsyncFileTestHelper::CreateFile( + sandbox_file_system_.file_system_context(), url); + operation_runner()->Remove(url, false /* recursive */, + base::Bind(&AssertFileErrorEq, FROM_HERE, + base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + change_observer()->ResetCount(); + + int64 total_usage; + GetUsageAndQuota(&total_usage, NULL); + return total_usage - base_usage; + } + + void GrantQuotaForCurrentUsage() { + int64 usage; + GetUsageAndQuota(&usage, NULL); + quota_manager()->SetQuota(sandbox_file_system_.origin(), + sandbox_file_system_.storage_type(), + usage); + } + + int64 GetUsage() { + int64 usage = 0; + GetUsageAndQuota(&usage, NULL); + return usage; + } + + void AddQuota(int64 quota_delta) { + int64 quota; + GetUsageAndQuota(NULL, "a); + quota_manager()->SetQuota(sandbox_file_system_.origin(), + sandbox_file_system_.storage_type(), + quota + quota_delta); + } + + base::MessageLoop message_loop_; + scoped_refptr<QuotaManager> quota_manager_; + scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; + + // Common temp base for nondestructive uses. + base::ScopedTempDir base_; + + SandboxFileSystemTestHelper sandbox_file_system_; + + // For post-operation status. + int status_; + base::PlatformFileInfo info_; + base::FilePath path_; + std::vector<DirectoryEntry> entries_; + scoped_refptr<ShareableFileReference> shareable_file_ref_; + + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + base::WeakPtrFactory<FileSystemOperationImplTest> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplTest); +}; + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDoesntExist) { + change_observer()->ResetCount(); + operation_runner()->Move(URLForPath("a"), URLForPath("b"), + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureContainsPath) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("src/dest")); + + operation_runner()->Move(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDirExistsDestFile) { + // Src exists and is dir. Dest is a file. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Move(src_dir, dest_file, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, + TestMoveFailureSrcFileExistsDestNonEmptyDir) { + // Src exists and is a directory. Dest is a non-empty directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Move(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcFileExistsDestDir) { + // Src exists and is a file. Dest is a directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL src_file(CreateFile("src/file")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_file, dest_dir, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + FileSystemURL src_dir(CreateDirectory("src")); + operation_runner()->Move(src_dir, URLForPath("nonexistent/deset"), + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndOverwrite) { + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_file(CreateFile("dest")); + + operation_runner()->Move(src_file, dest_file, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest")); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndNew) { + FileSystemURL src_file(CreateFile("src")); + + operation_runner()->Move(src_file, URLForPath("new"), + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndOverwrite) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("src")); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Make sure we've overwritten but not moved the source under the |dest_dir|. + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_FALSE(DirectoryExists("dest/src")); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndNew) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, URLForPath("dest/new"), + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("src")); + EXPECT_TRUE(DirectoryExists("dest/new")); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirRecursive) { + FileSystemURL src_dir(CreateDirectory("src")); + CreateDirectory("src/dir"); + CreateFile("src/dir/sub"); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/sub")); + + EXPECT_EQ(3, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDoesntExist) { + operation_runner()->Copy(URLForPath("a"), URLForPath("b"), + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureContainsPath) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("src/dir")); + + operation_runner()->Copy(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDirExistsDestFile) { + // Src exists and is dir. Dest is a file. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Copy(src_dir, dest_file, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, + TestCopyFailureSrcFileExistsDestNonEmptyDir) { + // Src exists and is a directory. Dest is a non-empty directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Copy(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcFileExistsDestDir) { + // Src exists and is a file. Dest is a directory. + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_file, dest_dir, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + FileSystemURL src_dir(CreateDirectory("src")); + + operation_runner()->Copy(src_dir, URLForPath("nonexistent/dest"), + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureByQuota) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL src_file(CreateFile("src/file")); + FileSystemURL dest_dir(CreateDirectory("dest")); + operation_runner()->Truncate(src_file, 6, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(6, GetFileSize("src/file")); + + FileSystemURL dest_file(URLForPath("dest/file")); + int64 dest_path_cost = ComputePathCost(dest_file); + GrantQuotaForCurrentUsage(); + AddQuota(6 + dest_path_cost - 1); + + operation_runner()->Copy(src_file, dest_file, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_FALSE(FileExists("dest/file")); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndOverwrite) { + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_file(CreateFile("dest")); + + operation_runner()->Copy(src_file, dest_file, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest")); + EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndNew) { + FileSystemURL src_file(CreateFile("src")); + + operation_runner()->Copy(src_file, URLForPath("new"), + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count()); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndOverwrite) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + // Make sure we've overwritten but not copied the source under the |dest_dir|. + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_FALSE(DirectoryExists("dest/src")); + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 3); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndNew) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir_new(URLForPath("dest")); + + operation_runner()->Copy(src_dir, dest_dir_new, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 2); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirRecursive) { + FileSystemURL src_dir(CreateDirectory("src")); + CreateDirectory("src/dir"); + CreateFile("src/dir/sub"); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_dir, dest_dir, + FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/sub")); + + // For recursive copy we may record multiple read access. + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 1); + + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileSuccess) { + base::FilePath src_local_disk_file_path; + base::CreateTemporaryFile(&src_local_disk_file_path); + const char test_data[] = "foo"; + int data_size = ARRAYSIZE_UNSAFE(test_data); + file_util::WriteFile(src_local_disk_file_path, test_data, data_size); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + int64 before_usage; + GetUsageAndQuota(&before_usage, NULL); + + // Check that the file copied and corresponding usage increased. + operation_runner()->CopyInForeignFile(src_local_disk_file_path, + URLForPath("dest/file"), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, change_observer()->create_file_count()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest/file")); + int64 after_usage; + GetUsageAndQuota(&after_usage, NULL); + EXPECT_GT(after_usage, before_usage); + + // Compare contents of src and copied file. + char buffer[100]; + EXPECT_EQ(data_size, base::ReadFile(PlatformPath("dest/file"), + buffer, data_size)); + for (int i = 0; i < data_size; ++i) + EXPECT_EQ(test_data[i], buffer[i]); +} + +TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileFailureByQuota) { + base::FilePath src_local_disk_file_path; + base::CreateTemporaryFile(&src_local_disk_file_path); + const char test_data[] = "foo"; + file_util::WriteFile(src_local_disk_file_path, test_data, + ARRAYSIZE_UNSAFE(test_data)); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + GrantQuotaForCurrentUsage(); + operation_runner()->CopyInForeignFile(src_local_disk_file_path, + URLForPath("dest/file"), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(FileExists("dest/file")); + EXPECT_EQ(0, change_observer()->create_file_count()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileFailure) { + // Already existing file and exclusive true. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateFile(file, true, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileExists) { + // Already existing file and exclusive false. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateFile(file, false, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("file")); + + // The file was already there; did nothing. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessExclusive) { + // File doesn't exist but exclusive is true. + operation_runner()->CreateFile(URLForPath("new"), true, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileDoesntExist) { + // Non existing file. + operation_runner()->CreateFile(URLForPath("nonexistent"), false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); +} + +TEST_F(FileSystemOperationImplTest, + TestCreateDirFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + operation_runner()->CreateDirectory( + URLForPath("nonexistent/dir"), false, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirFailureDirExists) { + // Exclusive and dir existing at path. + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->CreateDirectory(dir, true, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirFailureFileExists) { + // Exclusive true and file existing at path. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateDirectory(file, true, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirSuccess) { + // Dir exists and exclusive is false. + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->CreateDirectory(dir, false, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Dir doesn't exist. + operation_runner()->CreateDirectory(URLForPath("new"), false, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirSuccessExclusive) { + // Dir doesn't exist. + operation_runner()->CreateDirectory(URLForPath("new"), true, false, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataFailure) { + operation_runner()->GetMetadata(URLForPath("nonexistent"), + RecordMetadataCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + operation_runner()->FileExists(URLForPath("nonexistent"), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + operation_runner()->DirectoryExists(URLForPath("nonexistent"), + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataSuccess) { + FileSystemURL dir(CreateDirectory("dir")); + FileSystemURL file(CreateFile("dir/file")); + int read_access = 0; + + operation_runner()->DirectoryExists(dir, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + ++read_access; + + operation_runner()->GetMetadata(dir, RecordMetadataCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(info().is_directory); + ++read_access; + + operation_runner()->FileExists(file, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + ++read_access; + + operation_runner()->GetMetadata(file, RecordMetadataCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + ++read_access; + + EXPECT_EQ(read_access, + quota_manager_proxy()->notify_storage_accessed_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestTypeMismatchErrors) { + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->FileExists(dir, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, status()); + + FileSystemURL file(CreateFile("file")); + operation_runner()->DirectoryExists(file, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status()); +} + +TEST_F(FileSystemOperationImplTest, TestReadDirFailure) { + // Path doesn't exist + operation_runner()->ReadDirectory(URLForPath("nonexistent"), + RecordReadDirectoryCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + // File exists. + FileSystemURL file(CreateFile("file")); + operation_runner()->ReadDirectory(file, RecordReadDirectoryCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestReadDirSuccess) { + // parent_dir + // | | + // child_dir child_file + // Verify reading parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + FileSystemURL child_file(CreateFile("dir/child_file")); + + operation_runner()->ReadDirectory(parent_dir, RecordReadDirectoryCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(2u, entries().size()); + + for (size_t i = 0; i < entries().size(); ++i) { + if (entries()[i].is_directory) + EXPECT_EQ(FILE_PATH_LITERAL("child_dir"), entries()[i].name); + else + EXPECT_EQ(FILE_PATH_LITERAL("child_file"), entries()[i].name); + } + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveFailure) { + // Path doesn't exist. + operation_runner()->Remove(URLForPath("nonexistent"), false /* recursive */, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + // It's an error to try to remove a non-empty directory if recursive flag + // is false. + // parent_dir + // | | + // child_dir child_file + // Verify deleting parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + FileSystemURL child_file(CreateFile("dir/child_file")); + + operation_runner()->Remove(parent_dir, false /* recursive */, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveSuccess) { + FileSystemURL empty_dir(CreateDirectory("empty_dir")); + EXPECT_TRUE(DirectoryExists("empty_dir")); + operation_runner()->Remove(empty_dir, false /* recursive */, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("empty_dir")); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveSuccessRecursive) { + // Removing a non-empty directory with recursive flag == true should be ok. + // parent_dir + // | | + // child_dir child_files + // | + // child_files + // + // Verify deleting parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + for (int i = 0; i < 8; ++i) + CreateFile(base::StringPrintf("dir/file-%d", i)); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + for (int i = 0; i < 8; ++i) + CreateFile(base::StringPrintf("dir/child_dir/file-%d", i)); + + operation_runner()->Remove(parent_dir, true /* recursive */, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("parent_dir")); + + EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(16, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestTruncate) { + FileSystemURL file(CreateFile("file")); + base::FilePath platform_path = PlatformPath("file"); + + char test_data[] = "test data"; + int data_size = static_cast<int>(sizeof(test_data)); + EXPECT_EQ(data_size, + file_util::WriteFile(platform_path, test_data, data_size)); + + // Check that its length is the size of the data written. + operation_runner()->GetMetadata(file, RecordMetadataCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + EXPECT_EQ(data_size, info().size); + + // Extend the file by truncating it. + int length = 17; + operation_runner()->Truncate(file, length, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Check that its length is now 17 and that it's all zeroes after the test + // data. + EXPECT_EQ(length, GetFileSize("file")); + char data[100]; + EXPECT_EQ(length, base::ReadFile(platform_path, data, length)); + for (int i = 0; i < length; ++i) { + if (i < static_cast<int>(sizeof(test_data))) + EXPECT_EQ(test_data[i], data[i]); + else + EXPECT_EQ(0, data[i]); + } + + // Shorten the file by truncating it. + length = 3; + operation_runner()->Truncate(file, length, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Check that its length is now 3 and that it contains only bits of test data. + EXPECT_EQ(length, GetFileSize("file")); + EXPECT_EQ(length, base::ReadFile(platform_path, data, length)); + for (int i = 0; i < length; ++i) + EXPECT_EQ(test_data[i], data[i]); + + // Truncate is not a 'read' access. (Here expected access count is 1 + // since we made 1 read access for GetMetadata.) + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); +} + +TEST_F(FileSystemOperationImplTest, TestTruncateFailureByQuota) { + FileSystemURL dir(CreateDirectory("dir")); + FileSystemURL file(CreateFile("dir/file")); + + GrantQuotaForCurrentUsage(); + AddQuota(10); + + operation_runner()->Truncate(file, 10, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(10, GetFileSize("dir/file")); + + operation_runner()->Truncate(file, 11, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(10, GetFileSize("dir/file")); +} + +TEST_F(FileSystemOperationImplTest, TestTouchFile) { + FileSystemURL file(CreateFile("file")); + base::FilePath platform_path = PlatformPath("file"); + + base::PlatformFileInfo info; + EXPECT_TRUE(base::GetFileInfo(platform_path, &info)); + EXPECT_FALSE(info.is_directory); + EXPECT_EQ(0, info.size); + const base::Time last_modified = info.last_modified; + const base::Time last_accessed = info.last_accessed; + + const base::Time new_modified_time = base::Time::UnixEpoch(); + const base::Time new_accessed_time = new_modified_time + + base::TimeDelta::FromHours(77); + ASSERT_NE(last_modified, new_modified_time); + ASSERT_NE(last_accessed, new_accessed_time); + + operation_runner()->TouchFile(file, new_accessed_time, new_modified_time, + RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_TRUE(base::GetFileInfo(platform_path, &info)); + // We compare as time_t here to lower our resolution, to avoid false + // negatives caused by conversion to the local filesystem's native + // representation and back. + EXPECT_EQ(new_modified_time.ToTimeT(), info.last_modified.ToTimeT()); + EXPECT_EQ(new_accessed_time.ToTimeT(), info.last_accessed.ToTimeT()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateSnapshotFile) { + FileSystemURL dir(CreateDirectory("dir")); + + // Create a file for the testing. + operation_runner()->DirectoryExists(dir, RecordStatusCallback()); + FileSystemURL file(CreateFile("dir/file")); + operation_runner()->FileExists(file, RecordStatusCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + // See if we can get a 'snapshot' file info for the file. + // Since FileSystemOperationImpl assumes the file exists in the local + // directory it should just returns the same metadata and platform_path + // as the file itself. + operation_runner()->CreateSnapshotFile(file, RecordSnapshotFileCallback()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + EXPECT_EQ(PlatformPath("dir/file"), path()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // The FileSystemOpration implementation does not create a + // shareable file reference. + EXPECT_EQ(NULL, shareable_file_ref()); +} + +TEST_F(FileSystemOperationImplTest, + TestMoveSuccessSrcDirRecursiveWithQuota) { + FileSystemURL src(CreateDirectory("src")); + int src_path_cost = GetUsage(); + + FileSystemURL dest(CreateDirectory("dest")); + FileSystemURL child_file1(CreateFile("src/file1")); + FileSystemURL child_file2(CreateFile("src/file2")); + FileSystemURL child_dir(CreateDirectory("src/dir")); + FileSystemURL grandchild_file1(CreateFile("src/dir/file1")); + FileSystemURL grandchild_file2(CreateFile("src/dir/file2")); + + int total_path_cost = GetUsage(); + EXPECT_EQ(0, GetDataSizeOnDisk()); + + operation_runner()->Truncate( + child_file1, 5000, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + child_file2, 400, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file1, 30, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file2, 2, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + + const int64 all_file_size = 5000 + 400 + 30 + 2; + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(all_file_size + total_path_cost, GetUsage()); + + operation_runner()->Move( + src, dest, FileSystemOperation::OPTION_NONE, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(DirectoryExists("src/dir")); + EXPECT_FALSE(FileExists("src/dir/file2")); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/file2")); + + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(all_file_size + total_path_cost - src_path_cost, + GetUsage()); +} + +TEST_F(FileSystemOperationImplTest, + TestCopySuccessSrcDirRecursiveWithQuota) { + FileSystemURL src(CreateDirectory("src")); + FileSystemURL dest1(CreateDirectory("dest1")); + FileSystemURL dest2(CreateDirectory("dest2")); + + int64 usage = GetUsage(); + FileSystemURL child_file1(CreateFile("src/file1")); + FileSystemURL child_file2(CreateFile("src/file2")); + FileSystemURL child_dir(CreateDirectory("src/dir")); + int64 child_path_cost = GetUsage() - usage; + usage += child_path_cost; + + FileSystemURL grandchild_file1(CreateFile("src/dir/file1")); + FileSystemURL grandchild_file2(CreateFile("src/dir/file2")); + int64 total_path_cost = GetUsage(); + int64 grandchild_path_cost = total_path_cost - usage; + + EXPECT_EQ(0, GetDataSizeOnDisk()); + + operation_runner()->Truncate( + child_file1, 8000, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + child_file2, 700, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file1, 60, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file2, 5, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + + const int64 child_file_size = 8000 + 700; + const int64 grandchild_file_size = 60 + 5; + const int64 all_file_size = child_file_size + grandchild_file_size; + int64 expected_usage = all_file_size + total_path_cost; + + usage = GetUsage(); + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, usage); + + // Copy src to dest1. + operation_runner()->Copy( + src, dest1, FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + + expected_usage += all_file_size + child_path_cost + grandchild_path_cost; + EXPECT_TRUE(DirectoryExists("src/dir")); + EXPECT_TRUE(FileExists("src/dir/file2")); + EXPECT_TRUE(DirectoryExists("dest1/dir")); + EXPECT_TRUE(FileExists("dest1/dir/file2")); + + EXPECT_EQ(2 * all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, GetUsage()); + + // Copy src/dir to dest2. + operation_runner()->Copy( + child_dir, dest2, FileSystemOperation::OPTION_NONE, + FileSystemOperationRunner::CopyProgressCallback(), + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::RunLoop().RunUntilIdle(); + + expected_usage += grandchild_file_size + grandchild_path_cost; + usage = GetUsage(); + EXPECT_EQ(2 * child_file_size + 3 * grandchild_file_size, + GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, usage); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_operation_impl_write_unittest.cc b/chromium/content/browser/fileapi/file_system_operation_impl_write_unittest.cc new file mode 100644 index 00000000000..a92288af0fd --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_operation_impl_write_unittest.cc @@ -0,0 +1,328 @@ +// 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/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_backend.h" +#include "content/public/test/test_file_system_context.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/blob/blob_storage_context.h" +#include "webkit/browser/blob/blob_url_request_job.h" +#include "webkit/browser/blob/mock_blob_url_request_context.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/common/blob/blob_data.h" +#include "webkit/common/fileapi/file_system_util.h" + +using webkit_blob::MockBlobURLRequestContext; +using webkit_blob::ScopedTextBlob; + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://example.com"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +void AssertStatusEq(base::PlatformFileError expected, + base::PlatformFileError actual) { + ASSERT_EQ(expected, actual); +} + +} // namespace + +class FileSystemOperationImplWriteTest + : public testing::Test { + public: + FileSystemOperationImplWriteTest() + : status_(base::PLATFORM_FILE_OK), + cancel_status_(base::PLATFORM_FILE_ERROR_FAILED), + bytes_written_(0), + complete_(false), + weak_factory_(this) { + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + } + + virtual void SetUp() { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file")); + + file_system_context_ = CreateFileSystemContextForTesting( + quota_manager_->proxy(), dir_.path()); + url_request_context_.reset( + new MockBlobURLRequestContext(file_system_context_.get())); + + file_system_context_->operation_runner()->CreateFile( + URLForPath(virtual_path_), true /* exclusive */, + base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); + + static_cast<TestFileSystemBackend*>( + file_system_context_->GetFileSystemBackend(kFileSystemType)) + ->AddFileChangeObserver(change_observer()); + } + + virtual void TearDown() { + quota_manager_ = NULL; + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + base::PlatformFileError status() const { return status_; } + base::PlatformFileError cancel_status() const { return cancel_status_; } + void add_bytes_written(int64 bytes, bool complete) { + bytes_written_ += bytes; + EXPECT_FALSE(complete_); + complete_ = complete; + } + int64 bytes_written() const { return bytes_written_; } + bool complete() const { return complete_; } + + protected: + const ChangeObserverList& change_observers() const { + return change_observers_; + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + FileSystemURL URLForPath(const base::FilePath& path) const { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, path); + } + + // Callback function for recording test results. + FileSystemOperation::WriteCallback RecordWriteCallback() { + return base::Bind(&FileSystemOperationImplWriteTest::DidWrite, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::StatusCallback RecordCancelCallback() { + return base::Bind(&FileSystemOperationImplWriteTest::DidCancel, + weak_factory_.GetWeakPtr()); + } + + void DidWrite(base::PlatformFileError status, int64 bytes, bool complete) { + if (status == base::PLATFORM_FILE_OK) { + add_bytes_written(bytes, complete); + if (complete) + base::MessageLoop::current()->Quit(); + } else { + EXPECT_FALSE(complete_); + EXPECT_EQ(status_, base::PLATFORM_FILE_OK); + complete_ = true; + status_ = status; + if (base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + } + + void DidCancel(base::PlatformFileError status) { + cancel_status_ = status; + } + + const MockBlobURLRequestContext& url_request_context() const { + return *url_request_context_; + } + + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<quota::MockQuotaManager> quota_manager_; + + base::MessageLoopForIO loop_; + + base::ScopedTempDir dir_; + base::FilePath virtual_path_; + + // For post-operation status. + base::PlatformFileError status_; + base::PlatformFileError cancel_status_; + int64 bytes_written_; + bool complete_; + + scoped_ptr<MockBlobURLRequestContext> url_request_context_; + + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + base::WeakPtrFactory<FileSystemOperationImplWriteTest> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplWriteTest); +}; + +TEST_F(FileSystemOperationImplWriteTest, TestWriteSuccess) { + ScopedTextBlob blob(url_request_context(), + "blob-id:success", + "Hello, world!\n"); + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob.GetBlobDataHandle(), + 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(14, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteZero) { + ScopedTextBlob blob(url_request_context(), "blob_id:zero", ""); + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + + +TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidBlobUrl) { + scoped_ptr<webkit_blob::BlobDataHandle> null_handle; + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + null_handle.Pass(), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_FAILED, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidFile) { + ScopedTextBlob blob(url_request_context(), "blob_id:writeinvalidfile", + "It\'ll not be written."); + file_system_context_->operation_runner()->Write( + &url_request_context(), + URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteDir) { + base::FilePath virtual_dir_path(FILE_PATH_LITERAL("d")); + file_system_context_->operation_runner()->CreateDirectory( + URLForPath(virtual_dir_path), + true /* exclusive */, false /* recursive */, + base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); + + ScopedTextBlob blob(url_request_context(), "blob:writedir", + "It\'ll not be written, too."); + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_dir_path), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + // TODO(kinuko): This error code is platform- or fileutil- dependent + // right now. Make it return PLATFORM_FILE_ERROR_NOT_A_FILE in every case. + EXPECT_TRUE(status() == base::PLATFORM_FILE_ERROR_NOT_A_FILE || + status() == base::PLATFORM_FILE_ERROR_ACCESS_DENIED || + status() == base::PLATFORM_FILE_ERROR_FAILED); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) { + ScopedTextBlob blob(url_request_context(), "blob:success", + "Hello, world!\n"); + quota_manager_->SetQuota( + kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10); + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(10, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelSuccessfulWrite) { + ScopedTextBlob blob(url_request_context(), "blob:success", + "Hello, world!\n"); + FileSystemOperationRunner::OperationID id = + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback()); + // We use RunAllPendings() instead of Run() here, because we won't dispatch + // callbacks after Cancel() is issued (so no chance to Quit) nor do we need + // to run another write cycle. + base::RunLoop().RunUntilIdle(); + + // Issued Cancel() before receiving any response from Write(), + // so nothing should have happen. + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status()); + EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelFailingWrite) { + ScopedTextBlob blob(url_request_context(), "blob:writeinvalidfile", + "It\'ll not be written."); + FileSystemOperationRunner::OperationID id = + file_system_context_->operation_runner()->Write( + &url_request_context(), + URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))), + blob.GetBlobDataHandle(), 0, RecordWriteCallback()); + file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback()); + // We use RunAllPendings() instead of Run() here, because we won't dispatch + // callbacks after Cancel() is issued (so no chance to Quit) nor do we need + // to run another write cycle. + base::RunLoop().RunUntilIdle(); + + // Issued Cancel() before receiving any response from Write(), + // so nothing should have happen. + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status()); + EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +// TODO(ericu,dmikurube,kinuko): Add more tests for cancel cases. + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_operation_runner_unittest.cc b/chromium/content/browser/fileapi/file_system_operation_runner_unittest.cc new file mode 100644 index 00000000000..1fa711b8672 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_operation_runner_unittest.cc @@ -0,0 +1,162 @@ +// 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 "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +namespace fileapi { + +void GetStatus(bool* done, + base::PlatformFileError *status_out, + base::PlatformFileError status) { + ASSERT_FALSE(*done); + *done = true; + *status_out = status; +} + +void GetCancelStatus(bool* operation_done, + bool* cancel_done, + base::PlatformFileError *status_out, + base::PlatformFileError status) { + // Cancel callback must be always called after the operation's callback. + ASSERT_TRUE(*operation_done); + ASSERT_FALSE(*cancel_done); + *cancel_done = true; + *status_out = status; +} + +class FileSystemOperationRunnerTest : public testing::Test { + protected: + FileSystemOperationRunnerTest() {} + virtual ~FileSystemOperationRunnerTest() {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(base_.CreateUniqueTempDir()); + base::FilePath base_dir = base_.path(); + file_system_context_ = + CreateFileSystemContextForTesting(NULL, base_dir); + } + + virtual void TearDown() OVERRIDE { + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + FileSystemURL URL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + GURL("http://example.com"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe(path)); + } + + FileSystemOperationRunner* operation_runner() { + return file_system_context_->operation_runner(); + } + + private: + base::ScopedTempDir base_; + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunnerTest); +}; + +TEST_F(FileSystemOperationRunnerTest, NotFoundError) { + bool done = false; + base::PlatformFileError status = base::PLATFORM_FILE_ERROR_FAILED; + + // Regular NOT_FOUND error, which is called asynchronously. + operation_runner()->Truncate(URL("foo"), 0, + base::Bind(&GetStatus, &done, &status)); + ASSERT_FALSE(done); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(done); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status); +} + +TEST_F(FileSystemOperationRunnerTest, InvalidURLError) { + bool done = false; + base::PlatformFileError status = base::PLATFORM_FILE_ERROR_FAILED; + + // Invalid URL error, which calls DidFinish synchronously. + operation_runner()->Truncate(FileSystemURL(), 0, + base::Bind(&GetStatus, &done, &status)); + // The error call back shouldn't be fired synchronously. + ASSERT_FALSE(done); + + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(done); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_URL, status); +} + +TEST_F(FileSystemOperationRunnerTest, NotFoundErrorAndCancel) { + bool done = false; + bool cancel_done = false; + base::PlatformFileError status = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFileError cancel_status = base::PLATFORM_FILE_ERROR_FAILED; + + // Call Truncate with non-existent URL, and try to cancel it immediately + // after that (before its callback is fired). + FileSystemOperationRunner::OperationID id = + operation_runner()->Truncate(URL("foo"), 0, + base::Bind(&GetStatus, &done, &status)); + operation_runner()->Cancel(id, base::Bind(&GetCancelStatus, + &done, &cancel_done, + &cancel_status)); + + ASSERT_FALSE(done); + ASSERT_FALSE(cancel_done); + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(done); + ASSERT_TRUE(cancel_done); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, cancel_status); +} + +TEST_F(FileSystemOperationRunnerTest, InvalidURLErrorAndCancel) { + bool done = false; + bool cancel_done = false; + base::PlatformFileError status = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFileError cancel_status = base::PLATFORM_FILE_ERROR_FAILED; + + // Call Truncate with invalid URL, and try to cancel it immediately + // after that (before its callback is fired). + FileSystemOperationRunner::OperationID id = + operation_runner()->Truncate(FileSystemURL(), 0, + base::Bind(&GetStatus, &done, &status)); + operation_runner()->Cancel(id, base::Bind(&GetCancelStatus, + &done, &cancel_done, + &cancel_status)); + + ASSERT_FALSE(done); + ASSERT_FALSE(cancel_done); + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(done); + ASSERT_TRUE(cancel_done); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_URL, status); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, cancel_status); +} + +TEST_F(FileSystemOperationRunnerTest, CancelWithInvalidId) { + const FileSystemOperationRunner::OperationID kInvalidId = -1; + bool done = true; // The operation is not running. + bool cancel_done = false; + base::PlatformFileError cancel_status = base::PLATFORM_FILE_ERROR_FAILED; + operation_runner()->Cancel(kInvalidId, base::Bind(&GetCancelStatus, + &done, &cancel_done, + &cancel_status)); + + ASSERT_TRUE(cancel_done); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, cancel_status); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_quota_client_unittest.cc b/chromium/content/browser/fileapi/file_system_quota_client_unittest.cc new file mode 100644 index 00000000000..c4baa4f8aa6 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_quota_client_unittest.cc @@ -0,0 +1,561 @@ +// 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 "base/basictypes.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_quota_client.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { +namespace { + +const char kDummyURL1[] = "http://www.dummy.org"; +const char kDummyURL2[] = "http://www.example.com"; +const char kDummyURL3[] = "http://www.bleh"; + +// Declared to shorten the variable names. +const quota::StorageType kTemporary = quota::kStorageTypeTemporary; +const quota::StorageType kPersistent = quota::kStorageTypePersistent; + +} // namespace + +class FileSystemQuotaClientTest : public testing::Test { + public: + FileSystemQuotaClientTest() + : weak_factory_(this), + additional_callback_count_(0), + deletion_status_(quota::kQuotaStatusUnknown) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + file_system_context_ = CreateFileSystemContextForTesting( + NULL, data_dir_.path()); + } + + struct TestFile { + bool isDirectory; + const char* name; + int64 size; + const char* origin_url; + quota::StorageType type; + }; + + protected: + FileSystemQuotaClient* NewQuotaClient(bool is_incognito) { + return new FileSystemQuotaClient(file_system_context_.get(), is_incognito); + } + + void GetOriginUsageAsync(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + quota_client->GetOriginUsage( + GURL(origin_url), type, + base::Bind(&FileSystemQuotaClientTest::OnGetUsage, + weak_factory_.GetWeakPtr())); + } + + int64 GetOriginUsage(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + GetOriginUsageAsync(quota_client, origin_url, type); + base::RunLoop().RunUntilIdle(); + return usage_; + } + + const std::set<GURL>& GetOriginsForType(FileSystemQuotaClient* quota_client, + quota::StorageType type) { + origins_.clear(); + quota_client->GetOriginsForType( + type, + base::Bind(&FileSystemQuotaClientTest::OnGetOrigins, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return origins_; + } + + const std::set<GURL>& GetOriginsForHost(FileSystemQuotaClient* quota_client, + quota::StorageType type, + const std::string& host) { + origins_.clear(); + quota_client->GetOriginsForHost( + type, host, + base::Bind(&FileSystemQuotaClientTest::OnGetOrigins, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + return origins_; + } + + void RunAdditionalOriginUsageTask(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + quota_client->GetOriginUsage( + GURL(origin_url), type, + base::Bind(&FileSystemQuotaClientTest::OnGetAdditionalUsage, + weak_factory_.GetWeakPtr())); + } + + bool CreateFileSystemDirectory(const base::FilePath& file_path, + const std::string& origin_url, + quota::StorageType storage_type) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL(origin_url), type, file_path); + + base::PlatformFileError result = + AsyncFileTestHelper::CreateDirectory(file_system_context_, url); + return result == base::PLATFORM_FILE_OK; + } + + bool CreateFileSystemFile(const base::FilePath& file_path, + int64 file_size, + const std::string& origin_url, + quota::StorageType storage_type) { + if (file_path.empty()) + return false; + + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL(origin_url), type, file_path); + + base::PlatformFileError result = + AsyncFileTestHelper::CreateFile(file_system_context_, url); + if (result != base::PLATFORM_FILE_OK) + return false; + + result = AsyncFileTestHelper::TruncateFile( + file_system_context_, url, file_size); + return result == base::PLATFORM_FILE_OK; + } + + void InitializeOriginFiles(FileSystemQuotaClient* quota_client, + const TestFile* files, + int num_files) { + for (int i = 0; i < num_files; i++) { + base::FilePath path = base::FilePath().AppendASCII(files[i].name); + if (files[i].isDirectory) { + ASSERT_TRUE(CreateFileSystemDirectory( + path, files[i].origin_url, files[i].type)); + if (path.empty()) { + // Create the usage cache. + // HACK--we always create the root [an empty path] first. If we + // create it later, this will fail due to a quota mismatch. If we + // call this before we create the root, it succeeds, but hasn't + // actually created the cache. + ASSERT_EQ(0, GetOriginUsage( + quota_client, files[i].origin_url, files[i].type)); + } + } else { + ASSERT_TRUE(CreateFileSystemFile( + path, files[i].size, files[i].origin_url, files[i].type)); + } + } + } + + // This is a bit fragile--it depends on the test data always creating a + // directory before adding a file or directory to it, so that we can just + // count the basename of each addition. A recursive creation of a path, which + // created more than one directory in a single shot, would break this. + int64 ComputeFilePathsCostForOriginAndType(const TestFile* files, + int num_files, + const std::string& origin_url, + quota::StorageType type) { + int64 file_paths_cost = 0; + for (int i = 0; i < num_files; i++) { + if (files[i].type == type && + GURL(files[i].origin_url) == GURL(origin_url)) { + base::FilePath path = base::FilePath().AppendASCII(files[i].name); + if (!path.empty()) { + file_paths_cost += ObfuscatedFileUtil::ComputeFilePathCost(path); + } + } + } + return file_paths_cost; + } + + void DeleteOriginData(FileSystemQuotaClient* quota_client, + const std::string& origin, + quota::StorageType type) { + deletion_status_ = quota::kQuotaStatusUnknown; + quota_client->DeleteOriginData( + GURL(origin), type, + base::Bind(&FileSystemQuotaClientTest::OnDeleteOrigin, + weak_factory_.GetWeakPtr())); + } + + int64 usage() const { return usage_; } + quota::QuotaStatusCode status() { return deletion_status_; } + int additional_callback_count() const { return additional_callback_count_; } + void set_additional_callback_count(int count) { + additional_callback_count_ = count; + } + + private: + void OnGetUsage(int64 usage) { + usage_ = usage; + } + + void OnGetOrigins(const std::set<GURL>& origins) { + origins_ = origins; + } + + void OnGetAdditionalUsage(int64 usage_unused) { + ++additional_callback_count_; + } + + void OnDeleteOrigin(quota::QuotaStatusCode status) { + deletion_status_ = status; + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemQuotaClientTest> weak_factory_; + int64 usage_; + int additional_callback_count_; + std::set<GURL> origins_; + quota::QuotaStatusCode deletion_status_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClientTest); +}; + +TEST_F(FileSystemQuotaClientTest, NoFileSystemTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); +} + +TEST_F(FileSystemQuotaClientTest, NoFileTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, OneFileTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 4921, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(4921 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, TwoFilesTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 10310, kDummyURL1, kTemporary}, + {false, "bar", 41, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(10310 + 41 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 0, kDummyURL1, kTemporary}, + {false, "bar", 0, kDummyURL1, kTemporary}, + {false, "baz", 0, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dirtest", 0, kDummyURL1, kTemporary}, + {false, "dirtest/foo", 11921, kDummyURL1, kTemporary}, + {false, "bar", 4814, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(11921 + 4814 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, MultiTypeTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dirtest", 0, kDummyURL1, kTemporary}, + {false, "dirtest/foo", 133, kDummyURL1, kTemporary}, + {false, "bar", 14, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL1, kPersistent}, + {true, "dirtest", 0, kDummyURL1, kPersistent}, + {false, "dirtest/foo", 193, kDummyURL1, kPersistent}, + {false, "bar", 9, kDummyURL1, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + const int64 file_paths_cost_persistent = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(133 + 14 + file_paths_cost_temporary, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(193 + 9 + file_paths_cost_persistent, + GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + } +} + +TEST_F(FileSystemQuotaClientTest, MultiDomainTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dir1", 0, kDummyURL1, kTemporary}, + {false, "dir1/foo", 1331, kDummyURL1, kTemporary}, + {false, "bar", 134, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL1, kPersistent}, + {true, "dir2", 0, kDummyURL1, kPersistent}, + {false, "dir2/foo", 1903, kDummyURL1, kPersistent}, + {false, "bar", 19, kDummyURL1, kPersistent}, + {true, NULL, 0, kDummyURL2, kTemporary}, + {true, "dom", 0, kDummyURL2, kTemporary}, + {false, "dom/fan", 1319, kDummyURL2, kTemporary}, + {false, "bar", 113, kDummyURL2, kTemporary}, + {true, NULL, 0, kDummyURL2, kPersistent}, + {true, "dom", 0, kDummyURL2, kPersistent}, + {false, "dom/fan", 2013, kDummyURL2, kPersistent}, + {false, "baz", 18, kDummyURL2, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary1 = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + const int64 file_paths_cost_persistent1 = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + kDummyURL1, kPersistent); + const int64 file_paths_cost_temporary2 = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL2, kTemporary); + const int64 file_paths_cost_persistent2 = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + kDummyURL2, kPersistent); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(1331 + 134 + file_paths_cost_temporary1, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(1903 + 19 + file_paths_cost_persistent1, + GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + EXPECT_EQ(1319 + 113 + file_paths_cost_temporary2, + GetOriginUsage(quota_client.get(), kDummyURL2, kTemporary)); + EXPECT_EQ(2013 + 18 + file_paths_cost_persistent2, + GetOriginUsage(quota_client.get(), kDummyURL2, kPersistent)); + } +} + +TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 11, kDummyURL1, kTemporary}, + {false, "bar", 22, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + // Dispatching three GetUsage tasks. + set_additional_callback_count(0); + GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(11 + 22 + file_paths_cost, usage()); + EXPECT_EQ(2, additional_callback_count()); + + // Once more, in a different order. + set_additional_callback_count(0); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(11 + 22 + file_paths_cost, usage()); + EXPECT_EQ(2, additional_callback_count()); +} + +TEST_F(FileSystemQuotaClientTest, GetOriginsForType) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL2, kTemporary}, + {true, NULL, 0, kDummyURL3, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary); + EXPECT_EQ(2U, origins.size()); + EXPECT_TRUE(origins.find(GURL(kDummyURL1)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kDummyURL2)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kDummyURL3)) == origins.end()); +} + +TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const char* kURL1 = "http://foo.com/"; + const char* kURL2 = "https://foo.com/"; + const char* kURL3 = "http://foo.com:1/"; + const char* kURL4 = "http://foo2.com/"; + const char* kURL5 = "http://foo.com:2/"; + const TestFile kFiles[] = { + {true, NULL, 0, kURL1, kTemporary}, + {true, NULL, 0, kURL2, kTemporary}, + {true, NULL, 0, kURL3, kTemporary}, + {true, NULL, 0, kURL4, kTemporary}, + {true, NULL, 0, kURL5, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + std::set<GURL> origins = GetOriginsForHost( + quota_client.get(), kTemporary, "foo.com"); + EXPECT_EQ(3U, origins.size()); + EXPECT_TRUE(origins.find(GURL(kURL1)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL2)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL3)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL4)) == origins.end()); // Different host. + EXPECT_TRUE(origins.find(GURL(kURL5)) == origins.end()); // Different type. +} + +TEST_F(FileSystemQuotaClientTest, IncognitoTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(true)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 10, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + // Having files in the usual directory wouldn't affect the result + // queried in incognito mode. + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + + std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary); + EXPECT_EQ(0U, origins.size()); + origins = GetOriginsForHost(quota_client.get(), kTemporary, "www.dummy.org"); + EXPECT_EQ(0U, origins.size()); +} + +TEST_F(FileSystemQuotaClientTest, DeleteOriginTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, "http://foo.com/", kTemporary}, + {false, "a", 1, "http://foo.com/", kTemporary}, + {true, NULL, 0, "https://foo.com/", kTemporary}, + {false, "b", 2, "https://foo.com/", kTemporary}, + {true, NULL, 0, "http://foo.com/", kPersistent}, + {false, "c", 4, "http://foo.com/", kPersistent}, + {true, NULL, 0, "http://bar.com/", kTemporary}, + {false, "d", 8, "http://bar.com/", kTemporary}, + {true, NULL, 0, "http://bar.com/", kPersistent}, + {false, "e", 16, "http://bar.com/", kPersistent}, + {true, NULL, 0, "https://bar.com/", kPersistent}, + {false, "f", 32, "https://bar.com/", kPersistent}, + {true, NULL, 0, "https://bar.com/", kTemporary}, + {false, "g", 64, "https://bar.com/", kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary_foo_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://foo.com/", kTemporary); + const int64 file_paths_cost_persistent_foo = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "http://foo.com/", kPersistent); + const int64 file_paths_cost_temporary_bar = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "http://bar.com/", kTemporary); + const int64 file_paths_cost_temporary_bar_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://bar.com/", kTemporary); + const int64 file_paths_cost_persistent_bar_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://bar.com/", kPersistent); + + DeleteOriginData(quota_client.get(), "http://foo.com/", kTemporary); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + DeleteOriginData(quota_client.get(), "http://bar.com/", kPersistent); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + DeleteOriginData(quota_client.get(), "http://buz.com/", kTemporary); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://foo.com/", kTemporary)); + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://bar.com/", kPersistent)); + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://buz.com/", kTemporary)); + + EXPECT_EQ(2 + file_paths_cost_temporary_foo_https, + GetOriginUsage(quota_client.get(), + "https://foo.com/", + kTemporary)); + EXPECT_EQ(4 + file_paths_cost_persistent_foo, + GetOriginUsage(quota_client.get(), + "http://foo.com/", + kPersistent)); + EXPECT_EQ(8 + file_paths_cost_temporary_bar, + GetOriginUsage(quota_client.get(), + "http://bar.com/", + kTemporary)); + EXPECT_EQ(32 + file_paths_cost_persistent_bar_https, + GetOriginUsage(quota_client.get(), + "https://bar.com/", + kPersistent)); + EXPECT_EQ(64 + file_paths_cost_temporary_bar_https, + GetOriginUsage(quota_client.get(), + "https://bar.com/", + kTemporary)); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_system_url_request_job_unittest.cc b/chromium/content/browser/fileapi/file_system_url_request_job_unittest.cc new file mode 100644 index 00000000000..7edd26aa656 --- /dev/null +++ b/chromium/content/browser/fileapi/file_system_url_request_job_unittest.cc @@ -0,0 +1,368 @@ +// 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 "webkit/browser/fileapi/file_system_url_request_job.h" + +#include <string> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/rand_util.h" +#include "base/run_loop.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/load_flags.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/base/request_priority.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" + +namespace fileapi { +namespace { + +// We always use the TEMPORARY FileSystem in this test. +const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; +const char kTestFileData[] = "0123456789"; + +void FillBuffer(char* buffer, size_t len) { + base::RandBytes(buffer, len); +} + +} // namespace + +class FileSystemURLRequestJobTest : public testing::Test { + protected: + FileSystemURLRequestJobTest() : weak_factory_(this) { + } + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + // We use the main thread so that we can get the root path synchronously. + // TODO(adamk): Run this on the FILE thread we've created as well. + file_system_context_ = + CreateFileSystemContextForTesting(NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL("http://remote/"), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem, + weak_factory_.GetWeakPtr())); + base::RunLoop().RunUntilIdle(); + + net::URLRequest::Deprecated::RegisterProtocolFactory( + "filesystem", &FileSystemURLRequestJobFactory); + } + + virtual void TearDown() OVERRIDE { + net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL); + ClearUnusedJob(); + if (pending_job_.get()) { + pending_job_->Kill(); + pending_job_ = NULL; + } + // FileReader posts a task to close the file in destructor. + base::RunLoop().RunUntilIdle(); + } + + void OnOpenFileSystem(const GURL& root_url, + const std::string& name, + base::PlatformFileError result) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + void TestRequestHelper(const GURL& url, + const net::HttpRequestHeaders* headers, + bool run_to_completion, + FileSystemContext* file_system_context) { + delegate_.reset(new net::TestDelegate()); + // Make delegate_ exit the MessageLoop when the request is done. + delegate_->set_quit_on_complete(true); + delegate_->set_quit_on_redirect(true); + request_ = empty_context_.CreateRequest( + url, net::DEFAULT_PRIORITY, delegate_.get()); + if (headers) + request_->SetExtraRequestHeaders(*headers); + ASSERT_TRUE(!job_); + job_ = new FileSystemURLRequestJob( + request_.get(), NULL, file_system_context); + pending_job_ = job_; + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + if (run_to_completion) + base::MessageLoop::current()->Run(); + } + + void TestRequest(const GURL& url) { + TestRequestHelper(url, NULL, true, file_system_context_.get()); + } + + void TestRequestWithContext(const GURL& url, + FileSystemContext* file_system_context) { + TestRequestHelper(url, NULL, true, file_system_context); + } + + void TestRequestWithHeaders(const GURL& url, + const net::HttpRequestHeaders* headers) { + TestRequestHelper(url, headers, true, file_system_context_.get()); + } + + void TestRequestNoRun(const GURL& url) { + TestRequestHelper(url, NULL, false, file_system_context_.get()); + } + + void CreateDirectory(const base::StringPiece& dir_name) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(dir_name)); + ASSERT_EQ(base::PLATFORM_FILE_OK, AsyncFileTestHelper::CreateDirectory( + file_system_context_, url)); + } + + void WriteFile(const base::StringPiece& file_name, + const char* buf, int buf_size) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(file_name)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFileWithData( + file_system_context_, url, buf, buf_size)); + } + + GURL CreateFileSystemURL(const std::string& path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemURLRequestJobFactory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + static void ClearUnusedJob() { + if (job_) { + scoped_refptr<net::URLRequestJob> deleter = job_; + job_ = NULL; + } + } + + // Put the message loop at the top, so that it's the last thing deleted. + base::MessageLoopForIO message_loop_; + + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_; + + net::URLRequestContext empty_context_; + + // NOTE: order matters, request must die before delegate + scoped_ptr<net::TestDelegate> delegate_; + scoped_ptr<net::URLRequest> request_; + + scoped_refptr<net::URLRequestJob> pending_job_; + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL; + +namespace { + +TEST_F(FileSystemURLRequestJobTest, FileTest) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + TestRequest(CreateFileSystemURL("file1.dat")); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_EQ(kTestFileData, delegate_->data_received()); + EXPECT_EQ(200, request_->GetResponseCode()); + std::string cache_control; + request_->GetResponseHeaderByName("cache-control", &cache_control); + EXPECT_EQ("no-cache", cache_control); +} + +TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_ptr<char[]> buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + WriteFile("bigfile", buffer.get(), buffer_size); + + const size_t first_byte_position = 500; + const size_t last_byte_position = buffer_size - first_byte_position; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + last_byte_position + 1); + + net::HttpRequestHeaders headers; + headers.SetHeader( + net::HttpRequestHeaders::kRange, + net::HttpByteRange::Bounded( + first_byte_position, last_byte_position).GetHeaderValue()); + TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); +} + +TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_ptr<char[]> buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + WriteFile("bigfile", buffer.get(), buffer_size); + + const size_t first_byte_position = 500; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + buffer_size); + + net::HttpRequestHeaders headers; + headers.SetHeader( + net::HttpRequestHeaders::kRange, + net::HttpByteRange::RightUnbounded(first_byte_position).GetHeaderValue()); + TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. + EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); +} + + +TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kRange, + "bytes=0-5,10-200,200-300"); + TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + net::HttpRequestHeaders headers; + headers.SetHeader( + net::HttpRequestHeaders::kRange, + net::HttpByteRange::Bounded(500, 1000).GetHeaderValue()); + TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) { + CreateDirectory("dir"); + TestRequest(CreateFileSystemURL("dir")); + + EXPECT_EQ(1, delegate_->received_redirect_count()); + EXPECT_TRUE(request_->status().is_success()); + EXPECT_FALSE(delegate_->request_failed()); + + // We've deferred the redirect; now cancel the request to avoid following it. + request_->Cancel(); + base::MessageLoop::current()->Run(); +} + +TEST_F(FileSystemURLRequestJobTest, InvalidURL) { + TestRequest(GURL("filesystem:/foo/bar/baz")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) { + TestRequest(GURL("filesystem:http://remote/persistent/somefile")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, NoSuchFile) { + TestRequest(CreateFileSystemURL("somefile")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, Cancel) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + TestRequestNoRun(CreateFileSystemURL("file1.dat")); + + // Run StartAsync() and only StartAsync(). + base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); + base::RunLoop().RunUntilIdle(); + // If we get here, success! we didn't crash! +} + +TEST_F(FileSystemURLRequestJobTest, GetMimeType) { + const char kFilename[] = "hoge.html"; + + std::string mime_type_direct; + base::FilePath::StringType extension = + base::FilePath().AppendASCII(kFilename).Extension(); + if (!extension.empty()) + extension = extension.substr(1); + EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension( + extension, &mime_type_direct)); + + TestRequest(CreateFileSystemURL(kFilename)); + + std::string mime_type_from_job; + request_->GetMimeType(&mime_type_from_job); + EXPECT_EQ(mime_type_direct, mime_type_from_job); +} + +TEST_F(FileSystemURLRequestJobTest, Incognito) { + WriteFile("file", kTestFileData, arraysize(kTestFileData) - 1); + + // Creates a new filesystem context for incognito mode. + scoped_refptr<FileSystemContext> file_system_context = + CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path()); + + // The request should return NOT_FOUND error if it's in incognito mode. + TestRequestWithContext(CreateFileSystemURL("file"), + file_system_context.get()); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); + + // Make sure it returns success with regular (non-incognito) context. + TestRequest(CreateFileSystemURL("file")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(kTestFileData, delegate_->data_received()); + EXPECT_EQ(200, request_->GetResponseCode()); +} + +} // namespace +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/file_writer_delegate_unittest.cc b/chromium/content/browser/fileapi/file_writer_delegate_unittest.cc new file mode 100644 index 00000000000..c18877f7727 --- /dev/null +++ b/chromium/content/browser/fileapi/file_writer_delegate_unittest.cc @@ -0,0 +1,455 @@ +// 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 <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/io_buffer.h" +#include "net/base/request_priority.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_status.h" +#include "testing/platform_test.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/fileapi/file_writer_delegate.h" +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://example.com"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +const char kData[] = "The quick brown fox jumps over the lazy dog.\n"; +const int kDataSize = ARRAYSIZE_UNSAFE(kData) - 1; + +class Result { + public: + Result() + : status_(base::PLATFORM_FILE_OK), + bytes_written_(0), + write_status_(FileWriterDelegate::SUCCESS_IO_PENDING) {} + + base::PlatformFileError status() const { return status_; } + int64 bytes_written() const { return bytes_written_; } + FileWriterDelegate::WriteProgressStatus write_status() const { + return write_status_; + } + + void DidWrite(base::PlatformFileError status, int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status) { + write_status_ = write_status; + if (status == base::PLATFORM_FILE_OK) { + bytes_written_ += bytes; + if (write_status_ != FileWriterDelegate::SUCCESS_IO_PENDING) + base::MessageLoop::current()->Quit(); + } else { + EXPECT_EQ(base::PLATFORM_FILE_OK, status_); + status_ = status; + base::MessageLoop::current()->Quit(); + } + } + + private: + // For post-operation status. + base::PlatformFileError status_; + int64 bytes_written_; + FileWriterDelegate::WriteProgressStatus write_status_; +}; + +} // namespace (anonymous) + +class FileWriterDelegateTest : public PlatformTest { + public: + FileWriterDelegateTest() {} + + protected: + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + int64 usage() { + return file_system_context_->GetQuotaUtil(kFileSystemType) + ->GetOriginUsageOnFileThread( + file_system_context_.get(), kOrigin, kFileSystemType); + } + + int64 GetFileSizeOnDisk(const char* test_file_path) { + // There might be in-flight flush/write. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&base::DoNothing)); + base::RunLoop().RunUntilIdle(); + + FileSystemURL url = GetFileSystemURL(test_file_path); + base::PlatformFileInfo file_info; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context_, url, &file_info)); + return file_info.size; + } + + FileSystemURL GetFileSystemURL(const char* file_name) const { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name)); + } + + FileWriterDelegate* CreateWriterDelegate( + const char* test_file_path, + int64 offset, + int64 allowed_growth) { + SandboxFileStreamWriter* writer = new SandboxFileStreamWriter( + file_system_context_.get(), + GetFileSystemURL(test_file_path), + offset, + *file_system_context_->GetUpdateObservers(kFileSystemType)); + writer->set_default_quota(allowed_growth); + return new FileWriterDelegate(scoped_ptr<FileStreamWriter>(writer)); + } + + FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) { + return base::Bind(&Result::DidWrite, base::Unretained(result)); + } + + // Creates and sets up a FileWriterDelegate for writing the given |blob_url|, + // and creates a new FileWriterDelegate for the file. + void PrepareForWrite(const char* test_file_path, + const GURL& blob_url, + int64 offset, + int64 allowed_growth) { + file_writer_delegate_.reset( + CreateWriterDelegate(test_file_path, offset, allowed_growth)); + request_ = empty_context_.CreateRequest( + blob_url, net::DEFAULT_PRIORITY, file_writer_delegate_.get()); + } + + static net::URLRequest::ProtocolFactory Factory; + + // This should be alive until the very end of this instance. + base::MessageLoopForIO loop_; + + scoped_refptr<FileSystemContext> file_system_context_; + + net::URLRequestContext empty_context_; + scoped_ptr<FileWriterDelegate> file_writer_delegate_; + scoped_ptr<net::URLRequest> request_; + + base::ScopedTempDir dir_; + + static const char* content_; +}; + +const char* FileWriterDelegateTest::content_ = NULL; + +namespace { + +static std::string g_content; + +class FileWriterDelegateTestJob : public net::URLRequestJob { + public: + FileWriterDelegateTestJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& content) + : net::URLRequestJob(request, network_delegate), + content_(content), + remaining_bytes_(content.length()), + cursor_(0) { + } + + virtual void Start() OVERRIDE { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete, this)); + } + + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE { + if (remaining_bytes_ < buf_size) + buf_size = static_cast<int>(remaining_bytes_); + + for (int i = 0; i < buf_size; ++i) + buf->data()[i] = content_[cursor_++]; + remaining_bytes_ -= buf_size; + + SetStatus(net::URLRequestStatus()); + *bytes_read = buf_size; + return true; + } + + virtual int GetResponseCode() const OVERRIDE { + return 200; + } + + protected: + virtual ~FileWriterDelegateTestJob() {} + + private: + std::string content_; + int remaining_bytes_; + int cursor_; +}; + +} // namespace (anonymous) + +// static +net::URLRequestJob* FileWriterDelegateTest::Factory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + return new FileWriterDelegateTestJob( + request, network_delegate, FileWriterDelegateTest::content_); +} + +void FileWriterDelegateTest::SetUp() { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL, dir_.path()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFile( + file_system_context_, GetFileSystemURL("test"))); + net::URLRequest::Deprecated::RegisterProtocolFactory("blob", &Factory); +} + +void FileWriterDelegateTest::TearDown() { + net::URLRequest::Deprecated::RegisterProtocolFactory("blob", NULL); + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) { + const GURL kBlobURL("blob:nolimit"); + content_ = kData; + + PrepareForWrite("test", kBlobURL, 0, kint64max); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) { + const GURL kBlobURL("blob:just"); + content_ = kData; + const int64 kAllowedGrowth = kDataSize; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); +} + +TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) { + const GURL kBlobURL("blob:failure"); + content_ = kData; + const int64 kAllowedGrowth = kDataSize - 1; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status()); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); +} + +TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) { + const GURL kBlobURL("blob:zero"); + content_ = ""; + int64 kAllowedGrowth = 0; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) { + scoped_ptr<FileWriterDelegate> file_writer_delegate2; + scoped_ptr<net::URLRequest> request2; + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFile( + file_system_context_, GetFileSystemURL("test2"))); + + const GURL kBlobURL("blob:nolimitconcurrent"); + const GURL kBlobURL2("blob:nolimitconcurrent2"); + content_ = kData; + + PrepareForWrite("test", kBlobURL, 0, kint64max); + + // Credate another FileWriterDelegate for concurrent write. + file_writer_delegate2.reset(CreateWriterDelegate("test2", 0, kint64max)); + request2 = empty_context_.CreateRequest( + kBlobURL2, net::DEFAULT_PRIORITY, file_writer_delegate2.get()); + + Result result, result2; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + file_writer_delegate2->Start(request2.Pass(), GetWriteCallback(&result2)); + base::MessageLoop::current()->Run(); + if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING || + result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING) + base::MessageLoop::current()->Run(); + + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status()); + file_writer_delegate_.reset(); + file_writer_delegate2.reset(); + + ASSERT_EQ(kDataSize * 2, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage()); + + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + EXPECT_EQ(kDataSize, result2.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result2.status()); +} + +TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) { + const GURL kBlobURL("blob:failure-with-updated-quota"); + content_ = kData; + + // Writing kDataSize (=45) bytes data while allowed_growth is 100. + int64 offset = 0; + int64 allowed_growth = 100; + ASSERT_LT(kDataSize, allowed_growth); + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite kDataSize bytes data while allowed_growth is 20. + offset = 0; + allowed_growth = 20; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + EXPECT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + } + + // Trying to write kDataSize bytes data from offset 25 while + // allowed_growth is 55. + offset = 25; + allowed_growth = 55; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(offset + kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite 45 bytes data while allowed_growth is -20. + offset = 0; + allowed_growth = -20; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + int64 pre_write_usage = GetFileSizeOnDisk("test"); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(pre_write_usage, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite 45 bytes data with offset pre_write_usage - 20, + // while allowed_growth is 10. + const int kOverlap = 20; + offset = pre_write_usage - kOverlap; + allowed_growth = 10; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(pre_write_usage + allowed_growth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status()); + } +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/fileapi_message_filter.cc b/chromium/content/browser/fileapi/fileapi_message_filter.cc index 1a79ea2d349..b6b89fb50a6 100644 --- a/chromium/content/browser/fileapi/fileapi_message_filter.cc +++ b/chromium/content/browser/fileapi/fileapi_message_filter.cc @@ -34,7 +34,6 @@ #include "webkit/browser/fileapi/file_permission_policy.h" #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/isolated_context.h" -#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/blob/blob_data.h" #include "webkit/common/blob/shareable_file_reference.h" #include "webkit/common/fileapi/directory_entry.h" @@ -42,17 +41,10 @@ #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#if defined(ENABLE_PLUGINS) -#include "content/browser/renderer_host/pepper/pepper_security_helper.h" -#include "ppapi/shared_impl/file_type_conversion.h" -#endif // defined(ENABLE_PLUGINS) - using fileapi::FileSystemFileUtil; using fileapi::FileSystemBackend; using fileapi::FileSystemOperation; using fileapi::FileSystemURL; -using fileapi::FileUpdateObserver; -using fileapi::UpdateObserverList; using webkit_blob::BlobData; using webkit_blob::BlobStorageContext; using webkit_blob::BlobStorageHost; @@ -107,7 +99,6 @@ FileAPIMessageFilter::FileAPIMessageFilter( void FileAPIMessageFilter::OnChannelConnected(int32 peer_pid) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserMessageFilter::OnChannelConnected(peer_pid); if (request_context_getter_.get()) { DCHECK(!request_context_); @@ -124,7 +115,6 @@ void FileAPIMessageFilter::OnChannelConnected(int32 peer_pid) { void FileAPIMessageFilter::OnChannelClosing() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserMessageFilter::OnChannelClosing(); // Unregister all the blob and stream URLs that are previously registered in // this process. @@ -136,22 +126,6 @@ void FileAPIMessageFilter::OnChannelClosing() { in_transit_snapshot_files_.clear(); - // Close all files that are previously OpenFile()'ed in this process. - if (!on_close_callbacks_.IsEmpty()) { - DLOG(INFO) - << "File API: Renderer process shut down before NotifyCloseFile" - << " for " << on_close_callbacks_.size() << " files opened in PPAPI"; - } - - for (OnCloseCallbackMap::iterator itr(&on_close_callbacks_); - !itr.IsAtEnd(); itr.Advance()) { - const base::Closure* callback = itr.GetCurrentValue(); - DCHECK(callback); - if (!callback->is_null()) - callback->Run(); - } - - on_close_callbacks_.Clear(); operation_runner_.reset(); operations_.clear(); } @@ -168,31 +142,24 @@ bool FileAPIMessageFilter::OnMessageReceived( *message_was_ok = true; bool handled = true; IPC_BEGIN_MESSAGE_MAP_EX(FileAPIMessageFilter, message, *message_was_ok) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_Open, OnOpen) + IPC_MESSAGE_HANDLER(FileSystemHostMsg_OpenFileSystem, OnOpenFileSystem) IPC_MESSAGE_HANDLER(FileSystemHostMsg_ResolveURL, OnResolveURL) IPC_MESSAGE_HANDLER(FileSystemHostMsg_DeleteFileSystem, OnDeleteFileSystem) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Move, OnMove) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Copy, OnCopy) - IPC_MESSAGE_HANDLER(FileSystemMsg_Remove, OnRemove) + IPC_MESSAGE_HANDLER(FileSystemHostMsg_Remove, OnRemove) IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadMetadata, OnReadMetadata) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Create, OnCreate) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Exists, OnExists) IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadDirectory, OnReadDirectory) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Write, OnWrite) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_WriteDeprecated, OnWriteDeprecated) IPC_MESSAGE_HANDLER(FileSystemHostMsg_Truncate, OnTruncate) IPC_MESSAGE_HANDLER(FileSystemHostMsg_TouchFile, OnTouchFile) IPC_MESSAGE_HANDLER(FileSystemHostMsg_CancelWrite, OnCancel) -#if defined(ENABLE_PLUGINS) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_OpenPepperFile, OnOpenPepperFile) -#endif // defined(ENABLE_PLUGINS) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_NotifyCloseFile, OnNotifyCloseFile) IPC_MESSAGE_HANDLER(FileSystemHostMsg_CreateSnapshotFile, OnCreateSnapshotFile) IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidReceiveSnapshotFile, OnDidReceiveSnapshotFile) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_WillUpdate, OnWillUpdate) - IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidUpdate, OnDidUpdate) IPC_MESSAGE_HANDLER(FileSystemHostMsg_SyncGetPlatformPath, OnSyncGetPlatformPath) IPC_MESSAGE_HANDLER(BlobHostMsg_StartBuilding, OnStartBuildingBlob) @@ -208,12 +175,6 @@ bool FileAPIMessageFilter::OnMessageReceived( IPC_MESSAGE_HANDLER(BlobHostMsg_RegisterPublicURL, OnRegisterPublicBlobURL) IPC_MESSAGE_HANDLER(BlobHostMsg_RevokePublicURL, OnRevokePublicBlobURL) - IPC_MESSAGE_HANDLER(BlobHostMsg_DeprecatedRegisterBlobURL, - OnDeprecatedRegisterBlobURL) - IPC_MESSAGE_HANDLER(BlobHostMsg_DeprecatedRevokeBlobURL, - OnDeprecatedRevokeBlobURL) - IPC_MESSAGE_HANDLER(BlobHostMsg_DeprecatedCloneBlobURL, - OnDeprecatedCloneBlobURL) IPC_MESSAGE_HANDLER(StreamHostMsg_StartBuilding, OnStartBuildingStream) IPC_MESSAGE_HANDLER(StreamHostMsg_AppendBlobDataItem, OnAppendBlobDataItemToStream) @@ -235,19 +196,17 @@ void FileAPIMessageFilter::BadMessageReceived() { BrowserMessageFilter::BadMessageReceived(); } -void FileAPIMessageFilter::OnOpen( - int request_id, const GURL& origin_url, fileapi::FileSystemType type, - int64 requested_size, bool create) { +void FileAPIMessageFilter::OnOpenFileSystem(int request_id, + const GURL& origin_url, + fileapi::FileSystemType type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (type == fileapi::kFileSystemTypeTemporary) { RecordAction(UserMetricsAction("OpenFileSystemTemporary")); } else if (type == fileapi::kFileSystemTypePersistent) { RecordAction(UserMetricsAction("OpenFileSystemPersistent")); } - // TODO(kinuko): Use this mode for IPC too. fileapi::OpenFileSystemMode mode = - create ? fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT - : fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT; + fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT; context_->OpenFileSystem(origin_url, type, mode, base::Bind( &FileAPIMessageFilter::DidOpenFileSystem, this, request_id)); } @@ -288,7 +247,7 @@ void FileAPIMessageFilter::OnMove( return; } if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) || - !security_policy_->CanWriteFileSystemFile(process_id_, src_url) || + !security_policy_->CanDeleteFileSystemFile(process_id_, src_url) || !security_policy_->CanCreateFileSystemFile(process_id_, dest_url)) { Send(new FileSystemMsg_DidFail(request_id, base::PLATFORM_FILE_ERROR_SECURITY)); @@ -297,6 +256,7 @@ void FileAPIMessageFilter::OnMove( operations_[request_id] = operation_runner()->Move( src_url, dest_url, + fileapi::FileSystemOperation::OPTION_NONE, base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id)); } @@ -310,7 +270,7 @@ void FileAPIMessageFilter::OnCopy( return; } if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) || - !security_policy_->CanCreateFileSystemFile(process_id_, dest_url)) { + !security_policy_->CanCopyIntoFileSystemFile(process_id_, dest_url)) { Send(new FileSystemMsg_DidFail(request_id, base::PLATFORM_FILE_ERROR_SECURITY)); return; @@ -318,6 +278,7 @@ void FileAPIMessageFilter::OnCopy( operations_[request_id] = operation_runner()->Copy( src_url, dest_url, + fileapi::FileSystemOperation::OPTION_NONE, fileapi::FileSystemOperationRunner::CopyProgressCallback(), base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id)); } @@ -328,7 +289,7 @@ void FileAPIMessageFilter::OnRemove( FileSystemURL url(context_->CrackURL(path)); if (!ValidateFileSystemURL(request_id, url)) return; - if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) { + if (!security_policy_->CanDeleteFileSystemFile(process_id_, url)) { Send(new FileSystemMsg_DidFail(request_id, base::PLATFORM_FILE_ERROR_SECURITY)); return; @@ -419,16 +380,6 @@ void FileAPIMessageFilter::OnReadDirectory( this, request_id)); } -void FileAPIMessageFilter::OnWriteDeprecated( - int request_id, - const GURL& path, - const GURL& blob_url, - int64 offset) { - std::string uuid = - blob_storage_context_->context()->LookupUuidFromDeprecatedURL(blob_url); - OnWrite(request_id, path, uuid, offset); -} - void FileAPIMessageFilter::OnWrite( int request_id, const GURL& path, @@ -515,84 +466,6 @@ void FileAPIMessageFilter::OnCancel( } } -#if defined(ENABLE_PLUGINS) -void FileAPIMessageFilter::OnOpenPepperFile( - int request_id, const GURL& path, int pp_open_flags) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); -FileSystemURL url(context_->CrackURL(path)); - if (!ValidateFileSystemURL(request_id, url)) - return; - if (!CanOpenFileSystemURLWithPepperFlags(pp_open_flags, process_id_, url)) { - Send(new FileSystemMsg_DidFail( - request_id, base::PLATFORM_FILE_ERROR_SECURITY)); - return; - } - - quota::QuotaLimitType quota_policy = quota::kQuotaLimitTypeUnknown; - quota::QuotaManagerProxy* quota_manager_proxy = - context_->quota_manager_proxy(); - CHECK(quota_manager_proxy); - CHECK(quota_manager_proxy->quota_manager()); - - if (quota_manager_proxy->quota_manager()->IsStorageUnlimited( - url.origin(), FileSystemTypeToQuotaStorageType(url.type()))) { - quota_policy = quota::kQuotaLimitTypeUnlimited; - } else { - quota_policy = quota::kQuotaLimitTypeLimited; - } - - int platform_file_flags = 0; - if (!ppapi::PepperFileOpenFlagsToPlatformFileFlags(pp_open_flags, - &platform_file_flags)) { - // |pp_open_flags| should have already been checked in PepperFileIOHost. - NOTREACHED() << "Open file request with invalid pp_open_flags ignored."; - } - - operations_[request_id] = operation_runner()->OpenFile( - url, platform_file_flags, PeerHandle(), - base::Bind(&FileAPIMessageFilter::DidOpenFile, this, request_id, - quota_policy)); -} -#endif // defined(ENABLE_PLUGINS) - -void FileAPIMessageFilter::OnNotifyCloseFile(int file_open_id) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - - // Remove |file_open_id| from the map of |on_close_callback|s. - // It must only be called for a ID that is successfully opened and enrolled in - // DidOpenFile. - base::Closure* on_close_callback = on_close_callbacks_.Lookup(file_open_id); - if (on_close_callback && !on_close_callback->is_null()) { - on_close_callback->Run(); - on_close_callbacks_.Remove(file_open_id); - } -} - -void FileAPIMessageFilter::OnWillUpdate(const GURL& path) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - FileSystemURL url(context_->CrackURL(path)); - if (!url.is_valid()) - return; - const UpdateObserverList* observers = - context_->GetUpdateObservers(url.type()); - if (!observers) - return; - observers->Notify(&FileUpdateObserver::OnStartUpdate, MakeTuple(url)); -} - -void FileAPIMessageFilter::OnDidUpdate(const GURL& path, int64 delta) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - FileSystemURL url(context_->CrackURL(path)); - if (!url.is_valid()) - return; - const UpdateObserverList* observers = - context_->GetUpdateObservers(url.type()); - if (!observers) - return; - observers->Notify(&FileUpdateObserver::OnUpdate, MakeTuple(url, delta)); - observers->Notify(&FileUpdateObserver::OnEndUpdate, MakeTuple(url)); -} - void FileAPIMessageFilter::OnSyncGetPlatformPath( const GURL& path, base::FilePath* platform_path) { SyncGetPlatformPath(context_, process_id_, path, platform_path); @@ -706,23 +579,6 @@ void FileAPIMessageFilter::OnRevokePublicBlobURL(const GURL& public_url) { ignore_result(blob_storage_host_->RevokePublicBlobURL(public_url)); } -void FileAPIMessageFilter::OnDeprecatedRegisterBlobURL( - const GURL& url, const std::string& uuid) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - blob_storage_host_->DeprecatedRegisterBlobURL(url, uuid); -} - -void FileAPIMessageFilter::OnDeprecatedCloneBlobURL( - const GURL& url, const GURL& src_url) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - blob_storage_host_->DeprecatedCloneBlobURL(url, src_url); -} - -void FileAPIMessageFilter::OnDeprecatedRevokeBlobURL(const GURL& url) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - blob_storage_host_->DeprecatedRevokeBlobURL(url); -} - void FileAPIMessageFilter::OnStartBuildingStream( const GURL& url, const std::string& content_type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); @@ -851,31 +707,6 @@ void FileAPIMessageFilter::DidReadDirectory( operations_.erase(request_id); } -void FileAPIMessageFilter::DidOpenFile(int request_id, - quota::QuotaLimitType quota_policy, - base::PlatformFileError result, - base::PlatformFile file, - const base::Closure& on_close_callback, - base::ProcessHandle peer_handle) { - if (result == base::PLATFORM_FILE_OK) { - IPC::PlatformFileForTransit file_for_transit = - file != base::kInvalidPlatformFileValue ? - IPC::GetFileHandleForProcess(file, peer_handle, true) : - IPC::InvalidPlatformFileForTransit(); - int file_open_id = on_close_callbacks_.Add( - new base::Closure(on_close_callback)); - - Send(new FileSystemMsg_DidOpenFile(request_id, - file_for_transit, - file_open_id, - quota_policy)); - } else { - Send(new FileSystemMsg_DidFail(request_id, - result)); - } - operations_.erase(request_id); -} - void FileAPIMessageFilter::DidWrite(int request_id, base::PlatformFileError result, int64 bytes, @@ -891,9 +722,9 @@ void FileAPIMessageFilter::DidWrite(int request_id, } void FileAPIMessageFilter::DidOpenFileSystem(int request_id, - base::PlatformFileError result, + const GURL& root, const std::string& filesystem_name, - const GURL& root) { + base::PlatformFileError result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (result == base::PLATFORM_FILE_OK) { DCHECK(root.is_valid()); @@ -983,11 +814,22 @@ void FileAPIMessageFilter::DidCreateSnapshot( bool FileAPIMessageFilter::ValidateFileSystemURL( int request_id, const fileapi::FileSystemURL& url) { - if (FileSystemURLIsValid(context_, url)) - return true; - Send(new FileSystemMsg_DidFail(request_id, - base::PLATFORM_FILE_ERROR_INVALID_URL)); - return false; + if (!FileSystemURLIsValid(context_, url)) { + Send(new FileSystemMsg_DidFail(request_id, + base::PLATFORM_FILE_ERROR_INVALID_URL)); + return false; + } + + // Deny access to files in PluginPrivate FileSystem from JavaScript. + // TODO(nhiroki): Move this filter somewhere else since this is not for + // validation. + if (url.type() == fileapi::kFileSystemTypePluginPrivate) { + Send(new FileSystemMsg_DidFail(request_id, + base::PLATFORM_FILE_ERROR_SECURITY)); + return false; + } + + return true; } scoped_refptr<Stream> FileAPIMessageFilter::GetStreamForURL(const GURL& url) { diff --git a/chromium/content/browser/fileapi/fileapi_message_filter.h b/chromium/content/browser/fileapi/fileapi_message_filter.h index e9a707f4ac4..d734e85d217 100644 --- a/chromium/content/browser/fileapi/fileapi_message_filter.h +++ b/chromium/content/browser/fileapi/fileapi_message_filter.h @@ -12,7 +12,6 @@ #include "base/callback.h" #include "base/containers/hash_tables.h" #include "base/files/file_util_proxy.h" -#include "base/id_map.h" #include "base/memory/ref_counted.h" #include "base/memory/shared_memory.h" #include "base/platform_file.h" @@ -89,11 +88,9 @@ class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter { private: typedef fileapi::FileSystemOperationRunner::OperationID OperationID; - void OnOpen(int request_id, - const GURL& origin_url, - fileapi::FileSystemType type, - int64 requested_size, - bool create); + void OnOpenFileSystem(int request_id, + const GURL& origin_url, + fileapi::FileSystemType type); void OnResolveURL(int request_id, const GURL& filesystem_url); void OnDeleteFileSystem(int request_id, @@ -118,23 +115,12 @@ class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter { const GURL& path, const std::string& blob_uuid, int64 offset); - void OnWriteDeprecated( - int request_id, - const GURL& path, - const GURL& blob_url, - int64 offset); void OnTruncate(int request_id, const GURL& path, int64 length); void OnTouchFile(int request_id, const GURL& path, const base::Time& last_access_time, const base::Time& last_modified_time); void OnCancel(int request_id, int request_to_cancel); -#if defined(ENABLE_PLUGINS) - void OnOpenPepperFile(int request_id, const GURL& path, int pp_open_flags); -#endif // defined(ENABLE_PLUGINS) - void OnNotifyCloseFile(int file_open_id); - void OnWillUpdate(const GURL& path); - void OnDidUpdate(const GURL& path, int64 delta); void OnSyncGetPlatformPath(const GURL& path, base::FilePath* platform_path); void OnCreateSnapshotFile(int request_id, @@ -157,13 +143,6 @@ class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter { void OnRegisterPublicBlobURL(const GURL& public_url, const std::string& uuid); void OnRevokePublicBlobURL(const GURL& public_url); - // Extra methods to establish a mapping from old-style blobURLs to uuids, - // and to clone them. These won't be here for long, just during a - // transition period. See crbug/174200 - void OnDeprecatedRegisterBlobURL(const GURL& url, const std::string& uuid); - void OnDeprecatedCloneBlobURL(const GURL& url, const GURL& existing_url); - void OnDeprecatedRevokeBlobURL(const GURL& url); - // Handlers for StreamHostMsg_ family messages. // // TODO(tyoshino): Consider renaming BlobData to more generic one as it's now @@ -192,20 +171,14 @@ class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter { base::PlatformFileError result, const std::vector<fileapi::DirectoryEntry>& entries, bool has_more); - void DidOpenFile(int request_id, - quota::QuotaLimitType quota_policy, - base::PlatformFileError result, - base::PlatformFile file, - const base::Closure& on_close_callback, - base::ProcessHandle peer_handle); void DidWrite(int request_id, base::PlatformFileError result, int64 bytes, bool complete); void DidOpenFileSystem(int request_id, - base::PlatformFileError result, + const GURL& root, const std::string& filesystem_name, - const GURL& root); + base::PlatformFileError result); void DidResolveURL(int request_id, base::PlatformFileError result, const fileapi::FileSystemInfo& info, @@ -266,11 +239,6 @@ class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter { std::map<int, scoped_refptr<webkit_blob::ShareableFileReference> > in_transit_snapshot_files_; - // Keep track of file system file opened by OpenFile() in this process. - // Need to close all of them when the renderer process dies. - typedef IDMap<base::Closure, IDMapOwnPointer> OnCloseCallbackMap; - OnCloseCallbackMap on_close_callbacks_; - DISALLOW_COPY_AND_ASSIGN(FileAPIMessageFilter); }; diff --git a/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc b/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc index 90e422c7da6..3dbdcc3d538 100644 --- a/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc +++ b/chromium/content/browser/fileapi/fileapi_message_filter_unittest.cc @@ -21,11 +21,11 @@ #include "content/public/test/mock_render_process_host.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_file_system_context.h" #include "net/base/io_buffer.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/browser/blob/blob_storage_context.h" #include "webkit/browser/fileapi/file_system_context.h" -#include "webkit/browser/fileapi/mock_file_system_context.h" #include "webkit/common/blob/blob_data.h" namespace content { @@ -76,12 +76,10 @@ class FileAPIMessageFilterTest : public testing::Test { } // Tests via OnMessageReceived(const IPC::Message&). The channel proxy calls - // this method. Since OnMessageReceived is hidden on FileAPIMessageFilter, - // we need to cast it. + // this method. bool InvokeOnMessageReceived(const IPC::Message& message) { - IPC::ChannelProxy::MessageFilter* casted_filter = - static_cast<IPC::ChannelProxy::MessageFilter*>(filter_.get()); - return casted_filter->OnMessageReceived(message); + bool message_was_ok; + return filter_->OnMessageReceived(message, &message_was_ok); } base::MessageLoop message_loop_; @@ -108,13 +106,11 @@ TEST_F(FileAPIMessageFilterTest, CloseChannelWithInflightRequest) { // Complete initialization. message_loop_.RunUntilIdle(); - IPC::ChannelProxy::MessageFilter* casted_filter = - static_cast<IPC::ChannelProxy::MessageFilter*>(filter.get()); - int request_id = 0; const GURL kUrl("filesystem:http://example.com/temporary/foo"); FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl); - EXPECT_TRUE(casted_filter->OnMessageReceived(read_metadata)); + bool message_was_ok; + EXPECT_TRUE(filter->OnMessageReceived(read_metadata, &message_was_ok)); // Close the filter while it has inflight request. filter->OnChannelClosing(); @@ -144,13 +140,11 @@ TEST_F(FileAPIMessageFilterTest, MultipleFilters) { // Complete initialization. message_loop_.RunUntilIdle(); - IPC::ChannelProxy::MessageFilter* casted_filter = - static_cast<IPC::ChannelProxy::MessageFilter*>(filter1.get()); - int request_id = 0; const GURL kUrl("filesystem:http://example.com/temporary/foo"); FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl); - EXPECT_TRUE(casted_filter->OnMessageReceived(read_metadata)); + bool message_was_ok; + EXPECT_TRUE(filter1->OnMessageReceived(read_metadata, &message_was_ok)); // Close the other filter before the request for filter1 is processed. filter2->OnChannelClosing(); @@ -251,8 +245,7 @@ TEST_F(FileAPIMessageFilterTest, BuildStreamWithSharedMemory) { // OnAppendSharedMemoryToStream passes the peer process's handle to // SharedMemory's constructor. If it's incorrect, DuplicateHandle won't work // correctly. - static_cast<IPC::ChannelProxy::MessageFilter*>( - filter_.get())->OnChannelConnected(base::Process::Current().pid()); + filter_->set_peer_pid_for_testing(base::Process::Current().pid()); StreamHostMsg_StartBuilding start_message(kUrl, kFakeContentType); EXPECT_TRUE(InvokeOnMessageReceived(start_message)); diff --git a/chromium/content/browser/fileapi/local_file_util_unittest.cc b/chromium/content/browser/fileapi/local_file_util_unittest.cc new file mode 100644 index 00000000000..40995d69310 --- /dev/null +++ b/chromium/content/browser/fileapi/local_file_util_unittest.cc @@ -0,0 +1,388 @@ +// 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 <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/run_loop.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/async_file_util_adapter.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://foo/"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +} // namespace + +class LocalFileUtilTest : public testing::Test { + public: + LocalFileUtilTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + file_system_context_ = CreateFileSystemContextForTesting( + NULL, data_dir_.path()); + } + + virtual void TearDown() { + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + protected: + FileSystemOperationContext* NewContext() { + FileSystemOperationContext* context = + new FileSystemOperationContext(file_system_context_.get()); + context->set_update_observers( + *file_system_context_->GetUpdateObservers(kFileSystemType)); + return context; + } + + LocalFileUtil* file_util() { + AsyncFileUtilAdapter* adapter = static_cast<AsyncFileUtilAdapter*>( + file_system_context_->GetAsyncFileUtil(kFileSystemType)); + return static_cast<LocalFileUtil*>(adapter->sync_file_util()); + } + + FileSystemURL CreateURL(const std::string& file_name) { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name)); + } + + base::FilePath LocalPath(const char *file_name) { + base::FilePath path; + scoped_ptr<FileSystemOperationContext> context(NewContext()); + file_util()->GetLocalFilePath(context.get(), CreateURL(file_name), &path); + return path; + } + + bool FileExists(const char *file_name) { + return base::PathExists(LocalPath(file_name)) && + !base::DirectoryExists(LocalPath(file_name)); + } + + bool DirectoryExists(const char *file_name) { + return base::DirectoryExists(LocalPath(file_name)); + } + + int64 GetSize(const char *file_name) { + base::PlatformFileInfo info; + base::GetFileInfo(LocalPath(file_name), &info); + return info.size; + } + + base::PlatformFileError CreateFile(const char* file_name, + base::PlatformFile* file_handle, + bool* created) { + int file_flags = base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC; + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + return file_util()->CreateOrOpen( + context.get(), + CreateURL(file_name), + file_flags, file_handle, created); + } + + base::PlatformFileError EnsureFileExists(const char* file_name, + bool* created) { + scoped_ptr<FileSystemOperationContext> context(NewContext()); + return file_util()->EnsureFileExists( + context.get(), + CreateURL(file_name), created); + } + + FileSystemContext* file_system_context() { + return file_system_context_.get(); + } + + private: + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + base::ScopedTempDir data_dir_; + + DISALLOW_COPY_AND_ASSIGN(LocalFileUtilTest); +}; + +TEST_F(LocalFileUtilTest, CreateAndClose) { + const char *file_name = "test_file"; + base::PlatformFile file_handle; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(file_name, &file_handle, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Close(context.get(), file_handle)); +} + +// base::CreateSymbolicLink is only supported on POSIX. +#if defined(OS_POSIX) +TEST_F(LocalFileUtilTest, CreateFailForSymlink) { + // Create symlink target file. + const char *target_name = "symlink_target"; + base::PlatformFile target_handle; + bool symlink_target_created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(target_name, &target_handle, &symlink_target_created)); + ASSERT_TRUE(symlink_target_created); + base::FilePath target_path = LocalPath(target_name); + + // Create symlink where target must be real file. + const char *symlink_name = "symlink_file"; + base::FilePath symlink_path = LocalPath(symlink_name); + ASSERT_TRUE(base::CreateSymbolicLink(target_path, symlink_path)); + ASSERT_TRUE(FileExists(symlink_name)); + + // Try to open the symlink file which should fail. + scoped_ptr<FileSystemOperationContext> context(NewContext()); + FileSystemURL url = CreateURL(symlink_name); + int file_flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; + base::PlatformFile file_handle; + bool created = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_util()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + EXPECT_FALSE(created); +} +#endif + +TEST_F(LocalFileUtilTest, EnsureFileExists) { + const char *file_name = "foobar"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + EXPECT_FALSE(created); +} + +TEST_F(LocalFileUtilTest, TouchFile) { + const char *file_name = "test_file"; + base::PlatformFile file_handle; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(file_name, &file_handle, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + + base::PlatformFileInfo info; + ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info)); + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(context.get(), CreateURL(file_name), + new_accessed, new_modified)); + + ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Close(context.get(), file_handle)); +} + +TEST_F(LocalFileUtilTest, TouchDirectory) { + const char *dir_name = "test_dir"; + scoped_ptr<FileSystemOperationContext> context(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), + CreateURL(dir_name), + false /* exclusive */, + false /* recursive */)); + + base::PlatformFileInfo info; + ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info)); + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(context.get(), CreateURL(dir_name), + new_accessed, new_modified)); + + ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); +} + +TEST_F(LocalFileUtilTest, Truncate) { + const char *file_name = "truncated"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(file_name), 1020)); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(1020, GetSize(file_name)); +} + +TEST_F(LocalFileUtilTest, CopyFile) { + const char *from_file = "fromfile"; + const char *to_file1 = "tofile1"; + const char *to_file2 = "tofile2"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context; + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_file), + CreateURL(to_file1))); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_file), + CreateURL(to_file2))); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(FileExists(to_file1)); + EXPECT_EQ(1020, GetSize(to_file1)); + EXPECT_TRUE(FileExists(to_file2)); + EXPECT_EQ(1020, GetSize(to_file2)); +} + +TEST_F(LocalFileUtilTest, CopyDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir = "todir"; + const char *to_file = "todir/fromfile"; + bool created; + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), CreateURL(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_FALSE(DirectoryExists(to_dir)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_dir), CreateURL(to_dir))); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(DirectoryExists(to_dir)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +TEST_F(LocalFileUtilTest, MoveFile) { + const char *from_file = "fromfile"; + const char *to_file = "tofile"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Move(file_system_context(), + CreateURL(from_file), + CreateURL(to_file))); + + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +TEST_F(LocalFileUtilTest, MoveDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir = "todir"; + const char *to_file = "todir/fromfile"; + bool created; + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), CreateURL(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_FALSE(DirectoryExists(to_dir)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Move(file_system_context(), + CreateURL(from_dir), + CreateURL(to_dir))); + + EXPECT_FALSE(DirectoryExists(from_dir)); + EXPECT_TRUE(DirectoryExists(to_dir)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/obfuscated_file_util_unittest.cc b/chromium/content/browser/fileapi/obfuscated_file_util_unittest.cc new file mode 100644 index 00000000000..72dd36008dd --- /dev/null +++ b/chromium/content/browser/fileapi/obfuscated_file_util_unittest.cc @@ -0,0 +1,2490 @@ +// 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 <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#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/platform_file.h" +#include "base/run_loop.h" +#include "content/public/test/sandbox_file_system_test_helper.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/sandbox_directory_database.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" +#include "webkit/browser/fileapi/test_file_set.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/database/database_identifier.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { + +namespace { + +bool FileExists(const base::FilePath& path) { + return base::PathExists(path) && !base::DirectoryExists(path); +} + +int64 GetSize(const base::FilePath& path) { + int64 size; + EXPECT_TRUE(base::GetFileSize(path, &size)); + return size; +} + +// After a move, the dest exists and the source doesn't. +// After a copy, both source and dest exist. +struct CopyMoveTestCaseRecord { + bool is_copy_not_move; + const char source_path[64]; + const char dest_path[64]; + bool cause_overwrite; +}; + +const CopyMoveTestCaseRecord kCopyMoveTestCases[] = { + // This is the combinatoric set of: + // rename vs. same-name + // different directory vs. same directory + // overwrite vs. no-overwrite + // copy vs. move + // We can never be called with source and destination paths identical, so + // those cases are omitted. + {true, "dir0/file0", "dir0/file1", false}, + {false, "dir0/file0", "dir0/file1", false}, + {true, "dir0/file0", "dir0/file1", true}, + {false, "dir0/file0", "dir0/file1", true}, + + {true, "dir0/file0", "dir1/file0", false}, + {false, "dir0/file0", "dir1/file0", false}, + {true, "dir0/file0", "dir1/file0", true}, + {false, "dir0/file0", "dir1/file0", true}, + {true, "dir0/file0", "dir1/file1", false}, + {false, "dir0/file0", "dir1/file1", false}, + {true, "dir0/file0", "dir1/file1", true}, + {false, "dir0/file0", "dir1/file1", true}, +}; + +struct OriginEnumerationTestRecord { + std::string origin_url; + bool has_temporary; + bool has_persistent; +}; + +const OriginEnumerationTestRecord kOriginEnumerationTestRecords[] = { + {"http://example.com", false, true}, + {"http://example1.com", true, false}, + {"https://example1.com", true, true}, + {"file://", false, true}, + {"http://example.com:8000", false, true}, +}; + +FileSystemURL FileSystemURLAppend( + const FileSystemURL& url, const base::FilePath::StringType& child) { + return FileSystemURL::CreateForTest( + url.origin(), url.mount_type(), url.virtual_path().Append(child)); +} + +FileSystemURL FileSystemURLAppendUTF8( + const FileSystemURL& url, const std::string& child) { + return FileSystemURL::CreateForTest( + url.origin(), + url.mount_type(), + url.virtual_path().Append(base::FilePath::FromUTF8Unsafe(child))); +} + +FileSystemURL FileSystemURLDirName(const FileSystemURL& url) { + return FileSystemURL::CreateForTest( + url.origin(), url.mount_type(), VirtualPath::DirName(url.virtual_path())); +} + +std::string GetTypeString(FileSystemType type) { + return SandboxFileSystemBackendDelegate::GetTypeString(type); +} + +bool HasFileSystemType( + ObfuscatedFileUtil::AbstractOriginEnumerator* enumerator, + FileSystemType type) { + return enumerator->HasTypeDirectory(GetTypeString(type)); +} + +} // namespace + +// TODO(ericu): The vast majority of this and the other FSFU subclass tests +// could theoretically be shared. It would basically be a FSFU interface +// compliance test, and only the subclass-specific bits that look into the +// implementation would need to be written per-subclass. +class ObfuscatedFileUtilTest : public testing::Test { + public: + ObfuscatedFileUtilTest() + : origin_(GURL("http://www.example.com")), + type_(kFileSystemTypeTemporary), + weak_factory_(this), + sandbox_file_system_(origin_, type_), + quota_status_(quota::kQuotaStatusUnknown), + usage_(-1) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + + storage_policy_ = new quota::MockSpecialStoragePolicy(); + + quota_manager_ = + new quota::QuotaManager(false /* is_incognito */, + data_dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + storage_policy_.get()); + + // Every time we create a new sandbox_file_system helper, + // it creates another context, which creates another path manager, + // another sandbox_backend, and another OFU. + // We need to pass in the context to skip all that. + file_system_context_ = CreateFileSystemContextForTesting( + quota_manager_->proxy(), + data_dir_.path()); + + sandbox_file_system_.SetUp(file_system_context_.get()); + + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + } + + virtual void TearDown() { + quota_manager_ = NULL; + sandbox_file_system_.TearDown(); + } + + scoped_ptr<FileSystemOperationContext> LimitedContext( + int64 allowed_bytes_growth) { + scoped_ptr<FileSystemOperationContext> context( + sandbox_file_system_.NewOperationContext()); + context->set_allowed_bytes_growth(allowed_bytes_growth); + return context.Pass(); + } + + scoped_ptr<FileSystemOperationContext> UnlimitedContext() { + return LimitedContext(kint64max); + } + + FileSystemOperationContext* NewContext( + SandboxFileSystemTestHelper* file_system) { + change_observer()->ResetCount(); + FileSystemOperationContext* context; + if (file_system) + context = file_system->NewOperationContext(); + else + context = sandbox_file_system_.NewOperationContext(); + // Setting allowed_bytes_growth big enough for all tests. + context->set_allowed_bytes_growth(1024 * 1024); + context->set_change_observers(change_observers()); + return context; + } + + const ChangeObserverList& change_observers() const { + return change_observers_; + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + // This can only be used after SetUp has run and created file_system_context_ + // and obfuscated_file_util_. + // Use this for tests which need to run in multiple origins; we need a test + // helper per origin. + SandboxFileSystemTestHelper* NewFileSystem( + const GURL& origin, fileapi::FileSystemType type) { + SandboxFileSystemTestHelper* file_system = + new SandboxFileSystemTestHelper(origin, type); + + file_system->SetUp(file_system_context_.get()); + return file_system; + } + + ObfuscatedFileUtil* ofu() { + return static_cast<ObfuscatedFileUtil*>(sandbox_file_system_.file_util()); + } + + const base::FilePath& test_directory() const { + return data_dir_.path(); + } + + const GURL& origin() const { + return origin_; + } + + fileapi::FileSystemType type() const { + return type_; + } + + std::string type_string() const { + return GetTypeString(type_); + } + + int64 ComputeTotalFileSize() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + void GetUsageFromQuotaManager() { + int64 quota = -1; + quota_status_ = + AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(), + origin(), + sandbox_file_system_.type(), + &usage_, + "a); + EXPECT_EQ(quota::kQuotaStatusOk, quota_status_); + } + + void RevokeUsageCache() { + quota_manager_->ResetUsageTracker(sandbox_file_system_.storage_type()); + usage_cache()->Delete(sandbox_file_system_.GetUsageCachePath()); + } + + int64 SizeByQuotaUtil() { + return sandbox_file_system_.GetCachedOriginUsage(); + } + + int64 SizeInUsageFile() { + base::RunLoop().RunUntilIdle(); + int64 usage = 0; + return usage_cache()->GetUsage( + sandbox_file_system_.GetUsageCachePath(), &usage) ? usage : -1; + } + + bool PathExists(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath platform_path; + base::PlatformFileError error = ofu()->GetFileInfo( + context.get(), url, &file_info, &platform_path); + return error == base::PLATFORM_FILE_OK; + } + + bool DirectoryExists(const FileSystemURL& url) { + return AsyncFileTestHelper::DirectoryExists(file_system_context(), url); + } + + int64 usage() const { return usage_; } + FileSystemUsageCache* usage_cache() { + return sandbox_file_system_.usage_cache(); + } + + FileSystemURL CreateURLFromUTF8(const std::string& path) { + return sandbox_file_system_.CreateURLFromUTF8(path); + } + + int64 PathCost(const FileSystemURL& url) { + return ObfuscatedFileUtil::ComputeFilePathCost(url.path()); + } + + FileSystemURL CreateURL(const base::FilePath& path) { + return sandbox_file_system_.CreateURL(path); + } + + void CheckFileAndCloseHandle( + const FileSystemURL& url, base::PlatformFile file_handle) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + + base::PlatformFileInfo file_info0; + base::FilePath data_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info0, &data_path)); + EXPECT_EQ(data_path, local_path); + EXPECT_TRUE(FileExists(data_path)); + EXPECT_EQ(0, GetSize(data_path)); + + const char data[] = "test data"; + const int length = arraysize(data) - 1; + + if (base::kInvalidPlatformFileValue == file_handle) { + bool created = true; + base::PlatformFileError error; + file_handle = base::CreatePlatformFile( + data_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &created, + &error); + ASSERT_NE(base::kInvalidPlatformFileValue, file_handle); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + EXPECT_FALSE(created); + } + ASSERT_EQ(length, base::WritePlatformFile(file_handle, 0, data, length)); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + base::PlatformFileInfo file_info1; + EXPECT_EQ(length, GetSize(data_path)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info1, &data_path)); + EXPECT_EQ(data_path, local_path); + + EXPECT_FALSE(file_info0.is_directory); + EXPECT_FALSE(file_info1.is_directory); + EXPECT_FALSE(file_info0.is_symbolic_link); + EXPECT_FALSE(file_info1.is_symbolic_link); + EXPECT_EQ(0, file_info0.size); + EXPECT_EQ(length, file_info1.size); + EXPECT_LE(file_info0.last_modified, file_info1.last_modified); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, length * 2)); + EXPECT_EQ(length * 2, GetSize(data_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 0)); + EXPECT_EQ(0, GetSize(data_path)); + } + + void ValidateTestDirectory( + const FileSystemURL& root_url, + const std::set<base::FilePath::StringType>& files, + const std::set<base::FilePath::StringType>& directories) { + scoped_ptr<FileSystemOperationContext> context; + std::set<base::FilePath::StringType>::const_iterator iter; + for (iter = files.begin(); iter != files.end(); ++iter) { + bool created = true; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), FileSystemURLAppend(root_url, *iter), + &created)); + ASSERT_FALSE(created); + } + for (iter = directories.begin(); iter != directories.end(); ++iter) { + context.reset(NewContext(NULL)); + EXPECT_TRUE(DirectoryExists( + FileSystemURLAppend(root_url, *iter))); + } + } + + class UsageVerifyHelper { + public: + UsageVerifyHelper(scoped_ptr<FileSystemOperationContext> context, + SandboxFileSystemTestHelper* file_system, + int64 expected_usage) + : context_(context.Pass()), + sandbox_file_system_(file_system), + expected_usage_(expected_usage) {} + + ~UsageVerifyHelper() { + base::RunLoop().RunUntilIdle(); + Check(); + } + + FileSystemOperationContext* context() { + return context_.get(); + } + + private: + void Check() { + ASSERT_EQ(expected_usage_, + sandbox_file_system_->GetCachedOriginUsage()); + } + + scoped_ptr<FileSystemOperationContext> context_; + SandboxFileSystemTestHelper* sandbox_file_system_; + int64 expected_usage_; + }; + + scoped_ptr<UsageVerifyHelper> AllowUsageIncrease(int64 requested_growth) { + int64 usage = sandbox_file_system_.GetCachedOriginUsage(); + return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper( + LimitedContext(requested_growth), + &sandbox_file_system_, usage + requested_growth)); + } + + scoped_ptr<UsageVerifyHelper> DisallowUsageIncrease(int64 requested_growth) { + int64 usage = sandbox_file_system_.GetCachedOriginUsage(); + return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper( + LimitedContext(requested_growth - 1), &sandbox_file_system_, usage)); + } + + void FillTestDirectory( + const FileSystemURL& root_url, + std::set<base::FilePath::StringType>* files, + std::set<base::FilePath::StringType>* directories) { + scoped_ptr<FileSystemOperationContext> context; + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), root_url, &entries)); + EXPECT_EQ(0UL, entries.size()); + + files->clear(); + files->insert(FILE_PATH_LITERAL("first")); + files->insert(FILE_PATH_LITERAL("second")); + files->insert(FILE_PATH_LITERAL("third")); + directories->clear(); + directories->insert(FILE_PATH_LITERAL("fourth")); + directories->insert(FILE_PATH_LITERAL("fifth")); + directories->insert(FILE_PATH_LITERAL("sixth")); + std::set<base::FilePath::StringType>::iterator iter; + for (iter = files->begin(); iter != files->end(); ++iter) { + bool created = false; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + FileSystemURLAppend(root_url, *iter), + &created)); + ASSERT_TRUE(created); + } + for (iter = directories->begin(); iter != directories->end(); ++iter) { + bool exclusive = true; + bool recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory( + context.get(), + FileSystemURLAppend(root_url, *iter), + exclusive, recursive)); + } + ValidateTestDirectory(root_url, *files, *directories); + } + + void TestReadDirectoryHelper(const FileSystemURL& root_url) { + std::set<base::FilePath::StringType> files; + std::set<base::FilePath::StringType> directories; + FillTestDirectory(root_url, &files, &directories); + + scoped_ptr<FileSystemOperationContext> context; + std::vector<DirectoryEntry> entries; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), root_url, &entries)); + std::vector<DirectoryEntry>::iterator entry_iter; + EXPECT_EQ(files.size() + directories.size(), entries.size()); + EXPECT_TRUE(change_observer()->HasNoChange()); + for (entry_iter = entries.begin(); entry_iter != entries.end(); + ++entry_iter) { + const DirectoryEntry& entry = *entry_iter; + std::set<base::FilePath::StringType>::iterator iter = + files.find(entry.name); + if (iter != files.end()) { + EXPECT_FALSE(entry.is_directory); + files.erase(iter); + continue; + } + iter = directories.find(entry.name); + EXPECT_FALSE(directories.end() == iter); + EXPECT_TRUE(entry.is_directory); + directories.erase(iter); + } + } + + void TestTouchHelper(const FileSystemURL& url, bool is_file) { + base::Time last_access_time = base::Time::Now(); + base::Time last_modified_time = base::Time::Now(); + + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + // Currently we fire no change notifications for Touch. + EXPECT_TRUE(change_observer()->HasNoChange()); + base::FilePath local_path; + base::PlatformFileInfo file_info; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + // We compare as time_t here to lower our resolution, to avoid false + // negatives caused by conversion to the local filesystem's native + // representation and back. + EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT()); + + context.reset(NewContext(NULL)); + last_modified_time += base::TimeDelta::FromHours(1); + last_access_time += base::TimeDelta::FromHours(14); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT()); + if (is_file) // Directories in OFU don't support atime. + EXPECT_EQ(file_info.last_accessed.ToTimeT(), last_access_time.ToTimeT()); + } + + void TestCopyInForeignFileHelper(bool overwrite) { + base::ScopedTempDir source_dir; + ASSERT_TRUE(source_dir.CreateUniqueTempDir()); + base::FilePath root_file_path = source_dir.path(); + base::FilePath src_file_path = root_file_path.AppendASCII("file_name"); + FileSystemURL dest_url = CreateURLFromUTF8("new file"); + int64 src_file_length = 87; + + base::PlatformFileError error_code; + bool created = false; + int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + base::PlatformFile file_handle = + base::CreatePlatformFile( + src_file_path, file_flags, &created, &error_code); + EXPECT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, error_code); + ASSERT_NE(base::kInvalidPlatformFileValue, file_handle); + ASSERT_TRUE(base::TruncatePlatformFile(file_handle, src_file_length)); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + scoped_ptr<FileSystemOperationContext> context; + + if (overwrite) { + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), dest_url, &created)); + EXPECT_TRUE(created); + + // We must have observed one (and only one) create_file_count. + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + } + + const int64 path_cost = + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()); + if (!overwrite) { + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(path_cost + src_file_length - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyInForeignFile(context.get(), + src_file_path, dest_url)); + } + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(path_cost + src_file_length); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyInForeignFile(context.get(), + src_file_path, dest_url)); + + EXPECT_TRUE(PathExists(dest_url)); + EXPECT_FALSE(DirectoryExists(dest_url)); + + context.reset(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath data_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), dest_url, &file_info, &data_path)); + EXPECT_NE(data_path, src_file_path); + EXPECT_TRUE(FileExists(data_path)); + EXPECT_EQ(src_file_length, GetSize(data_path)); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), dest_url)); + } + + void ClearTimestamp(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch(context.get(), url, base::Time(), base::Time())); + EXPECT_EQ(base::Time(), GetModifiedTime(url)); + } + + base::Time GetModifiedTime(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::FilePath data_path; + base::PlatformFileInfo file_info; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo(context.get(), url, &file_info, &data_path)); + EXPECT_TRUE(change_observer()->HasNoChange()); + return file_info.last_modified; + } + + void TestDirectoryTimestampHelper(const FileSystemURL& base_dir, + bool copy, + bool overwrite) { + scoped_ptr<FileSystemOperationContext> context; + const FileSystemURL src_dir_url( + FileSystemURLAppendUTF8(base_dir, "foo_dir")); + const FileSystemURL dest_dir_url( + FileSystemURLAppendUTF8(base_dir, "bar_dir")); + + const FileSystemURL src_file_url( + FileSystemURLAppendUTF8(src_dir_url, "hoge")); + const FileSystemURL dest_file_url( + FileSystemURLAppendUTF8(dest_dir_url, "fuga")); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), src_dir_url, true, true)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dest_dir_url, true, true)); + + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), src_file_url, &created)); + if (overwrite) { + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), + dest_file_url, &created)); + } + + ClearTimestamp(src_dir_url); + ClearTimestamp(dest_dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), + src_file_url, dest_file_url, + FileSystemOperation::OPTION_NONE, + copy)); + if (copy) + EXPECT_EQ(base::Time(), GetModifiedTime(src_dir_url)); + else + EXPECT_NE(base::Time(), GetModifiedTime(src_dir_url)); + EXPECT_NE(base::Time(), GetModifiedTime(dest_dir_url)); + } + + int64 ComputeCurrentUsage() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + FileSystemContext* file_system_context() { + return sandbox_file_system_.file_system_context(); + } + + const base::FilePath& data_dir_path() const { + return data_dir_.path(); + } + + protected: + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy_; + scoped_refptr<quota::QuotaManager> quota_manager_; + scoped_refptr<FileSystemContext> file_system_context_; + GURL origin_; + fileapi::FileSystemType type_; + base::WeakPtrFactory<ObfuscatedFileUtilTest> weak_factory_; + SandboxFileSystemTestHelper sandbox_file_system_; + quota::QuotaStatusCode quota_status_; + int64 usage_; + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + private: + DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest); +}; + +TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) { + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + bool created; + FileSystemURL url = CreateURLFromUTF8("fake/file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, + &created)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteFile(context.get(), url)); + + url = CreateURLFromUTF8("test file"); + + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + + CheckFileAndCloseHandle(url, file_handle); + + context.reset(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_TRUE(base::PathExists(local_path)); + + // Verify that deleting a file isn't stopped by zero quota, and that it frees + // up quote from its path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_FALSE(base::PathExists(local_path)); + EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()), + context->allowed_bytes_growth()); + + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + FileSystemURL directory_url = CreateURLFromUTF8( + "series/of/directories"); + url = FileSystemURLAppendUTF8(directory_url, "file name"); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), directory_url, exclusive, recursive)); + // The oepration created 3 directories recursively. + EXPECT_EQ(3, change_observer()->get_and_reset_create_directory_count()); + + context.reset(NewContext(NULL)); + file_handle = base::kInvalidPlatformFileValue; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + + CheckFileAndCloseHandle(url, file_handle); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_TRUE(base::PathExists(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_FALSE(base::PathExists(local_path)); + + // Make sure we have no unexpected changes. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestTruncate) { + bool created = false; + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Truncate(context.get(), url, 4)); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + + context.reset(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_EQ(0, GetSize(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 10)); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_EQ(10, GetSize(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 1)); + EXPECT_EQ(1, GetSize(local_path)); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + // Make sure we have no unexpected changes. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation) { + bool created = false; + FileSystemURL url = CreateURLFromUTF8("file"); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(url))->context(), + url, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(-1020)->context(), + url, 0)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->Truncate( + DisallowUsageIncrease(1021)->context(), + url, 1021)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(0)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + // quota exceeded + { + scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(-1); + helper->context()->set_allowed_bytes_growth( + helper->context()->allowed_bytes_growth() - 1); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(helper->context(), url, 1019)); + ASSERT_EQ(1019, ComputeTotalFileSize()); + } + + // Delete backing file to make following truncation fail. + base::FilePath local_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath( + UnlimitedContext().get(), + url, &local_path)); + ASSERT_FALSE(local_path.empty()); + ASSERT_TRUE(base::DeleteFile(local_path, false)); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Truncate( + LimitedContext(1234).get(), + url, 1234)); + ASSERT_EQ(0, ComputeTotalFileSize()); +} + +TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists) { + FileSystemURL url = CreateURLFromUTF8("fake/file"); + bool created = false; + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->EnsureFileExists( + context.get(), url, &created)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + url = CreateURLFromUTF8("test file"); + created = false; + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_FALSE(created); + EXPECT_TRUE(change_observer()->HasNoChange()); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + + CheckFileAndCloseHandle(url, base::kInvalidPlatformFileValue); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_FALSE(created); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Also test in a subdirectory. + url = CreateURLFromUTF8("path/to/file.txt"); + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(url), + exclusive, recursive)); + // 2 directories: path/ and path/to. + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool exclusive = false; + bool recursive = false; + FileSystemURL url = CreateURLFromUTF8("foo/bar"); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteDirectory(context.get(), url)); + + FileSystemURL root = CreateURLFromUTF8(std::string()); + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + context.reset(NewContext(NULL)); + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), root)); + + context.reset(NewContext(NULL)); + exclusive = false; + recursive = true; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + context.reset(NewContext(NULL)); + EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), root)); + EXPECT_TRUE(DirectoryExists(FileSystemURLDirName(url))); + + context.reset(NewContext(NULL)); + EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), + FileSystemURLDirName(url))); + + // Can't remove a non-empty directory. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, + ofu()->DeleteDirectory(context.get(), + FileSystemURLDirName(url))); + EXPECT_TRUE(change_observer()->HasNoChange()); + + base::PlatformFileInfo file_info; + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + EXPECT_TRUE(local_path.empty()); + EXPECT_TRUE(file_info.is_directory); + EXPECT_FALSE(file_info.is_symbolic_link); + + // Same create again should succeed, since exclusive is false. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + exclusive = true; + recursive = true; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that deleting a directory isn't stopped by zero quota, and that it + // frees up quota from its path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()), + context->allowed_bytes_growth()); + + url = CreateURLFromUTF8("foo/bop"); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + + context.reset(NewContext(NULL)); + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + + // Verify that file creation requires sufficient quota for the path. + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + exclusive = true; + recursive = false; + url = CreateURLFromUTF8("foo"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + url = CreateURLFromUTF8("blah"); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadDirectory) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + FileSystemURL url = CreateURLFromUTF8("directory/to/use"); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + TestReadDirectoryHelper(url); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash) { + TestReadDirectoryHelper(CreateURLFromUTF8(std::string())); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) { + TestReadDirectoryHelper(CreateURLFromUTF8("/")); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) { + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), url, &entries)); + + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestTouch) { + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + base::Time last_access_time = base::Time::Now(); + base::Time last_modified_time = base::Time::Now(); + + // It's not there yet. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + + // OK, now create it. + context.reset(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + TestTouchHelper(url, true); + + // Now test a directory: + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = false; + url = CreateURLFromUTF8("dir"); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(context.get(), + url, exclusive, recursive)); + TestTouchHelper(url, false); +} + +TEST_F(ObfuscatedFileUtilTest, TestPathQuotas) { + FileSystemURL url = CreateURLFromUTF8("fake/file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + url = CreateURLFromUTF8("file name"); + context->set_allowed_bytes_growth(5); + bool created = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_FALSE(created); + context->set_allowed_bytes_growth(1024); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + int64 path_cost = ObfuscatedFileUtil::ComputeFilePathCost(url.path()); + EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth()); + + context->set_allowed_bytes_growth(1024); + bool exclusive = true; + bool recursive = true; + url = CreateURLFromUTF8("directory/to/use"); + std::vector<base::FilePath::StringType> components; + url.path().GetComponents(&components); + path_cost = 0; + typedef std::vector<base::FilePath::StringType>::iterator iterator; + for (iterator iter = components.begin(); + iter != components.end(); ++iter) { + path_cost += ObfuscatedFileUtil::ComputeFilePathCost( + base::FilePath(*iter)); + } + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(1024); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) { + FileSystemURL source_url = CreateURLFromUTF8("path0.txt"); + FileSystemURL dest_url = CreateURLFromUTF8("path1.txt"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool is_copy_not_move = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + FileSystemOperation::OPTION_NONE, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + is_copy_not_move = true; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + FileSystemOperation::OPTION_NONE, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + source_url = CreateURLFromUTF8("dir/dir/file"); + bool exclusive = true; + bool recursive = true; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(source_url), + exclusive, recursive)); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + is_copy_not_move = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + FileSystemOperation::OPTION_NONE, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + is_copy_not_move = true; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + FileSystemOperation::OPTION_NONE, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) { + const int64 kSourceLength = 5; + const int64 kDestLength = 50; + + for (size_t i = 0; i < arraysize(kCopyMoveTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i); + const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i]; + SCOPED_TRACE(testing::Message() << "\t is_copy_not_move " << + test_case.is_copy_not_move); + SCOPED_TRACE(testing::Message() << "\t source_path " << + test_case.source_path); + SCOPED_TRACE(testing::Message() << "\t dest_path " << + test_case.dest_path); + SCOPED_TRACE(testing::Message() << "\t cause_overwrite " << + test_case.cause_overwrite); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool exclusive = false; + bool recursive = true; + FileSystemURL source_url = CreateURLFromUTF8(test_case.source_path); + FileSystemURL dest_url = CreateURLFromUTF8(test_case.dest_path); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(source_url), + exclusive, recursive)); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(dest_url), + exclusive, recursive)); + + bool created = false; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), source_url, &created)); + ASSERT_TRUE(created); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), source_url, kSourceLength)); + + if (test_case.cause_overwrite) { + context.reset(NewContext(NULL)); + created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), dest_url, &created)); + ASSERT_TRUE(created); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), dest_url, kDestLength)); + } + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CopyOrMoveFile( + context.get(), source_url, dest_url, FileSystemOperation::OPTION_NONE, + test_case.is_copy_not_move)); + + if (test_case.is_copy_not_move) { + base::PlatformFileInfo file_info; + base::FilePath local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), source_url, &file_info, &local_path)); + EXPECT_EQ(kSourceLength, file_info.size); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), source_url)); + } else { + base::PlatformFileInfo file_info; + base::FilePath local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo( + context.get(), source_url, &file_info, &local_path)); + } + base::PlatformFileInfo file_info; + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), dest_url, &file_info, &local_path)); + EXPECT_EQ(kSourceLength, file_info.size); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), dest_url)); + } +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + FileSystemURL dest_url = CreateURLFromUTF8("destination path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool is_copy = true; + // Copy, no overwrite. + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + + // Copy, with overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); +} + +TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + FileSystemURL dest_url = CreateURLFromUTF8("destination path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool is_copy = false; + // Move, rename, no overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + // Move, rename, with overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); +} + +TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool exclusive = true; + bool recursive = false; + FileSystemURL dir_url = CreateURLFromUTF8("directory path"); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), dir_url, exclusive, recursive)); + + FileSystemURL dest_url = FileSystemURLAppend( + dir_url, src_url.path().value()); + + bool is_copy = false; + int64 allowed_bytes_growth = -1000; // Over quota, this should still work. + // Move, no rename, no overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(allowed_bytes_growth); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + EXPECT_EQ(allowed_bytes_growth, context->allowed_bytes_growth()); + + // Move, no rename, with overwrite. + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(allowed_bytes_growth); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), + src_url, dest_url, FileSystemOperation::OPTION_NONE, is_copy)); + EXPECT_EQ( + allowed_bytes_growth + + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()), + context->allowed_bytes_growth()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile) { + TestCopyInForeignFileHelper(false /* overwrite */); + TestCopyInForeignFileHelper(true /* overwrite */); +} + +TEST_F(ObfuscatedFileUtilTest, TestEnumerator) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + FileSystemURL src_url = CreateURLFromUTF8("source dir"); + bool exclusive = true; + bool recursive = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), src_url, exclusive, recursive)); + + std::set<base::FilePath::StringType> files; + std::set<base::FilePath::StringType> directories; + FillTestDirectory(src_url, &files, &directories); + + FileSystemURL dest_url = CreateURLFromUTF8("destination dir"); + + EXPECT_FALSE(DirectoryExists(dest_url)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy( + file_system_context(), src_url, dest_url)); + + ValidateTestDirectory(dest_url, files, directories); + EXPECT_TRUE(DirectoryExists(src_url)); + EXPECT_TRUE(DirectoryExists(dest_url)); + recursive = true; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Remove( + file_system_context(), dest_url, recursive)); + EXPECT_FALSE(DirectoryExists(dest_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator) { + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> + enumerator(ofu()->CreateOriginEnumerator()); + // The test helper starts out with a single filesystem. + EXPECT_TRUE(enumerator.get()); + EXPECT_EQ(origin(), enumerator->Next()); + ASSERT_TRUE(type() == kFileSystemTypeTemporary); + EXPECT_TRUE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary)); + EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent)); + EXPECT_EQ(GURL(), enumerator->Next()); + EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary)); + EXPECT_FALSE(HasFileSystemType(enumerator.get(), kFileSystemTypePersistent)); + + std::set<GURL> origins_expected; + origins_expected.insert(origin()); + + for (size_t i = 0; i < arraysize(kOriginEnumerationTestRecords); ++i) { + SCOPED_TRACE(testing::Message() << + "Validating kOriginEnumerationTestRecords " << i); + const OriginEnumerationTestRecord& record = + kOriginEnumerationTestRecords[i]; + GURL origin_url(record.origin_url); + origins_expected.insert(origin_url); + if (record.has_temporary) { + scoped_ptr<SandboxFileSystemTestHelper> file_system( + NewFileSystem(origin_url, kFileSystemTypeTemporary)); + scoped_ptr<FileSystemOperationContext> context( + NewContext(file_system.get())); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + file_system->CreateURLFromUTF8("file"), + &created)); + EXPECT_TRUE(created); + } + if (record.has_persistent) { + scoped_ptr<SandboxFileSystemTestHelper> file_system( + NewFileSystem(origin_url, kFileSystemTypePersistent)); + scoped_ptr<FileSystemOperationContext> context( + NewContext(file_system.get())); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + file_system->CreateURLFromUTF8("file"), + &created)); + EXPECT_TRUE(created); + } + } + enumerator.reset(ofu()->CreateOriginEnumerator()); + EXPECT_TRUE(enumerator.get()); + std::set<GURL> origins_found; + GURL origin_url; + while (!(origin_url = enumerator->Next()).is_empty()) { + origins_found.insert(origin_url); + SCOPED_TRACE(testing::Message() << "Handling " << origin_url.spec()); + bool found = false; + for (size_t i = 0; !found && i < arraysize(kOriginEnumerationTestRecords); + ++i) { + const OriginEnumerationTestRecord& record = + kOriginEnumerationTestRecords[i]; + if (GURL(record.origin_url) != origin_url) + continue; + found = true; + EXPECT_EQ(record.has_temporary, + HasFileSystemType(enumerator.get(), kFileSystemTypeTemporary)); + EXPECT_EQ(record.has_persistent, + HasFileSystemType(enumerator.get(), kFileSystemTypePersistent)); + } + // Deal with the default filesystem created by the test helper. + if (!found && origin_url == origin()) { + ASSERT_TRUE(type() == kFileSystemTypeTemporary); + EXPECT_TRUE(HasFileSystemType(enumerator.get(), + kFileSystemTypeTemporary)); + EXPECT_FALSE(HasFileSystemType(enumerator.get(), + kFileSystemTypePersistent)); + found = true; + } + EXPECT_TRUE(found); + } + + std::set<GURL> diff; + std::set_symmetric_difference(origins_expected.begin(), + origins_expected.end(), origins_found.begin(), origins_found.end(), + inserter(diff, diff.begin())); + EXPECT_TRUE(diff.empty()); +} + +TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + int64 expected_quota = 0; + + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + SCOPED_TRACE(testing::Message() << "Creating kRegularTestCase " << i); + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + base::FilePath file_path(test_case.path); + expected_quota += ObfuscatedFileUtil::ComputeFilePathCost(file_path); + if (test_case.is_directory) { + bool exclusive = true; + bool recursive = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), CreateURL(file_path), + exclusive, recursive)); + } else { + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), CreateURL(file_path), + &created)); + ASSERT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), + CreateURL(file_path), + test_case.data_file_size)); + expected_quota += test_case.data_file_size; + } + } + + // Usually raw size in usage cache and the usage returned by QuotaUtil + // should be same. + EXPECT_EQ(expected_quota, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + + RevokeUsageCache(); + EXPECT_EQ(-1, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + + // This should reconstruct the cache. + GetUsageFromQuotaManager(); + EXPECT_EQ(expected_quota, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + EXPECT_EQ(expected_quota, usage()); +} + +TEST_F(ObfuscatedFileUtilTest, TestInconsistency) { + const FileSystemURL kPath1 = CreateURLFromUTF8("hoge"); + const FileSystemURL kPath2 = CreateURLFromUTF8("fuga"); + + scoped_ptr<FileSystemOperationContext> context; + base::PlatformFile file; + base::PlatformFileInfo file_info; + base::FilePath data_path; + bool created = false; + + // Create a non-empty file. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), kPath1, 10)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo( + context.get(), kPath1, &file_info, &data_path)); + EXPECT_EQ(10, file_info.size); + + // Destroy database to make inconsistency between database and filesystem. + ofu()->DestroyDirectoryDatabase(origin(), type_string()); + + // Try to get file info of broken file. + EXPECT_FALSE(PathExists(kPath1)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo( + context.get(), kPath1, &file_info, &data_path)); + EXPECT_EQ(0, file_info.size); + + // Make another broken file to |kPath2|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath2, &created)); + EXPECT_TRUE(created); + + // Destroy again. + ofu()->DestroyDirectoryDatabase(origin(), type_string()); + + // Repair broken |kPath1|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Touch(context.get(), kPath1, base::Time::Now(), + base::Time::Now())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + + // Copy from sound |kPath1| to broken |kPath2|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), kPath1, kPath2, + FileSystemOperation::OPTION_NONE, + true /* copy */)); + + ofu()->DestroyDirectoryDatabase(origin(), type_string()); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), kPath1, + base::PLATFORM_FILE_READ | base::PLATFORM_FILE_CREATE, + &file, &created)); + EXPECT_TRUE(created); + EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info)); + EXPECT_EQ(0, file_info.size); + EXPECT_TRUE(base::ClosePlatformFile(file)); +} + +TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) { + const FileSystemURL kPath[] = { + CreateURLFromUTF8("foo"), + CreateURLFromUTF8("bar"), + CreateURLFromUTF8("baz") + }; + const FileSystemURL empty_path = CreateURL(base::FilePath()); + scoped_ptr<FileSystemOperationContext> context; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPath); ++i) { + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath[i], &created)); + EXPECT_TRUE(created); + } + + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), empty_path, &entries)); + EXPECT_EQ(3u, entries.size()); + + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), kPath[0], &local_path)); + EXPECT_TRUE(base::DeleteFile(local_path, false)); + + entries.clear(); + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), empty_path, &entries)); + EXPECT_EQ(ARRAYSIZE_UNSAFE(kPath) - 1, entries.size()); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir"); + + // Create working directory. + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir_url, false, false)); + + // EnsureFileExists, create case. + FileSystemURL url(FileSystemURLAppendUTF8( + dir_url, "EnsureFileExists_file")); + bool created = false; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // non create case. + created = true; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_FALSE(created); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // fail case. + url = FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_dir"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url, false, false)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CreateOrOpen, create case. + url = FileSystemURLAppendUTF8(dir_url, "CreateOrOpen_file"); + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + created = false; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + EXPECT_TRUE(created); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // open case. + file_handle = base::kInvalidPlatformFileValue; + created = true; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + EXPECT_FALSE(created); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // fail case + file_handle = base::kInvalidPlatformFileValue; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_EQ(base::kInvalidPlatformFileValue, file_handle); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CreateDirectory, create case. + // Creating CreateDirectory_dir and CreateDirectory_dir/subdir. + url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir"); + FileSystemURL subdir_url(FileSystemURLAppendUTF8(url, "subdir")); + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), subdir_url, + true /* exclusive */, true /* recursive */)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // create subdir case. + // Creating CreateDirectory_dir/subdir2. + subdir_url = FileSystemURLAppendUTF8(url, "subdir2"); + ClearTimestamp(dir_url); + ClearTimestamp(url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), subdir_url, + true /* exclusive */, true /* recursive */)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + EXPECT_NE(base::Time(), GetModifiedTime(url)); + + // fail case. + url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir"); + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, + ofu()->CreateDirectory(context.get(), url, + true /* exclusive */, true /* recursive */)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CopyInForeignFile, create case. + url = FileSystemURLAppendUTF8(dir_url, "CopyInForeignFile_file"); + FileSystemURL src_path = FileSystemURLAppendUTF8( + dir_url, "CopyInForeignFile_src_file"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), src_path, &created)); + EXPECT_TRUE(created); + base::FilePath src_local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), src_path, &src_local_path)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyInForeignFile(context.get(), + src_local_path, + url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir"); + + // Create working directory. + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir_url, false, false)); + + // DeleteFile, delete case. + FileSystemURL url = FileSystemURLAppendUTF8( + dir_url, "DeleteFile_file"); + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // fail case. + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // DeleteDirectory, fail case. + url = FileSystemURLAppendUTF8(dir_url, "DeleteDirectory_dir"); + FileSystemURL file_path(FileSystemURLAppendUTF8(url, "pakeratta")); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url, true, true)); + created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), file_path, &created)); + EXPECT_TRUE(created); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, + ofu()->DeleteDirectory(context.get(), url)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // delete case. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), file_path)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) { + TestDirectoryTimestampHelper( + CreateURLFromUTF8("copy overwrite"), true, true); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("copy non-overwrite"), true, false); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("move overwrite"), false, true); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("move non-overwrite"), false, false); +} + +TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) { + FileSystemURL dir = CreateURLFromUTF8("foo"); + FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar"); + FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz"); + + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir, false, false)); + + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url1, &created)); + EXPECT_TRUE(created); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url2, false, false)); + + base::FilePath file_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), url1, &file_path)); + EXPECT_FALSE(file_path.empty()); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch(context.get(), url1, + base::Time::Now() + base::TimeDelta::FromHours(1), + base::Time())); + + context.reset(NewContext(NULL)); + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( + ofu()->CreateFileEnumerator(context.get(), dir, false)); + + int count = 0; + base::FilePath file_path_each; + while (!(file_path_each = file_enum->Next()).empty()) { + context.reset(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath file_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo(context.get(), + FileSystemURL::CreateForTest( + dir.origin(), + dir.mount_type(), + file_path_each), + &file_info, &file_path)); + EXPECT_EQ(file_info.is_directory, file_enum->IsDirectory()); + EXPECT_EQ(file_info.last_modified, file_enum->LastModifiedTime()); + EXPECT_EQ(file_info.size, file_enum->Size()); + ++count; + } + EXPECT_EQ(2, count); +} + +// crbug.com/176470 +#if defined(OS_WIN) || defined(OS_ANDROID) +#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile +#else +#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile +#endif +TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) { + FileSystemURL from_file(CreateURLFromUTF8("fromfile")); + FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile")); + FileSystemURL to_file1(CreateURLFromUTF8("tofile1")); + FileSystemURL to_file2(CreateURLFromUTF8("tofile2")); + bool created; + + int64 expected_total_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(obstacle_file))->context(), + obstacle_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 obstacle_file_size = 1; + expected_total_file_size += obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(obstacle_file_size)->context(), + obstacle_file, obstacle_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 to_file1_size = from_file_size; + expected_total_file_size += to_file1_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + PathCost(to_file1) + to_file1_size)->context(), + from_file, to_file1, + FileSystemOperation::OPTION_NONE, + true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile( + DisallowUsageIncrease( + PathCost(to_file2) + from_file_size)->context(), + from_file, to_file2, FileSystemOperation::OPTION_NONE, + true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + expected_total_file_size += obstacle_file_size - old_obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + obstacle_file_size - old_obstacle_file_size)->context(), + from_file, obstacle_file, + FileSystemOperation::OPTION_NONE, + true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_from_file_size = from_file_size; + from_file_size = old_from_file_size - 1; + expected_total_file_size += from_file_size - old_from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease( + from_file_size - old_from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + // quota exceeded + { + old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + expected_total_file_size += obstacle_file_size - old_obstacle_file_size; + scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease( + obstacle_file_size - old_obstacle_file_size); + helper->context()->set_allowed_bytes_growth( + helper->context()->allowed_bytes_growth() - 1); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + helper->context(), + from_file, obstacle_file, + FileSystemOperation::OPTION_NONE, + true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + } +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) { + FileSystemURL from_file(CreateURLFromUTF8("fromfile")); + FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile")); + FileSystemURL to_file(CreateURLFromUTF8("tofile")); + bool created; + + int64 expected_total_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 to_file_size ALLOW_UNUSED = from_file_size; + from_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease(-PathCost(from_file) + + PathCost(to_file))->context(), + from_file, to_file, + FileSystemOperation::OPTION_NONE, + false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(obstacle_file))->context(), + obstacle_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 obstacle_file_size = 1; + expected_total_file_size += obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1)->context(), + obstacle_file, obstacle_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + from_file_size = 0; + expected_total_file_size -= old_obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + -old_obstacle_file_size - PathCost(from_file))->context(), + from_file, obstacle_file, + FileSystemOperation::OPTION_NONE, + false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + from_file_size = 10; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + // quota exceeded even after operation + old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + from_file_size = 0; + expected_total_file_size -= old_obstacle_file_size; + scoped_ptr<FileSystemOperationContext> context = + LimitedContext(-old_obstacle_file_size - PathCost(from_file) - 1); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), from_file, obstacle_file, + FileSystemOperation::OPTION_NONE, + false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + context.reset(); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove) { + FileSystemURL dir(CreateURLFromUTF8("dir")); + FileSystemURL file(CreateURLFromUTF8("file")); + FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1")); + FileSystemURL dfile2(CreateURLFromUTF8("dir/dfile2")); + bool created; + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(file))->context(), + file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory( + AllowUsageIncrease(PathCost(dir))->context(), + dir, false, false)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(dfile1))->context(), + dfile1, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(dfile2))->context(), + dfile2, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(340)->context(), + file, 340)); + ASSERT_EQ(340, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + dfile1, 1020)); + ASSERT_EQ(1360, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(120)->context(), + dfile2, 120)); + ASSERT_EQ(1480, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile( + AllowUsageIncrease(-PathCost(file) - 340)->context(), + file)); + ASSERT_EQ(1140, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Remove( + file_system_context(), dir, true /* recursive */)); + ASSERT_EQ(0, ComputeTotalFileSize()); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen) { + FileSystemURL file(CreateURLFromUTF8("file")); + base::PlatformFile file_handle; + bool created; + + // Creating a file. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(file))->context(), + file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + // Opening it, which shouldn't change the usage. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(0)->context(), file, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + const int length = 33; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(length)->context(), file, length)); + ASSERT_EQ(length, ComputeTotalFileSize()); + + // Opening it with CREATE_ALWAYS flag, which should truncate the file size. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(-length)->context(), file, + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + // Extending the file again. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(length)->context(), file, length)); + ASSERT_EQ(length, ComputeTotalFileSize()); + + // Opening it with TRUNCATED flag, which should truncate the file size. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(-length)->context(), file, + base::PLATFORM_FILE_OPEN_TRUNCATED | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); +} + +TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) { + scoped_ptr<ObfuscatedFileUtil> file_util( + ObfuscatedFileUtil::CreateForTesting( + NULL, data_dir_path(), + base::MessageLoopProxy::current().get())); + file_util->InitOriginDatabase(GURL(), true /*create*/); + ASSERT_TRUE(file_util->origin_database_ != NULL); + + // Callback to Drop DB is called while ObfuscatedFileUtilTest is still alive. + file_util->db_flush_delay_seconds_ = 0; + file_util->MarkUsed(); + base::RunLoop().RunUntilIdle(); + + ASSERT_TRUE(file_util->origin_database_ == NULL); +} + +TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) { + // Run message loop after OFU is already deleted to make sure callback doesn't + // cause a crash for use after free. + { + scoped_ptr<ObfuscatedFileUtil> file_util( + ObfuscatedFileUtil::CreateForTesting( + NULL, data_dir_path(), + base::MessageLoopProxy::current().get())); + file_util->InitOriginDatabase(GURL(), true /*create*/); + file_util->db_flush_delay_seconds_ = 0; + file_util->MarkUsed(); + } + + // At this point the callback is still in the message queue but OFU is gone. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) { + storage_policy_->AddIsolated(origin_); + scoped_ptr<ObfuscatedFileUtil> file_util( + ObfuscatedFileUtil::CreateForTesting( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get())); + const FileSystemURL url = FileSystemURL::CreateForTest( + origin_, kFileSystemTypePersistent, base::FilePath()); + + // Create DirectoryDatabase for isolated origin. + SandboxDirectoryDatabase* db = + file_util->GetDirectoryDatabase(url, true /* create */); + ASSERT_TRUE(db != NULL); + + // Destory it. + ASSERT_TRUE(file_util->DestroyDirectoryDatabase( + url.origin(), GetTypeString(url.type()))); + ASSERT_TRUE(file_util->directories_.empty()); +} + +TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) { + storage_policy_->AddIsolated(origin_); + scoped_ptr<ObfuscatedFileUtil> file_util( + ObfuscatedFileUtil::CreateForTesting( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get())); + const FileSystemURL url = FileSystemURL::CreateForTest( + origin_, kFileSystemTypePersistent, base::FilePath()); + + // Create DirectoryDatabase for isolated origin. + SandboxDirectoryDatabase* db = + file_util->GetDirectoryDatabase(url, true /* create */); + ASSERT_TRUE(db != NULL); + ASSERT_EQ(1U, file_util->directories_.size()); + + // Remove isolated. + storage_policy_->RemoveIsolated(url.origin()); + + // This should still get the same database. + SandboxDirectoryDatabase* db2 = + file_util->GetDirectoryDatabase(url, false /* create */); + ASSERT_EQ(db, db2); +} + +TEST_F(ObfuscatedFileUtilTest, MigrationBackFromIsolated) { + std::string kFakeDirectoryData("0123456789"); + base::FilePath old_directory_db_path; + + // Initialize the directory with one origin using + // SandboxIsolatedOriginDatabase. + { + std::string origin_string = + webkit_database::GetIdentifierFromOrigin(origin_); + SandboxIsolatedOriginDatabase database_old( + origin_string, data_dir_path(), + base::FilePath( + SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory)); + base::FilePath path; + EXPECT_TRUE(database_old.GetPathForOrigin(origin_string, &path)); + EXPECT_FALSE(path.empty()); + + // Populate the origin directory with some fake data. + old_directory_db_path = data_dir_path().Append(path); + ASSERT_TRUE(base::CreateDirectory(old_directory_db_path)); + EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()), + file_util::WriteFile(old_directory_db_path.AppendASCII("dummy"), + kFakeDirectoryData.data(), + kFakeDirectoryData.size())); + } + + storage_policy_->AddIsolated(origin_); + scoped_ptr<ObfuscatedFileUtil> file_util( + ObfuscatedFileUtil::CreateForTesting( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get())); + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::FilePath origin_directory = file_util->GetDirectoryForOrigin( + origin_, true /* create */, &error); + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + + // The database is migrated from the old one. + EXPECT_TRUE(base::DirectoryExists(origin_directory)); + EXPECT_FALSE(base::DirectoryExists(old_directory_db_path)); + + // Check we see the same contents in the new origin directory. + std::string origin_db_data; + EXPECT_TRUE(base::PathExists(origin_directory.AppendASCII("dummy"))); + EXPECT_TRUE(base::ReadFileToString( + origin_directory.AppendASCII("dummy"), &origin_db_data)); + EXPECT_EQ(kFakeDirectoryData, origin_db_data); +} + +TEST_F(ObfuscatedFileUtilTest, OpenPathInNonDirectory) { + FileSystemURL file(CreateURLFromUTF8("file")); + FileSystemURL path_in_file(CreateURLFromUTF8("file/file")); + bool created; + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(UnlimitedContext().get(), file, &created)); + ASSERT_TRUE(created); + + created = false; + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + ofu()->CreateOrOpen(UnlimitedContext().get(), + path_in_file, + file_flags, + &file_handle, + &created)); + ASSERT_FALSE(created); + ASSERT_EQ(base::kInvalidPlatformFileValue, file_handle); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + ofu()->CreateDirectory(UnlimitedContext().get(), + path_in_file, + false /* exclusive */, + false /* recursive */)); +} + +TEST_F(ObfuscatedFileUtilTest, CreateDirectory_NotADirectoryInRecursive) { + FileSystemURL file(CreateURLFromUTF8("file")); + FileSystemURL path_in_file(CreateURLFromUTF8("file/child")); + FileSystemURL path_in_file_in_file( + CreateURLFromUTF8("file/child/grandchild")); + bool created; + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(UnlimitedContext().get(), file, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + ofu()->CreateDirectory(UnlimitedContext().get(), + path_in_file, + false /* exclusive */, + true /* recursive */)); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + ofu()->CreateDirectory(UnlimitedContext().get(), + path_in_file_in_file, + false /* exclusive */, + true /* recursive */)); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/plugin_private_file_system_backend_unittest.cc b/chromium/content/browser/fileapi/plugin_private_file_system_backend_unittest.cc new file mode 100644 index 00000000000..0cc2f48bb00 --- /dev/null +++ b/chromium/content/browser/fileapi/plugin_private_file_system_backend_unittest.cc @@ -0,0 +1,147 @@ +// 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 "base/basictypes.h" +#include "base/file_util.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/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "content/public/test/test_file_system_options.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/plugin_private_file_system_backend.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://www.example.com"); +const std::string kPlugin1("plugin1"); +const std::string kPlugin2("plugin2"); +const FileSystemType kType = kFileSystemTypePluginPrivate; +const std::string kRootName = "pluginprivate"; + +void DidOpenFileSystem(base::PlatformFileError* error_out, + base::PlatformFileError error) { + *error_out = error; +} + +std::string RegisterFileSystem() { + return IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath( + kType, kRootName, base::FilePath()); +} + +} // namespace + +class PluginPrivateFileSystemBackendTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + context_ = CreateFileSystemContextForTesting( + NULL /* quota_manager_proxy */, + data_dir_.path()); + } + + FileSystemURL CreateURL(const GURL& root_url, const std::string& relative) { + FileSystemURL root = context_->CrackURL(root_url); + return context_->CreateCrackedFileSystemURL( + root.origin(), + root.mount_type(), + root.virtual_path().AppendASCII(relative)); + } + + PluginPrivateFileSystemBackend* backend() const { + return context_->plugin_private_backend(); + } + + const base::FilePath& base_path() const { return backend()->base_path(); } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> context_; + std::string filesystem_id_; +}; + +TEST_F(PluginPrivateFileSystemBackendTest, OpenFileSystemBasic) { + const std::string filesystem_id1 = RegisterFileSystem(); + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + backend()->OpenPrivateFileSystem( + kOrigin, kType, filesystem_id1, kPlugin1, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&DidOpenFileSystem, &error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + // Run this again with FAIL_IF_NONEXISTENT to see if it succeeds. + const std::string filesystem_id2 = RegisterFileSystem(); + error = base::PLATFORM_FILE_ERROR_FAILED; + backend()->OpenPrivateFileSystem( + kOrigin, kType, filesystem_id2, kPlugin1, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + base::Bind(&DidOpenFileSystem, &error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + const GURL root_url( + GetIsolatedFileSystemRootURIString(kOrigin, filesystem_id1, kRootName)); + FileSystemURL file = CreateURL(root_url, "foo"); + base::FilePath platform_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFile(context_.get(), file)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetPlatformPath(context_.get(), file, + &platform_path)); + EXPECT_TRUE(base_path().AppendASCII("000").AppendASCII(kPlugin1).IsParent( + platform_path)); +} + +TEST_F(PluginPrivateFileSystemBackendTest, PluginIsolation) { + // Open filesystem for kPlugin1 and kPlugin2. + const std::string filesystem_id1 = RegisterFileSystem(); + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + backend()->OpenPrivateFileSystem( + kOrigin, kType, filesystem_id1, kPlugin1, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&DidOpenFileSystem, &error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + const std::string filesystem_id2 = RegisterFileSystem(); + error = base::PLATFORM_FILE_ERROR_FAILED; + backend()->OpenPrivateFileSystem( + kOrigin, kType, filesystem_id2, kPlugin2, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&DidOpenFileSystem, &error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + // Create 'foo' in kPlugin1. + const GURL root_url1( + GetIsolatedFileSystemRootURIString(kOrigin, filesystem_id1, kRootName)); + FileSystemURL file1 = CreateURL(root_url1, "foo"); + base::FilePath platform_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFile(context_.get(), file1)); + EXPECT_TRUE(AsyncFileTestHelper::FileExists( + context_.get(), file1, AsyncFileTestHelper::kDontCheckSize)); + + // See the same path is not available in kPlugin2. + const GURL root_url2( + GetIsolatedFileSystemRootURIString(kOrigin, filesystem_id2, kRootName)); + FileSystemURL file2 = CreateURL(root_url2, "foo"); + EXPECT_FALSE(AsyncFileTestHelper::FileExists( + context_.get(), file2, AsyncFileTestHelper::kDontCheckSize)); +} + +// TODO(kinuko,nhiroki): also test if DeleteOriginDataOnFileThread +// works fine when there's multiple plugin partitions. + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/recursive_operation_delegate_unittest.cc b/chromium/content/browser/fileapi/recursive_operation_delegate_unittest.cc new file mode 100644 index 00000000000..d3671fe89f8 --- /dev/null +++ b/chromium/content/browser/fileapi/recursive_operation_delegate_unittest.cc @@ -0,0 +1,280 @@ +// 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 "webkit/browser/fileapi/recursive_operation_delegate.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "content/public/test/sandbox_file_system_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +namespace fileapi { +namespace { + +class LoggingRecursiveOperation : public 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) + : RecursiveOperationDelegate(file_system_context), + root_(root), + callback_(callback), + weak_factory_(this) { + } + virtual ~LoggingRecursiveOperation() {} + + const std::vector<LogEntry>& log_entries() const { return log_entries_; } + + // RecursiveOperationDelegate overrides. + virtual void Run() OVERRIDE { + NOTREACHED(); + } + + virtual void RunRecursively() OVERRIDE { + StartRecursiveOperation(root_, callback_); + } + + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE { + RecordLogEntry(LogEntry::PROCESS_FILE, url); + operation_runner()->GetMetadata( + url, + base::Bind(&LoggingRecursiveOperation::DidGetMetadata, + weak_factory_.GetWeakPtr(), callback)); + } + + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE { + RecordLogEntry(LogEntry::PROCESS_DIRECTORY, url); + callback.Run(base::PLATFORM_FILE_OK); + } + + virtual void PostProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE { + RecordLogEntry(LogEntry::POST_PROCESS_DIRECTORY, url); + callback.Run(base::PLATFORM_FILE_OK); + } + + 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::PlatformFileError result, + const base::PlatformFileInfo& file_info) { + if (result != base::PLATFORM_FILE_OK) { + callback.Run(result); + return; + } + + callback.Run(file_info.is_directory ? + base::PLATFORM_FILE_ERROR_NOT_A_FILE : + base::PLATFORM_FILE_OK); + } + + FileSystemURL root_; + StatusCallback callback_; + std::vector<LogEntry> log_entries_; + + base::WeakPtrFactory<LoggingRecursiveOperation> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(LoggingRecursiveOperation); +}; + +void ReportStatus(base::PlatformFileError* out_error, + base::PlatformFileError error) { + DCHECK(out_error); + *out_error = error; +} + +// To test the Cancel() during operation, calls Cancel() of |operation| +// after |counter| times message posting. +void CallCancelLater(RecursiveOperationDelegate* operation, int counter) { + if (counter > 0) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&CallCancelLater, base::Unretained(operation), counter - 1)); + return; + } + + operation->Cancel(); +} + +} // namespace + +class RecursiveOperationDelegateTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + EXPECT_TRUE(base_.CreateUniqueTempDir()); + sandbox_file_system_.SetUp(base_.path().AppendASCII("filesystem")); + } + + virtual void TearDown() OVERRIDE { + sandbox_file_system_.TearDown(); + } + + scoped_ptr<FileSystemOperationContext> 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); + } + + 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::PLATFORM_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::PLATFORM_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::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + scoped_ptr<FileSystemOperationContext> context = NewContext(); + scoped_ptr<LoggingRecursiveOperation> operation( + new LoggingRecursiveOperation( + context->file_system_context(), src_file, + base::Bind(&ReportStatus, &error))); + operation->RunRecursively(); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + const std::vector<LoggingRecursiveOperation::LogEntry>& 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::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + scoped_ptr<FileSystemOperationContext> context = NewContext(); + scoped_ptr<LoggingRecursiveOperation> operation( + new LoggingRecursiveOperation( + context->file_system_context(), src_root, + base::Bind(&ReportStatus, &error))); + operation->RunRecursively(); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + + const std::vector<LoggingRecursiveOperation::LogEntry>& 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::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + scoped_ptr<FileSystemOperationContext> context = NewContext(); + scoped_ptr<LoggingRecursiveOperation> 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::PLATFORM_FILE_ERROR_ABORT, error); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc b/chromium/content/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc new file mode 100644 index 00000000000..c87043b74ca --- /dev/null +++ b/chromium/content/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc @@ -0,0 +1,82 @@ +// 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 "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" + +#include "base/basictypes.h" +#include "base/file_util.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 "content/public/test/test_file_system_options.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { + +namespace { + +FileSystemURL CreateFileSystemURL(const char* path) { + const GURL kOrigin("http://foo/"); + return FileSystemURL::CreateForTest( + kOrigin, kFileSystemTypeTemporary, base::FilePath::FromUTF8Unsafe(path)); +} + +} // namespace + +class SandboxFileSystemBackendDelegateTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + delegate_.reset(new SandboxFileSystemBackendDelegate( + NULL /* quota_manager_proxy */, + base::MessageLoopProxy::current().get(), + data_dir_.path(), + NULL /* special_storage_policy */, + CreateAllowFileAccessOptions())); + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_ptr<SandboxFileSystemBackendDelegate> delegate_; +}; + +TEST_F(SandboxFileSystemBackendDelegateTest, IsAccessValid) { + // Normal case. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("a"))); + + // Access to a path with parent references ('..') should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("a/../b"))); + + // Access from non-allowed scheme should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid( + FileSystemURL::CreateForTest( + GURL("unknown://bar"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("foo")))); + + // Access with restricted name should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("."))); + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".."))); + + // This is also disallowed due to Windows XP parent path handling. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("..."))); + + // These are identified as unsafe cases due to weird path handling + // on Windows. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(" .."))); + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".. "))); + + // Similar but safe cases. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(" ."))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(". "))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("b."))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(".b"))); + + // A path that looks like a drive letter. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("c:"))); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/sandbox_file_system_backend_unittest.cc b/chromium/content/browser/fileapi/sandbox_file_system_backend_unittest.cc new file mode 100644 index 00000000000..4bb12261a97 --- /dev/null +++ b/chromium/content/browser/fileapi/sandbox_file_system_backend_unittest.cc @@ -0,0 +1,319 @@ +// 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 "webkit/browser/fileapi/sandbox_file_system_backend.h" + +#include <set> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_options.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/common/fileapi/file_system_util.h" + +// PS stands for path separator. +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +#define PS "\\" +#else +#define PS "/" +#endif + +namespace fileapi { + +namespace { + +const struct RootPathTest { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; +} kRootPathTestCases[] = { + { fileapi::kFileSystemTypeTemporary, "http://foo:1/", + "000" PS "t" }, + { fileapi::kFileSystemTypePersistent, "http://foo:1/", + "000" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "http://bar.com/", + "001" PS "t" }, + { fileapi::kFileSystemTypePersistent, "http://bar.com/", + "001" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "https://foo:2/", + "002" PS "t" }, + { fileapi::kFileSystemTypePersistent, "https://foo:2/", + "002" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "https://bar.com/", + "003" PS "t" }, + { fileapi::kFileSystemTypePersistent, "https://bar.com/", + "003" PS "p" }, +}; + +const struct RootPathFileURITest { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; + const char* virtual_path; +} kRootPathFileURITestCases[] = { + { fileapi::kFileSystemTypeTemporary, "file:///", + "000" PS "t", NULL }, + { fileapi::kFileSystemTypePersistent, "file:///", + "000" PS "p", NULL }, +}; + +void DidOpenFileSystem(base::PlatformFileError* error_out, + const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + *error_out = error; +} + +} // namespace + +class SandboxFileSystemBackendTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + SetUpNewDelegate(CreateAllowFileAccessOptions()); + } + + void SetUpNewDelegate(const FileSystemOptions& options) { + delegate_.reset(new SandboxFileSystemBackendDelegate( + NULL /* quota_manager_proxy */, + base::MessageLoopProxy::current().get(), + data_dir_.path(), + NULL /* special_storage_policy */, + options)); + } + + void SetUpNewBackend(const FileSystemOptions& options) { + SetUpNewDelegate(options); + backend_.reset(new SandboxFileSystemBackend(delegate_.get())); + } + + SandboxFileSystemBackendDelegate::OriginEnumerator* + CreateOriginEnumerator() const { + return backend_->CreateOriginEnumerator(); + } + + void CreateOriginTypeDirectory(const GURL& origin, + fileapi::FileSystemType type) { + base::FilePath target = delegate_-> + GetBaseDirectoryForOriginAndType(origin, type, true); + ASSERT_TRUE(!target.empty()); + ASSERT_TRUE(base::DirectoryExists(target)); + } + + bool GetRootPath(const GURL& origin_url, + fileapi::FileSystemType type, + OpenFileSystemMode mode, + base::FilePath* root_path) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + backend_->OpenFileSystem( + origin_url, type, mode, + base::Bind(&DidOpenFileSystem, &error)); + base::RunLoop().RunUntilIdle(); + if (error != base::PLATFORM_FILE_OK) + return false; + base::FilePath returned_root_path = + delegate_->GetBaseDirectoryForOriginAndType( + origin_url, type, false /* create */); + if (root_path) + *root_path = returned_root_path; + return !returned_root_path.empty(); + } + + base::FilePath file_system_path() const { + return data_dir_.path().Append( + SandboxFileSystemBackendDelegate::kFileSystemDirectory); + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_ptr<SandboxFileSystemBackendDelegate> delegate_; + scoped_ptr<SandboxFileSystemBackend> backend_; +}; + +TEST_F(SandboxFileSystemBackendTest, Empty) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator( + CreateOriginEnumerator()); + ASSERT_TRUE(enumerator->Next().is_empty()); +} + +TEST_F(SandboxFileSystemBackendTest, EnumerateOrigins) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + const char* temporary_origins[] = { + "http://www.bar.com/", + "http://www.foo.com/", + "http://www.foo.com:1/", + "http://www.example.com:8080/", + "http://www.google.com:80/", + }; + const char* persistent_origins[] = { + "http://www.bar.com/", + "http://www.foo.com:8080/", + "http://www.foo.com:80/", + }; + size_t temporary_size = ARRAYSIZE_UNSAFE(temporary_origins); + size_t persistent_size = ARRAYSIZE_UNSAFE(persistent_origins); + std::set<GURL> temporary_set, persistent_set; + for (size_t i = 0; i < temporary_size; ++i) { + CreateOriginTypeDirectory(GURL(temporary_origins[i]), + fileapi::kFileSystemTypeTemporary); + temporary_set.insert(GURL(temporary_origins[i])); + } + for (size_t i = 0; i < persistent_size; ++i) { + CreateOriginTypeDirectory(GURL(persistent_origins[i]), + kFileSystemTypePersistent); + persistent_set.insert(GURL(persistent_origins[i])); + } + + scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator( + CreateOriginEnumerator()); + size_t temporary_actual_size = 0; + size_t persistent_actual_size = 0; + GURL current; + while (!(current = enumerator->Next()).is_empty()) { + SCOPED_TRACE(testing::Message() << "EnumerateOrigin " << current.spec()); + if (enumerator->HasFileSystemType(kFileSystemTypeTemporary)) { + ASSERT_TRUE(temporary_set.find(current) != temporary_set.end()); + ++temporary_actual_size; + } + if (enumerator->HasFileSystemType(kFileSystemTypePersistent)) { + ASSERT_TRUE(persistent_set.find(current) != persistent_set.end()); + ++persistent_actual_size; + } + } + + EXPECT_EQ(temporary_size, temporary_actual_size); + EXPECT_EQ(persistent_size, persistent_actual_size); +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) { + std::vector<base::FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + SetUpNewBackend(CreateAllowFileAccessOptions()); + + // Create a new root directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " " + << kRootPathTestCases[i].expected_path); + + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path)); + + base::FilePath expected = file_system_path().AppendASCII( + kRootPathTestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.value()); + EXPECT_TRUE(base::DirectoryExists(root_path)); + ASSERT_TRUE(returned_root_path.size() > i); + returned_root_path[i] = root_path; + } + + // Get the root directory with create=false and see if we get the + // same directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (get) #" << i << " " + << kRootPathTestCases[i].expected_path); + + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + &root_path)); + ASSERT_TRUE(returned_root_path.size() > i); + EXPECT_EQ(returned_root_path[i].value(), root_path.value()); + } +} + +TEST_F(SandboxFileSystemBackendTest, + GetRootPathCreateAndExamineWithNewBackend) { + std::vector<base::FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + SetUpNewBackend(CreateAllowFileAccessOptions()); + + GURL origin_url("http://foo.com:1/"); + + base::FilePath root_path1; + EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path1)); + + SetUpNewBackend(CreateDisallowFileAccessOptions()); + base::FilePath root_path2; + EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + &root_path2)); + + EXPECT_EQ(root_path1.value(), root_path2.value()); +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathGetWithoutCreate) { + SetUpNewBackend(CreateDisallowFileAccessOptions()); + + // Try to get a root directory without creating. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (create=false) #" << i << " " + << kRootPathTestCases[i].expected_path); + EXPECT_FALSE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathInIncognito) { + SetUpNewBackend(CreateIncognitoFileSystemOptions()); + + // Try to get a root directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (incognito) #" << i << " " + << kRootPathTestCases[i].expected_path); + EXPECT_FALSE( + GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURI) { + SetUpNewBackend(CreateDisallowFileAccessOptions()); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + EXPECT_FALSE( + GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURIWithAllowFlag) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path)); + base::FilePath expected = file_system_path().AppendASCII( + kRootPathFileURITestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.value()); + EXPECT_TRUE(base::DirectoryExists(root_path)); + } +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/transient_file_util_unittest.cc b/chromium/content/browser/fileapi/transient_file_util_unittest.cc new file mode 100644 index 00000000000..2ddb315c55f --- /dev/null +++ b/chromium/content/browser/fileapi/transient_file_util_unittest.cc @@ -0,0 +1,121 @@ +// 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 "base/basictypes.h" +#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/platform_file.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/transient_file_util.h" +#include "webkit/common/blob/scoped_file.h" + +namespace fileapi { + +class TransientFileUtilTest : public testing::Test { + public: + TransientFileUtilTest() {} + virtual ~TransientFileUtilTest() {} + + virtual void SetUp() OVERRIDE { + file_system_context_ = CreateFileSystemContextForTesting( + NULL, base::FilePath(FILE_PATH_LITERAL("dummy"))); + transient_file_util_.reset(new TransientFileUtil); + + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + } + + virtual void TearDown() OVERRIDE { + file_system_context_ = NULL; + base::RunLoop().RunUntilIdle(); + } + + void CreateAndRegisterTemporaryFile( + FileSystemURL* file_url, + base::FilePath* file_path) { + EXPECT_TRUE(base::CreateTemporaryFileInDir(data_dir_.path(), file_path)); + IsolatedContext* isolated_context = IsolatedContext::GetInstance(); + std::string name = "tmp"; + std::string fsid = isolated_context->RegisterFileSystemForPath( + kFileSystemTypeForTransientFile, + *file_path, + &name); + ASSERT_TRUE(!fsid.empty()); + base::FilePath virtual_path = isolated_context->CreateVirtualRootPath( + fsid).AppendASCII(name); + *file_url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://foo"), + kFileSystemTypeIsolated, + virtual_path); + } + + scoped_ptr<FileSystemOperationContext> NewOperationContext() { + return make_scoped_ptr( + new FileSystemOperationContext(file_system_context_.get())); + } + + FileSystemFileUtil* file_util() { return transient_file_util_.get(); } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + scoped_ptr<TransientFileUtil> transient_file_util_; + + DISALLOW_COPY_AND_ASSIGN(TransientFileUtilTest); +}; + +TEST_F(TransientFileUtilTest, TransientFile) { + FileSystemURL temp_url; + base::FilePath temp_path; + + CreateAndRegisterTemporaryFile(&temp_url, &temp_path); + + base::PlatformFileError error; + base::PlatformFileInfo file_info; + base::FilePath path; + + // Make sure the file is there. + ASSERT_TRUE(temp_url.is_valid()); + ASSERT_TRUE(base::PathExists(temp_path)); + ASSERT_FALSE(base::DirectoryExists(temp_path)); + + // Create a snapshot file. + { + webkit_blob::ScopedFile scoped_file = + file_util()->CreateSnapshotFile(NewOperationContext().get(), + temp_url, + &error, + &file_info, + &path); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_EQ(temp_path, path); + ASSERT_FALSE(file_info.is_directory); + + // The file should be still there. + ASSERT_TRUE(base::PathExists(temp_path)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(NewOperationContext().get(), + temp_url, &file_info, &path)); + ASSERT_EQ(temp_path, path); + ASSERT_FALSE(file_info.is_directory); + } + + // The file's now scoped out. + base::RunLoop().RunUntilIdle(); + + // Now the temporary file and the transient filesystem must be gone too. + ASSERT_FALSE(base::PathExists(temp_path)); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + file_util()->GetFileInfo(NewOperationContext().get(), + temp_url, &file_info, &path)); +} + +} // namespace fileapi diff --git a/chromium/content/browser/fileapi/upload_file_system_file_element_reader_unittest.cc b/chromium/content/browser/fileapi/upload_file_system_file_element_reader_unittest.cc new file mode 100644 index 00000000000..4ae03b9b344 --- /dev/null +++ b/chromium/content/browser/fileapi/upload_file_system_file_element_reader_unittest.cc @@ -0,0 +1,278 @@ +// 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 "webkit/browser/fileapi/upload_file_system_file_element_reader.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "content/public/test/test_file_system_context.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { + +namespace { + +const char kFileSystemURLOrigin[] = "http://remote"; +const fileapi::FileSystemType kFileSystemType = + fileapi::kFileSystemTypeTemporary; + +} // namespace + +class UploadFileSystemFileElementReaderTest : public testing::Test { + public: + UploadFileSystemFileElementReaderTest() {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_system_context_ = fileapi::CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL(kFileSystemURLOrigin), + kFileSystemType, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&UploadFileSystemFileElementReaderTest::OnOpenFileSystem, + base::Unretained(this))); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(file_system_root_url_.is_valid()); + + // Prepare a file on file system. + const char kTestData[] = "abcdefghijklmnop0123456789"; + file_data_.assign(kTestData, kTestData + arraysize(kTestData) - 1); + const char kFilename[] = "File.dat"; + file_url_ = GetFileSystemURL(kFilename); + WriteFileSystemFile(kFilename, &file_data_[0], file_data_.size(), + &file_modification_time_); + + // Create and initialize a reader. + reader_.reset( + new UploadFileSystemFileElementReader(file_system_context_.get(), + file_url_, + 0, + kuint64max, + file_modification_time_)); + net::TestCompletionCallback callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(callback.callback())); + EXPECT_EQ(net::OK, callback.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + EXPECT_FALSE(reader_->IsInMemory()); + } + + virtual void TearDown() OVERRIDE { + reader_.reset(); + base::RunLoop().RunUntilIdle(); + } + + protected: + GURL GetFileSystemURL(const std::string& filename) { + return GURL(file_system_root_url_.spec() + filename); + } + + void WriteFileSystemFile(const std::string& filename, + const char* buf, + int buf_size, + base::Time* modification_time) { + fileapi::FileSystemURL url = + file_system_context_->CreateCrackedFileSystemURL( + GURL(kFileSystemURLOrigin), + kFileSystemType, + base::FilePath().AppendASCII(filename)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateFileWithData( + file_system_context_, url, buf, buf_size)); + + base::PlatformFileInfo file_info; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context_, url, &file_info)); + *modification_time = file_info.last_modified; + } + + void OnOpenFileSystem(const GURL& root, + const std::string& name, + base::PlatformFileError result) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + ASSERT_TRUE(root.is_valid()); + file_system_root_url_ = root; + } + + base::MessageLoopForIO message_loop_; + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + GURL file_system_root_url_; + std::vector<char> file_data_; + GURL file_url_; + base::Time file_modification_time_; + scoped_ptr<UploadFileSystemFileElementReader> reader_; +}; + +TEST_F(UploadFileSystemFileElementReaderTest, ReadAll) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(buf->size(), read_callback.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); + // Try to read again. + EXPECT_EQ(0, reader_->Read(buf.get(), buf->size(), read_callback.callback())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, ReadPartially) { + const size_t kHalfSize = file_data_.size() / 2; + ASSERT_EQ(file_data_.size(), kHalfSize * 2); + + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(kHalfSize); + + net::TestCompletionCallback read_callback1; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + EXPECT_EQ(buf->size(), read_callback1.WaitForResult()); + EXPECT_EQ(file_data_.size() - buf->size(), reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + kHalfSize, + buf->data())); + + net::TestCompletionCallback read_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback2.callback())); + EXPECT_EQ(buf->size(), read_callback2.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin() + kHalfSize, file_data_.end(), + buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, ReadTooMuch) { + const size_t kTooLargeSize = file_data_.size() * 2; + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(kTooLargeSize); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(static_cast<int>(file_data_.size()), read_callback.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, MultipleInit) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + + // Read all. + net::TestCompletionCallback read_callback1; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + EXPECT_EQ(buf->size(), read_callback1.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); + + // Call Init() again to reset the state. + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::OK, init_callback.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + + // Read again. + net::TestCompletionCallback read_callback2; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback2.callback())); + EXPECT_EQ(buf->size(), read_callback2.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, InitDuringAsyncOperation) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + + // Start reading all. + net::TestCompletionCallback read_callback1; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + + // Call Init to cancel the previous read. + net::TestCompletionCallback init_callback1; + EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback1.callback())); + + // Call Init again to cancel the previous init. + net::TestCompletionCallback init_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback2.callback())); + EXPECT_EQ(net::OK, init_callback2.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + + // Read half. + scoped_refptr<net::IOBufferWithSize> buf2 = + new net::IOBufferWithSize(file_data_.size() / 2); + net::TestCompletionCallback read_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf2.get(), buf2->size(), read_callback2.callback())); + EXPECT_EQ(buf2->size(), read_callback2.WaitForResult()); + EXPECT_EQ(file_data_.size() - buf2->size(), reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + buf2->size(), + buf2->data())); + + // Make sure callbacks are not called for cancelled operations. + EXPECT_FALSE(read_callback1.have_result()); + EXPECT_FALSE(init_callback1.have_result()); +} + +TEST_F(UploadFileSystemFileElementReaderTest, Range) { + const int kOffset = 2; + const int kLength = file_data_.size() - kOffset * 3; + reader_.reset(new UploadFileSystemFileElementReader( + file_system_context_.get(), file_url_, kOffset, kLength, base::Time())); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::OK, init_callback.WaitForResult()); + EXPECT_EQ(static_cast<uint64>(kLength), reader_->GetContentLength()); + EXPECT_EQ(static_cast<uint64>(kLength), reader_->BytesRemaining()); + scoped_refptr<net::IOBufferWithSize> buf = new net::IOBufferWithSize(kLength); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(kLength, read_callback.WaitForResult()); + EXPECT_TRUE(std::equal(file_data_.begin() + kOffset, + file_data_.begin() + kOffset + kLength, + buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, FileChanged) { + // Expect one second before the actual modification time to simulate change. + const base::Time expected_modification_time = + file_modification_time_ - base::TimeDelta::FromSeconds(1); + reader_.reset( + new UploadFileSystemFileElementReader(file_system_context_.get(), + file_url_, + 0, + kuint64max, + expected_modification_time)); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, init_callback.WaitForResult()); +} + +TEST_F(UploadFileSystemFileElementReaderTest, WrongURL) { + const GURL wrong_url = GetFileSystemURL("wrong_file_name.dat"); + reader_.reset(new UploadFileSystemFileElementReader( + file_system_context_.get(), wrong_url, 0, kuint64max, base::Time())); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, init_callback.WaitForResult()); +} + +} // namespace fileapi |