test_file_error_injector.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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
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  // 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
138DownloadFileWithErrors::~DownloadFileWithErrors() {
139  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
140  destruction_callback_.Run(source_url_);
141}
142
143void 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
169DownloadInterruptReason 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
176void 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
203void 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
230bool 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
245DownloadInterruptReason 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.
256class 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
288DownloadFileWithErrorsFactory::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
295DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() {
296}
297
298DownloadFile* 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
338bool 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
346void DownloadFileWithErrorsFactory::ClearErrors() {
347  injected_errors_.clear();
348}
349
350TestFileErrorInjector::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
372TestFileErrorInjector::~TestFileErrorInjector() {
373}
374
375bool 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
386void TestFileErrorInjector::ClearErrors() {
387  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
388  injected_errors_.clear();
389}
390
391bool 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
408size_t TestFileErrorInjector::CurrentFileCount() const {
409  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
410  return files_.size();
411}
412
413size_t TestFileErrorInjector::TotalFileCount() const {
414  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
415  return found_files_.size();
416}
417
418
419bool TestFileErrorInjector::HadFile(const GURL& url) const {
420  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421
422  return (found_files_.find(url) != found_files_.end());
423}
424
425void TestFileErrorInjector::ClearFoundFiles() {
426  found_files_.clear();
427}
428
429void 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
437void TestFileErrorInjector::DestroyingDownloadFile(GURL url) {
438  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
439  DCHECK(files_.find(url) != files_.end());
440
441  files_.erase(url);
442}
443
444void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) {
445  BrowserThread::PostTask(
446      BrowserThread::UI,
447      FROM_HERE,
448      base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url));
449}
450
451void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) {
452  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
453      base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url));
454}
455
456// static
457scoped_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
470std::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