| // 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 |