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.
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>
17 #include <windows.h> // For Sleep()
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"
30 using namespace nacl_io;
31 using namespace sdk_util;
34 using ::testing::DoAll;
35 using ::testing::Invoke;
36 using ::testing::Mock;
37 using ::testing::Return;
41 class Html5FsForTesting : public Html5Fs {
43 Html5FsForTesting(StringMap_t& string_map, PepperInterface* ppapi,
44 int expected_error = 0) {
46 args.string_map = string_map;
48 Error error = Init(args);
49 EXPECT_EQ(expected_error, error);
53 class Html5FsTest : public ::testing::Test {
58 FakePepperInterfaceHtml5Fs ppapi_html5_;
59 PepperInterfaceMock ppapi_mock_;
60 PepperInterfaceDelegate ppapi_;
63 Html5FsTest::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());
76 TEST_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};
82 const char* expected_size_strings[] = {"100", "12345", NULL};
83 const int expected_size_values[] = {100, 12345};
85 FileSystemInterfaceMock* filesystem_mock =
86 ppapi_mock_.GetFileSystemInterface();
88 FakeFileSystemInterface* filesystem_fake =
89 static_cast<FakeFileSystemInterface*>(
90 ppapi_html5_.GetFileSystemInterface());
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];
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];
100 ppapi_.SetFileSystemInterfaceDelegate(filesystem_mock);
102 ON_CALL(*filesystem_mock, Create(_, _)).WillByDefault(
103 Invoke(filesystem_fake, &FakeFileSystemInterface::Create));
105 EXPECT_CALL(*filesystem_mock,
106 Create(ppapi_.GetInstance(), expected_filesystem_type));
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))));
113 map["type"] = filesystem_type_string;
114 map["expected_size"] = expected_size_string;
115 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
117 Mock::VerifyAndClearExpectations(&filesystem_mock);
122 TEST_F(Html5FsTest, PassFilesystemResource) {
123 // Fail if given a bad resource.
126 map["filesystem_resource"] = "0";
127 ScopedRef<Html5FsForTesting> fs(
128 new Html5FsForTesting(map, &ppapi_, EINVAL));
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);
136 ASSERT_EQ(int32_t(PP_OK), ppapi_html5_.GetFileSystemInterface()->Open(
137 filesystem, 0, PP_BlockUntilComplete()));
141 snprintf(buffer, 30, "%d", filesystem);
142 map["filesystem_resource"] = buffer;
143 ScopedRef<Html5FsForTesting> fs(
144 new Html5FsForTesting(map, &ppapi_));
146 ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK));
148 ppapi_html5_.GetCoreInterface()->ReleaseResource(filesystem);
152 TEST_F(Html5FsTest, MountSubtree) {
153 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo/bar",
156 map["SOURCE"] = "/foo";
157 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
159 ASSERT_EQ(0, fs->Access(Path("/bar"), R_OK | W_OK | X_OK));
160 ASSERT_EQ(ENOENT, fs->Access(Path("/foo/bar"), F_OK));
163 TEST_F(Html5FsTest, Access) {
164 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
167 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
169 ASSERT_EQ(0, fs->Access(Path("/foo"), R_OK | W_OK | X_OK));
170 ASSERT_EQ(ENOENT, fs->Access(Path("/bar"), F_OK));
173 TEST_F(Html5FsTest, Mkdir) {
175 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
177 // mkdir at the root should return EEXIST, not EACCES.
178 EXPECT_EQ(EEXIST, fs->Mkdir(Path("/"), 0644));
181 ASSERT_EQ(ENOENT, fs->Access(path, F_OK));
182 ASSERT_EQ(0, fs->Mkdir(path, 0644));
186 ASSERT_EQ(0, fs->Open(path, O_RDONLY, &node));
187 EXPECT_EQ(0, node->GetStat(&stat));
188 EXPECT_EQ(S_IFDIR, stat.st_mode & S_IFDIR);
191 TEST_F(Html5FsTest, Remove) {
192 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
195 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
198 ASSERT_EQ(0, fs->Access(path, F_OK));
199 ASSERT_EQ(0, fs->Remove(path));
200 EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
203 TEST_F(Html5FsTest, Unlink) {
204 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
205 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
208 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
210 ASSERT_EQ(EISDIR, fs->Unlink(Path("/dir")));
211 EXPECT_EQ(0, fs->Unlink(Path("/file")));
212 EXPECT_EQ(ENOENT, fs->Access(Path("/file"), F_OK));
213 EXPECT_EQ(0, fs->Access(Path("/dir"), F_OK));
216 TEST_F(Html5FsTest, Rmdir) {
217 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
218 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
221 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
223 ASSERT_EQ(ENOTDIR, fs->Rmdir(Path("/file")));
224 EXPECT_EQ(0, fs->Rmdir(Path("/dir")));
225 EXPECT_EQ(ENOENT, fs->Access(Path("/dir"), F_OK));
226 EXPECT_EQ(0, fs->Access(Path("/file"), F_OK));
229 TEST_F(Html5FsTest, Rename) {
230 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
233 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
236 Path newpath("/bar");
237 ASSERT_EQ(0, fs->Access(path, F_OK));
238 ASSERT_EQ(0, fs->Rename(path, newpath));
239 EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
240 EXPECT_EQ(0, fs->Access(newpath, F_OK));
243 TEST_F(Html5FsTest, OpenForCreate) {
245 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
248 EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
251 ASSERT_EQ(0, fs->Open(path, O_CREAT | O_RDWR, &node));
254 char contents[] = "contents";
255 int bytes_written = 0;
256 EXPECT_EQ(0, node->Write(HandleAttr(), &contents[0], strlen(contents),
258 EXPECT_EQ(strlen(contents), bytes_written);
261 ASSERT_EQ(0, fs->Open(path, O_CREAT, &node));
263 // Check that the file still has data.
265 EXPECT_EQ(0, node->GetSize(&size));
266 EXPECT_EQ(strlen(contents), size);
269 EXPECT_EQ(EEXIST, fs->Open(path, O_CREAT | O_EXCL, &node));
271 // Try to truncate without write access.
272 EXPECT_EQ(EINVAL, fs->Open(path, O_CREAT | O_TRUNC, &node));
274 // Open and truncate.
275 ASSERT_EQ(0, fs->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node));
277 // File should be empty.
278 EXPECT_EQ(0, node->GetSize(&size));
282 TEST_F(Html5FsTest, Read) {
283 const char contents[] = "contents";
285 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
286 ASSERT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
288 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
291 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
293 char buffer[10] = {0};
296 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
297 ASSERT_EQ(strlen(contents), bytes_read);
298 ASSERT_STREQ(contents, buffer);
300 // Read nothing past the end of the file.
302 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
303 ASSERT_EQ(0, bytes_read);
305 // Read part of the data.
307 ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
308 ASSERT_EQ(strlen(contents) - 4, bytes_read);
309 buffer[bytes_read] = 0;
310 ASSERT_STREQ("ents", buffer);
312 // Writing should fail.
313 int bytes_written = 1; // Set to a non-zero value.
316 node->Write(attr, &buffer[0], sizeof(buffer), &bytes_written));
317 ASSERT_EQ(0, bytes_written);
319 // Reading from a directory should fail.
320 ASSERT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
321 ASSERT_EQ(EISDIR, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
324 TEST_F(Html5FsTest, Write) {
325 const char contents[] = "contents";
327 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
328 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
331 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
334 ASSERT_EQ(0, fs->Open(Path("/file"), O_WRONLY, &node));
336 // Reading should fail.
338 int bytes_read = 1; // Set to a non-zero value.
340 EXPECT_EQ(EACCES, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
341 EXPECT_EQ(0, bytes_read);
343 // Reopen as read-write.
344 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
346 int bytes_written = 1; // Set to a non-zero value.
348 EXPECT_EQ(0, node->Write(attr, "struct", 6, &bytes_written));
349 EXPECT_EQ(6, bytes_written);
352 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
353 EXPECT_EQ(9, bytes_read);
354 buffer[bytes_read] = 0;
355 EXPECT_STREQ("construct", buffer);
357 // Writing to a directory should fail.
358 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDWR, &node));
359 EXPECT_EQ(EISDIR, node->Write(attr, &buffer[0], sizeof(buffer), &bytes_read));
362 TEST_F(Html5FsTest, GetStat) {
363 const int creation_time = 1000;
364 const int access_time = 2000;
365 const int modified_time = 3000;
366 const char contents[] = "contents";
369 FakeHtml5FsNode* fake_node;
370 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile(
371 "/file", contents, &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);
376 // Create fake directory.
378 ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node));
379 fake_node->set_creation_time(creation_time);
380 fake_node->set_last_access_time(access_time);
381 fake_node->set_last_modified_time(modified_time);
384 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
387 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
390 EXPECT_EQ(0, node->GetStat(&statbuf));
391 EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
392 EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
393 S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
394 EXPECT_EQ(strlen(contents), statbuf.st_size);
395 EXPECT_EQ(access_time, statbuf.st_atime);
396 EXPECT_EQ(creation_time, statbuf.st_ctime);
397 EXPECT_EQ(modified_time, statbuf.st_mtime);
399 // Test Get* and Isa* methods.
401 EXPECT_EQ(0, node->GetSize(&size));
402 EXPECT_EQ(strlen(contents), size);
403 EXPECT_FALSE(node->IsaDir());
404 EXPECT_TRUE(node->IsaFile());
405 EXPECT_EQ(ENOTTY, node->Isatty());
407 // GetStat on a directory...
408 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
409 EXPECT_EQ(0, node->GetStat(&statbuf));
410 EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT);
411 EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
412 S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
413 EXPECT_EQ(0, statbuf.st_size);
414 EXPECT_EQ(access_time, statbuf.st_atime);
415 EXPECT_EQ(creation_time, statbuf.st_ctime);
416 EXPECT_EQ(modified_time, statbuf.st_mtime);
418 // Test Get* and Isa* methods.
419 EXPECT_EQ(0, node->GetSize(&size));
421 EXPECT_TRUE(node->IsaDir());
422 EXPECT_FALSE(node->IsaFile());
423 EXPECT_EQ(ENOTTY, node->Isatty());
426 TEST_F(Html5FsTest, FTruncate) {
427 const char contents[] = "contents";
429 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
430 EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
433 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
436 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
439 char buffer[10] = {0};
442 // First make the file shorter...
443 EXPECT_EQ(0, node->FTruncate(4));
444 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
445 EXPECT_EQ(4, bytes_read);
446 buffer[bytes_read] = 0;
447 EXPECT_STREQ("cont", buffer);
449 // Now make the file longer...
450 EXPECT_EQ(0, node->FTruncate(8));
451 EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
452 EXPECT_EQ(8, bytes_read);
453 buffer[bytes_read] = 0;
454 EXPECT_STREQ("cont\0\0\0\0", buffer);
456 // Ftruncate should fail for a directory.
457 EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
458 EXPECT_EQ(EISDIR, node->FTruncate(4));
461 TEST_F(Html5FsTest, GetDents) {
462 const char contents[] = "contents";
464 ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
467 ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
470 ASSERT_EQ(0, fs->Open(Path("/"), O_RDONLY, &root));
473 ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
475 // Should fail for regular files.
476 const size_t kMaxDirents = 5;
477 dirent dirents[kMaxDirents];
478 int bytes_read = 1; // Set to a non-zero value.
480 memset(&dirents[0], 0, sizeof(dirents));
482 node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
483 EXPECT_EQ(0, bytes_read);
485 // Should work with root directory.
486 // +2 to test a size that is not a multiple of sizeof(dirent).
487 // Expect it to round down.
488 memset(&dirents[0], 0, sizeof(dirents));
490 0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read));
493 size_t num_dirents = bytes_read / sizeof(dirent);
494 EXPECT_EQ(3, num_dirents);
495 EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
497 std::multiset<std::string> dirnames;
498 for (size_t i = 0; i < num_dirents; ++i) {
499 EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
500 EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
501 dirnames.insert(dirents[i].d_name);
504 EXPECT_EQ(1, dirnames.count("file"));
505 EXPECT_EQ(1, dirnames.count("."));
506 EXPECT_EQ(1, dirnames.count(".."));
509 // Add another file...
510 ASSERT_EQ(0, fs->Open(Path("/file2"), O_CREAT, &node));
512 // Read the root directory again.
513 memset(&dirents[0], 0, sizeof(dirents));
514 EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
517 size_t num_dirents = bytes_read / sizeof(dirent);
518 EXPECT_EQ(4, num_dirents);
519 EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
521 std::multiset<std::string> dirnames;
522 for (size_t i = 0; i < num_dirents; ++i) {
523 EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
524 EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
525 dirnames.insert(dirents[i].d_name);
528 EXPECT_EQ(1, dirnames.count("file"));
529 EXPECT_EQ(1, dirnames.count("file2"));
530 EXPECT_EQ(1, dirnames.count("."));
531 EXPECT_EQ(1, dirnames.count(".."));