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
19namespace content {
20class ByteStreamReader;
21
22namespace {
23
24// A class that performs file operations and injects errors.
25class 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
86static void InitializeErrorCallback(
87    const DownloadFile::InitializeCallback original_callback,
88    DownloadInterruptReason overwrite_error,
89    DownloadInterruptReason original_error) {
90  original_callback.Run(overwrite_error);
91}
92
93static 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
104DownloadFileWithErrors::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
134DownloadFileWithErrors::~DownloadFileWithErrors() {
135  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
136  destruction_callback_.Run(source_url_);
137}
138
139void 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
165DownloadInterruptReason 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
172void 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
199void 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
226bool 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
241DownloadInterruptReason 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.
252class 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
284DownloadFileWithErrorsFactory::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
291DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() {
292}
293
294DownloadFile* 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
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