Chromium Code Reviews| Index: content/test/test_file_error_injector.cc |
| diff --git a/content/test/test_file_error_injector.cc b/content/test/test_file_error_injector.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d4bbfa2fab576c99a83e2225c36bc251a34c85e1 |
| --- /dev/null |
| +++ b/content/test/test_file_error_injector.cc |
| @@ -0,0 +1,435 @@ |
| +// 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 "content/test/test_file_error_injector.h" |
| + |
| +#include <map> |
| +#include <vector> |
| + |
| +#include "base/compiler_specific.h" |
| +#include "base/logging.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "content/browser/download/download_create_info.h" |
| +#include "content/browser/download/download_file_impl.h" |
| +#include "content/browser/download/download_file_manager.h" |
| +#include "content/browser/renderer_host/resource_dispatcher_host.h" |
| +#include "googleurl/src/gurl.h" |
| + |
| +namespace { |
| + |
| +class DownloadFileWithErrors; |
| +class DownloadFileWithErrorsFactory; |
| + |
| +typedef std::map<std::string, content::TestFileErrorInjector::FileErrorInfo> |
| + ErrorMap; |
| + |
| +DownloadFileManager* GetDownloadFileManager() { |
| + ResourceDispatcherHost* rdh = ResourceDispatcherHost::Get(); |
| + DCHECK(rdh != NULL); |
| + return rdh->download_file_manager(); |
| +} |
| + |
| +class TestFileErrorInjectorImpl : public content::TestFileErrorInjector { |
| + public: |
| + typedef base::Callback<void(GURL url, content::DownloadId id)> |
| + ConstructionCallback; |
| + typedef base::Callback<void(GURL url)> DestructionCallback; |
| + |
| + TestFileErrorInjectorImpl(); |
| + |
| + // content::TestFileErrorInjector interface. |
| + virtual bool AddError(const FileErrorInfo& error_info) OVERRIDE; |
| + virtual void ClearErrors() OVERRIDE; |
| + virtual bool InjectErrors() OVERRIDE; |
| + virtual size_t CurrentFileCount() const OVERRIDE; |
| + virtual size_t TotalFileCount() const OVERRIDE; |
| + virtual bool HadFile(const GURL& url) const OVERRIDE; |
| + virtual const content::DownloadId GetId(const GURL& url) const OVERRIDE; |
| + virtual void ClearFoundFiles() OVERRIDE; |
| + |
| + private: |
| + typedef std::map<GURL, content::DownloadId> FileMap; |
| + |
| + virtual ~TestFileErrorInjectorImpl(); |
| + |
| + void RecordDownloadFileConstruction(GURL url, content::DownloadId id); |
| + void RecordDownloadFileDestruction(GURL url); |
| + |
| + // Callbacks from the download file, to record lifetimes. |
| + void DownloadFileCreated(GURL url, content::DownloadId id); |
| + void DestroyingDownloadFile(GURL url); |
| + |
| + // Our injected error list, mapped by file index. One per file. |
| + ErrorMap injected_errors_; |
| + |
| + // Keep track of active DownloadFiles. |
| + FileMap files_; |
| + |
| + // Keep track of found DownloadFiles. |
| + FileMap found_files_; |
| + |
| + // We have created a factory. Used for validation. |
| + bool created_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestFileErrorInjectorImpl); |
| +}; |
| + |
| +// A class that performs file operations and injects errors. |
| +class DownloadFileWithErrors: public DownloadFileImpl { |
| + public: |
| + DownloadFileWithErrors( |
| + const DownloadCreateInfo* info, |
| + DownloadRequestHandleInterface* request_handle, |
| + content::DownloadManager* download_manager, |
| + bool calculate_hash, |
| + const net::BoundNetLog& bound_net_log, |
| + const content::TestFileErrorInjector::FileErrorInfo& error_info, |
| + const TestFileErrorInjectorImpl::ConstructionCallback& ctor_callback, |
| + const TestFileErrorInjectorImpl::DestructionCallback& dtor_callback); |
| + |
| + ~DownloadFileWithErrors(); |
| + |
| + // DownloadFile interface. |
| + virtual net::Error Initialize() OVERRIDE; |
| + virtual net::Error AppendDataToFile(const char* data, |
| + size_t data_len) OVERRIDE; |
| + virtual net::Error Rename(const FilePath& full_path) OVERRIDE; |
| + |
| + private: |
| + // Error generating helper. |
| + net::Error ShouldReturnError( |
| + content::TestFileErrorInjector::FileOperationCode code, |
| + net::Error original_net_error); |
| + |
| + // Source URL for the file being downloaded. |
| + GURL source_url_; |
| + |
| + // Our injected error. Only one per file. |
| + content::TestFileErrorInjector::FileErrorInfo error_info_; |
| + |
| + // Count per operation. 0-based. |
| + std::map<content::TestFileErrorInjector::FileOperationCode, int> |
| + operation_counter_; |
| + |
| + // Callback for destruction. |
| + TestFileErrorInjectorImpl::DestructionCallback destruction_callback_; |
| +}; |
| + |
| +// A factory for constructing DownloadFiles that inject errors. |
| +class DownloadFileWithErrorsFactory |
| + : public DownloadFileManager::DownloadFileFactory { |
| + public: |
| + |
| + DownloadFileWithErrorsFactory( |
| + const TestFileErrorInjectorImpl::ConstructionCallback& ctor_callback, |
| + const TestFileErrorInjectorImpl::DestructionCallback& dtor_callback); |
| + virtual ~DownloadFileWithErrorsFactory(); |
| + |
| + // DownloadFileFactory interface. |
| + virtual content::DownloadFile* CreateFile( |
| + DownloadCreateInfo* info, |
| + const DownloadRequestHandle& request_handle, |
| + content::DownloadManager* download_manager, |
| + bool calculate_hash, |
| + const net::BoundNetLog& bound_net_log); |
| + |
| + bool AddError( |
| + const content::TestFileErrorInjector::FileErrorInfo& error_info); |
| + |
| + void ClearErrors(); |
| + |
| + private: |
| + // Our injected error list, mapped by file index. One per file. |
| + ErrorMap injected_errors_; |
| + |
| + // Callback for creation and destruction. |
| + TestFileErrorInjectorImpl::ConstructionCallback construction_callback_; |
| + TestFileErrorInjectorImpl::DestructionCallback destruction_callback_; |
| +}; |
| + |
| +// Implementations. |
| + |
| +DownloadFileWithErrors::DownloadFileWithErrors( |
| + const DownloadCreateInfo* info, |
| + DownloadRequestHandleInterface* request_handle, |
| + content::DownloadManager* download_manager, |
| + bool calculate_hash, |
| + const net::BoundNetLog& bound_net_log, |
| + const content::TestFileErrorInjector::FileErrorInfo& error_info, |
| + const TestFileErrorInjectorImpl::ConstructionCallback& ctor_callback, |
| + const TestFileErrorInjectorImpl::DestructionCallback& dtor_callback) |
| + : DownloadFileImpl(info, |
| + request_handle, |
| + download_manager, |
| + calculate_hash, |
| + bound_net_log), |
| + source_url_(info->url()), |
| + error_info_(error_info), |
| + destruction_callback_(dtor_callback) { |
| + ctor_callback.Run(source_url_, info->download_id); |
| +} |
| + |
| +DownloadFileWithErrors::~DownloadFileWithErrors() { |
| + destruction_callback_.Run(source_url_); |
| +} |
| + |
| + |
| +net::Error DownloadFileWithErrors::Initialize() { |
| + return ShouldReturnError( |
| + content::TestFileErrorInjector::FILE_OPERATION_INITIALIZE, |
| + DownloadFileImpl::Initialize()); |
| +} |
| + |
| +net::Error DownloadFileWithErrors::AppendDataToFile(const char* data, |
| + size_t data_len) { |
| + return ShouldReturnError( |
| + content::TestFileErrorInjector::FILE_OPERATION_WRITE, |
| + DownloadFileImpl::AppendDataToFile(data, data_len)); |
| +} |
| + |
| +net::Error DownloadFileWithErrors::Rename(const FilePath& full_path) { |
| + return ShouldReturnError( |
| + content::TestFileErrorInjector::FILE_OPERATION_RENAME, |
| + DownloadFileImpl::Rename(full_path)); |
| +} |
| + |
| +net::Error DownloadFileWithErrors::ShouldReturnError( |
| + content::TestFileErrorInjector::FileOperationCode code, |
| + net::Error original_net_error) { |
| + int counter = operation_counter_[code]; |
| + ++operation_counter_[code]; |
| + |
| + if (code != error_info_.code) |
| + return original_net_error; |
| + |
| + if (counter != error_info_.operation_instance) |
| + return original_net_error; |
| + |
| + VLOG(1) << " " << __FUNCTION__ << "()" |
| + << " url = '" << source_url_.spec() << "'" |
| + << " code = " << content::TestFileErrorInjector::DebugString(code) |
| + << " (" << code << ")" |
| + << " counter = " << counter |
| + << " original_error = " << net::ErrorToString(original_net_error) |
| + << " (" << original_net_error << ")" |
| + << " new error = " << net::ErrorToString(error_info_.net_error) |
| + << " (" << error_info_.net_error << ")"; |
| + |
| + return error_info_.net_error; |
| +} |
| + |
| +DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( |
| + const TestFileErrorInjectorImpl::ConstructionCallback& ctor_callback, |
| + const TestFileErrorInjectorImpl::DestructionCallback& dtor_callback) |
| + : construction_callback_(ctor_callback), |
| + destruction_callback_(dtor_callback) { |
| +} |
| + |
| +DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() { |
| +} |
| + |
| +content::DownloadFile* DownloadFileWithErrorsFactory::CreateFile( |
| + DownloadCreateInfo* info, |
| + const DownloadRequestHandle& request_handle, |
| + content::DownloadManager* download_manager, |
| + bool calculate_hash, |
| + const net::BoundNetLog& bound_net_log) { |
| + std::string url = info->url().spec(); |
| + |
| + if (injected_errors_.find(url) == injected_errors_.end()) { |
|
cbentzel
2012/03/02 20:45:19
Just as a question - do you ever expect this case
ahendrickson
2012/03/02 21:58:47
Yes, I would like to handle the case where I have
|
| + // Have to create entry, because FileErrorInfo is not a POD type. |
| + content::TestFileErrorInjector::FileErrorInfo err_info = { |
|
cbentzel
2012/03/02 20:45:19
You could also define a default constructor, that
ahendrickson
2012/03/02 21:58:47
If I have a constructor, I can't use initializer l
Randy Smith (Not in Mondays)
2012/03/06 23:05:19
I don't think there's any conflict between having
ahendrickson
2012/03/08 22:00:08
I actually added a default constructor & tried to
|
| + url, |
| + content::TestFileErrorInjector::FILE_OPERATION_INITIALIZE, |
| + -1, |
| + net::OK |
| + }; |
| + injected_errors_[url] = err_info; |
| + } |
| + |
| + return new DownloadFileWithErrors(info, |
| + new DownloadRequestHandle(request_handle), |
| + download_manager, |
| + calculate_hash, |
| + bound_net_log, |
| + injected_errors_[url], |
| + construction_callback_, |
| + destruction_callback_); |
| +} |
| + |
| +bool DownloadFileWithErrorsFactory::AddError( |
| + const content::TestFileErrorInjector::FileErrorInfo& error_info) { |
| + // Creates an empty entry if necessary. Duplicate entries overwrite. |
| + injected_errors_[error_info.url] = error_info; |
| + |
| + return true; |
| +} |
| + |
| +void DownloadFileWithErrorsFactory::ClearErrors() { |
| + injected_errors_.clear(); |
| +} |
| + |
| +TestFileErrorInjectorImpl::TestFileErrorInjectorImpl() |
| + : created_factory_(false) { |
| +} |
| + |
| +TestFileErrorInjectorImpl::~TestFileErrorInjectorImpl() { |
| +} |
| + |
| +bool TestFileErrorInjectorImpl::AddError(const FileErrorInfo& error_info) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + DCHECK_LE(0, error_info.operation_instance); |
| + DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end()); |
| + |
| + // Creates an empty entry if necessary. |
| + injected_errors_[error_info.url] = error_info; |
| + |
| + return true; |
| +} |
| + |
| +void TestFileErrorInjectorImpl::ClearErrors() { |
| + injected_errors_.clear(); |
| +} |
| + |
| +bool TestFileErrorInjectorImpl::InjectErrors() { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + |
| + DownloadFileManager* download_file_manager = GetDownloadFileManager(); |
| + DCHECK(download_file_manager); |
| + |
| + ClearFoundFiles(); |
| + |
| + if (!created_factory_) { |
| + scoped_ptr<DownloadFileManager::DownloadFileFactory> download_file_factory( |
| + new DownloadFileWithErrorsFactory( |
| + base::Bind(&TestFileErrorInjectorImpl:: |
| + RecordDownloadFileConstruction, |
| + this), |
| + base::Bind(&TestFileErrorInjectorImpl:: |
| + RecordDownloadFileDestruction, |
| + this))); |
| + |
| + download_file_manager->SetFileFactoryForTesting( |
| + download_file_factory.Pass()); |
| + |
| + created_factory_ = true; |
| + } |
| + |
| + DownloadFileWithErrorsFactory* file_factory = |
| + static_cast<DownloadFileWithErrorsFactory*>( |
| + download_file_manager->GetFileFactoryForTesting()); |
| + |
| + file_factory->ClearErrors(); |
| + |
| + for (ErrorMap::const_iterator it = injected_errors_.begin(); |
| + it != injected_errors_.end(); |
| + ++it) { |
| + file_factory->AddError(it->second); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +size_t TestFileErrorInjectorImpl::CurrentFileCount() const { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + return files_.size(); |
| +} |
| + |
| +size_t TestFileErrorInjectorImpl::TotalFileCount() const { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + return found_files_.size(); |
| +} |
| + |
| + |
| +bool TestFileErrorInjectorImpl::HadFile(const GURL& url) const { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + |
| + return (found_files_.find(url) != found_files_.end()); |
| +} |
| + |
| +const content::DownloadId TestFileErrorInjectorImpl::GetId( |
| + const GURL& url) const { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + |
| + FileMap::const_iterator it = found_files_.find(url); |
| + if (it == found_files_.end()) |
| + return content::DownloadId::Invalid(); |
| + |
| + return it->second; |
| +} |
| + |
| +void TestFileErrorInjectorImpl::ClearFoundFiles() { |
| + found_files_.clear(); |
| +} |
| + |
| +void TestFileErrorInjectorImpl::DownloadFileCreated(GURL url, |
| + content::DownloadId id) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + DCHECK(files_.find(url) == files_.end()); |
| + |
| + files_[url] = id; |
| + found_files_[url] = id; |
| +} |
| + |
| +void TestFileErrorInjectorImpl::DestroyingDownloadFile(GURL url) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + DCHECK(files_.find(url) != files_.end()); |
| + |
| + files_.erase(url); |
| +} |
| + |
| +void TestFileErrorInjectorImpl::RecordDownloadFileConstruction( |
| + GURL url, content::DownloadId id) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&TestFileErrorInjectorImpl::DownloadFileCreated, |
| + this, |
| + url, |
| + id)); |
| +} |
| + |
| +void TestFileErrorInjectorImpl::RecordDownloadFileDestruction(GURL url) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&TestFileErrorInjectorImpl::DestroyingDownloadFile, |
| + this, |
| + url)); |
| +} |
| + |
| +} // namespace |
| + |
| +namespace content { |
| + |
| +TestFileErrorInjector::~TestFileErrorInjector() { |
| +} |
| + |
| +// static |
| +scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Get() { |
| + scoped_refptr<TestFileErrorInjector> single_injector_; |
| + if (single_injector_.get() == NULL) |
| + single_injector_ = new TestFileErrorInjectorImpl; |
| + return single_injector_; |
| +} |
| + |
| +std::string TestFileErrorInjector::DebugString(FileOperationCode code) { |
| + |
| +#define TO_STRING(code) \ |
| + case TestFileErrorInjector::FILE_OPERATION_##code: return #code |
| + |
| + switch (code) { |
| + TO_STRING(INITIALIZE); |
| + TO_STRING(WRITE); |
| + TO_STRING(RENAME); |
| + default: |
| + break; |
| + } |
| + |
| +#undef TO_STRING |
| + |
| + return "Unknown"; |
| +} |
| + |
| +} // namespace content |