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