test_file_error_injector.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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_impl.h"
12#include "content/browser/download/download_file_factory.h"
13#include "content/browser/download/download_interrupt_reasons_impl.h"
14#include "content/browser/download/download_manager_impl.h"
15#include "content/browser/power_save_blocker.h"
16#include "content/browser/renderer_host/resource_dispatcher_host_impl.h"
17#include "content/public/browser/browser_thread.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 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  ~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 FilePath& full_path,
54      const RenameCompletionCallback& callback) OVERRIDE;
55  virtual void RenameAndAnnotate(
56      const 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 FilePath& path_result) {
100  original_callback.Run(
101      overwrite_error,
102      overwrite_error == DOWNLOAD_INTERRUPT_REASON_NONE ?
103      path_result : FilePath());
104}
105
106DownloadFileWithErrors::DownloadFileWithErrors(
107    scoped_ptr<DownloadSaveInfo> save_info,
108    const 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    callback_to_use = base::Bind(&InitializeErrorCallback, callback,
143                                 error_to_return);
144  }
145
146  DownloadFileImpl::Initialize(callback_to_use);
147}
148
149DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile(
150    const char* data, size_t data_len) {
151  return ShouldReturnError(
152      TestFileErrorInjector::FILE_OPERATION_WRITE,
153      DownloadFileImpl::AppendDataToFile(data, data_len));
154}
155
156void DownloadFileWithErrors::RenameAndUniquify(
157    const FilePath& full_path,
158    const RenameCompletionCallback& callback) {
159  DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE;
160  RenameCompletionCallback callback_to_use = callback;
161
162  // Replace callback if the error needs to be overwritten.
163  if (OverwriteError(
164          TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY,
165          &error_to_return)) {
166    callback_to_use = base::Bind(&RenameErrorCallback, callback,
167                                 error_to_return);
168  }
169
170  DownloadFileImpl::RenameAndUniquify(full_path, callback_to_use);
171}
172
173void DownloadFileWithErrors::RenameAndAnnotate(
174    const FilePath& full_path,
175    const RenameCompletionCallback& callback) {
176  DownloadInterruptReason error_to_return = DOWNLOAD_INTERRUPT_REASON_NONE;
177  RenameCompletionCallback callback_to_use = callback;
178
179  // Replace callback if the error needs to be overwritten.
180  if (OverwriteError(
181          TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE,
182          &error_to_return)) {
183    callback_to_use = base::Bind(&RenameErrorCallback, callback,
184                                 error_to_return);
185  }
186
187  DownloadFileImpl::RenameAndAnnotate(full_path, callback_to_use);
188}
189
190bool DownloadFileWithErrors::OverwriteError(
191    TestFileErrorInjector::FileOperationCode code,
192    DownloadInterruptReason* output_error) {
193  int counter = operation_counter_[code]++;
194
195  if (code != error_info_.code)
196    return false;
197
198  if (counter != error_info_.operation_instance)
199    return false;
200
201  *output_error = error_info_.error;
202  return true;
203}
204
205DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError(
206    TestFileErrorInjector::FileOperationCode code,
207    DownloadInterruptReason original_error) {
208  DownloadInterruptReason output_error = original_error;
209  OverwriteError(code, &output_error);
210  return output_error;
211}
212
213}  // namespace
214
215// A factory for constructing DownloadFiles that inject errors.
216class DownloadFileWithErrorsFactory : public DownloadFileFactory {
217 public:
218  DownloadFileWithErrorsFactory(
219      const DownloadFileWithErrors::ConstructionCallback& ctor_callback,
220      const DownloadFileWithErrors::DestructionCallback& dtor_callback);
221  virtual ~DownloadFileWithErrorsFactory();
222
223  // DownloadFileFactory interface.
224  virtual DownloadFile* CreateFile(
225      scoped_ptr<DownloadSaveInfo> save_info,
226      const FilePath& default_download_directory,
227      const GURL& url,
228      const GURL& referrer_url,
229      bool calculate_hash,
230      scoped_ptr<ByteStreamReader> stream,
231      const net::BoundNetLog& bound_net_log,
232      base::WeakPtr<DownloadDestinationObserver> observer) OVERRIDE;
233
234  bool AddError(
235      const TestFileErrorInjector::FileErrorInfo& error_info);
236
237  void ClearErrors();
238
239 private:
240  // Our injected error list, mapped by URL.  One per file.
241   TestFileErrorInjector::ErrorMap injected_errors_;
242
243  // Callback for creation and destruction.
244  DownloadFileWithErrors::ConstructionCallback construction_callback_;
245  DownloadFileWithErrors::DestructionCallback destruction_callback_;
246};
247
248DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory(
249    const DownloadFileWithErrors::ConstructionCallback& ctor_callback,
250    const DownloadFileWithErrors::DestructionCallback& dtor_callback)
251        : construction_callback_(ctor_callback),
252          destruction_callback_(dtor_callback) {
253}
254
255DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() {
256}
257
258DownloadFile* DownloadFileWithErrorsFactory::CreateFile(
259    scoped_ptr<DownloadSaveInfo> save_info,
260    const FilePath& default_download_directory,
261    const GURL& url,
262    const GURL& referrer_url,
263    bool calculate_hash,
264    scoped_ptr<ByteStreamReader> stream,
265    const net::BoundNetLog& bound_net_log,
266    base::WeakPtr<DownloadDestinationObserver> observer) {
267  if (injected_errors_.find(url.spec()) == injected_errors_.end()) {
268    // Have to create entry, because FileErrorInfo is not a POD type.
269    TestFileErrorInjector::FileErrorInfo err_info = {
270      url.spec(),
271      TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
272      -1,
273      DOWNLOAD_INTERRUPT_REASON_NONE
274    };
275    injected_errors_[url.spec()] = err_info;
276  }
277
278  scoped_ptr<PowerSaveBlocker> psb(
279      new PowerSaveBlocker(
280          PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
281          "Download in progress"));
282
283  return new DownloadFileWithErrors(
284      save_info.Pass(),
285      default_download_directory,
286      url,
287      referrer_url,
288      calculate_hash,
289      stream.Pass(),
290      bound_net_log,
291      psb.Pass(),
292      observer,
293      injected_errors_[url.spec()],
294      construction_callback_,
295      destruction_callback_);
296}
297
298bool DownloadFileWithErrorsFactory::AddError(
299    const TestFileErrorInjector::FileErrorInfo& error_info) {
300  // Creates an empty entry if necessary.  Duplicate entries overwrite.
301  injected_errors_[error_info.url] = error_info;
302
303  return true;
304}
305
306void DownloadFileWithErrorsFactory::ClearErrors() {
307  injected_errors_.clear();
308}
309
310TestFileErrorInjector::TestFileErrorInjector(
311    scoped_refptr<DownloadManager> download_manager)
312    : created_factory_(NULL),
313      // This code is only used for browser_tests, so a
314      // DownloadManager is always a DownloadManagerImpl.
315      download_manager_(
316          static_cast<DownloadManagerImpl*>(download_manager.release())) {
317  // Record the value of the pointer, for later validation.
318  created_factory_ =
319      new DownloadFileWithErrorsFactory(
320          base::Bind(&TestFileErrorInjector::RecordDownloadFileConstruction,
321                     this),
322          base::Bind(&TestFileErrorInjector::RecordDownloadFileDestruction,
323                     this));
324
325  // We will transfer ownership of the factory to the download manager.
326  scoped_ptr<DownloadFileFactory> download_file_factory(
327      created_factory_);
328
329  download_manager_->SetDownloadFileFactoryForTesting(
330      download_file_factory.Pass());
331}
332
333TestFileErrorInjector::~TestFileErrorInjector() {
334}
335
336bool TestFileErrorInjector::AddError(const FileErrorInfo& error_info) {
337  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338  DCHECK_LE(0, error_info.operation_instance);
339  DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end());
340
341  // Creates an empty entry if necessary.
342  injected_errors_[error_info.url] = error_info;
343
344  return true;
345}
346
347void TestFileErrorInjector::ClearErrors() {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349  injected_errors_.clear();
350}
351
352bool TestFileErrorInjector::InjectErrors() {
353  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354
355  ClearFoundFiles();
356
357  DCHECK_EQ(static_cast<DownloadFileFactory*>(created_factory_),
358            download_manager_->GetDownloadFileFactoryForTesting());
359
360  created_factory_->ClearErrors();
361
362  for (ErrorMap::const_iterator it = injected_errors_.begin();
363       it != injected_errors_.end(); ++it)
364    created_factory_->AddError(it->second);
365
366  return true;
367}
368
369size_t TestFileErrorInjector::CurrentFileCount() const {
370  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
371  return files_.size();
372}
373
374size_t TestFileErrorInjector::TotalFileCount() const {
375  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
376  return found_files_.size();
377}
378
379
380bool TestFileErrorInjector::HadFile(const GURL& url) const {
381  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
382
383  return (found_files_.find(url) != found_files_.end());
384}
385
386void TestFileErrorInjector::ClearFoundFiles() {
387  found_files_.clear();
388}
389
390void TestFileErrorInjector::DownloadFileCreated(GURL url) {
391  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
392  DCHECK(files_.find(url) == files_.end());
393
394  files_.insert(url);
395  found_files_.insert(url);
396}
397
398void TestFileErrorInjector::DestroyingDownloadFile(GURL url) {
399  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
400  DCHECK(files_.find(url) != files_.end());
401
402  files_.erase(url);
403}
404
405void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL& url) {
406  BrowserThread::PostTask(
407      BrowserThread::UI,
408      FROM_HERE,
409      base::Bind(&TestFileErrorInjector::DownloadFileCreated, this, url));
410}
411
412void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) {
413  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
414      base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, this, url));
415}
416
417// static
418scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Create(
419    scoped_refptr<DownloadManager> download_manager) {
420  static bool visited = false;
421  DCHECK(!visited);  // Only allowed to be called once.
422  visited = true;
423
424  scoped_refptr<TestFileErrorInjector> single_injector(
425      new TestFileErrorInjector(download_manager));
426
427  return single_injector;
428}
429
430// static
431std::string TestFileErrorInjector::DebugString(FileOperationCode code) {
432  switch (code) {
433    case FILE_OPERATION_INITIALIZE:
434      return "INITIALIZE";
435    case FILE_OPERATION_WRITE:
436      return "WRITE";
437    case FILE_OPERATION_RENAME_UNIQUIFY:
438      return "RENAME_UNIQUIFY";
439    case FILE_OPERATION_RENAME_ANNOTATE:
440      return "RENAME_ANNOTATE";
441    default:
442      break;
443  }
444
445  return "Unknown";
446}
447
448}  // namespace content
449