| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <vector> |
| |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/scoped_temp_dir.h" |
| #include "googleurl/src/gurl.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 "webkit/blob/blob_data.h" |
| #include "webkit/blob/blob_storage_controller.h" |
| #include "webkit/blob/blob_url_request_job.h" |
| #include "webkit/fileapi/file_system_context.h" |
| #include "webkit/fileapi/file_system_file_util.h" |
| #include "webkit/fileapi/file_system_util.h" |
| #include "webkit/fileapi/local_file_system_operation.h" |
| #include "webkit/fileapi/local_file_system_test_helper.h" |
| #include "webkit/fileapi/local_file_util.h" |
| #include "webkit/fileapi/mock_file_change_observer.h" |
| #include "webkit/quota/quota_manager.h" |
| |
| using quota::QuotaManager; |
| |
| namespace fileapi { |
| |
| namespace { |
| |
| void AssertStatusEq(base::PlatformFileError expected, |
| base::PlatformFileError actual) { |
| ASSERT_EQ(expected, actual); |
| } |
| |
| class MockQuotaManager : public QuotaManager { |
| public: |
| MockQuotaManager(const FilePath& base_dir, int64 quota) |
| : QuotaManager(false /* is_incognito */, base_dir, |
| base::MessageLoopProxy::current(), |
| base::MessageLoopProxy::current(), |
| NULL /* special_storage_policy */), |
| usage_(0), |
| quota_(quota) {} |
| |
| virtual void GetUsageAndQuota( |
| const GURL& origin, quota::StorageType type, |
| const GetUsageAndQuotaCallback& callback) OVERRIDE { |
| callback.Run(quota::kQuotaStatusOk, usage_, quota_); |
| } |
| |
| void set_usage(int64 usage) { usage_ = usage; } |
| void set_quota(int64 quota) { quota_ = quota; } |
| |
| protected: |
| virtual ~MockQuotaManager() {} |
| |
| private: |
| int64 usage_; |
| int64 quota_; |
| }; |
| |
| } // namespace |
| |
| class LocalFileSystemOperationWriteTest |
| : public testing::Test, |
| public base::SupportsWeakPtr<LocalFileSystemOperationWriteTest> { |
| public: |
| LocalFileSystemOperationWriteTest() |
| : test_helper_(GURL("http://example.com"), kFileSystemTypeTest), |
| loop_(MessageLoop::TYPE_IO), |
| status_(base::PLATFORM_FILE_OK), |
| cancel_status_(base::PLATFORM_FILE_ERROR_FAILED), |
| bytes_written_(0), |
| complete_(false) { |
| change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); |
| } |
| |
| LocalFileSystemOperation* operation(); |
| |
| 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_; } |
| |
| virtual void SetUp(); |
| virtual void TearDown(); |
| |
| protected: |
| const ChangeObserverList& change_observers() const { |
| return change_observers_; |
| } |
| |
| MockFileChangeObserver* change_observer() { |
| return &change_observer_; |
| } |
| |
| FileSystemURL URLForPath(const FilePath& path) const { |
| return test_helper_.CreateURL(path); |
| } |
| |
| // Callback function for recording test results. |
| FileSystemOperation::WriteCallback RecordWriteCallback() { |
| return base::Bind(&LocalFileSystemOperationWriteTest::DidWrite, |
| AsWeakPtr()); |
| } |
| |
| FileSystemOperation::StatusCallback RecordCancelCallback() { |
| return base::Bind(&LocalFileSystemOperationWriteTest::DidCancel, |
| AsWeakPtr()); |
| } |
| |
| void DidWrite(base::PlatformFileError status, int64 bytes, bool complete) { |
| if (status == base::PLATFORM_FILE_OK) { |
| add_bytes_written(bytes, complete); |
| if (complete) |
| MessageLoop::current()->Quit(); |
| } else { |
| EXPECT_FALSE(complete_); |
| EXPECT_EQ(status_, base::PLATFORM_FILE_OK); |
| complete_ = true; |
| status_ = status; |
| if (MessageLoop::current()->is_running()) |
| MessageLoop::current()->Quit(); |
| } |
| } |
| |
| void DidCancel(base::PlatformFileError status) { |
| cancel_status_ = status; |
| } |
| |
| FileSystemFileUtil* file_util() { |
| return test_helper_.file_util(); |
| } |
| |
| scoped_refptr<MockQuotaManager> quota_manager_; |
| LocalFileSystemTestOriginHelper test_helper_; |
| |
| MessageLoop loop_; |
| |
| ScopedTempDir dir_; |
| FilePath virtual_path_; |
| |
| // For post-operation status. |
| base::PlatformFileError status_; |
| base::PlatformFileError cancel_status_; |
| int64 bytes_written_; |
| bool complete_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LocalFileSystemOperationWriteTest); |
| |
| private: |
| MockFileChangeObserver change_observer_; |
| ChangeObserverList change_observers_; |
| }; |
| |
| namespace { |
| |
| class TestProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { |
| public: |
| explicit TestProtocolHandler( |
| webkit_blob::BlobStorageController* blob_storage_controller) |
| : blob_storage_controller_(blob_storage_controller) {} |
| |
| virtual ~TestProtocolHandler() {} |
| |
| virtual net::URLRequestJob* MaybeCreateJob( |
| net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) const OVERRIDE { |
| return new webkit_blob::BlobURLRequestJob( |
| request, |
| network_delegate, |
| blob_storage_controller_->GetBlobDataFromUrl(request->url()), |
| base::MessageLoopProxy::current()); |
| } |
| |
| private: |
| webkit_blob::BlobStorageController* const blob_storage_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestProtocolHandler); |
| }; |
| |
| class TestURLRequestContext : public net::URLRequestContext { |
| public: |
| TestURLRequestContext() |
| : blob_storage_controller_(new webkit_blob::BlobStorageController) { |
| // Job factory owns the protocol handler. |
| job_factory_.SetProtocolHandler( |
| "blob", new TestProtocolHandler(blob_storage_controller_.get())); |
| set_job_factory(&job_factory_); |
| } |
| |
| virtual ~TestURLRequestContext() {} |
| |
| webkit_blob::BlobStorageController* blob_storage_controller() const { |
| return blob_storage_controller_.get(); |
| } |
| |
| private: |
| net::URLRequestJobFactoryImpl job_factory_; |
| scoped_ptr<webkit_blob::BlobStorageController> blob_storage_controller_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestURLRequestContext); |
| }; |
| |
| } // namespace (anonymous) |
| |
| void LocalFileSystemOperationWriteTest::SetUp() { |
| ASSERT_TRUE(dir_.CreateUniqueTempDir()); |
| FilePath base_dir = dir_.path().AppendASCII("filesystem"); |
| |
| quota_manager_ = new MockQuotaManager(base_dir, 1024); |
| test_helper_.SetUp(base_dir, |
| false /* unlimited quota */, |
| quota_manager_->proxy(), |
| NULL); |
| virtual_path_ = FilePath(FILE_PATH_LITERAL("temporary file")); |
| |
| operation()->CreateFile( |
| URLForPath(virtual_path_), true /* exclusive */, |
| base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); |
| } |
| |
| void LocalFileSystemOperationWriteTest::TearDown() { |
| quota_manager_ = NULL; |
| test_helper_.TearDown(); |
| } |
| |
| LocalFileSystemOperation* LocalFileSystemOperationWriteTest::operation() { |
| LocalFileSystemOperation* operation = test_helper_.NewOperation(); |
| operation->operation_context()->set_change_observers(change_observers()); |
| return operation; |
| } |
| |
| TEST_F(LocalFileSystemOperationWriteTest, TestWriteSuccess) { |
| GURL blob_url("blob:success"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("Hello, world!\n"); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| operation()->Write(&url_request_context, URLForPath(virtual_path_), blob_url, |
| 0, RecordWriteCallback()); |
| MessageLoop::current()->Run(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| 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(LocalFileSystemOperationWriteTest, TestWriteZero) { |
| GURL blob_url("blob:zero"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| operation()->Write(&url_request_context, URLForPath(virtual_path_), |
| blob_url, 0, RecordWriteCallback()); |
| MessageLoop::current()->Run(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| 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(LocalFileSystemOperationWriteTest, TestWriteInvalidBlobUrl) { |
| TestURLRequestContext url_request_context; |
| |
| operation()->Write(&url_request_context, URLForPath(virtual_path_), |
| GURL("blob:invalid"), 0, RecordWriteCallback()); |
| 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(LocalFileSystemOperationWriteTest, TestWriteInvalidFile) { |
| GURL blob_url("blob:writeinvalidfile"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("It\'ll not be written."); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| operation()->Write(&url_request_context, |
| URLForPath(FilePath(FILE_PATH_LITERAL("nonexist"))), |
| blob_url, 0, RecordWriteCallback()); |
| MessageLoop::current()->Run(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| 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(LocalFileSystemOperationWriteTest, TestWriteDir) { |
| FilePath virtual_dir_path(FILE_PATH_LITERAL("d")); |
| operation()->CreateDirectory( |
| URLForPath(virtual_dir_path), |
| true /* exclusive */, false /* recursive */, |
| base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); |
| |
| GURL blob_url("blob:writedir"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("It\'ll not be written, too."); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| operation()->Write(&url_request_context, URLForPath(virtual_dir_path), |
| blob_url, 0, RecordWriteCallback()); |
| MessageLoop::current()->Run(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| 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(LocalFileSystemOperationWriteTest, TestWriteFailureByQuota) { |
| GURL blob_url("blob:success"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("Hello, world!\n"); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| quota_manager_->set_quota(10); |
| operation()->Write(&url_request_context, URLForPath(virtual_path_), blob_url, |
| 0, RecordWriteCallback()); |
| MessageLoop::current()->Run(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| 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(LocalFileSystemOperationWriteTest, TestImmediateCancelSuccessfulWrite) { |
| GURL blob_url("blob:success"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("Hello, world!\n"); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| FileSystemOperation* write_operation = operation(); |
| write_operation->Write(&url_request_context, URLForPath(virtual_path_), |
| blob_url, 0, RecordWriteCallback()); |
| write_operation->Cancel(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. |
| MessageLoop::current()->RunAllPending(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| // 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(LocalFileSystemOperationWriteTest, TestImmediateCancelFailingWrite) { |
| GURL blob_url("blob:writeinvalidfile"); |
| scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); |
| blob_data->AppendData("It\'ll not be written."); |
| |
| TestURLRequestContext url_request_context; |
| url_request_context.blob_storage_controller()->AddFinishedBlob( |
| blob_url, blob_data); |
| |
| FileSystemOperation* write_operation = operation(); |
| write_operation->Write(&url_request_context, |
| URLForPath(FilePath(FILE_PATH_LITERAL("nonexist"))), |
| blob_url, 0, RecordWriteCallback()); |
| write_operation->Cancel(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. |
| MessageLoop::current()->RunAllPending(); |
| |
| url_request_context.blob_storage_controller()->RemoveBlob(blob_url); |
| |
| // 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 |