test_file_error_injector.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/public/test/test_file_error_injector.h" 6 7#include <vector> 8 9#include "base/compiler_specific.h" 10#include "base/logging.h" 11#include "content/browser/download/download_file_factory.h" 12#include "content/browser/download/download_file_impl.h" 13#include "content/browser/download/download_interrupt_reasons_impl.h" 14#include "content/browser/download/download_manager_impl.h" 15#include "content/browser/loader/resource_dispatcher_host_impl.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/browser/power_save_blocker.h" 18#include "googleurl/src/gurl.h" 19 20namespace content { 21class ByteStreamReader; 22 23namespace { 24 25// A class that performs file operations and injects errors. 26class DownloadFileWithErrors: public DownloadFileImpl { 27 public: 28 typedef base::Callback<void(const GURL& url)> ConstructionCallback; 29 typedef base::Callback<void(const GURL& url)> DestructionCallback; 30 31 DownloadFileWithErrors( 32 scoped_ptr<DownloadSaveInfo> save_info, 33 const base::FilePath& default_download_directory, 34 const GURL& url, 35 const GURL& referrer_url, 36 bool calculate_hash, 37 scoped_ptr<ByteStreamReader> stream, 38 const net::BoundNetLog& bound_net_log, 39 scoped_ptr<PowerSaveBlocker> power_save_blocker, 40 base::WeakPtr<DownloadDestinationObserver> observer, 41 const TestFileErrorInjector::FileErrorInfo& error_info, 42 const ConstructionCallback& ctor_callback, 43 const DestructionCallback& dtor_callback); 44 45 virtual ~DownloadFileWithErrors(); 46 47 virtual void Initialize(const InitializeCallback& callback) OVERRIDE; 48 49 // DownloadFile interface. 50 virtual DownloadInterruptReason AppendDataToFile( 51 const char* data, size_t data_len) OVERRIDE; 52 virtual void RenameAndUniquify( 53 const base::FilePath& full_path, 54 const RenameCompletionCallback& callback) OVERRIDE; 55 virtual void RenameAndAnnotate( 56 const base::FilePath& full_path, 57 const RenameCompletionCallback& callback) OVERRIDE; 58 59 private: 60 // Error generating helper. 61 DownloadInterruptReason ShouldReturnError( 62 TestFileErrorInjector::FileOperationCode code, 63 DownloadInterruptReason original_error); 64 65 // Determine whether to overwrite an operation with the given code 66 // with a substitute error; if returns true, |*original_error| is 67 // written with the error to use for overwriting. 68 // NOTE: This routine changes state; specifically, it increases the 69 // operations counts for the specified code. It should only be called 70 // once per operation. 71 bool OverwriteError( 72 TestFileErrorInjector::FileOperationCode code, 73 DownloadInterruptReason* output_error); 74 75 // Source URL for the file being downloaded. 76 GURL source_url_; 77 78 // Our injected error. Only one per file. 79 TestFileErrorInjector::FileErrorInfo error_info_; 80 81 // Count per operation. 0-based. 82 std::map<TestFileErrorInjector::FileOperationCode, int> operation_counter_; 83 84 // Callback for destruction. 85 DestructionCallback destruction_callback_; 86}; 87 88static void InitializeErrorCallback( 89 const DownloadFile::InitializeCallback original_callback, 90 DownloadInterruptReason overwrite_error, 91 DownloadInterruptReason original_error) { 92 original_callback.Run(overwrite_error); 93} 94 95static void RenameErrorCallback( 96 const DownloadFile::RenameCompletionCallback original_callback, 97 DownloadInterruptReason overwrite_error, 98 DownloadInterruptReason original_error, 99 const base::FilePath& path_result) { 100 original_callback.Run( 101 overwrite_error, 102 overwrite_error == DOWNLOAD_INTERRUPT_REASON_NONE ? 103 path_result : base::FilePath()); 104} 105 106DownloadFileWithErrors::DownloadFileWithErrors( 107 scoped_ptr<DownloadSaveInfo> save_info, 108 const base::FilePath& default_download_directory, 109 const GURL& url, 110 const GURL& referrer_url, 111 bool calculate_hash, 112 scoped_ptr<ByteStreamReader> stream, 113 const net::BoundNetLog& bound_net_log, 114 scoped_ptr<PowerSaveBlocker> power_save_blocker, 115 base::WeakPtr<DownloadDestinationObserver> observer, 116 const TestFileErrorInjector::FileErrorInfo& error_info, 117 const ConstructionCallback& ctor_callback, 118 const DestructionCallback& dtor_callback) 119 : DownloadFileImpl( 120 save_info.Pass(), default_download_directory, url, referrer_url, 121 calculate_hash, stream.Pass(), bound_net_log, 122 power_save_blocker.Pass(), observer), 123 source_url_(url), 124 error_info_(error_info), 125 destruction_callback_(dtor_callback) { 126 ctor_callback.Run(source_url_); 127} 128 129DownloadFileWithErrors::~DownloadFileWithErrors() { 130 destruction_callback_.Run(source_url_); 131} 132 133void DownloadFileWithErrors::Initialize( 134 const InitializeCallback& callback) { 135 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 136 InitializeCallback callback_to_use = callback; 137 138 // Replace callback if the error needs to be overwritten. 139 if (OverwriteError( 140 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 141 &error_to_return)) { 142 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 143 // Don't execute a, probably successful, Initialize; just 144 // return the error. 145 BrowserThread::PostTask( 146 BrowserThread::UI, FROM_HERE, base::Bind( 147 callback, error_to_return)); 148 return; 149 } 150 151 // Otherwise, just wrap the return. 152 callback_to_use = base::Bind(&InitializeErrorCallback, callback, 153 error_to_return); 154 } 155 156 DownloadFileImpl::Initialize(callback_to_use); 157} 158 159DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile( 160 const char* data, size_t data_len) { 161 return ShouldReturnError( 162 TestFileErrorInjector::FILE_OPERATION_WRITE, 163 DownloadFileImpl::AppendDataToFile(data, data_len)); 164} 165 166void DownloadFileWithErrors::RenameAndUniquify( 167 const base::FilePath& full_path, 168 const RenameCompletionCallback& callback) { 169 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 170 RenameCompletionCallback callback_to_use = callback; 171 172 // Replace callback if the error needs to be overwritten. 173 if (OverwriteError( 174 TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 175 &error_to_return)) { 176 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 177 // Don't execute a, probably successful, RenameAndUniquify; just 178 // return the error. 179 BrowserThread::PostTask( 180 BrowserThread::UI, FROM_HERE, base::Bind( 181 callback, error_to_return, base::FilePath())); 182 return; 183 } 184 185 // Otherwise, just wrap the return. 186 callback_to_use = base::Bind(&RenameErrorCallback, callback, 187 error_to_return); 188 } 189 190 DownloadFileImpl::RenameAndUniquify(full_path, callback_to_use); 191} 192 193void DownloadFileWithErrors::RenameAndAnnotate( 194 const base::FilePath& full_path, 195 const RenameCompletionCallback& callback) { 196 DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE; 197 RenameCompletionCallback callback_to_use = callback; 198 199 // Replace callback if the error needs to be overwritten. 200 if (OverwriteError( 201 TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 202 &error_to_return)) { 203 if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) { 204 // Don't execute a, probably successful, RenameAndAnnotate; just 205 // return the error. 206 BrowserThread::PostTask( 207 BrowserThread::UI, FROM_HERE, base::Bind( 208 callback, error_to_return, base::FilePath())); 209 return; 210 } 211 212 // Otherwise, just wrap the return. 213 callback_to_use = base::Bind(&RenameErrorCallback, callback, 214 error_to_return); 215 } 216 217 DownloadFileImpl::RenameAndAnnotate(full_path, callback_to_use); 218} 219 220bool DownloadFileWithErrors::OverwriteError( 221 TestFileErrorInjector::FileOperationCode code, 222 DownloadInterruptReason* output_error) { 223 int counter = operation_counter_[code]++; 224 225 if (code != error_info_.code) 226 return false; 227 228 if (counter != error_info_.operation_instance) 229 return false; 230 231 *output_error = error_info_.error; 232 return true; 233} 234 235DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError( 236 TestFileErrorInjector::FileOperationCode code, 237 DownloadInterruptReason original_error) { 238 DownloadInterruptReason output_error = original_error; 239 OverwriteError(code, &output_error); 240 return output_error; 241} 242 243} // namespace 244 245// A factory for constructing DownloadFiles that inject errors. 246class DownloadFileWithErrorsFactory : public DownloadFileFactory { 247 public: 248 DownloadFileWithErrorsFactory( 249 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 250 const DownloadFileWithErrors::DestructionCallback& dtor_callback); 251 virtual ~DownloadFileWithErrorsFactory(); 252 253 // DownloadFileFactory interface. 254 virtual DownloadFile* CreateFile( 255 scoped_ptr<DownloadSaveInfo> save_info, 256 const base::FilePath& default_download_directory, 257 const GURL& url, 258 const GURL& referrer_url, 259 bool calculate_hash, 260 scoped_ptr<ByteStreamReader> stream, 261 const net::BoundNetLog& bound_net_log, 262 base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE; 263 264 bool AddError( 265 const TestFileErrorInjector::FileErrorInfo& error_info); 266 267 void ClearErrors(); 268 269 private: 270 // Our injected error list, mapped by URL. One per file. 271 TestFileErrorInjector::ErrorMap injected_errors_; 272 273 // Callback for creation and destruction. 274 DownloadFileWithErrors::ConstructionCallback construction_callback_; 275 DownloadFileWithErrors::DestructionCallback destruction_callback_; 276}; 277 278DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( 279 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, 280 const DownloadFileWithErrors::DestructionCallback& dtor_callback) 281 : construction_callback_(ctor_callback), 282 destruction_callback_(dtor_callback) { 283} 284 285DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() { 286} 287 288DownloadFile* DownloadFileWithErrorsFactory::CreateFile( 289 scoped_ptr<DownloadSaveInfo> save_info, 290 const base::FilePath& default_download_directory, 291 const GURL& url, 292 const GURL& referrer_url, 293 bool calculate_hash, 294 scoped_ptr<ByteStreamReader> stream, 295 const net::BoundNetLog& bound_net_log, 296 base::WeakPtr<DownloadDestinationObserver> observer) { 297 if (injected_errors_.find(url.spec()) == injected_errors_.end()) { 298 // Have to create entry, because FileErrorInfo is not a POD type. 299 TestFileErrorInjector::FileErrorInfo err_info = { 300 url.spec(), 301 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 302 -1, 303 DOWNLOAD_INTERRUPT_REASON_NONE 304 }; 305 injected_errors_[url.spec()] = err_info; 306 } 307 308 scoped_ptr<PowerSaveBlocker> psb( 309 PowerSaveBlocker::Create( 310 PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, 311 "Download in progress")); 312 313 return new DownloadFileWithErrors( 314 save_info.Pass(), 315 default_download_directory, 316 url, 317 referrer_url, 318 calculate_hash, 319 stream.Pass(), 320 bound_net_log, 321 psb.Pass(), 322 observer, 323 injected_errors_[url.spec()], 324 construction_callback_, 325 destruction_callback_); 326} 327 328bool DownloadFileWithErrorsFactory::AddError( 329 const TestFileErrorInjector::FileErrorInfo& error_info) { 330 // Creates an empty entry if necessary. Duplicate entries overwrite. 331 injected_errors_[error_info.url] = error_info; 332 333 return true; 334} 335 336void DownloadFileWithErrorsFactory::ClearErrors() { 337 injected_errors_.clear(); 338} 339 340TestFileErrorInjector::TestFileErrorInjector( 341 scoped_refptr<DownloadManager> download_manager) 342 : created_factory_(NULL), 343 // This code is only used for browser_tests, so a 344 // DownloadManager is always a DownloadManagerImpl. 345 download_manager_( 346 static_cast<DownloadManagerImpl*>(download_manager.get())) { 347 // Record the value of the pointer, for later validation. 348 created_factory_ = 349 new DownloadFileWithErrorsFactory( 350 base::Bind(&TestFileErrorInjector::RecordDownloadFileConstruction, 351 this), 352 base::Bind(&TestFileErrorInjector::RecordDownloadFileDestruction, 353 this)); 354 355 // We will transfer ownership of the factory to the download manager. 356 scoped_ptr<DownloadFileFactory> download_file_factory( 357 created_factory_); 358 359 download_manager_->SetDownloadFileFactoryForTesting( 360 download_file_factory.Pass()); 361} 362 363TestFileErrorInjector::~TestFileErrorInjector() { 364} 365 366bool TestFileErrorInjector::AddError(const FileErrorInfo& error_info) { 367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 368 DCHECK_LE(0, error_info.operation_instance); 369 DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end()); 370 371 // Creates an empty entry if necessary. 372 injected_errors_[error_info.url] = error_info; 373 374 return true; 375} 376 377void TestFileErrorInjector::ClearErrors() { 378 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 379 injected_errors_.clear(); 380} 381 382bool TestFileErrorInjector::InjectErrors() { 383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 384 385 ClearFoundFiles(); 386 387 DCHECK_EQ(static_cast<DownloadFileFactory*>(created_factory_), 388 download_manager_->GetDownloadFileFactoryForTesting()); 389 390 created_factory_->ClearErrors(); 391 392 for (ErrorMap::const_iterator it = injected_errors_.begin(); 393 it != injected_errors_.end(); ++it) 394 created_factory_->AddError(it->second); 395 396 return true; 397} 398 399size_t TestFileErrorInjector::CurrentFileCount() const { 400 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 401 return files_.size(); 402} 403 404size_t TestFileErrorInjector::TotalFileCount() const { 405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 406 return found_files_.size(); 407} 408 409 410bool TestFileErrorInjector::HadFile(const GURL& url) const { 411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 412 413 return (found_files_.find(url) != found_files_.end()); 414} 415 416void TestFileErrorInjector::ClearFoundFiles() { 417 found_files_.clear(); 418} 419 420void TestFileErrorInjector::DownloadFileCreated(GURL url) { 421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 422 DCHECK(files_.find(url) == files_.end()); 423 424 files_.insert(url); 425 found_files_.insert(url); 426} 427 428void TestFileErrorInjector::DestroyingDownloadFile(GURL url) { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 DCHECK(files_.find(url) != files_.end()); 431 432 files_.erase(url); 433} 434 435void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) { 436 BrowserThread::PostTask( 437 BrowserThread::UI, 438 FROM_HERE, 439 base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url)); 440} 441 442void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) { 443 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 444 base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url)); 445} 446 447// static 448scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Create( 449 scoped_refptr<DownloadManager> download_manager) { 450 static bool visited = false; 451 DCHECK(!visited); // Only allowed to be called once. 452 visited = true; 453 454 scoped_refptr<TestFileErrorInjector> single_injector( 455 new TestFileErrorInjector(download_manager)); 456 457 return single_injector; 458} 459 460// static 461std::string TestFileErrorInjector::DebugString(FileOperationCode code) { 462 switch (code) { 463 case FILE_OPERATION_INITIALIZE: 464 return "INITIALIZE"; 465 case FILE_OPERATION_WRITE: 466 return "WRITE"; 467 case FILE_OPERATION_RENAME_UNIQUIFY: 468 return "RENAME_UNIQUIFY"; 469 case FILE_OPERATION_RENAME_ANNOTATE: 470 return "RENAME_ANNOTATE"; 471 default: 472 break; 473 } 474 475 return "Unknown"; 476} 477 478} // namespace content 479