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
);
52 bool Exists(const char* filename
) {
54 if (Open(Path(filename
), O_RDONLY
, &node
))
58 return node
->GetStat(&buf
) == 0;
62 class Html5FsTest
: public ::testing::Test
{
67 FakePepperInterfaceHtml5Fs ppapi_html5_
;
68 PepperInterfaceMock ppapi_mock_
;
69 PepperInterfaceDelegate ppapi_
;
72 Html5FsTest::Html5FsTest()
73 : ppapi_mock_(ppapi_html5_
.GetInstance()),
74 ppapi_(ppapi_html5_
.GetInstance()) {
75 // Default delegation to the html5 pepper interface.
76 ppapi_
.SetCoreInterfaceDelegate(ppapi_html5_
.GetCoreInterface());
77 ppapi_
.SetFileSystemInterfaceDelegate(ppapi_html5_
.GetFileSystemInterface());
78 ppapi_
.SetFileRefInterfaceDelegate(ppapi_html5_
.GetFileRefInterface());
79 ppapi_
.SetFileIoInterfaceDelegate(ppapi_html5_
.GetFileIoInterface());
80 ppapi_
.SetVarInterfaceDelegate(ppapi_html5_
.GetVarInterface());
85 TEST_F(Html5FsTest
, FilesystemType
) {
86 const char* filesystem_type_strings
[] = {"", "PERSISTENT", "TEMPORARY", NULL
};
87 PP_FileSystemType filesystem_type_values
[] = {
88 PP_FILESYSTEMTYPE_LOCALPERSISTENT
, // Default to persistent.
89 PP_FILESYSTEMTYPE_LOCALPERSISTENT
, PP_FILESYSTEMTYPE_LOCALTEMPORARY
};
91 const char* expected_size_strings
[] = {"100", "12345", NULL
};
92 const int expected_size_values
[] = {100, 12345};
94 FileSystemInterfaceMock
* filesystem_mock
=
95 ppapi_mock_
.GetFileSystemInterface();
97 FakeFileSystemInterface
* filesystem_fake
=
98 static_cast<FakeFileSystemInterface
*>(
99 ppapi_html5_
.GetFileSystemInterface());
101 for (int i
= 0; filesystem_type_strings
[i
] != NULL
; ++i
) {
102 const char* filesystem_type_string
= filesystem_type_strings
[i
];
103 PP_FileSystemType expected_filesystem_type
= filesystem_type_values
[i
];
105 for (int j
= 0; expected_size_strings
[j
] != NULL
; ++j
) {
106 const char* expected_size_string
= expected_size_strings
[j
];
107 int64_t expected_expected_size
= expected_size_values
[j
];
109 ppapi_
.SetFileSystemInterfaceDelegate(filesystem_mock
);
111 ON_CALL(*filesystem_mock
, Create(_
, _
)).WillByDefault(
112 Invoke(filesystem_fake
, &FakeFileSystemInterface::Create
));
114 EXPECT_CALL(*filesystem_mock
,
115 Create(ppapi_
.GetInstance(), expected_filesystem_type
));
117 EXPECT_CALL(*filesystem_mock
, Open(_
, expected_expected_size
, _
))
118 .WillOnce(DoAll(CallCallback
<2>(int32_t(PP_OK
)),
119 Return(int32_t(PP_OK_COMPLETIONPENDING
))));
122 map
["type"] = filesystem_type_string
;
123 map
["expected_size"] = expected_size_string
;
124 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
126 Mock::VerifyAndClearExpectations(&filesystem_mock
);
131 TEST_F(Html5FsTest
, PassFilesystemResource
) {
132 // Fail if given a bad resource.
135 map
["filesystem_resource"] = "0";
136 ScopedRef
<Html5FsForTesting
> fs(
137 new Html5FsForTesting(map
, &ppapi_
, EINVAL
));
141 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile("/foo", NULL
));
142 PP_Resource filesystem
= ppapi_html5_
.GetFileSystemInterface()->Create(
143 ppapi_html5_
.GetInstance(), PP_FILESYSTEMTYPE_LOCALPERSISTENT
);
145 ASSERT_EQ(int32_t(PP_OK
), ppapi_html5_
.GetFileSystemInterface()->Open(
146 filesystem
, 0, PP_BlockUntilComplete()));
150 snprintf(buffer
, 30, "%d", filesystem
);
151 map
["filesystem_resource"] = buffer
;
152 ScopedRef
<Html5FsForTesting
> fs(
153 new Html5FsForTesting(map
, &ppapi_
));
155 ASSERT_TRUE(fs
->Exists("/foo"));
157 ppapi_html5_
.GetCoreInterface()->ReleaseResource(filesystem
);
161 TEST_F(Html5FsTest
, MountSubtree
) {
162 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile("/foo/bar",
165 map
["SOURCE"] = "/foo";
166 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
168 ASSERT_TRUE(fs
->Exists("/bar"));
169 ASSERT_FALSE(fs
->Exists("/foo/bar"));
172 TEST_F(Html5FsTest
, Mkdir
) {
174 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
176 // mkdir at the root should return EEXIST, not EACCES.
177 EXPECT_EQ(EEXIST
, fs
->Mkdir(Path("/"), 0644));
180 ASSERT_FALSE(fs
->Exists("/foo"));
181 ASSERT_EQ(0, fs
->Mkdir(path
, 0644));
185 ASSERT_EQ(0, fs
->Open(path
, O_RDONLY
, &node
));
186 EXPECT_EQ(0, node
->GetStat(&stat
));
187 EXPECT_TRUE(S_ISDIR(stat
.st_mode
));
190 TEST_F(Html5FsTest
, Remove
) {
191 const char* kPath
= "/foo";
192 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile(kPath
, NULL
));
195 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
198 ASSERT_TRUE(fs
->Exists(kPath
));
199 ASSERT_EQ(0, fs
->Remove(path
));
200 EXPECT_FALSE(fs
->Exists(kPath
));
201 ASSERT_EQ(ENOENT
, fs
->Remove(path
));
204 TEST_F(Html5FsTest
, Unlink
) {
205 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile("/file", NULL
));
206 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddDirectory("/dir", NULL
));
209 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
211 ASSERT_TRUE(fs
->Exists("/dir"));
212 ASSERT_TRUE(fs
->Exists("/file"));
213 ASSERT_EQ(0, fs
->Unlink(Path("/file")));
214 ASSERT_EQ(EISDIR
, fs
->Unlink(Path("/dir")));
215 EXPECT_FALSE(fs
->Exists("/file"));
216 EXPECT_TRUE(fs
->Exists("/dir"));
217 ASSERT_EQ(ENOENT
, fs
->Unlink(Path("/file")));
220 TEST_F(Html5FsTest
, Rmdir
) {
221 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile("/file", NULL
));
222 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddDirectory("/dir", NULL
));
225 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
227 ASSERT_EQ(ENOTDIR
, fs
->Rmdir(Path("/file")));
228 EXPECT_EQ(0, fs
->Rmdir(Path("/dir")));
229 EXPECT_FALSE(fs
->Exists("/dir"));
230 EXPECT_TRUE(fs
->Exists("/file"));
231 EXPECT_EQ(ENOENT
, fs
->Rmdir(Path("/dir")));
234 TEST_F(Html5FsTest
, Rename
) {
235 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddEmptyFile("/foo", NULL
));
238 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
240 ASSERT_TRUE(fs
->Exists("/foo"));
241 ASSERT_EQ(0, fs
->Rename(Path("/foo"), Path("/bar")));
242 EXPECT_FALSE(fs
->Exists("/foo"));
243 EXPECT_TRUE(fs
->Exists("/bar"));
246 TEST_F(Html5FsTest
, OpenForCreate
) {
248 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
250 EXPECT_FALSE(fs
->Exists("/foo"));
254 ASSERT_EQ(0, fs
->Open(path
, O_CREAT
| O_RDWR
, &node
));
257 const char* contents
= "contents";
258 int bytes_written
= 0;
259 EXPECT_EQ(0, node
->Write(HandleAttr(), contents
, strlen(contents
),
261 EXPECT_EQ(strlen(contents
), bytes_written
);
264 ASSERT_EQ(0, fs
->Open(path
, O_CREAT
, &node
));
266 // Check that the file still has data.
268 EXPECT_EQ(0, node
->GetSize(&size
));
269 EXPECT_EQ(strlen(contents
), size
);
272 EXPECT_EQ(EEXIST
, fs
->Open(path
, O_CREAT
| O_EXCL
, &node
));
274 // Try to truncate without write access.
275 EXPECT_EQ(EINVAL
, fs
->Open(path
, O_CREAT
| O_TRUNC
, &node
));
277 // Open and truncate.
278 ASSERT_EQ(0, fs
->Open(path
, O_CREAT
| O_TRUNC
| O_WRONLY
, &node
));
280 // File should be empty.
281 EXPECT_EQ(0, node
->GetSize(&size
));
285 TEST_F(Html5FsTest
, Read
) {
286 const char* contents
= "contents";
288 ppapi_html5_
.filesystem_template()->AddFile("/file", contents
, NULL
));
289 ASSERT_TRUE(ppapi_html5_
.filesystem_template()->AddDirectory("/dir", NULL
));
291 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
294 ASSERT_EQ(0, fs
->Open(Path("/file"), O_RDONLY
, &node
));
296 char buffer
[10] = {0};
299 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
300 ASSERT_EQ(strlen(contents
), bytes_read
);
301 ASSERT_STREQ(contents
, buffer
);
303 // Read nothing past the end of the file.
305 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
306 ASSERT_EQ(0, bytes_read
);
308 // Read part of the data.
310 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
311 ASSERT_EQ(strlen(contents
) - 4, bytes_read
);
312 buffer
[bytes_read
] = 0;
313 ASSERT_STREQ("ents", buffer
);
315 // Writing should fail.
316 int bytes_written
= 1; // Set to a non-zero value.
319 node
->Write(attr
, &buffer
[0], sizeof(buffer
), &bytes_written
));
320 ASSERT_EQ(0, bytes_written
);
322 // Reading from a directory should fail.
323 ASSERT_EQ(0, fs
->Open(Path("/dir"), O_RDONLY
, &node
));
324 ASSERT_EQ(EISDIR
, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
327 TEST_F(Html5FsTest
, Write
) {
328 const char* contents
= "contents";
330 ppapi_html5_
.filesystem_template()->AddFile("/file", contents
, NULL
));
331 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddDirectory("/dir", NULL
));
334 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
337 ASSERT_EQ(0, fs
->Open(Path("/file"), O_WRONLY
, &node
));
339 // Reading should fail.
341 int bytes_read
= 1; // Set to a non-zero value.
343 EXPECT_EQ(EACCES
, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
344 EXPECT_EQ(0, bytes_read
);
346 // Reopen as read-write.
347 ASSERT_EQ(0, fs
->Open(Path("/file"), O_RDWR
, &node
));
349 int bytes_written
= 1; // Set to a non-zero value.
351 EXPECT_EQ(0, node
->Write(attr
, "struct", 6, &bytes_written
));
352 EXPECT_EQ(6, bytes_written
);
355 EXPECT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
356 EXPECT_EQ(9, bytes_read
);
357 buffer
[bytes_read
] = 0;
358 EXPECT_STREQ("construct", buffer
);
360 // Writing to a directory should fail.
361 EXPECT_EQ(0, fs
->Open(Path("/dir"), O_RDWR
, &node
));
362 EXPECT_EQ(EISDIR
, node
->Write(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
365 TEST_F(Html5FsTest
, GetStat
) {
366 const int creation_time
= 1000;
367 const int access_time
= 2000;
368 const int modified_time
= 3000;
369 const char* contents
= "contents";
372 FakeHtml5FsNode
* fake_node
;
373 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddFile(
374 "/file", contents
, &fake_node
));
375 fake_node
->set_creation_time(creation_time
);
376 fake_node
->set_last_access_time(access_time
);
377 fake_node
->set_last_modified_time(modified_time
);
379 // Create fake directory.
381 ppapi_html5_
.filesystem_template()->AddDirectory("/dir", &fake_node
));
382 fake_node
->set_creation_time(creation_time
);
383 fake_node
->set_last_access_time(access_time
);
384 fake_node
->set_last_modified_time(modified_time
);
387 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
390 ASSERT_EQ(0, fs
->Open(Path("/file"), O_RDONLY
, &node
));
393 EXPECT_EQ(0, node
->GetStat(&statbuf
));
394 EXPECT_TRUE(S_ISREG(statbuf
.st_mode
));
395 EXPECT_EQ(S_IRALL
| S_IWALL
| S_IXALL
, statbuf
.st_mode
& S_MODEBITS
);
396 EXPECT_EQ(strlen(contents
), statbuf
.st_size
);
397 EXPECT_EQ(access_time
, statbuf
.st_atime
);
398 EXPECT_EQ(creation_time
, statbuf
.st_ctime
);
399 EXPECT_EQ(modified_time
, statbuf
.st_mtime
);
401 // Test Get* and Isa* methods.
403 EXPECT_EQ(0, node
->GetSize(&size
));
404 EXPECT_EQ(strlen(contents
), size
);
405 EXPECT_FALSE(node
->IsaDir());
406 EXPECT_TRUE(node
->IsaFile());
407 EXPECT_EQ(ENOTTY
, node
->Isatty());
409 // GetStat on a directory...
410 EXPECT_EQ(0, fs
->Open(Path("/dir"), O_RDONLY
, &node
));
411 EXPECT_EQ(0, node
->GetStat(&statbuf
));
412 EXPECT_TRUE(S_ISDIR(statbuf
.st_mode
));
413 EXPECT_EQ(S_IRALL
| S_IWALL
| S_IXALL
, statbuf
.st_mode
& S_MODEBITS
);
414 EXPECT_EQ(0, statbuf
.st_size
);
415 EXPECT_EQ(access_time
, statbuf
.st_atime
);
416 EXPECT_EQ(creation_time
, statbuf
.st_ctime
);
417 EXPECT_EQ(modified_time
, statbuf
.st_mtime
);
419 // Test Get* and Isa* methods.
420 EXPECT_EQ(0, node
->GetSize(&size
));
422 EXPECT_TRUE(node
->IsaDir());
423 EXPECT_FALSE(node
->IsaFile());
424 EXPECT_EQ(ENOTTY
, node
->Isatty());
427 TEST_F(Html5FsTest
, FTruncate
) {
428 const char* contents
= "contents";
430 ppapi_html5_
.filesystem_template()->AddFile("/file", contents
, NULL
));
431 EXPECT_TRUE(ppapi_html5_
.filesystem_template()->AddDirectory("/dir", NULL
));
434 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
437 ASSERT_EQ(0, fs
->Open(Path("/file"), O_RDWR
, &node
));
440 char buffer
[10] = {0};
443 // First make the file shorter...
444 EXPECT_EQ(0, node
->FTruncate(4));
445 EXPECT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
446 EXPECT_EQ(4, bytes_read
);
447 buffer
[bytes_read
] = 0;
448 EXPECT_STREQ("cont", buffer
);
450 // Now make the file longer...
451 EXPECT_EQ(0, node
->FTruncate(8));
452 EXPECT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
453 EXPECT_EQ(8, bytes_read
);
454 buffer
[bytes_read
] = 0;
455 EXPECT_STREQ("cont\0\0\0\0", buffer
);
457 // Ftruncate should fail for a directory.
458 EXPECT_EQ(0, fs
->Open(Path("/dir"), O_RDONLY
, &node
));
459 EXPECT_EQ(EISDIR
, node
->FTruncate(4));
462 TEST_F(Html5FsTest
, Chmod
) {
464 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
466 ASSERT_EQ(0, fs
->Open(Path("/"), O_RDONLY
, &node
));
467 ASSERT_EQ(0, node
->Fchmod(0777));
470 TEST_F(Html5FsTest
, GetDents
) {
471 const char* contents
= "contents";
473 ppapi_html5_
.filesystem_template()->AddFile("/file", contents
, NULL
));
476 ScopedRef
<Html5FsForTesting
> fs(new Html5FsForTesting(map
, &ppapi_
));
479 ASSERT_EQ(0, fs
->Open(Path("/"), O_RDONLY
, &root
));
482 ASSERT_EQ(0, fs
->Open(Path("/file"), O_RDWR
, &node
));
485 ASSERT_EQ(0, node
->GetStat(&stat
));
486 ino_t file1_ino
= stat
.st_ino
;
488 // Should fail for regular files.
489 const size_t kMaxDirents
= 5;
490 dirent dirents
[kMaxDirents
];
491 int bytes_read
= 1; // Set to a non-zero value.
493 memset(&dirents
[0], 0, sizeof(dirents
));
495 node
->GetDents(0, &dirents
[0], sizeof(dirents
), &bytes_read
));
496 EXPECT_EQ(0, bytes_read
);
498 // Should work with root directory.
499 // +2 to test a size that is not a multiple of sizeof(dirent).
500 // Expect it to round down.
501 memset(&dirents
[0], 0, sizeof(dirents
));
503 0, root
->GetDents(0, &dirents
[0], sizeof(dirent
) * 3 + 2, &bytes_read
));
506 size_t num_dirents
= bytes_read
/ sizeof(dirent
);
507 EXPECT_EQ(3, num_dirents
);
508 EXPECT_EQ(sizeof(dirent
) * num_dirents
, bytes_read
);
510 std::multiset
<std::string
> dirnames
;
511 for (size_t i
= 0; i
< num_dirents
; ++i
) {
512 EXPECT_EQ(sizeof(dirent
), dirents
[i
].d_reclen
);
513 dirnames
.insert(dirents
[i
].d_name
);
516 EXPECT_EQ(1, dirnames
.count("file"));
517 EXPECT_EQ(1, dirnames
.count("."));
518 EXPECT_EQ(1, dirnames
.count(".."));
521 // Add another file...
522 ASSERT_EQ(0, fs
->Open(Path("/file2"), O_CREAT
, &node
));
523 ASSERT_EQ(0, node
->GetStat(&stat
));
524 ino_t file2_ino
= stat
.st_ino
;
526 // These files SHOULD not hash to the same value but COULD.
527 EXPECT_NE(file1_ino
, file2_ino
);
529 // Read the root directory again.
530 memset(&dirents
[0], 0, sizeof(dirents
));
531 EXPECT_EQ(0, root
->GetDents(0, &dirents
[0], sizeof(dirents
), &bytes_read
));
534 size_t num_dirents
= bytes_read
/ sizeof(dirent
);
535 EXPECT_EQ(4, num_dirents
);
536 EXPECT_EQ(sizeof(dirent
) * num_dirents
, bytes_read
);
538 std::multiset
<std::string
> dirnames
;
539 for (size_t i
= 0; i
< num_dirents
; ++i
) {
540 EXPECT_EQ(sizeof(dirent
), dirents
[i
].d_reclen
);
541 dirnames
.insert(dirents
[i
].d_name
);
543 if (!strcmp(dirents
[i
].d_name
, "file")) {
544 EXPECT_EQ(dirents
[i
].d_ino
, file1_ino
);
546 if (!strcmp(dirents
[i
].d_name
, "file2")) {
547 EXPECT_EQ(dirents
[i
].d_ino
, file2_ino
);
551 EXPECT_EQ(1, dirnames
.count("file"));
552 EXPECT_EQ(1, dirnames
.count("file2"));
553 EXPECT_EQ(1, dirnames
.count("."));
554 EXPECT_EQ(1, dirnames
.count(".."));