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