Home | History | Annotate | Download | only in test
      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 "url/gurl.h"
     19 
     20 namespace content {
     21 class ByteStreamReader;
     22 
     23 namespace {
     24 
     25 // A class that performs file operations and injects errors.
     26 class 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 
     88 static void InitializeErrorCallback(
     89     const DownloadFile::InitializeCallback original_callback,
     90     DownloadInterruptReason overwrite_error,
     91     DownloadInterruptReason original_error) {
     92   original_callback.Run(overwrite_error);
     93 }
     94 
     95 static 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 
    106 DownloadFileWithErrors::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   // DownloadFiles are created on the UI thread and are destroyed on the FILE
    127   // thread. Schedule the ConstructionCallback on the FILE thread so that if a
    128   // DownloadItem schedules a DownloadFile to be destroyed and creates another
    129   // one (as happens during download resumption), then the DestructionCallback
    130   // for the old DownloadFile is run before the ConstructionCallback for the
    131   // next DownloadFile.
    132   BrowserThread::PostTask(
    133       BrowserThread::FILE,
    134       FROM_HERE,
    135       base::Bind(ctor_callback, source_url_));
    136 }
    137 
    138 DownloadFileWithErrors::~DownloadFileWithErrors() {
    139   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    140   destruction_callback_.Run(source_url_);
    141 }
    142 
    143 void DownloadFileWithErrors::Initialize(
    144     const InitializeCallback& callback) {
    145   DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE;
    146   InitializeCallback callback_to_use = callback;
    147 
    148   // Replace callback if the error needs to be overwritten.
    149   if (OverwriteError(
    150           TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
    151           &error_to_return)) {
    152     if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) {
    153       // Don't execute a, probably successful, Initialize; just
    154       // return the error.
    155       BrowserThread::PostTask(
    156           BrowserThread::UI, FROM_HERE, base::Bind(
    157               callback, error_to_return));
    158       return;
    159     }
    160 
    161     // Otherwise, just wrap the return.
    162     callback_to_use = base::Bind(&InitializeErrorCallback, callback,
    163                                  error_to_return);
    164   }
    165 
    166   DownloadFileImpl::Initialize(callback_to_use);
    167 }
    168 
    169 DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile(
    170     const char* data, size_t data_len) {
    171   return ShouldReturnError(
    172       TestFileErrorInjector::FILE_OPERATION_WRITE,
    173       DownloadFileImpl::AppendDataToFile(data, data_len));
    174 }
    175 
    176 void DownloadFileWithErrors::RenameAndUniquify(
    177     const base::FilePath& full_path,
    178     const RenameCompletionCallback& callback) {
    179   DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE;
    180   RenameCompletionCallback callback_to_use = callback;
    181 
    182   // Replace callback if the error needs to be overwritten.
    183   if (OverwriteError(
    184           TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY,
    185           &error_to_return)) {
    186     if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) {
    187       // Don't execute a, probably successful, RenameAndUniquify; just
    188       // return the error.
    189       BrowserThread::PostTask(
    190           BrowserThread::UI, FROM_HERE, base::Bind(
    191               callback, error_to_return, base::FilePath()));
    192       return;
    193     }
    194 
    195     // Otherwise, just wrap the return.
    196     callback_to_use = base::Bind(&RenameErrorCallback, callback,
    197                                  error_to_return);
    198   }
    199 
    200   DownloadFileImpl::RenameAndUniquify(full_path, callback_to_use);
    201 }
    202 
    203 void DownloadFileWithErrors::RenameAndAnnotate(
    204     const base::FilePath& full_path,
    205     const RenameCompletionCallback& callback) {
    206   DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE;
    207   RenameCompletionCallback callback_to_use = callback;
    208 
    209   // Replace callback if the error needs to be overwritten.
    210   if (OverwriteError(
    211           TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE,
    212           &error_to_return)) {
    213     if (DOWNLOAD_INTERRUPT_REASON_NONE != error_to_return) {
    214       // Don't execute a, probably successful, RenameAndAnnotate; just
    215       // return the error.
    216       BrowserThread::PostTask(
    217           BrowserThread::UI, FROM_HERE, base::Bind(
    218               callback, error_to_return, base::FilePath()));
    219       return;
    220     }
    221 
    222     // Otherwise, just wrap the return.
    223     callback_to_use = base::Bind(&RenameErrorCallback, callback,
    224                                  error_to_return);
    225   }
    226 
    227   DownloadFileImpl::RenameAndAnnotate(full_path, callback_to_use);
    228 }
    229 
    230 bool DownloadFileWithErrors::OverwriteError(
    231     TestFileErrorInjector::FileOperationCode code,
    232     DownloadInterruptReason* output_error) {
    233   int counter = operation_counter_[code]++;
    234 
    235   if (code != error_info_.code)
    236     return false;
    237 
    238   if (counter != error_info_.operation_instance)
    239     return false;
    240 
    241   *output_error = error_info_.error;
    242   return true;
    243 }
    244 
    245 DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError(
    246     TestFileErrorInjector::FileOperationCode code,
    247     DownloadInterruptReason original_error) {
    248   DownloadInterruptReason output_error = original_error;
    249   OverwriteError(code, &output_error);
    250   return output_error;
    251 }
    252 
    253 }  // namespace
    254 
    255 // A factory for constructing DownloadFiles that inject errors.
    256 class DownloadFileWithErrorsFactory : public DownloadFileFactory {
    257  public:
    258   DownloadFileWithErrorsFactory(
    259       const DownloadFileWithErrors::ConstructionCallback& ctor_callback,
    260       const DownloadFileWithErrors::DestructionCallback& dtor_callback);
    261   virtual ~DownloadFileWithErrorsFactory();
    262 
    263   // DownloadFileFactory interface.
    264   virtual DownloadFile* CreateFile(
    265       scoped_ptr<DownloadSaveInfo> save_info,
    266       const base::FilePath& default_download_directory,
    267       const GURL& url,
    268       const GURL& referrer_url,
    269       bool calculate_hash,
    270       scoped_ptr<ByteStreamReader> stream,
    271       const net::BoundNetLog& bound_net_log,
    272       base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE;
    273 
    274   bool AddError(
    275       const TestFileErrorInjector::FileErrorInfo& error_info);
    276 
    277   void ClearErrors();
    278 
    279  private:
    280   // Our injected error list, mapped by URL.  One per file.
    281    TestFileErrorInjector::ErrorMap injected_errors_;
    282 
    283   // Callback for creation and destruction.
    284   DownloadFileWithErrors::ConstructionCallback construction_callback_;
    285   DownloadFileWithErrors::DestructionCallback destruction_callback_;
    286 };
    287 
    288 DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory(
    289     const DownloadFileWithErrors::ConstructionCallback& ctor_callback,
    290     const DownloadFileWithErrors::DestructionCallback& dtor_callback)
    291         : construction_callback_(ctor_callback),
    292           destruction_callback_(dtor_callback) {
    293 }
    294 
    295 DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() {
    296 }
    297 
    298 DownloadFile* DownloadFileWithErrorsFactory::CreateFile(
    299     scoped_ptr<DownloadSaveInfo> save_info,
    300     const base::FilePath& default_download_directory,
    301     const GURL& url,
    302     const GURL& referrer_url,
    303     bool calculate_hash,
    304     scoped_ptr<ByteStreamReader> stream,
    305     const net::BoundNetLog& bound_net_log,
    306     base::WeakPtr<DownloadDestinationObserver> observer) {
    307   if (injected_errors_.find(url.spec()) == injected_errors_.end()) {
    308     // Have to create entry, because FileErrorInfo is not a POD type.
    309     TestFileErrorInjector::FileErrorInfo err_info = {
    310       url.spec(),
    311       TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
    312       -1,
    313       DOWNLOAD_INTERRUPT_REASON_NONE
    314     };
    315     injected_errors_[url.spec()] = err_info;
    316   }
    317 
    318   scoped_ptr<PowerSaveBlocker> psb(
    319       PowerSaveBlocker::Create(
    320           PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
    321           "Download in progress"));
    322 
    323   return new DownloadFileWithErrors(
    324       save_info.Pass(),
    325       default_download_directory,
    326       url,
    327       referrer_url,
    328       calculate_hash,
    329       stream.Pass(),
    330       bound_net_log,
    331       psb.Pass(),
    332       observer,
    333       injected_errors_[url.spec()],
    334       construction_callback_,
    335       destruction_callback_);
    336 }
    337 
    338 bool DownloadFileWithErrorsFactory::AddError(
    339     const TestFileErrorInjector::FileErrorInfo& error_info) {
    340   // Creates an empty entry if necessary.  Duplicate entries overwrite.
    341   injected_errors_[error_info.url] = error_info;
    342 
    343   return true;
    344 }
    345 
    346 void DownloadFileWithErrorsFactory::ClearErrors() {
    347   injected_errors_.clear();
    348 }
    349 
    350 TestFileErrorInjector::TestFileErrorInjector(
    351     DownloadManager* download_manager)
    352     : created_factory_(NULL),
    353       // This code is only used for browser_tests, so a
    354       // DownloadManager is always a DownloadManagerImpl.
    355       download_manager_(static_cast<DownloadManagerImpl*>(download_manager)) {
    356   // Record the value of the pointer, for later validation.
    357   created_factory_ =
    358       new DownloadFileWithErrorsFactory(
    359           base::Bind(&TestFileErrorInjector::RecordDownloadFileConstruction,
    360                      this),
    361           base::Bind(&TestFileErrorInjector::RecordDownloadFileDestruction,
    362                      this));
    363 
    364   // We will transfer ownership of the factory to the download manager.
    365   scoped_ptr<DownloadFileFactory> download_file_factory(
    366       created_factory_);
    367 
    368   download_manager_->SetDownloadFileFactoryForTesting(
    369       download_file_factory.Pass());
    370 }
    371 
    372 TestFileErrorInjector::~TestFileErrorInjector() {
    373 }
    374 
    375 bool TestFileErrorInjector::AddError(const FileErrorInfo& error_info) {
    376   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    377   DCHECK_LE(0, error_info.operation_instance);
    378   DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end());
    379 
    380   // Creates an empty entry if necessary.
    381   injected_errors_[error_info.url] = error_info;
    382 
    383   return true;
    384 }
    385 
    386 void TestFileErrorInjector::ClearErrors() {
    387   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    388   injected_errors_.clear();
    389 }
    390 
    391 bool TestFileErrorInjector::InjectErrors() {
    392   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    393 
    394   ClearFoundFiles();
    395 
    396   DCHECK_EQ(static_cast<DownloadFileFactory*>(created_factory_),
    397             download_manager_->GetDownloadFileFactoryForTesting());
    398 
    399   created_factory_->ClearErrors();
    400 
    401   for (ErrorMap::const_iterator it = injected_errors_.begin();
    402        it != injected_errors_.end(); ++it)
    403     created_factory_->AddError(it->second);
    404 
    405   return true;
    406 }
    407 
    408 size_t TestFileErrorInjector::CurrentFileCount() const {
    409   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    410   return files_.size();
    411 }
    412 
    413 size_t TestFileErrorInjector::TotalFileCount() const {
    414   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    415   return found_files_.size();
    416 }
    417 
    418 
    419 bool TestFileErrorInjector::HadFile(const GURL& url) const {
    420   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    421 
    422   return (found_files_.find(url) != found_files_.end());
    423 }
    424 
    425 void TestFileErrorInjector::ClearFoundFiles() {
    426   found_files_.clear();
    427 }
    428 
    429 void TestFileErrorInjector::DownloadFileCreated(GURL url) {
    430   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    431   DCHECK(files_.find(url) == files_.end());
    432 
    433   files_.insert(url);
    434   found_files_.insert(url);
    435 }
    436 
    437 void TestFileErrorInjector::DestroyingDownloadFile(GURL url) {
    438   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    439   DCHECK(files_.find(url) != files_.end());
    440 
    441   files_.erase(url);
    442 }
    443 
    444 void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) {
    445   BrowserThread::PostTask(
    446       BrowserThread::UI,
    447       FROM_HERE,
    448       base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url));
    449 }
    450 
    451 void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) {
    452   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    453       base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url));
    454 }
    455 
    456 // static
    457 scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Create(
    458     DownloadManager* download_manager) {
    459   static bool visited = false;
    460   DCHECK(!visited);  // Only allowed to be called once.
    461   visited = true;
    462 
    463   scoped_refptr<TestFileErrorInjector> single_injector(
    464       new TestFileErrorInjector(download_manager));
    465 
    466   return single_injector;
    467 }
    468 
    469 // static
    470 std::string TestFileErrorInjector::DebugString(FileOperationCode code) {
    471   switch (code) {
    472     case FILE_OPERATION_INITIALIZE:
    473       return "INITIALIZE";
    474     case FILE_OPERATION_WRITE:
    475       return "WRITE";
    476     case FILE_OPERATION_RENAME_UNIQUIFY:
    477       return "RENAME_UNIQUIFY";
    478     case FILE_OPERATION_RENAME_ANNOTATE:
    479       return "RENAME_ANNOTATE";
    480     default:
    481       break;
    482   }
    483 
    484   return "Unknown";
    485 }
    486 
    487 }  // namespace content
    488