// 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 #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_impl.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_id.h" #include "googleurl/src/gurl.h" namespace { DownloadFileManager* GetDownloadFileManager() { content::ResourceDispatcherHostImpl* rdh = content::ResourceDispatcherHostImpl::Get(); DCHECK(rdh != NULL); return rdh->download_file_manager(); } // A class that performs file operations and injects errors. class DownloadFileWithErrors: public DownloadFileImpl { public: typedef base::Callback ConstructionCallback; typedef base::Callback DestructionCallback; 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 ConstructionCallback& ctor_callback, const 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 operation_counter_; // Callback for destruction. DestructionCallback destruction_callback_; }; 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 ConstructionCallback& ctor_callback, const 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; } } // namespace namespace content { // A factory for constructing DownloadFiles that inject errors. class DownloadFileWithErrorsFactory : public DownloadFileManager::DownloadFileFactory { public: DownloadFileWithErrorsFactory( const DownloadFileWithErrors::ConstructionCallback& ctor_callback, const DownloadFileWithErrors::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 TestFileErrorInjector::FileErrorInfo& error_info); void ClearErrors(); private: // Our injected error list, mapped by URL. One per file. TestFileErrorInjector::ErrorMap injected_errors_; // Callback for creation and destruction. DownloadFileWithErrors::ConstructionCallback construction_callback_; DownloadFileWithErrors::DestructionCallback destruction_callback_; }; DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( const DownloadFileWithErrors::ConstructionCallback& ctor_callback, const DownloadFileWithErrors::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. TestFileErrorInjector::FileErrorInfo err_info = { url, 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 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(); } TestFileErrorInjector::TestFileErrorInjector() : created_factory_(NULL) { // Record the value of the pointer, for later validation. created_factory_ = new DownloadFileWithErrorsFactory( base::Bind(&TestFileErrorInjector:: RecordDownloadFileConstruction, this), base::Bind(&TestFileErrorInjector:: RecordDownloadFileDestruction, this)); // We will transfer ownership of the factory to the download file manager. scoped_ptr download_file_factory( created_factory_); content::BrowserThread::PostTask( content::BrowserThread::FILE, FROM_HERE, base::Bind(&TestFileErrorInjector::AddFactory, this, base::Passed(&download_file_factory))); } TestFileErrorInjector::~TestFileErrorInjector() { } void TestFileErrorInjector::AddFactory( scoped_ptr factory) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); DownloadFileManager* download_file_manager = GetDownloadFileManager(); DCHECK(download_file_manager); // Convert to base class pointer, for GCC. scoped_ptr plain_factory( factory.release()); download_file_manager->SetFileFactoryForTesting(plain_factory.Pass()); } bool TestFileErrorInjector::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 TestFileErrorInjector::ClearErrors() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); injected_errors_.clear(); } bool TestFileErrorInjector::InjectErrors() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); ClearFoundFiles(); content::BrowserThread::PostTask( content::BrowserThread::FILE, FROM_HERE, base::Bind(&TestFileErrorInjector::InjectErrorsOnFileThread, this, injected_errors_, created_factory_)); return true; } void TestFileErrorInjector::InjectErrorsOnFileThread( ErrorMap map, DownloadFileWithErrorsFactory* factory) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); // Validate that our factory is in use. DownloadFileManager* download_file_manager = GetDownloadFileManager(); DCHECK(download_file_manager); DownloadFileManager::DownloadFileFactory* file_factory = download_file_manager->GetFileFactoryForTesting(); // Validate that we still have the same factory. DCHECK_EQ(static_cast(factory), file_factory); // We want to replace all existing injection errors. factory->ClearErrors(); for (ErrorMap::const_iterator it = map.begin(); it != map.end(); ++it) factory->AddError(it->second); } size_t TestFileErrorInjector::CurrentFileCount() const { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); return files_.size(); } size_t TestFileErrorInjector::TotalFileCount() const { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); return found_files_.size(); } bool TestFileErrorInjector::HadFile(const GURL& url) const { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); return (found_files_.find(url) != found_files_.end()); } const content::DownloadId TestFileErrorInjector::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 TestFileErrorInjector::ClearFoundFiles() { found_files_.clear(); } void TestFileErrorInjector::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 TestFileErrorInjector::DestroyingDownloadFile(GURL url) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(files_.find(url) != files_.end()); files_.erase(url); } void TestFileErrorInjector::RecordDownloadFileConstruction( const GURL& url, content::DownloadId id) { content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url, id)); } void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) { content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url)); } // static scoped_refptr TestFileErrorInjector::Create() { static bool visited = false; DCHECK(!visited); // Only allowed to be called once. visited = true; scoped_refptr single_injector( new TestFileErrorInjector); return single_injector; } // static std::string TestFileErrorInjector::DebugString(FileOperationCode code) { switch (code) { case FILE_OPERATION_INITIALIZE: return "INITIALIZE"; case FILE_OPERATION_WRITE: return "WRITE"; case FILE_OPERATION_RENAME: return "RENAME"; default: break; } return "Unknown"; } } // namespace content