OLD | NEW |
| (Empty) |
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_create_info.h" | |
12 #include "content/browser/download/download_file_impl.h" | |
13 #include "content/browser/download/download_file_manager.h" | |
14 #include "content/browser/download/download_interrupt_reasons_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 "content/public/browser/download_id.h" | |
19 #include "googleurl/src/gurl.h" | |
20 | |
21 namespace content { | |
22 class ByteStreamReader; | |
23 } | |
24 | |
25 namespace { | |
26 | |
27 DownloadFileManager* GetDownloadFileManager() { | |
28 content::ResourceDispatcherHostImpl* rdh = | |
29 content::ResourceDispatcherHostImpl::Get(); | |
30 DCHECK(rdh != NULL); | |
31 return rdh->download_file_manager(); | |
32 } | |
33 | |
34 // A class that performs file operations and injects errors. | |
35 class DownloadFileWithErrors: public DownloadFileImpl { | |
36 public: | |
37 typedef base::Callback<void(const GURL& url, content::DownloadId id)> | |
38 ConstructionCallback; | |
39 typedef base::Callback<void(const GURL& url)> DestructionCallback; | |
40 | |
41 DownloadFileWithErrors( | |
42 const DownloadCreateInfo* info, | |
43 scoped_ptr<content::ByteStreamReader> stream, | |
44 DownloadRequestHandleInterface* request_handle, | |
45 content::DownloadManager* download_manager, | |
46 bool calculate_hash, | |
47 const net::BoundNetLog& bound_net_log, | |
48 const content::TestFileErrorInjector::FileErrorInfo& error_info, | |
49 const ConstructionCallback& ctor_callback, | |
50 const DestructionCallback& dtor_callback); | |
51 | |
52 ~DownloadFileWithErrors(); | |
53 | |
54 // DownloadFile interface. | |
55 virtual content::DownloadInterruptReason Initialize() OVERRIDE; | |
56 virtual content::DownloadInterruptReason AppendDataToFile( | |
57 const char* data, size_t data_len) OVERRIDE; | |
58 virtual void Rename(const FilePath& full_path, | |
59 bool overwrite_existing_file, | |
60 const RenameCompletionCallback& callback) OVERRIDE; | |
61 | |
62 private: | |
63 // Error generating helper. | |
64 content::DownloadInterruptReason ShouldReturnError( | |
65 content::TestFileErrorInjector::FileOperationCode code, | |
66 content::DownloadInterruptReason original_error); | |
67 | |
68 // Used in place of original rename callback to intercept with | |
69 // ShouldReturnError. | |
70 void RenameErrorCallback( | |
71 const RenameCompletionCallback& original_callback, | |
72 content::DownloadInterruptReason original_error, | |
73 const FilePath& path_result); | |
74 | |
75 // Source URL for the file being downloaded. | |
76 GURL source_url_; | |
77 | |
78 // Our injected error. Only one per file. | |
79 content::TestFileErrorInjector::FileErrorInfo error_info_; | |
80 | |
81 // Count per operation. 0-based. | |
82 std::map<content::TestFileErrorInjector::FileOperationCode, int> | |
83 operation_counter_; | |
84 | |
85 // Callback for destruction. | |
86 DestructionCallback destruction_callback_; | |
87 }; | |
88 | |
89 DownloadFileWithErrors::DownloadFileWithErrors( | |
90 const DownloadCreateInfo* info, | |
91 scoped_ptr<content::ByteStreamReader> stream, | |
92 DownloadRequestHandleInterface* request_handle, | |
93 content::DownloadManager* download_manager, | |
94 bool calculate_hash, | |
95 const net::BoundNetLog& bound_net_log, | |
96 const content::TestFileErrorInjector::FileErrorInfo& error_info, | |
97 const ConstructionCallback& ctor_callback, | |
98 const DestructionCallback& dtor_callback) | |
99 : DownloadFileImpl(info, | |
100 stream.Pass(), | |
101 request_handle, | |
102 download_manager, | |
103 calculate_hash, | |
104 scoped_ptr<content::PowerSaveBlocker>(NULL).Pass(), | |
105 bound_net_log), | |
106 source_url_(info->url()), | |
107 error_info_(error_info), | |
108 destruction_callback_(dtor_callback) { | |
109 ctor_callback.Run(source_url_, info->download_id); | |
110 } | |
111 | |
112 DownloadFileWithErrors::~DownloadFileWithErrors() { | |
113 destruction_callback_.Run(source_url_); | |
114 } | |
115 | |
116 content::DownloadInterruptReason DownloadFileWithErrors::Initialize() { | |
117 return ShouldReturnError( | |
118 content::TestFileErrorInjector::FILE_OPERATION_INITIALIZE, | |
119 DownloadFileImpl::Initialize()); | |
120 } | |
121 | |
122 content::DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile( | |
123 const char* data, size_t data_len) { | |
124 return ShouldReturnError( | |
125 content::TestFileErrorInjector::FILE_OPERATION_WRITE, | |
126 DownloadFileImpl::AppendDataToFile(data, data_len)); | |
127 } | |
128 | |
129 void DownloadFileWithErrors::Rename( | |
130 const FilePath& full_path, | |
131 bool overwrite_existing_file, | |
132 const RenameCompletionCallback& callback) { | |
133 DownloadFileImpl::Rename( | |
134 full_path, overwrite_existing_file, | |
135 base::Bind(&DownloadFileWithErrors::RenameErrorCallback, | |
136 // Unretained since this'll only be called from | |
137 // the DownloadFileImpl slice of the same object. | |
138 base::Unretained(this), callback)); | |
139 } | |
140 | |
141 content::DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError( | |
142 content::TestFileErrorInjector::FileOperationCode code, | |
143 content::DownloadInterruptReason original_error) { | |
144 int counter = operation_counter_[code]; | |
145 ++operation_counter_[code]; | |
146 | |
147 if (code != error_info_.code) | |
148 return original_error; | |
149 | |
150 if (counter != error_info_.operation_instance) | |
151 return original_error; | |
152 | |
153 VLOG(1) << " " << __FUNCTION__ << "()" | |
154 << " url = '" << source_url_.spec() << "'" | |
155 << " code = " << content::TestFileErrorInjector::DebugString(code) | |
156 << " (" << code << ")" | |
157 << " counter = " << counter | |
158 << " original_error = " | |
159 << content::InterruptReasonDebugString(original_error) | |
160 << " (" << original_error << ")" | |
161 << " new error = " | |
162 << content::InterruptReasonDebugString(error_info_.error) | |
163 << " (" << error_info_.error << ")"; | |
164 | |
165 return error_info_.error; | |
166 } | |
167 | |
168 void DownloadFileWithErrors::RenameErrorCallback( | |
169 const RenameCompletionCallback& original_callback, | |
170 content::DownloadInterruptReason original_error, | |
171 const FilePath& path_result) { | |
172 original_callback.Run(ShouldReturnError( | |
173 content::TestFileErrorInjector::FILE_OPERATION_RENAME, | |
174 original_error), path_result); | |
175 } | |
176 | |
177 } // namespace | |
178 | |
179 namespace content { | |
180 | |
181 // A factory for constructing DownloadFiles that inject errors. | |
182 class DownloadFileWithErrorsFactory | |
183 : public DownloadFileManager::DownloadFileFactory { | |
184 public: | |
185 | |
186 DownloadFileWithErrorsFactory( | |
187 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, | |
188 const DownloadFileWithErrors::DestructionCallback& dtor_callback); | |
189 virtual ~DownloadFileWithErrorsFactory(); | |
190 | |
191 // DownloadFileFactory interface. | |
192 virtual DownloadFile* CreateFile( | |
193 DownloadCreateInfo* info, | |
194 scoped_ptr<content::ByteStreamReader> stream, | |
195 content::DownloadManager* download_manager, | |
196 bool calculate_hash, | |
197 const net::BoundNetLog& bound_net_log); | |
198 | |
199 bool AddError( | |
200 const TestFileErrorInjector::FileErrorInfo& error_info); | |
201 | |
202 void ClearErrors(); | |
203 | |
204 private: | |
205 // Our injected error list, mapped by URL. One per file. | |
206 TestFileErrorInjector::ErrorMap injected_errors_; | |
207 | |
208 // Callback for creation and destruction. | |
209 DownloadFileWithErrors::ConstructionCallback construction_callback_; | |
210 DownloadFileWithErrors::DestructionCallback destruction_callback_; | |
211 }; | |
212 | |
213 DownloadFileWithErrorsFactory::DownloadFileWithErrorsFactory( | |
214 const DownloadFileWithErrors::ConstructionCallback& ctor_callback, | |
215 const DownloadFileWithErrors::DestructionCallback& dtor_callback) | |
216 : construction_callback_(ctor_callback), | |
217 destruction_callback_(dtor_callback) { | |
218 } | |
219 | |
220 DownloadFileWithErrorsFactory::~DownloadFileWithErrorsFactory() { | |
221 } | |
222 | |
223 content::DownloadFile* DownloadFileWithErrorsFactory::CreateFile( | |
224 DownloadCreateInfo* info, | |
225 scoped_ptr<content::ByteStreamReader> stream, | |
226 content::DownloadManager* download_manager, | |
227 bool calculate_hash, | |
228 const net::BoundNetLog& bound_net_log) { | |
229 std::string url = info->url().spec(); | |
230 | |
231 if (injected_errors_.find(url) == injected_errors_.end()) { | |
232 // Have to create entry, because FileErrorInfo is not a POD type. | |
233 TestFileErrorInjector::FileErrorInfo err_info = { | |
234 url, | |
235 TestFileErrorInjector::FILE_OPERATION_INITIALIZE, | |
236 -1, | |
237 content::DOWNLOAD_INTERRUPT_REASON_NONE | |
238 }; | |
239 injected_errors_[url] = err_info; | |
240 } | |
241 | |
242 return new DownloadFileWithErrors( | |
243 info, | |
244 stream.Pass(), | |
245 new DownloadRequestHandle(info->request_handle), | |
246 download_manager, | |
247 calculate_hash, | |
248 bound_net_log, | |
249 injected_errors_[url], | |
250 construction_callback_, | |
251 destruction_callback_); | |
252 } | |
253 | |
254 bool DownloadFileWithErrorsFactory::AddError( | |
255 const TestFileErrorInjector::FileErrorInfo& error_info) { | |
256 // Creates an empty entry if necessary. Duplicate entries overwrite. | |
257 injected_errors_[error_info.url] = error_info; | |
258 | |
259 return true; | |
260 } | |
261 | |
262 void DownloadFileWithErrorsFactory::ClearErrors() { | |
263 injected_errors_.clear(); | |
264 } | |
265 | |
266 TestFileErrorInjector::TestFileErrorInjector() | |
267 : created_factory_(NULL) { | |
268 // Record the value of the pointer, for later validation. | |
269 created_factory_ = | |
270 new DownloadFileWithErrorsFactory( | |
271 base::Bind(&TestFileErrorInjector:: | |
272 RecordDownloadFileConstruction, | |
273 this), | |
274 base::Bind(&TestFileErrorInjector:: | |
275 RecordDownloadFileDestruction, | |
276 this)); | |
277 | |
278 // We will transfer ownership of the factory to the download file manager. | |
279 scoped_ptr<DownloadFileWithErrorsFactory> download_file_factory( | |
280 created_factory_); | |
281 | |
282 content::BrowserThread::PostTask( | |
283 content::BrowserThread::FILE, | |
284 FROM_HERE, | |
285 base::Bind(&TestFileErrorInjector::AddFactory, | |
286 this, | |
287 base::Passed(&download_file_factory))); | |
288 } | |
289 | |
290 TestFileErrorInjector::~TestFileErrorInjector() { | |
291 } | |
292 | |
293 void TestFileErrorInjector::AddFactory( | |
294 scoped_ptr<DownloadFileWithErrorsFactory> factory) { | |
295 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); | |
296 | |
297 DownloadFileManager* download_file_manager = GetDownloadFileManager(); | |
298 DCHECK(download_file_manager); | |
299 | |
300 // Convert to base class pointer, for GCC. | |
301 scoped_ptr<DownloadFileManager::DownloadFileFactory> plain_factory( | |
302 factory.release()); | |
303 | |
304 download_file_manager->SetFileFactoryForTesting(plain_factory.Pass()); | |
305 } | |
306 | |
307 bool TestFileErrorInjector::AddError(const FileErrorInfo& error_info) { | |
308 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
309 DCHECK_LE(0, error_info.operation_instance); | |
310 DCHECK(injected_errors_.find(error_info.url) == injected_errors_.end()); | |
311 | |
312 // Creates an empty entry if necessary. | |
313 injected_errors_[error_info.url] = error_info; | |
314 | |
315 return true; | |
316 } | |
317 | |
318 void TestFileErrorInjector::ClearErrors() { | |
319 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
320 injected_errors_.clear(); | |
321 } | |
322 | |
323 bool TestFileErrorInjector::InjectErrors() { | |
324 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
325 | |
326 ClearFoundFiles(); | |
327 | |
328 content::BrowserThread::PostTask( | |
329 content::BrowserThread::FILE, | |
330 FROM_HERE, | |
331 base::Bind(&TestFileErrorInjector::InjectErrorsOnFileThread, | |
332 this, | |
333 injected_errors_, | |
334 created_factory_)); | |
335 | |
336 return true; | |
337 } | |
338 | |
339 void TestFileErrorInjector::InjectErrorsOnFileThread( | |
340 ErrorMap map, DownloadFileWithErrorsFactory* factory) { | |
341 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); | |
342 | |
343 // Validate that our factory is in use. | |
344 DownloadFileManager* download_file_manager = GetDownloadFileManager(); | |
345 DCHECK(download_file_manager); | |
346 | |
347 DownloadFileManager::DownloadFileFactory* file_factory = | |
348 download_file_manager->GetFileFactoryForTesting(); | |
349 | |
350 // Validate that we still have the same factory. | |
351 DCHECK_EQ(static_cast<DownloadFileManager::DownloadFileFactory*>(factory), | |
352 file_factory); | |
353 | |
354 // We want to replace all existing injection errors. | |
355 factory->ClearErrors(); | |
356 | |
357 for (ErrorMap::const_iterator it = map.begin(); it != map.end(); ++it) | |
358 factory->AddError(it->second); | |
359 } | |
360 | |
361 size_t TestFileErrorInjector::CurrentFileCount() const { | |
362 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
363 return files_.size(); | |
364 } | |
365 | |
366 size_t TestFileErrorInjector::TotalFileCount() const { | |
367 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
368 return found_files_.size(); | |
369 } | |
370 | |
371 | |
372 bool TestFileErrorInjector::HadFile(const GURL& url) const { | |
373 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
374 | |
375 return (found_files_.find(url) != found_files_.end()); | |
376 } | |
377 | |
378 const content::DownloadId TestFileErrorInjector::GetId( | |
379 const GURL& url) const { | |
380 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
381 | |
382 FileMap::const_iterator it = found_files_.find(url); | |
383 if (it == found_files_.end()) | |
384 return content::DownloadId::Invalid(); | |
385 | |
386 return it->second; | |
387 } | |
388 | |
389 void TestFileErrorInjector::ClearFoundFiles() { | |
390 found_files_.clear(); | |
391 } | |
392 | |
393 void TestFileErrorInjector::DownloadFileCreated(GURL url, | |
394 content::DownloadId id) { | |
395 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
396 DCHECK(files_.find(url) == files_.end()); | |
397 | |
398 files_[url] = id; | |
399 found_files_[url] = id; | |
400 } | |
401 | |
402 void TestFileErrorInjector::DestroyingDownloadFile(GURL url) { | |
403 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | |
404 DCHECK(files_.find(url) != files_.end()); | |
405 | |
406 files_.erase(url); | |
407 } | |
408 | |
409 void TestFileErrorInjector::RecordDownloadFileConstruction( | |
410 const GURL& url, content::DownloadId id) { | |
411 content::BrowserThread::PostTask( | |
412 content::BrowserThread::UI, | |
413 FROM_HERE, | |
414 base::Bind(&TestFileErrorInjector::DownloadFileCreated, | |
415 this, | |
416 url, | |
417 id)); | |
418 } | |
419 | |
420 void TestFileErrorInjector::RecordDownloadFileDestruction(const GURL& url) { | |
421 content::BrowserThread::PostTask( | |
422 content::BrowserThread::UI, | |
423 FROM_HERE, | |
424 base::Bind(&TestFileErrorInjector::DestroyingDownloadFile, | |
425 this, | |
426 url)); | |
427 } | |
428 | |
429 // static | |
430 scoped_refptr<TestFileErrorInjector> TestFileErrorInjector::Create() { | |
431 static bool visited = false; | |
432 DCHECK(!visited); // Only allowed to be called once. | |
433 visited = true; | |
434 | |
435 scoped_refptr<TestFileErrorInjector> single_injector( | |
436 new TestFileErrorInjector); | |
437 | |
438 return single_injector; | |
439 } | |
440 | |
441 // static | |
442 std::string TestFileErrorInjector::DebugString(FileOperationCode code) { | |
443 switch (code) { | |
444 case FILE_OPERATION_INITIALIZE: | |
445 return "INITIALIZE"; | |
446 case FILE_OPERATION_WRITE: | |
447 return "WRITE"; | |
448 case FILE_OPERATION_RENAME: | |
449 return "RENAME"; | |
450 default: | |
451 break; | |
452 } | |
453 | |
454 return "Unknown"; | |
455 } | |
456 | |
457 } // namespace content | |
OLD | NEW |