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