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..40d00587ea0d982a2e4052a74831ff299bab714d |
| --- /dev/null |
| +++ b/content/test/test_file_error_injector.cc |
| @@ -0,0 +1,460 @@ |
| +// 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/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 "content/public/browser/browser_thread.h" |
| +#include "content/public/browser/download_id.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 InjectErrorsOnFileThread(scoped_ptr<ErrorMap> map); |
| + |
| + void RecordDownloadFileConstruction(GURL url, content::DownloadId id); |
|
Randy Smith (Not in Mondays)
2012/03/06 23:05:19
I'd be inclined to say that you should document th
ahendrickson
2012/03/08 22:00:08
Done.
|
| + 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 URL. One per file. |
| + ErrorMap injected_errors_; |
| + |
| + // Keep track of active DownloadFiles. |
| + FileMap files_; |
| + |
| + // Keep track of found DownloadFiles. |
| + FileMap found_files_; |
| + |
| + // The factory we created. USED FOR VALIDATION ONLY. |
| + DownloadFileManager::DownloadFileFactory* 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()) { |
| + // Have to create entry, because FileErrorInfo is not a POD type. |
| + content::TestFileErrorInjector::FileErrorInfo err_info = { |
| + 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_(NULL) { |
| + DownloadFileManager* download_file_manager = GetDownloadFileManager(); |
| + DCHECK(download_file_manager); |
| + |
| + scoped_ptr<DownloadFileManager::DownloadFileFactory> download_file_factory( |
| + new DownloadFileWithErrorsFactory( |
| + base::Bind(&TestFileErrorInjectorImpl:: |
| + RecordDownloadFileConstruction, |
| + this), |
| + base::Bind(&TestFileErrorInjectorImpl:: |
| + RecordDownloadFileDestruction, |
| + this))); |
| + |
| + // Record the value of the pointer, for later validation. |
| + created_factory_ = download_file_factory.get(); |
| + |
| + download_file_manager->SetFileFactoryForTesting( |
| + download_file_factory.Pass()); |
| +} |
| + |
| +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)); |
| + |
| + ClearFoundFiles(); |
| + |
| + scoped_ptr<ErrorMap> injected_errors_copy(new ErrorMap()); |
| + |
| + *injected_errors_copy = injected_errors_; |
| + |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::FILE, |
| + FROM_HERE, |
| + base::Bind(&TestFileErrorInjectorImpl::InjectErrorsOnFileThread, |
| + this, |
| + base::Passed(injected_errors_copy.Pass()))); |
|
Randy Smith (Not in Mondays)
2012/03/06 23:05:19
Why not pass this by value? I'm not sure it's the
ahendrickson
2012/03/08 22:00:08
Done.
|
| + |
| + return true; |
| +} |
| + |
| +void TestFileErrorInjectorImpl::InjectErrorsOnFileThread( |
| + scoped_ptr<ErrorMap> map) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); |
| + |
| + DownloadFileManager* download_file_manager = GetDownloadFileManager(); |
| + DCHECK(download_file_manager); |
| + |
| + DownloadFileWithErrorsFactory* file_factory = |
| + static_cast<DownloadFileWithErrorsFactory*>( |
| + download_file_manager->GetFileFactoryForTesting()); |
| + |
| + // Validate that we still have the same factory. |
| + DCHECK_EQ(created_factory_, file_factory); |
| + |
| + // We want to replace all existing injection errors. |
| + file_factory->ClearErrors(); |
| + |
| + for (ErrorMap::const_iterator it = map->begin(); it != map->end(); ++it) |
| + file_factory->AddError(it->second); |
| +} |
| + |
| +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::Create() { |
| + static bool visited = false; |
| + DCHECK(!visited); // Only allowed to be called once. |
| + visited = true; |
| + |
| + scoped_refptr<TestFileErrorInjector> 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 |