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.
5 #include "content/public/test/test_file_error_injector.h"
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"
21 class ByteStreamReader
;
25 // A class that performs file operations and injects errors.
26 class DownloadFileWithErrors
: public DownloadFileImpl
{
28 typedef base::Callback
<void(const GURL
& url
)> ConstructionCallback
;
29 typedef base::Callback
<void(const GURL
& url
)> DestructionCallback
;
31 DownloadFileWithErrors(
32 scoped_ptr
<DownloadSaveInfo
> save_info
,
33 const base::FilePath
& default_download_directory
,
35 const GURL
& referrer_url
,
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
);
45 virtual ~DownloadFileWithErrors();
47 virtual void Initialize(const InitializeCallback
& callback
) OVERRIDE
;
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
;
60 // Error generating helper.
61 DownloadInterruptReason
ShouldReturnError(
62 TestFileErrorInjector::FileOperationCode code
,
63 DownloadInterruptReason original_error
);
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.
72 TestFileErrorInjector::FileOperationCode code
,
73 DownloadInterruptReason
* output_error
);
75 // Source URL for the file being downloaded.
78 // Our injected error. Only one per file.
79 TestFileErrorInjector::FileErrorInfo error_info_
;
81 // Count per operation. 0-based.
82 std::map
<TestFileErrorInjector::FileOperationCode
, int> operation_counter_
;
84 // Callback for destruction.
85 DestructionCallback destruction_callback_
;
88 static void InitializeErrorCallback(
89 const DownloadFile::InitializeCallback original_callback
,
90 DownloadInterruptReason overwrite_error
,
91 DownloadInterruptReason original_error
) {
92 original_callback
.Run(overwrite_error
);
95 static 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(
102 overwrite_error
== DOWNLOAD_INTERRUPT_REASON_NONE
?
103 path_result
: base::FilePath());
106 DownloadFileWithErrors::DownloadFileWithErrors(
107 scoped_ptr
<DownloadSaveInfo
> save_info
,
108 const base::FilePath
& default_download_directory
,
110 const GURL
& referrer_url
,
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
)
120 save_info
.Pass(), default_download_directory
, url
, referrer_url
,
121 calculate_hash
, stream
.Pass(), bound_net_log
,
122 power_save_blocker
.Pass(), observer
),
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(
135 base::Bind(ctor_callback
, source_url_
));
138 DownloadFileWithErrors::~DownloadFileWithErrors() {
139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
140 destruction_callback_
.Run(source_url_
);
143 void DownloadFileWithErrors::Initialize(
144 const InitializeCallback
& callback
) {
145 DownloadInterruptReason error_to_return
= DOWNLOAD_INTERRUPT_REASON_NONE
;
146 InitializeCallback callback_to_use
= callback
;
148 // Replace callback if the error needs to be overwritten.
150 TestFileErrorInjector::FILE_OPERATION_INITIALIZE
,
152 if (DOWNLOAD_INTERRUPT_REASON_NONE
!= error_to_return
) {
153 // Don't execute a, probably successful, Initialize; just
155 BrowserThread::PostTask(
156 BrowserThread::UI
, FROM_HERE
, base::Bind(
157 callback
, error_to_return
));
161 // Otherwise, just wrap the return.
162 callback_to_use
= base::Bind(&InitializeErrorCallback
, callback
,
166 DownloadFileImpl::Initialize(callback_to_use
);
169 DownloadInterruptReason
DownloadFileWithErrors::AppendDataToFile(
170 const char* data
, size_t data_len
) {
171 return ShouldReturnError(
172 TestFileErrorInjector::FILE_OPERATION_WRITE
,
173 DownloadFileImpl::AppendDataToFile(data
, data_len
));
176 void 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
;
182 // Replace callback if the error needs to be overwritten.
184 TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY
,
186 if (DOWNLOAD_INTERRUPT_REASON_NONE
!= error_to_return
) {
187 // Don't execute a, probably successful, RenameAndUniquify; just
189 BrowserThread::PostTask(
190 BrowserThread::UI
, FROM_HERE
, base::Bind(
191 callback
, error_to_return
, base::FilePath()));
195 // Otherwise, just wrap the return.
196 callback_to_use
= base::Bind(&RenameErrorCallback
, callback
,
200 DownloadFileImpl::RenameAndUniquify(full_path
, callback_to_use
);
203 void 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
;
209 // Replace callback if the error needs to be overwritten.
211 TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE
,
213 if (DOWNLOAD_INTERRUPT_REASON_NONE
!= error_to_return
) {
214 // Don't execute a, probably successful, RenameAndAnnotate; just
216 BrowserThread::PostTask(
217 BrowserThread::UI
, FROM_HERE
, base::Bind(
218 callback
, error_to_return
, base::FilePath()));
222 // Otherwise, just wrap the return.
223 callback_to_use
= base::Bind(&RenameErrorCallback
, callback
,
227 DownloadFileImpl::RenameAndAnnotate(full_path
, callback_to_use
);
230 bool DownloadFileWithErrors::OverwriteError(
231 TestFileErrorInjector::FileOperationCode code
,
232 DownloadInterruptReason
* output_error
) {
233 int counter
= operation_counter_
[code
]++;
235 if (code
!= error_info_
.code
)
238 if (counter
!= error_info_
.operation_instance
)
241 *output_error
= error_info_
.error
;
245 DownloadInterruptReason
DownloadFileWithErrors::ShouldReturnError(
246 TestFileErrorInjector::FileOperationCode code
,
247 DownloadInterruptReason original_error
) {
248 DownloadInterruptReason output_error
= original_error
;
249 OverwriteError(code
, &output_error
);
255 // A factory for constructing DownloadFiles that inject errors.
256 class DownloadFileWithErrorsFactory
: public DownloadFileFactory
{
258 DownloadFileWithErrorsFactory(
259 const DownloadFileWithErrors::ConstructionCallback
& ctor_callback
,
260 const DownloadFileWithErrors::DestructionCallback
& dtor_callback
);
261 virtual ~DownloadFileWithErrorsFactory();
263 // DownloadFileFactory interface.
264 virtual DownloadFile
* CreateFile(
265 scoped_ptr
<DownloadSaveInfo
> save_info
,
266 const base::FilePath
& default_download_directory
,
268 const GURL
& referrer_url
,
270 scoped_ptr
<ByteStreamReader
> stream
,
271 const net::BoundNetLog
& bound_net_log
,
272 base::WeakPtr
<DownloadDestinationObserver
> observer
) OVERRIDE
;
275 const TestFileErrorInjector::FileErrorInfo
& error_info
);
280 // Our injected error list, mapped by URL. One per file.
281 TestFileErrorInjector::ErrorMap injected_errors_
;
283 // Callback for creation and destruction.
284 DownloadFileWithErrors::ConstructionCallback construction_callback_
;
285 DownloadFileWithErrors::DestructionCallback destruction_callback_
;
288 DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory(
289 const DownloadFileWithErrors::ConstructionCallback
& ctor_callback
,
290 const DownloadFileWithErrors::DestructionCallback
& dtor_callback
)
291 : construction_callback_(ctor_callback
),
292 destruction_callback_(dtor_callback
) {
295 DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() {
298 DownloadFile
* DownloadFileWithErrorsFactory::CreateFile(
299 scoped_ptr
<DownloadSaveInfo
> save_info
,
300 const base::FilePath
& default_download_directory
,
302 const GURL
& referrer_url
,
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
= {
311 TestFileErrorInjector::FILE_OPERATION_INITIALIZE
,
313 DOWNLOAD_INTERRUPT_REASON_NONE
315 injected_errors_
[url
.spec()] = err_info
;
318 scoped_ptr
<PowerSaveBlocker
> psb(
319 PowerSaveBlocker::Create(
320 PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
321 "Download in progress"));
323 return new DownloadFileWithErrors(
325 default_download_directory
,
333 injected_errors_
[url
.spec()],
334 construction_callback_
,
335 destruction_callback_
);
338 bool 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
;
346 void DownloadFileWithErrorsFactory::ClearErrors() {
347 injected_errors_
.clear();
350 TestFileErrorInjector::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.
358 new DownloadFileWithErrorsFactory(
359 base::Bind(&TestFileErrorInjector::RecordDownloadFileConstruction
,
361 base::Bind(&TestFileErrorInjector::RecordDownloadFileDestruction
,
364 // We will transfer ownership of the factory to the download manager.
365 scoped_ptr
<DownloadFileFactory
> download_file_factory(
368 download_manager_
->SetDownloadFileFactoryForTesting(
369 download_file_factory
.Pass());
372 TestFileErrorInjector::~TestFileErrorInjector() {
375 bool 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());
380 // Creates an empty entry if necessary.
381 injected_errors_
[error_info
.url
] = error_info
;
386 void TestFileErrorInjector::ClearErrors() {
387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
388 injected_errors_
.clear();
391 bool TestFileErrorInjector::InjectErrors() {
392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
396 DCHECK_EQ(static_cast<DownloadFileFactory
*>(created_factory_
),
397 download_manager_
->GetDownloadFileFactoryForTesting());
399 created_factory_
->ClearErrors();
401 for (ErrorMap::const_iterator it
= injected_errors_
.begin();
402 it
!= injected_errors_
.end(); ++it
)
403 created_factory_
->AddError(it
->second
);
408 size_t TestFileErrorInjector::CurrentFileCount() const {
409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
410 return files_
.size();
413 size_t TestFileErrorInjector::TotalFileCount() const {
414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
415 return found_files_
.size();
419 bool TestFileErrorInjector::HadFile(const GURL
& url
) const {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
422 return (found_files_
.find(url
) != found_files_
.end());
425 void TestFileErrorInjector::ClearFoundFiles() {
426 found_files_
.clear();
429 void TestFileErrorInjector::DownloadFileCreated(GURL url
) {
430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
431 DCHECK(files_
.find(url
) == files_
.end());
434 found_files_
.insert(url
);
437 void TestFileErrorInjector::DestroyingDownloadFile(GURL url
) {
438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
439 DCHECK(files_
.find(url
) != files_
.end());
444 void TestFileErrorInjector::RecordDownloadFileConstruction(const GURL
& url
) {
445 BrowserThread::PostTask(
448 base::Bind(&TestFileErrorInjector::DownloadFileCreated
, this, url
));
451 void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL
& url
) {
452 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
453 base::Bind(&TestFileErrorInjector::DestroyingDownloadFile
, this, url
));
457 scoped_refptr
<TestFileErrorInjector
> TestFileErrorInjector::Create(
458 DownloadManager
* download_manager
) {
459 static bool visited
= false;
460 DCHECK(!visited
); // Only allowed to be called once.
463 scoped_refptr
<TestFileErrorInjector
> single_injector(
464 new TestFileErrorInjector(download_manager
));
466 return single_injector
;
470 std::string
TestFileErrorInjector::DebugString(FileOperationCode code
) {
472 case FILE_OPERATION_INITIALIZE
:
474 case FILE_OPERATION_WRITE
:
476 case FILE_OPERATION_RENAME_UNIQUIFY
:
477 return "RENAME_UNIQUIFY";
478 case FILE_OPERATION_RENAME_ANNOTATE
:
479 return "RENAME_ANNOTATE";
487 } // namespace content