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/browser/download/download_file_manager.h" | |
6 | |
7 #include "base/file_path.h" | |
8 #include "base/file_util.h" | |
9 #include "base/message_loop.h" | |
10 #include "base/scoped_temp_dir.h" | |
11 #include "base/string_number_conversions.h" | |
12 #include "content/browser/browser_thread_impl.h" | |
13 #include "content/browser/download/byte_stream.h" | |
14 #include "content/browser/download/download_create_info.h" | |
15 #include "content/browser/download/download_interrupt_reasons_impl.h" | |
16 #include "content/browser/download/download_file_factory.h" | |
17 #include "content/browser/download/download_request_handle.h" | |
18 #include "content/browser/download/mock_download_file.h" | |
19 #include "content/public/browser/download_id.h" | |
20 #include "content/public/test/mock_download_manager.h" | |
21 #include "net/base/io_buffer.h" | |
22 #include "net/base/net_errors.h" | |
23 #include "testing/gmock/include/gmock/gmock.h" | |
24 #include "testing/gtest/include/gtest/gtest.h" | |
25 | |
26 using content::BrowserThread; | |
27 using content::BrowserThreadImpl; | |
28 using content::DownloadId; | |
29 using content::MockDownloadManager; | |
30 | |
31 using ::testing::_; | |
32 using ::testing::AtLeast; | |
33 using ::testing::Mock; | |
34 using ::testing::Return; | |
35 using ::testing::SaveArg; | |
36 using ::testing::StrictMock; | |
37 using ::testing::StrEq; | |
38 | |
39 namespace { | |
40 | |
41 // MockDownloadManager with the addition of a mock callback used for testing. | |
42 class TestDownloadManager : public MockDownloadManager { | |
43 public: | |
44 MOCK_METHOD3(OnDownloadRenamed, | |
45 void(int download_id, | |
46 content::DownloadInterruptReason reason, | |
47 const FilePath& full_path)); | |
48 private: | |
49 ~TestDownloadManager() {} | |
50 }; | |
51 | |
52 class MockDownloadFileFactory : public content::DownloadFileFactory { | |
53 | |
54 public: | |
55 MockDownloadFileFactory() {} | |
56 virtual ~MockDownloadFileFactory() {} | |
57 | |
58 virtual content::DownloadFile* CreateFile( | |
59 scoped_ptr<DownloadCreateInfo> info, | |
60 scoped_ptr<content::ByteStreamReader> stream, | |
61 content::DownloadManager* download_manager, | |
62 bool calculate_hash, | |
63 const net::BoundNetLog& bound_net_log) OVERRIDE; | |
64 | |
65 MockDownloadFile* GetExistingFile(const DownloadId& id); | |
66 | |
67 private: | |
68 std::map<DownloadId, MockDownloadFile*> files_; | |
69 }; | |
70 | |
71 content::DownloadFile* MockDownloadFileFactory::CreateFile( | |
72 scoped_ptr<DownloadCreateInfo> info, | |
73 scoped_ptr<content::ByteStreamReader> stream, | |
74 content::DownloadManager* download_manager, | |
75 bool calculate_hash, | |
76 const net::BoundNetLog& bound_net_log) { | |
77 DCHECK(files_.end() == files_.find(info->download_id)); | |
78 MockDownloadFile* created_file = new StrictMock<MockDownloadFile>(); | |
79 files_[info->download_id] = created_file; | |
80 | |
81 ON_CALL(*created_file, GetDownloadManager()) | |
82 .WillByDefault(Return(download_manager)); | |
83 EXPECT_CALL(*created_file, Initialize()); | |
84 | |
85 return created_file; | |
86 } | |
87 | |
88 MockDownloadFile* MockDownloadFileFactory::GetExistingFile( | |
89 const DownloadId& id) { | |
90 DCHECK(files_.find(id) != files_.end()); | |
91 return files_[id]; | |
92 } | |
93 | |
94 class MockDownloadRequestHandle : public DownloadRequestHandle { | |
95 public: | |
96 MockDownloadRequestHandle(content::DownloadManager* manager) | |
97 : manager_(manager) {} | |
98 | |
99 virtual content::DownloadManager* GetDownloadManager() const OVERRIDE; | |
100 | |
101 private: | |
102 | |
103 content::DownloadManager* manager_; | |
104 }; | |
105 | |
106 content::DownloadManager* MockDownloadRequestHandle::GetDownloadManager() | |
107 const { | |
108 return manager_; | |
109 } | |
110 | |
111 void NullCallback() { } | |
112 | |
113 } // namespace | |
114 | |
115 class DownloadFileManagerTest : public testing::Test { | |
116 public: | |
117 // State of renamed file. Used with RenameFile(). | |
118 enum RenameFileState { | |
119 IN_PROGRESS, | |
120 COMPLETE | |
121 }; | |
122 | |
123 // Whether to overwrite the target filename in RenameFile(). | |
124 enum RenameFileOverwrite { | |
125 OVERWRITE, | |
126 DONT_OVERWRITE | |
127 }; | |
128 | |
129 static const char* kTestData1; | |
130 static const char* kTestData2; | |
131 static const char* kTestData3; | |
132 static const char* kTestData4; | |
133 static const char* kTestData5; | |
134 static const char* kTestData6; | |
135 static const int32 kDummyDownloadId; | |
136 static const int32 kDummyDownloadId2; | |
137 static const int kDummyChildId; | |
138 static const int kDummyRequestId; | |
139 | |
140 // We need a UI |BrowserThread| in order to destruct |download_manager_|, | |
141 // which has trait |BrowserThread::DeleteOnUIThread|. Without this, | |
142 // calling Release() on |download_manager_| won't ever result in its | |
143 // destructor being called and we get a leak. | |
144 DownloadFileManagerTest() | |
145 : last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE), | |
146 ui_thread_(BrowserThread::UI, &loop_), | |
147 file_thread_(BrowserThread::FILE, &loop_) { | |
148 } | |
149 | |
150 ~DownloadFileManagerTest() { | |
151 } | |
152 | |
153 virtual void SetUp() { | |
154 download_manager_ = new TestDownloadManager(); | |
155 request_handle_.reset(new MockDownloadRequestHandle(download_manager_)); | |
156 download_file_factory_ = new MockDownloadFileFactory; | |
157 download_file_manager_ = new DownloadFileManager(download_file_factory_); | |
158 } | |
159 | |
160 virtual void TearDown() { | |
161 // When a DownloadManager's reference count drops to 0, it is not | |
162 // deleted immediately. Instead, a task is posted to the UI thread's | |
163 // message loop to delete it. | |
164 // So, drop the reference count to 0 and run the message loop once | |
165 // to ensure that all resources are cleaned up before the test exits. | |
166 download_manager_ = NULL; | |
167 ui_thread_.message_loop()->RunAllPending(); | |
168 } | |
169 | |
170 void ProcessAllPendingMessages() { | |
171 loop_.RunAllPending(); | |
172 } | |
173 | |
174 // Clears all gmock expectations for the download file |id| and the manager. | |
175 void ClearExpectations(DownloadId id) { | |
176 MockDownloadFile* file = download_file_factory_->GetExistingFile(id); | |
177 Mock::VerifyAndClearExpectations(file); | |
178 Mock::VerifyAndClearExpectations(download_manager_); | |
179 } | |
180 | |
181 void OnDownloadFileCreated(content::DownloadInterruptReason reason) { | |
182 last_reason_ = reason; | |
183 } | |
184 | |
185 // Create a download item on the DFM. | |
186 // |info| is the information needed to create a new download file. | |
187 void CreateDownloadFile(scoped_ptr<DownloadCreateInfo> info) { | |
188 // Anything that isn't DOWNLOAD_INTERRUPT_REASON_NONE. | |
189 last_reason_ = content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; | |
190 | |
191 // Mostly null out args; they'll be passed to MockDownloadFileFactory | |
192 // to be ignored anyway. | |
193 download_file_manager_->CreateDownloadFile( | |
194 info.Pass(), scoped_ptr<content::ByteStreamReader>(), | |
195 download_manager_, true, net::BoundNetLog(), | |
196 base::Bind(&DownloadFileManagerTest::OnDownloadFileCreated, | |
197 // The test jig will outlive all download files. | |
198 base::Unretained(this))); | |
199 | |
200 ProcessAllPendingMessages(); | |
201 EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, last_reason_); | |
202 } | |
203 | |
204 // Renames the download file. | |
205 // |id| is the download ID of the download file. | |
206 // |new_path| is the new file path. | |
207 // |unique_path| is the actual path that the download file will be | |
208 // renamed to. If there is an existing file at | |
209 // |new_path| and |replace| is false, then |new_path| | |
210 // will be uniquified. | |
211 // |rename_error| is the error to inject. For no error, | |
212 // use content::DOWNLOAD_INTERRUPT_REASON_NONE. | |
213 // |state| whether we are renaming an in-progress download or a | |
214 // completed download. | |
215 // |should_overwrite| indicates whether to replace or uniquify the file. | |
216 void RenameFile(const DownloadId& id, | |
217 const FilePath& new_path, | |
218 bool should_overwrite) { | |
219 MockDownloadFile* file = download_file_factory_->GetExistingFile(id); | |
220 ASSERT_TRUE(file != NULL); | |
221 content::DownloadFile::RenameCompletionCallback rename_callback; | |
222 | |
223 EXPECT_CALL(*file, Rename(new_path, should_overwrite, _)) | |
224 .WillOnce(SaveArg<2>(&rename_callback)); | |
225 | |
226 content::DownloadFile::RenameCompletionCallback passed_callback( | |
227 base::Bind(&TestDownloadManager::OnDownloadRenamed, | |
228 download_manager_, id.local())); | |
229 | |
230 download_file_manager_->RenameDownloadFile( | |
231 id, new_path, should_overwrite, passed_callback); | |
232 | |
233 EXPECT_TRUE(rename_callback.Equals(passed_callback)); | |
234 } | |
235 | |
236 void Complete(DownloadId id) { | |
237 MockDownloadFile* file = download_file_factory_->GetExistingFile(id); | |
238 ASSERT_TRUE(file != NULL); | |
239 | |
240 EXPECT_CALL(*file, Detach(_)) | |
241 .WillOnce(Return()); | |
242 int num_downloads = download_file_manager_->NumberOfActiveDownloads(); | |
243 EXPECT_LT(0, num_downloads); | |
244 download_file_manager_->CompleteDownload(id, base::Bind(NullCallback)); | |
245 EXPECT_EQ(num_downloads - 1, | |
246 download_file_manager_->NumberOfActiveDownloads()); | |
247 EXPECT_EQ(NULL, download_file_manager_->GetDownloadFile(id)); | |
248 } | |
249 | |
250 void CleanUp(DownloadId id) { | |
251 // Expected calls: | |
252 // DownloadFileManager::CancelDownload | |
253 // DownloadFile::Cancel | |
254 // DownloadFileManager::EraseDownload | |
255 // if no more downloads: | |
256 // DownloadFileManager::StopUpdateTimer | |
257 MockDownloadFile* file = download_file_factory_->GetExistingFile(id); | |
258 ASSERT_TRUE(file != NULL); | |
259 | |
260 EXPECT_CALL(*file, Cancel()); | |
261 | |
262 download_file_manager_->CancelDownload(id); | |
263 | |
264 EXPECT_EQ(NULL, download_file_manager_->GetDownloadFile(id)); | |
265 } | |
266 | |
267 protected: | |
268 scoped_refptr<TestDownloadManager> download_manager_; | |
269 scoped_ptr<MockDownloadRequestHandle> request_handle_; | |
270 MockDownloadFileFactory* download_file_factory_; | |
271 scoped_refptr<DownloadFileManager> download_file_manager_; | |
272 | |
273 // Error from creating download file. | |
274 content::DownloadInterruptReason last_reason_; | |
275 | |
276 // Per-download statistics. | |
277 std::map<DownloadId, int64> byte_count_; | |
278 std::map<DownloadId, int> error_count_; | |
279 | |
280 private: | |
281 MessageLoop loop_; | |
282 | |
283 // UI thread. | |
284 BrowserThreadImpl ui_thread_; | |
285 | |
286 // File thread to satisfy debug checks in DownloadFile. | |
287 BrowserThreadImpl file_thread_; | |
288 }; | |
289 | |
290 const char* DownloadFileManagerTest::kTestData1 = | |
291 "Let's write some data to the file!\n"; | |
292 const char* DownloadFileManagerTest::kTestData2 = "Writing more data.\n"; | |
293 const char* DownloadFileManagerTest::kTestData3 = "Final line."; | |
294 const char* DownloadFileManagerTest::kTestData4 = "Writing, writing, writing\n"; | |
295 const char* DownloadFileManagerTest::kTestData5 = "All we do is writing,\n"; | |
296 const char* DownloadFileManagerTest::kTestData6 = "Rawhide!"; | |
297 | |
298 const int32 DownloadFileManagerTest::kDummyDownloadId = 23; | |
299 const int32 DownloadFileManagerTest::kDummyDownloadId2 = 77; | |
300 const int DownloadFileManagerTest::kDummyChildId = 3; | |
301 const int DownloadFileManagerTest::kDummyRequestId = 67; | |
302 | |
303 TEST_F(DownloadFileManagerTest, Cancel) { | |
304 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
305 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
306 info->download_id = dummy_id; | |
307 | |
308 CreateDownloadFile(info.Pass()); | |
309 | |
310 CleanUp(dummy_id); | |
311 } | |
312 | |
313 TEST_F(DownloadFileManagerTest, Complete) { | |
314 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
315 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
316 info->download_id = dummy_id; | |
317 | |
318 CreateDownloadFile(info.Pass()); | |
319 | |
320 Complete(dummy_id); | |
321 } | |
322 | |
323 TEST_F(DownloadFileManagerTest, Rename) { | |
324 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
325 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
326 info->download_id = dummy_id; | |
327 ScopedTempDir download_dir; | |
328 ASSERT_TRUE(download_dir.CreateUniqueTempDir()); | |
329 | |
330 CreateDownloadFile(info.Pass()); | |
331 | |
332 FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); | |
333 RenameFile(dummy_id, foo, true); | |
334 | |
335 CleanUp(dummy_id); | |
336 } | |
337 | |
338 TEST_F(DownloadFileManagerTest, RenameNoOverwrite) { | |
339 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
340 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
341 info->download_id = dummy_id; | |
342 ScopedTempDir download_dir; | |
343 ASSERT_TRUE(download_dir.CreateUniqueTempDir()); | |
344 | |
345 CreateDownloadFile(info.Pass()); | |
346 | |
347 FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); | |
348 RenameFile(dummy_id, foo, false); | |
349 | |
350 CleanUp(dummy_id); | |
351 } | |
352 | |
353 TEST_F(DownloadFileManagerTest, RenameTwice) { | |
354 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
355 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
356 info->download_id = dummy_id; | |
357 ScopedTempDir download_dir; | |
358 ASSERT_TRUE(download_dir.CreateUniqueTempDir()); | |
359 | |
360 CreateDownloadFile(info.Pass()); | |
361 | |
362 FilePath crfoo(download_dir.path().Append( | |
363 FILE_PATH_LITERAL("foo.txt.crdownload"))); | |
364 RenameFile(dummy_id, crfoo, true); | |
365 | |
366 FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); | |
367 RenameFile(dummy_id, foo, true); | |
368 | |
369 CleanUp(dummy_id); | |
370 } | |
371 | |
372 TEST_F(DownloadFileManagerTest, TwoDownloads) { | |
373 // Same as StartDownload, at first. | |
374 scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); | |
375 DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); | |
376 info->download_id = dummy_id; | |
377 scoped_ptr<DownloadCreateInfo> info2(new DownloadCreateInfo); | |
378 DownloadId dummy_id2(download_manager_.get(), kDummyDownloadId2); | |
379 info2->download_id = dummy_id2; | |
380 ScopedTempDir download_dir; | |
381 ASSERT_TRUE(download_dir.CreateUniqueTempDir()); | |
382 | |
383 CreateDownloadFile(info.Pass()); | |
384 CreateDownloadFile(info2.Pass()); | |
385 | |
386 FilePath crbar(download_dir.path().Append( | |
387 FILE_PATH_LITERAL("bar.txt.crdownload"))); | |
388 RenameFile(dummy_id2, crbar, true); | |
389 | |
390 FilePath crfoo(download_dir.path().Append( | |
391 FILE_PATH_LITERAL("foo.txt.crdownload"))); | |
392 RenameFile(dummy_id, crfoo, true); | |
393 | |
394 | |
395 FilePath bar(download_dir.path().Append(FILE_PATH_LITERAL("bar.txt"))); | |
396 RenameFile(dummy_id2, bar, true); | |
397 | |
398 CleanUp(dummy_id2); | |
399 | |
400 FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); | |
401 RenameFile(dummy_id, foo, true); | |
402 | |
403 CleanUp(dummy_id); | |
404 } | |
405 | |
406 // TODO(ahendrickson) -- A test for download manager shutdown. | |
407 // Expected call sequence: | |
408 // OnDownloadManagerShutdown | |
409 // DownloadFile::GetDownloadManager | |
410 // DownloadFile::CancelDownloadRequest | |
411 // DownloadFile::~DownloadFile | |
OLD | NEW |