test_file_error_injector.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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    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
362TestFileErrorInjector::~TestFileErrorInjector() {
363}
364
365bool 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
376void TestFileErrorInjector::ClearErrors() {
377  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
378  injected_errors_.clear();
379}
380
381bool 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
398size_t TestFileErrorInjector::CurrentFileCount() const {
399  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
400  return files_.size();
401}
402
403size_t TestFileErrorInjector::TotalFileCount() const {
404  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
405  return found_files_.size();
406}
407
408
409bool TestFileErrorInjector::HadFile(const GURL& url) const {
410  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411
412  return (found_files_.find(url) != found_files_.end());
413}
414
415void TestFileErrorInjector::ClearFoundFiles() {
416  found_files_.clear();
417}
418
419void 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
427void TestFileErrorInjector::DestroyingDownloadFile(GURL url) {
428  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
429  DCHECK(files_.find(url) != files_.end());
430
431  files_.erase(url);
432}
433
434void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) {
435  BrowserThread::PostTask(
436      BrowserThread::UI,
437      FROM_HERE,
438      base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url));
439}
440
441void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) {
442  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
443      base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url));
444}
445
446// static
447scoped_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
460std::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