1 // Copyright 2013 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 <map> 6 #include <queue> 7 #include <set> 8 #include <string> 9 #include <vector> 10 11 #include "base/files/file_enumerator.h" 12 #include "base/files/file_util.h" 13 #include "base/files/scoped_temp_dir.h" 14 #include "base/logging.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/message_loop/message_loop_proxy.h" 17 #include "base/time/time.h" 18 #include "content/public/test/async_file_test_helper.h" 19 #include "content/public/test/test_file_system_context.h" 20 #include "content/test/fileapi_test_file_set.h" 21 #include "storage/browser/fileapi/dragged_file_util.h" 22 #include "storage/browser/fileapi/file_system_context.h" 23 #include "storage/browser/fileapi/file_system_operation_context.h" 24 #include "storage/browser/fileapi/isolated_context.h" 25 #include "storage/browser/fileapi/local_file_util.h" 26 #include "storage/browser/fileapi/native_file_util.h" 27 #include "testing/gtest/include/gtest/gtest.h" 28 29 using content::AsyncFileTestHelper; 30 using storage::FileSystemContext; 31 using storage::FileSystemOperationContext; 32 using storage::FileSystemType; 33 using storage::FileSystemURL; 34 35 namespace content { 36 37 namespace { 38 39 typedef AsyncFileTestHelper::FileEntryList FileEntryList; 40 41 // Used in DraggedFileUtilTest::SimulateDropFiles(). 42 // Random root paths in which we create each file/directory of the 43 // RegularTestCases (so that we can simulate a drop with files/directories 44 // from multiple directories). 45 static const base::FilePath::CharType* kRootPaths[] = { 46 FILE_PATH_LITERAL("a"), 47 FILE_PATH_LITERAL("b/c"), 48 FILE_PATH_LITERAL("etc"), 49 }; 50 51 base::FilePath GetTopLevelPath(const base::FilePath& path) { 52 std::vector<base::FilePath::StringType> components; 53 path.GetComponents(&components); 54 return base::FilePath(components[0]); 55 } 56 57 bool IsDirectoryEmpty(FileSystemContext* context, const FileSystemURL& url) { 58 FileEntryList entries; 59 EXPECT_EQ(base::File::FILE_OK, 60 AsyncFileTestHelper::ReadDirectory(context, url, &entries)); 61 return entries.empty(); 62 } 63 64 FileSystemURL GetEntryURL(FileSystemContext* file_system_context, 65 const FileSystemURL& dir, 66 const base::FilePath::StringType& name) { 67 return file_system_context->CreateCrackedFileSystemURL( 68 dir.origin(), 69 dir.mount_type(), 70 dir.virtual_path().Append(name)); 71 } 72 73 base::FilePath GetRelativeVirtualPath(const FileSystemURL& root, 74 const FileSystemURL& url) { 75 if (root.virtual_path().empty()) 76 return url.virtual_path(); 77 base::FilePath relative; 78 const bool success = root.virtual_path().AppendRelativePath( 79 url.virtual_path(), &relative); 80 DCHECK(success); 81 return relative; 82 } 83 84 FileSystemURL GetOtherURL(FileSystemContext* file_system_context, 85 const FileSystemURL& root, 86 const FileSystemURL& other_root, 87 const FileSystemURL& url) { 88 return file_system_context->CreateCrackedFileSystemURL( 89 other_root.origin(), 90 other_root.mount_type(), 91 other_root.virtual_path().Append(GetRelativeVirtualPath(root, url))); 92 } 93 94 } // namespace 95 96 class DraggedFileUtilTest : public testing::Test { 97 public: 98 DraggedFileUtilTest() {} 99 100 virtual void SetUp() { 101 ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); 102 ASSERT_TRUE(partition_dir_.CreateUniqueTempDir()); 103 file_util_.reset(new storage::DraggedFileUtil()); 104 105 // Register the files/directories of RegularTestCases (with random 106 // root paths) as dropped files. 107 SimulateDropFiles(); 108 109 file_system_context_ = CreateFileSystemContextForTesting( 110 NULL /* quota_manager */, 111 partition_dir_.path()); 112 113 isolated_context()->AddReference(filesystem_id_); 114 } 115 116 virtual void TearDown() { 117 isolated_context()->RemoveReference(filesystem_id_); 118 } 119 120 protected: 121 storage::IsolatedContext* isolated_context() const { 122 return storage::IsolatedContext::GetInstance(); 123 } 124 const base::FilePath& root_path() const { 125 return data_dir_.path(); 126 } 127 FileSystemContext* file_system_context() const { 128 return file_system_context_.get(); 129 } 130 storage::FileSystemFileUtil* file_util() const { return file_util_.get(); } 131 std::string filesystem_id() const { return filesystem_id_; } 132 133 base::FilePath GetTestCasePlatformPath( 134 const base::FilePath::StringType& path) { 135 return toplevel_root_map_[GetTopLevelPath(base::FilePath(path))] 136 .Append(path).NormalizePathSeparators(); 137 } 138 139 base::FilePath GetTestCaseLocalPath(const base::FilePath& path) { 140 base::FilePath relative; 141 if (data_dir_.path().AppendRelativePath(path, &relative)) 142 return relative; 143 return path; 144 } 145 146 FileSystemURL GetFileSystemURL(const base::FilePath& path) const { 147 base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath( 148 filesystem_id()).Append(path); 149 return file_system_context_->CreateCrackedFileSystemURL( 150 GURL("http://example.com"), 151 storage::kFileSystemTypeIsolated, 152 virtual_path); 153 } 154 155 FileSystemURL GetOtherFileSystemURL(const base::FilePath& path) const { 156 return file_system_context()->CreateCrackedFileSystemURL( 157 GURL("http://example.com"), 158 storage::kFileSystemTypeTemporary, 159 base::FilePath().AppendASCII("dest").Append(path)); 160 } 161 162 void VerifyFilesHaveSameContent(const FileSystemURL& url1, 163 const FileSystemURL& url2) { 164 // Get the file info and the platform path for url1. 165 base::File::Info info1; 166 ASSERT_EQ(base::File::FILE_OK, 167 AsyncFileTestHelper::GetMetadata( 168 file_system_context(), url1, &info1)); 169 base::FilePath platform_path1; 170 ASSERT_EQ(base::File::FILE_OK, 171 AsyncFileTestHelper::GetPlatformPath( 172 file_system_context(), url1, &platform_path1)); 173 174 // Get the file info and the platform path for url2. 175 base::File::Info info2; 176 ASSERT_EQ(base::File::FILE_OK, 177 AsyncFileTestHelper::GetMetadata( 178 file_system_context(), url2, &info2)); 179 base::FilePath platform_path2; 180 ASSERT_EQ(base::File::FILE_OK, 181 AsyncFileTestHelper::GetPlatformPath( 182 file_system_context(), url2, &platform_path2)); 183 184 // See if file info matches with the other one. 185 EXPECT_EQ(info1.is_directory, info2.is_directory); 186 EXPECT_EQ(info1.size, info2.size); 187 EXPECT_EQ(info1.is_symbolic_link, info2.is_symbolic_link); 188 EXPECT_NE(platform_path1, platform_path2); 189 190 std::string content1, content2; 191 EXPECT_TRUE(base::ReadFileToString(platform_path1, &content1)); 192 EXPECT_TRUE(base::ReadFileToString(platform_path2, &content2)); 193 EXPECT_EQ(content1, content2); 194 } 195 196 void VerifyDirectoriesHaveSameContent(const FileSystemURL& root1, 197 const FileSystemURL& root2) { 198 base::FilePath root_path1 = root1.path(); 199 base::FilePath root_path2 = root2.path(); 200 201 FileEntryList entries; 202 std::queue<FileSystemURL> directories; 203 204 directories.push(root1); 205 std::set<base::FilePath> file_set1; 206 while (!directories.empty()) { 207 FileSystemURL dir = directories.front(); 208 directories.pop(); 209 210 ASSERT_EQ(base::File::FILE_OK, 211 AsyncFileTestHelper::ReadDirectory( 212 file_system_context(), dir, &entries)); 213 for (size_t i = 0; i < entries.size(); ++i) { 214 FileSystemURL url = GetEntryURL(file_system_context(), 215 dir, entries[i].name); 216 if (entries[i].is_directory) { 217 directories.push(url); 218 continue; 219 } 220 file_set1.insert(GetRelativeVirtualPath(root1, url)); 221 } 222 } 223 224 directories.push(root2); 225 while (!directories.empty()) { 226 FileSystemURL dir = directories.front(); 227 directories.pop(); 228 229 ASSERT_EQ(base::File::FILE_OK, 230 AsyncFileTestHelper::ReadDirectory( 231 file_system_context(), dir, &entries)); 232 for (size_t i = 0; i < entries.size(); ++i) { 233 FileSystemURL url2 = GetEntryURL(file_system_context(), 234 dir, entries[i].name); 235 FileSystemURL url1 = GetOtherURL(file_system_context(), 236 root2, root1, url2); 237 if (entries[i].is_directory) { 238 directories.push(url2); 239 EXPECT_EQ(IsDirectoryEmpty(file_system_context(), url1), 240 IsDirectoryEmpty(file_system_context(), url2)); 241 continue; 242 } 243 base::FilePath relative = GetRelativeVirtualPath(root2, url2); 244 EXPECT_TRUE(file_set1.find(relative) != file_set1.end()); 245 VerifyFilesHaveSameContent(url1, url2); 246 } 247 } 248 } 249 250 scoped_ptr<storage::FileSystemOperationContext> GetOperationContext() { 251 return make_scoped_ptr(new storage::FileSystemOperationContext( 252 file_system_context())).Pass(); 253 } 254 255 256 private: 257 void SimulateDropFiles() { 258 size_t root_path_index = 0; 259 260 storage::IsolatedContext::FileInfoSet toplevels; 261 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 262 const FileSystemTestCaseRecord& test_case = 263 kRegularFileSystemTestCases[i]; 264 base::FilePath path(test_case.path); 265 base::FilePath toplevel = GetTopLevelPath(path); 266 267 // We create the test case files under one of the kRootPaths 268 // to simulate a drop with multiple directories. 269 if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) { 270 base::FilePath root = root_path().Append( 271 kRootPaths[(root_path_index++) % arraysize(kRootPaths)]); 272 toplevel_root_map_[toplevel] = root; 273 toplevels.AddPath(root.Append(path), NULL); 274 } 275 276 SetUpOneFileSystemTestCase(toplevel_root_map_[toplevel], test_case); 277 } 278 279 // Register the toplevel entries. 280 filesystem_id_ = isolated_context()->RegisterDraggedFileSystem(toplevels); 281 } 282 283 base::ScopedTempDir data_dir_; 284 base::ScopedTempDir partition_dir_; 285 base::MessageLoopForIO message_loop_; 286 std::string filesystem_id_; 287 scoped_refptr<FileSystemContext> file_system_context_; 288 std::map<base::FilePath, base::FilePath> toplevel_root_map_; 289 scoped_ptr<storage::DraggedFileUtil> file_util_; 290 DISALLOW_COPY_AND_ASSIGN(DraggedFileUtilTest); 291 }; 292 293 TEST_F(DraggedFileUtilTest, BasicTest) { 294 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 295 SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i); 296 const FileSystemTestCaseRecord& test_case = 297 kRegularFileSystemTestCases[i]; 298 299 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 300 301 // See if we can query the file info via the isolated FileUtil. 302 // (This should succeed since we have registered all the top-level 303 // entries of the test cases in SetUp()) 304 base::File::Info info; 305 base::FilePath platform_path; 306 FileSystemOperationContext context(file_system_context()); 307 ASSERT_EQ(base::File::FILE_OK, 308 file_util()->GetFileInfo(&context, url, &info, &platform_path)); 309 310 // See if the obtained file info is correct. 311 if (!test_case.is_directory) 312 ASSERT_EQ(test_case.data_file_size, info.size); 313 ASSERT_EQ(test_case.is_directory, info.is_directory); 314 ASSERT_EQ(GetTestCasePlatformPath(test_case.path), 315 platform_path.NormalizePathSeparators()); 316 } 317 } 318 319 TEST_F(DraggedFileUtilTest, UnregisteredPathsTest) { 320 static const FileSystemTestCaseRecord kUnregisteredCases[] = { 321 {true, FILE_PATH_LITERAL("nonexistent"), 0}, 322 {true, FILE_PATH_LITERAL("nonexistent/dir foo"), 0}, 323 {false, FILE_PATH_LITERAL("nonexistent/false"), 0}, 324 {false, FILE_PATH_LITERAL("foo"), 30}, 325 {false, FILE_PATH_LITERAL("bar"), 20}, 326 }; 327 328 for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { 329 SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); 330 const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i]; 331 332 // Prepare the test file/directory. 333 SetUpOneFileSystemTestCase(root_path(), test_case); 334 335 // Make sure regular GetFileInfo succeeds. 336 base::File::Info info; 337 ASSERT_TRUE(base::GetFileInfo(root_path().Append(test_case.path), &info)); 338 if (!test_case.is_directory) 339 ASSERT_EQ(test_case.data_file_size, info.size); 340 ASSERT_EQ(test_case.is_directory, info.is_directory); 341 } 342 343 for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { 344 SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); 345 const FileSystemTestCaseRecord& test_case = kUnregisteredCases[i]; 346 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 347 348 // We should not be able to get the valid URL for unregistered files. 349 ASSERT_FALSE(url.is_valid()); 350 } 351 } 352 353 TEST_F(DraggedFileUtilTest, ReadDirectoryTest) { 354 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 355 const FileSystemTestCaseRecord& test_case = 356 kRegularFileSystemTestCases[i]; 357 if (!test_case.is_directory) 358 continue; 359 360 SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i 361 << ": " << test_case.path); 362 363 // Read entries in the directory to construct the expected results map. 364 typedef std::map<base::FilePath::StringType, storage::DirectoryEntry> 365 EntryMap; 366 EntryMap expected_entry_map; 367 368 base::FilePath dir_path = GetTestCasePlatformPath(test_case.path); 369 base::FileEnumerator file_enum( 370 dir_path, false /* not recursive */, 371 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); 372 base::FilePath current; 373 while (!(current = file_enum.Next()).empty()) { 374 base::FileEnumerator::FileInfo file_info = file_enum.GetInfo(); 375 storage::DirectoryEntry entry; 376 entry.is_directory = file_info.IsDirectory(); 377 entry.name = current.BaseName().value(); 378 entry.size = file_info.GetSize(); 379 entry.last_modified_time = file_info.GetLastModifiedTime(); 380 expected_entry_map[entry.name] = entry; 381 382 #if defined(OS_POSIX) 383 // Creates a symlink for each file/directory. 384 // They should be ignored by ReadDirectory, so we don't add them 385 // to expected_entry_map. 386 base::CreateSymbolicLink( 387 current, 388 dir_path.Append(current.BaseName().AddExtension( 389 FILE_PATH_LITERAL("link")))); 390 #endif 391 } 392 393 // Perform ReadDirectory in the isolated filesystem. 394 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 395 FileEntryList entries; 396 ASSERT_EQ(base::File::FILE_OK, 397 AsyncFileTestHelper::ReadDirectory( 398 file_system_context(), url, &entries)); 399 400 EXPECT_EQ(expected_entry_map.size(), entries.size()); 401 for (size_t i = 0; i < entries.size(); ++i) { 402 const storage::DirectoryEntry& entry = entries[i]; 403 EntryMap::iterator found = expected_entry_map.find(entry.name); 404 EXPECT_TRUE(found != expected_entry_map.end()); 405 EXPECT_EQ(found->second.name, entry.name); 406 EXPECT_EQ(found->second.is_directory, entry.is_directory); 407 EXPECT_EQ(found->second.size, entry.size); 408 EXPECT_EQ(found->second.last_modified_time.ToDoubleT(), 409 entry.last_modified_time.ToDoubleT()); 410 } 411 } 412 } 413 414 TEST_F(DraggedFileUtilTest, GetLocalFilePathTest) { 415 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 416 const FileSystemTestCaseRecord& test_case = 417 kRegularFileSystemTestCases[i]; 418 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 419 420 FileSystemOperationContext context(file_system_context()); 421 422 base::FilePath local_file_path; 423 EXPECT_EQ(base::File::FILE_OK, 424 file_util()->GetLocalFilePath(&context, url, &local_file_path)); 425 EXPECT_EQ(GetTestCasePlatformPath(test_case.path).value(), 426 local_file_path.value()); 427 } 428 } 429 430 TEST_F(DraggedFileUtilTest, CopyOutFileTest) { 431 FileSystemURL src_root = GetFileSystemURL(base::FilePath()); 432 FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); 433 434 FileEntryList entries; 435 std::queue<FileSystemURL> directories; 436 directories.push(src_root); 437 438 ASSERT_EQ(base::File::FILE_OK, 439 AsyncFileTestHelper::CreateDirectory(file_system_context(), 440 dest_root)); 441 442 while (!directories.empty()) { 443 FileSystemURL dir = directories.front(); 444 directories.pop(); 445 ASSERT_EQ(base::File::FILE_OK, 446 AsyncFileTestHelper::ReadDirectory(file_system_context(), 447 dir, &entries)); 448 for (size_t i = 0; i < entries.size(); ++i) { 449 FileSystemURL src_url = GetEntryURL(file_system_context(), 450 dir, entries[i].name); 451 FileSystemURL dest_url = GetOtherURL(file_system_context(), 452 src_root, dest_root, src_url); 453 454 if (entries[i].is_directory) { 455 ASSERT_EQ(base::File::FILE_OK, 456 AsyncFileTestHelper::CreateDirectory(file_system_context(), 457 dest_url)); 458 directories.push(src_url); 459 continue; 460 } 461 SCOPED_TRACE(testing::Message() << "Testing file copy " 462 << src_url.path().value()); 463 ASSERT_EQ(base::File::FILE_OK, 464 AsyncFileTestHelper::Copy(file_system_context(), 465 src_url, dest_url)); 466 VerifyFilesHaveSameContent(src_url, dest_url); 467 } 468 } 469 } 470 471 TEST_F(DraggedFileUtilTest, CopyOutDirectoryTest) { 472 FileSystemURL src_root = GetFileSystemURL(base::FilePath()); 473 FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); 474 475 ASSERT_EQ(base::File::FILE_OK, 476 AsyncFileTestHelper::CreateDirectory(file_system_context(), 477 dest_root)); 478 479 FileEntryList entries; 480 ASSERT_EQ(base::File::FILE_OK, 481 AsyncFileTestHelper::ReadDirectory(file_system_context(), 482 src_root, &entries)); 483 for (size_t i = 0; i < entries.size(); ++i) { 484 if (!entries[i].is_directory) 485 continue; 486 FileSystemURL src_url = GetEntryURL(file_system_context(), 487 src_root, entries[i].name); 488 FileSystemURL dest_url = GetOtherURL(file_system_context(), 489 src_root, dest_root, src_url); 490 SCOPED_TRACE(testing::Message() << "Testing file copy " 491 << src_url.path().value()); 492 ASSERT_EQ(base::File::FILE_OK, 493 AsyncFileTestHelper::Copy(file_system_context(), 494 src_url, dest_url)); 495 VerifyDirectoriesHaveSameContent(src_url, dest_url); 496 } 497 } 498 499 TEST_F(DraggedFileUtilTest, TouchTest) { 500 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 501 const FileSystemTestCaseRecord& test_case = 502 kRegularFileSystemTestCases[i]; 503 if (test_case.is_directory) 504 continue; 505 SCOPED_TRACE(testing::Message() << test_case.path); 506 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 507 508 base::Time last_access_time = base::Time::FromTimeT(1000); 509 base::Time last_modified_time = base::Time::FromTimeT(2000); 510 511 EXPECT_EQ(base::File::FILE_OK, 512 file_util()->Touch(GetOperationContext().get(), url, 513 last_access_time, 514 last_modified_time)); 515 516 // Verification. 517 base::File::Info info; 518 base::FilePath platform_path; 519 ASSERT_EQ(base::File::FILE_OK, 520 file_util()->GetFileInfo(GetOperationContext().get(), url, 521 &info, &platform_path)); 522 EXPECT_EQ(last_access_time.ToTimeT(), info.last_accessed.ToTimeT()); 523 EXPECT_EQ(last_modified_time.ToTimeT(), info.last_modified.ToTimeT()); 524 } 525 } 526 527 TEST_F(DraggedFileUtilTest, TruncateTest) { 528 for (size_t i = 0; i < kRegularFileSystemTestCaseSize; ++i) { 529 const FileSystemTestCaseRecord& test_case = 530 kRegularFileSystemTestCases[i]; 531 if (test_case.is_directory) 532 continue; 533 534 SCOPED_TRACE(testing::Message() << test_case.path); 535 FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); 536 537 // Truncate to 0. 538 base::File::Info info; 539 base::FilePath platform_path; 540 EXPECT_EQ(base::File::FILE_OK, 541 file_util()->Truncate(GetOperationContext().get(), url, 0)); 542 ASSERT_EQ(base::File::FILE_OK, 543 file_util()->GetFileInfo(GetOperationContext().get(), url, 544 &info, &platform_path)); 545 EXPECT_EQ(0, info.size); 546 547 // Truncate (extend) to 999. 548 EXPECT_EQ(base::File::FILE_OK, 549 file_util()->Truncate(GetOperationContext().get(), url, 999)); 550 ASSERT_EQ(base::File::FILE_OK, 551 file_util()->GetFileInfo(GetOperationContext().get(), url, 552 &info, &platform_path)); 553 EXPECT_EQ(999, info.size); 554 } 555 } 556 557 } // namespace content 558