html5_fs_test.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 <errno.h> 6#include <fcntl.h> 7 8#include <set> 9#include <string> 10 11#include <gmock/gmock.h> 12#include <ppapi/c/ppb_file_io.h> 13#include <ppapi/c/pp_directory_entry.h> 14#include <ppapi/c/pp_errors.h> 15#include <ppapi/c/pp_instance.h> 16#if defined(WIN32) 17#include <windows.h> // For Sleep() 18#endif 19 20#include "fake_ppapi/fake_pepper_interface_html5_fs.h" 21#include "nacl_io/kernel_handle.h" 22#include "nacl_io/html5fs/html5_fs.h" 23#include "nacl_io/osdirent.h" 24#include "nacl_io/osunistd.h" 25#include "nacl_io/pepper_interface_delegate.h" 26#include "sdk_util/scoped_ref.h" 27#include "mock_util.h" 28#include "pepper_interface_mock.h" 29 30using namespace nacl_io; 31using namespace sdk_util; 32 33using ::testing::_; 34using ::testing::DoAll; 35using ::testing::Invoke; 36using ::testing::Mock; 37using ::testing::Return; 38 39namespace { 40 41class Html5FsForTesting : public Html5Fs { 42 public: 43 Html5FsForTesting(StringMap_t& string_map, PepperInterface* ppapi, 44 int expected_error = 0) { 45 FsInitArgs args; 46 args.string_map = string_map; 47 args.ppapi = ppapi; 48 Error error = Init(args); 49 EXPECT_EQ(expected_error, error); 50 } 51}; 52 53class Html5FsTest : public ::testing::Test { 54 public: 55 Html5FsTest(); 56 57 protected: 58 FakePepperInterfaceHtml5Fs ppapi_html5_; 59 PepperInterfaceMock ppapi_mock_; 60 PepperInterfaceDelegate ppapi_; 61}; 62 63Html5FsTest::Html5FsTest() 64 : ppapi_mock_(ppapi_html5_.GetInstance()), 65 ppapi_(ppapi_html5_.GetInstance()) { 66 // Default delegation to the html5 pepper interface. 67 ppapi_.SetCoreInterfaceDelegate(ppapi_html5_.GetCoreInterface()); 68 ppapi_.SetFileSystemInterfaceDelegate(ppapi_html5_.GetFileSystemInterface()); 69 ppapi_.SetFileRefInterfaceDelegate(ppapi_html5_.GetFileRefInterface()); 70 ppapi_.SetFileIoInterfaceDelegate(ppapi_html5_.GetFileIoInterface()); 71 ppapi_.SetVarInterfaceDelegate(ppapi_html5_.GetVarInterface()); 72} 73 74} // namespace 75 76TEST_F(Html5FsTest, FilesystemType) { 77 const char* filesystem_type_strings[] = {"", "PERSISTENT", "TEMPORARY", NULL}; 78 PP_FileSystemType filesystem_type_values[] = { 79 PP_FILESYSTEMTYPE_LOCALPERSISTENT, // Default to persistent. 80 PP_FILESYSTEMTYPE_LOCALPERSISTENT, PP_FILESYSTEMTYPE_LOCALTEMPORARY}; 81 82 const char* expected_size_strings[] = {"100", "12345", NULL}; 83 const int expected_size_values[] = {100, 12345}; 84 85 FileSystemInterfaceMock* filesystem_mock = 86 ppapi_mock_.GetFileSystemInterface(); 87 88 FakeFileSystemInterface* filesystem_fake = 89 static_cast<FakeFileSystemInterface*>( 90 ppapi_html5_.GetFileSystemInterface()); 91 92 for (int i = 0; filesystem_type_strings[i] != NULL; ++i) { 93 const char* filesystem_type_string = filesystem_type_strings[i]; 94 PP_FileSystemType expected_filesystem_type = filesystem_type_values[i]; 95 96 for (int j = 0; expected_size_strings[j] != NULL; ++j) { 97 const char* expected_size_string = expected_size_strings[j]; 98 int64_t expected_expected_size = expected_size_values[j]; 99 100 ppapi_.SetFileSystemInterfaceDelegate(filesystem_mock); 101 102 ON_CALL(*filesystem_mock, Create(_, _)).WillByDefault( 103 Invoke(filesystem_fake, &FakeFileSystemInterface::Create)); 104 105 EXPECT_CALL(*filesystem_mock, 106 Create(ppapi_.GetInstance(), expected_filesystem_type)); 107 108 EXPECT_CALL(*filesystem_mock, Open(_, expected_expected_size, _)) 109 .WillOnce(DoAll(CallCallback<2>(int32_t(PP_OK)), 110 Return(int32_t(PP_OK_COMPLETIONPENDING)))); 111 112 StringMap_t map; 113 map["type"] = filesystem_type_string; 114 map["expected_size"] = expected_size_string; 115 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 116 117 Mock::VerifyAndClearExpectations(&filesystem_mock); 118 } 119 } 120} 121 122TEST_F(Html5FsTest, PassFilesystemResource) { 123 // Fail if given a bad resource. 124 { 125 StringMap_t map; 126 map["filesystem_resource"] = "0"; 127 ScopedRef<Html5FsForTesting> fs( 128 new Html5FsForTesting(map, &ppapi_, EINVAL)); 129 } 130 131 { 132 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL)); 133 PP_Resource filesystem = ppapi_html5_.GetFileSystemInterface()->Create( 134 ppapi_html5_.GetInstance(), PP_FILESYSTEMTYPE_LOCALPERSISTENT); 135 136 ASSERT_EQ(int32_t(PP_OK), ppapi_html5_.GetFileSystemInterface()->Open( 137 filesystem, 0, PP_BlockUntilComplete())); 138 139 StringMap_t map; 140 char buffer[30]; 141 snprintf(buffer, 30, "%d", filesystem); 142 map["filesystem_resource"] = buffer; 143 ScopedRef<Html5FsForTesting> fs( 144 new Html5FsForTesting(map, &ppapi_)); 145 146 ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK)); 147 148 ppapi_html5_.GetCoreInterface()->ReleaseResource(filesystem); 149 } 150} 151 152TEST_F(Html5FsTest, Access) { 153 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL)); 154 155 StringMap_t map; 156 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 157 158 ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK)); 159 ASSERT_EQ(ENOENT, fs->Access(Path("/bar"), F_OK)); 160} 161 162TEST_F(Html5FsTest, Mkdir) { 163 StringMap_t map; 164 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 165 166 // mkdir at the root should return EEXIST, not EACCES. 167 EXPECT_EQ(EEXIST, fs->Mkdir(Path("/"), 0644)); 168 169 Path path("/foo"); 170 ASSERT_EQ(ENOENT, fs->Access(path, F_OK)); 171 ASSERT_EQ(0, fs->Mkdir(path, 0644)); 172 173 struct stat stat; 174 ScopedNode node; 175 ASSERT_EQ(0, fs->Open(path, O_RDONLY, &node)); 176 EXPECT_EQ(0, node->GetStat(&stat)); 177 EXPECT_EQ(S_IFDIR, stat.st_mode & S_IFDIR); 178} 179 180TEST_F(Html5FsTest, Remove) { 181 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL)); 182 183 StringMap_t map; 184 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 185 186 Path path("/foo"); 187 ASSERT_EQ(0, fs->Access(path, F_OK)); 188 ASSERT_EQ(0, fs->Remove(path)); 189 EXPECT_EQ(ENOENT, fs->Access(path, F_OK)); 190} 191 192// Unlink + Rmdir forward to Remove unconditionally, which will not fail if the 193// file type is wrong. 194TEST_F(Html5FsTest, DISABLED_Unlink) { 195 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL)); 196 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL)); 197 198 StringMap_t map; 199 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 200 201 ASSERT_EQ(EISDIR, fs->Unlink(Path("/dir"))); 202 EXPECT_EQ(0, fs->Unlink(Path("/file"))); 203 EXPECT_EQ(ENOENT, fs->Access(Path("/file"), F_OK)); 204 EXPECT_EQ(0, fs->Access(Path("/dir"), F_OK)); 205} 206 207// Unlink + Rmdir forward to Remove unconditionally, which will not fail if the 208// file type is wrong. 209TEST_F(Html5FsTest, DISABLED_Rmdir) { 210 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL)); 211 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL)); 212 213 StringMap_t map; 214 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 215 216 ASSERT_EQ(ENOTDIR, fs->Rmdir(Path("/file"))); 217 EXPECT_EQ(0, fs->Rmdir(Path("/dir"))); 218 EXPECT_EQ(ENOENT, fs->Access(Path("/dir"), F_OK)); 219 EXPECT_EQ(0, fs->Access(Path("/file"), F_OK)); 220} 221 222TEST_F(Html5FsTest, Rename) { 223 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL)); 224 225 StringMap_t map; 226 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 227 228 Path path("/foo"); 229 Path newpath("/bar"); 230 ASSERT_EQ(0, fs->Access(path, F_OK)); 231 ASSERT_EQ(0, fs->Rename(path, newpath)); 232 EXPECT_EQ(ENOENT, fs->Access(path, F_OK)); 233 EXPECT_EQ(0, fs->Access(newpath, F_OK)); 234} 235 236TEST_F(Html5FsTest, OpenForCreate) { 237 StringMap_t map; 238 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 239 240 Path path("/foo"); 241 EXPECT_EQ(ENOENT, fs->Access(path, F_OK)); 242 243 ScopedNode node; 244 ASSERT_EQ(0, fs->Open(path, O_CREAT | O_RDWR, &node)); 245 246 // Write some data. 247 char contents[] = "contents"; 248 int bytes_written = 0; 249 EXPECT_EQ(0, node->Write(HandleAttr(), &contents[0], strlen(contents), 250 &bytes_written)); 251 EXPECT_EQ(strlen(contents), bytes_written); 252 253 // Create again. 254 ASSERT_EQ(0, fs->Open(path, O_CREAT, &node)); 255 256 // Check that the file still has data. 257 off_t size; 258 EXPECT_EQ(0, node->GetSize(&size)); 259 EXPECT_EQ(strlen(contents), size); 260 261 // Open exclusively. 262 EXPECT_EQ(EEXIST, fs->Open(path, O_CREAT | O_EXCL, &node)); 263 264 // Try to truncate without write access. 265 EXPECT_EQ(EINVAL, fs->Open(path, O_CREAT | O_TRUNC, &node)); 266 267 // Open and truncate. 268 ASSERT_EQ(0, fs->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node)); 269 270 // File should be empty. 271 EXPECT_EQ(0, node->GetSize(&size)); 272 EXPECT_EQ(0, size); 273} 274 275TEST_F(Html5FsTest, Read) { 276 const char contents[] = "contents"; 277 ASSERT_TRUE( 278 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL)); 279 ASSERT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL)); 280 StringMap_t map; 281 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 282 283 ScopedNode node; 284 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node)); 285 286 char buffer[10] = {0}; 287 int bytes_read = 0; 288 HandleAttr attr; 289 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 290 ASSERT_EQ(strlen(contents), bytes_read); 291 ASSERT_STREQ(contents, buffer); 292 293 // Read nothing past the end of the file. 294 attr.offs = 100; 295 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 296 ASSERT_EQ(0, bytes_read); 297 298 // Read part of the data. 299 attr.offs = 4; 300 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 301 ASSERT_EQ(strlen(contents) - 4, bytes_read); 302 buffer[bytes_read] = 0; 303 ASSERT_STREQ("ents", buffer); 304 305 // Writing should fail. 306 int bytes_written = 1; // Set to a non-zero value. 307 attr.offs = 0; 308 ASSERT_EQ(EACCES, 309 node->Write(attr, &buffer[0], sizeof(buffer), &bytes_written)); 310 ASSERT_EQ(0, bytes_written); 311 312 // Reading from a directory should fail. 313 ASSERT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node)); 314 ASSERT_EQ(EISDIR, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 315} 316 317TEST_F(Html5FsTest, Write) { 318 const char contents[] = "contents"; 319 EXPECT_TRUE( 320 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL)); 321 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL)); 322 323 StringMap_t map; 324 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 325 326 ScopedNode node; 327 ASSERT_EQ(0, fs->Open(Path("/file"), O_WRONLY, &node)); 328 329 // Reading should fail. 330 char buffer[10]; 331 int bytes_read = 1; // Set to a non-zero value. 332 HandleAttr attr; 333 EXPECT_EQ(EACCES, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 334 EXPECT_EQ(0, bytes_read); 335 336 // Reopen as read-write. 337 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node)); 338 339 int bytes_written = 1; // Set to a non-zero value. 340 attr.offs = 3; 341 EXPECT_EQ(0, node->Write(attr, "struct", 6, &bytes_written)); 342 EXPECT_EQ(6, bytes_written); 343 344 attr.offs = 0; 345 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 346 EXPECT_EQ(9, bytes_read); 347 buffer[bytes_read] = 0; 348 EXPECT_STREQ("construct", buffer); 349 350 // Writing to a directory should fail. 351 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDWR, &node)); 352 EXPECT_EQ(EISDIR, node->Write(attr, &buffer[0], sizeof(buffer), &bytes_read)); 353} 354 355TEST_F(Html5FsTest, GetStat) { 356 const int creation_time = 1000; 357 const int access_time = 2000; 358 const int modified_time = 3000; 359 const char contents[] = "contents"; 360 361 // Create fake file. 362 FakeHtml5FsNode* fake_node; 363 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile( 364 "/file", contents, &fake_node)); 365 fake_node->set_creation_time(creation_time); 366 fake_node->set_last_access_time(access_time); 367 fake_node->set_last_modified_time(modified_time); 368 369 // Create fake directory. 370 EXPECT_TRUE( 371 ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node)); 372 fake_node->set_creation_time(creation_time); 373 fake_node->set_last_access_time(access_time); 374 fake_node->set_last_modified_time(modified_time); 375 376 StringMap_t map; 377 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 378 379 ScopedNode node; 380 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node)); 381 382 struct stat statbuf; 383 EXPECT_EQ(0, node->GetStat(&statbuf)); 384 EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT); 385 EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH | 386 S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT); 387 EXPECT_EQ(strlen(contents), statbuf.st_size); 388 EXPECT_EQ(access_time, statbuf.st_atime); 389 EXPECT_EQ(creation_time, statbuf.st_ctime); 390 EXPECT_EQ(modified_time, statbuf.st_mtime); 391 392 // Test Get* and Isa* methods. 393 off_t size; 394 EXPECT_EQ(0, node->GetSize(&size)); 395 EXPECT_EQ(strlen(contents), size); 396 EXPECT_FALSE(node->IsaDir()); 397 EXPECT_TRUE(node->IsaFile()); 398 EXPECT_EQ(ENOTTY, node->Isatty()); 399 400 // GetStat on a directory... 401 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node)); 402 EXPECT_EQ(0, node->GetStat(&statbuf)); 403 EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT); 404 EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH | 405 S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT); 406 EXPECT_EQ(0, statbuf.st_size); 407 EXPECT_EQ(access_time, statbuf.st_atime); 408 EXPECT_EQ(creation_time, statbuf.st_ctime); 409 EXPECT_EQ(modified_time, statbuf.st_mtime); 410 411 // Test Get* and Isa* methods. 412 EXPECT_EQ(0, node->GetSize(&size)); 413 EXPECT_EQ(0, size); 414 EXPECT_TRUE(node->IsaDir()); 415 EXPECT_FALSE(node->IsaFile()); 416 EXPECT_EQ(ENOTTY, node->Isatty()); 417} 418 419TEST_F(Html5FsTest, FTruncate) { 420 const char contents[] = "contents"; 421 EXPECT_TRUE( 422 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL)); 423 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL)); 424 425 StringMap_t map; 426 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 427 428 ScopedNode node; 429 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node)); 430 431 HandleAttr attr; 432 char buffer[10] = {0}; 433 int bytes_read = 0; 434 435 // First make the file shorter... 436 EXPECT_EQ(0, node->FTruncate(4)); 437 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 438 EXPECT_EQ(4, bytes_read); 439 buffer[bytes_read] = 0; 440 EXPECT_STREQ("cont", buffer); 441 442 // Now make the file longer... 443 EXPECT_EQ(0, node->FTruncate(8)); 444 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read)); 445 EXPECT_EQ(8, bytes_read); 446 buffer[bytes_read] = 0; 447 EXPECT_STREQ("cont\0\0\0\0", buffer); 448 449 // Ftruncate should fail for a directory. 450 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node)); 451 EXPECT_EQ(EISDIR, node->FTruncate(4)); 452} 453 454TEST_F(Html5FsTest, GetDents) { 455 const char contents[] = "contents"; 456 EXPECT_TRUE( 457 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL)); 458 459 StringMap_t map; 460 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_)); 461 462 ScopedNode root; 463 ASSERT_EQ(0, fs->Open(Path("/"), O_RDONLY, &root)); 464 465 ScopedNode node; 466 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node)); 467 468 // Should fail for regular files. 469 const size_t kMaxDirents = 5; 470 dirent dirents[kMaxDirents]; 471 int bytes_read = 1; // Set to a non-zero value. 472 473 memset(&dirents[0], 0, sizeof(dirents)); 474 EXPECT_EQ(ENOTDIR, 475 node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read)); 476 EXPECT_EQ(0, bytes_read); 477 478 // Should work with root directory. 479 // +2 to test a size that is not a multiple of sizeof(dirent). 480 // Expect it to round down. 481 memset(&dirents[0], 0, sizeof(dirents)); 482 EXPECT_EQ( 483 0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read)); 484 485 { 486 size_t num_dirents = bytes_read / sizeof(dirent); 487 EXPECT_EQ(3, num_dirents); 488 EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read); 489 490 std::multiset<std::string> dirnames; 491 for (size_t i = 0; i < num_dirents; ++i) { 492 EXPECT_EQ(sizeof(dirent), dirents[i].d_off); 493 EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen); 494 dirnames.insert(dirents[i].d_name); 495 } 496 497 EXPECT_EQ(1, dirnames.count("file")); 498 EXPECT_EQ(1, dirnames.count(".")); 499 EXPECT_EQ(1, dirnames.count("..")); 500 } 501 502 // Add another file... 503 ASSERT_EQ(0, fs->Open(Path("/file2"), O_CREAT, &node)); 504 505 // Read the root directory again. 506 memset(&dirents[0], 0, sizeof(dirents)); 507 EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read)); 508 509 { 510 size_t num_dirents = bytes_read / sizeof(dirent); 511 EXPECT_EQ(4, num_dirents); 512 EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read); 513 514 std::multiset<std::string> dirnames; 515 for (size_t i = 0; i < num_dirents; ++i) { 516 EXPECT_EQ(sizeof(dirent), dirents[i].d_off); 517 EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen); 518 dirnames.insert(dirents[i].d_name); 519 } 520 521 EXPECT_EQ(1, dirnames.count("file")); 522 EXPECT_EQ(1, dirnames.count("file2")); 523 EXPECT_EQ(1, dirnames.count(".")); 524 EXPECT_EQ(1, dirnames.count("..")); 525 } 526} 527