blob: 906e5ca589c08e36d3ec39ec3545f38d394873da [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <set>
#include <string>
#include <gmock/gmock.h>
#include <ppapi/c/ppb_file_io.h>
#include <ppapi/c/pp_directory_entry.h>
#include <ppapi/c/pp_errors.h>
#include <ppapi/c/pp_instance.h>
#if defined(WIN32)
#include <windows.h> // For Sleep()
#endif
#include "nacl_io/mount_html5fs.h"
#include "nacl_io/osdirent.h"
#include "nacl_io/osunistd.h"
#include "nacl_io/pepper_interface_delegate.h"
#include "sdk_util/scoped_ref.h"
#include "fake_pepper_interface_html5fs.h"
#include "mock_util.h"
#include "pepper_interface_mock.h"
using namespace nacl_io;
using namespace sdk_util;
using ::testing::_;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
namespace {
class MountHtml5FsForTesting : public MountHtml5Fs {
public:
MountHtml5FsForTesting(StringMap_t& args, PepperInterface* ppapi) {
Error error = Init(1, args, ppapi);
EXPECT_EQ(0, error);
}
};
class MountHtml5FsTest : public ::testing::Test {
public:
MountHtml5FsTest();
protected:
FakePepperInterfaceHtml5Fs ppapi_html5_;
PepperInterfaceMock ppapi_mock_;
PepperInterfaceDelegate ppapi_;
};
MountHtml5FsTest::MountHtml5FsTest()
: ppapi_mock_(ppapi_html5_.GetInstance()),
ppapi_(ppapi_html5_.GetInstance()) {
// Default delegation to the html5 pepper interface.
ppapi_.SetCoreInterfaceDelegate(ppapi_html5_.GetCoreInterface());
ppapi_.SetFileSystemInterfaceDelegate(ppapi_html5_.GetFileSystemInterface());
ppapi_.SetFileRefInterfaceDelegate(ppapi_html5_.GetFileRefInterface());
ppapi_.SetFileIoInterfaceDelegate(ppapi_html5_.GetFileIoInterface());
ppapi_.SetVarInterfaceDelegate(ppapi_html5_.GetVarInterface());
}
} // namespace
TEST_F(MountHtml5FsTest, FilesystemType) {
const char* filesystem_type_strings[] = {"", "PERSISTENT", "TEMPORARY", NULL};
PP_FileSystemType filesystem_type_values[] = {
PP_FILESYSTEMTYPE_LOCALPERSISTENT, // Default to persistent.
PP_FILESYSTEMTYPE_LOCALPERSISTENT, PP_FILESYSTEMTYPE_LOCALTEMPORARY};
const char* expected_size_strings[] = {"100", "12345", NULL};
const int expected_size_values[] = {100, 12345};
FileSystemInterfaceMock* filesystem_mock =
ppapi_mock_.GetFileSystemInterface();
FakeFileSystemInterface* filesystem_fake =
static_cast<FakeFileSystemInterface*>(
ppapi_html5_.GetFileSystemInterface());
for (int i = 0; filesystem_type_strings[i] != NULL; ++i) {
const char* filesystem_type_string = filesystem_type_strings[i];
PP_FileSystemType expected_filesystem_type = filesystem_type_values[i];
for (int j = 0; expected_size_strings[j] != NULL; ++j) {
const char* expected_size_string = expected_size_strings[j];
int64_t expected_expected_size = expected_size_values[j];
ppapi_.SetFileSystemInterfaceDelegate(filesystem_mock);
ON_CALL(*filesystem_mock, Create(_, _)).WillByDefault(
Invoke(filesystem_fake, &FakeFileSystemInterface::Create));
EXPECT_CALL(*filesystem_mock,
Create(ppapi_.GetInstance(), expected_filesystem_type));
EXPECT_CALL(*filesystem_mock, Open(_, expected_expected_size, _))
.WillOnce(DoAll(CallCallback<2>(int32_t(PP_OK)),
Return(int32_t(PP_OK_COMPLETIONPENDING))));
StringMap_t map;
map["type"] = filesystem_type_string;
map["expected_size"] = expected_size_string;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
Mock::VerifyAndClearExpectations(&filesystem_mock);
}
}
}
TEST_F(MountHtml5FsTest, Access) {
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ASSERT_EQ(0, mnt->Access(Path("/foo"), R_OK | W_OK | X_OK));
ASSERT_EQ(ENOENT, mnt->Access(Path("/bar"), F_OK));
}
TEST_F(MountHtml5FsTest, Mkdir) {
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
// mkdir at the root should return EEXIST, not EACCES.
EXPECT_EQ(EEXIST, mnt->Mkdir(Path("/"), 0644));
Path path("/foo");
ASSERT_EQ(ENOENT, mnt->Access(path, F_OK));
ASSERT_EQ(0, mnt->Mkdir(path, 0644));
struct stat stat;
ScopedMountNode node;
ASSERT_EQ(0, mnt->Open(path, O_RDONLY, &node));
EXPECT_EQ(0, node->GetStat(&stat));
EXPECT_EQ(S_IFDIR, stat.st_mode & S_IFDIR);
}
TEST_F(MountHtml5FsTest, Remove) {
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
Path path("/foo");
ASSERT_EQ(0, mnt->Access(path, F_OK));
ASSERT_EQ(0, mnt->Remove(path));
EXPECT_EQ(ENOENT, mnt->Access(path, F_OK));
}
// Unlink + Rmdir forward to Remove unconditionally, which will not fail if the
// file type is wrong.
TEST_F(MountHtml5FsTest, DISABLED_Unlink) {
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ASSERT_EQ(EISDIR, mnt->Unlink(Path("/dir")));
EXPECT_EQ(0, mnt->Unlink(Path("/file")));
EXPECT_EQ(ENOENT, mnt->Access(Path("/file"), F_OK));
EXPECT_EQ(0, mnt->Access(Path("/dir"), F_OK));
}
// Unlink + Rmdir forward to Remove unconditionally, which will not fail if the
// file type is wrong.
TEST_F(MountHtml5FsTest, DISABLED_Rmdir) {
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ASSERT_EQ(ENOTDIR, mnt->Rmdir(Path("/file")));
EXPECT_EQ(0, mnt->Rmdir(Path("/dir")));
EXPECT_EQ(ENOENT, mnt->Access(Path("/dir"), F_OK));
EXPECT_EQ(0, mnt->Access(Path("/file"), F_OK));
}
TEST_F(MountHtml5FsTest, OpenForCreate) {
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
Path path("/foo");
EXPECT_EQ(ENOENT, mnt->Access(path, F_OK));
ScopedMountNode node;
EXPECT_EQ(0, mnt->Open(path, O_CREAT | O_RDWR, &node));
// Write some data.
char contents[] = "contents";
int bytes_written = 0;
EXPECT_EQ(0, node->Write(0, &contents[0], strlen(contents), &bytes_written));
EXPECT_EQ(strlen(contents), bytes_written);
// Create again.
ASSERT_EQ(0, mnt->Open(path, O_CREAT, &node));
// Check that the file still has data.
size_t size;
EXPECT_EQ(0, node->GetSize(&size));
EXPECT_EQ(strlen(contents), size);
// Open exclusively.
EXPECT_EQ(EEXIST, mnt->Open(path, O_CREAT | O_EXCL, &node));
// Try to truncate without write access.
EXPECT_EQ(EINVAL, mnt->Open(path, O_CREAT | O_TRUNC, &node));
// Open and truncate.
ASSERT_EQ(0, mnt->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node));
// File should be empty.
EXPECT_EQ(0, node->GetSize(&size));
EXPECT_EQ(0, size);
}
TEST_F(MountHtml5FsTest, Read) {
const char contents[] = "contents";
EXPECT_TRUE(
ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ScopedMountNode node;
EXPECT_EQ(0, mnt->Open(Path("/file"), O_RDONLY, &node));
char buffer[10] = {0};
int bytes_read = 0;
EXPECT_EQ(0, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(strlen(contents), bytes_read);
EXPECT_STREQ(contents, buffer);
// Read nothing past the end of the file.
EXPECT_EQ(0, node->Read(100, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(0, bytes_read);
// Read part of the data.
EXPECT_EQ(0, node->Read(4, &buffer[0], sizeof(buffer), &bytes_read));
ASSERT_EQ(strlen(contents) - 4, bytes_read);
buffer[bytes_read] = 0;
EXPECT_STREQ("ents", buffer);
// Writing should fail.
int bytes_written = 1; // Set to a non-zero value.
EXPECT_EQ(EACCES, node->Write(0, &buffer[0], sizeof(buffer), &bytes_written));
EXPECT_EQ(0, bytes_written);
// Reading from a directory should fail.
EXPECT_EQ(0, mnt->Open(Path("/dir"), O_RDONLY, &node));
EXPECT_EQ(EISDIR, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
}
TEST_F(MountHtml5FsTest, Write) {
const char contents[] = "contents";
EXPECT_TRUE(
ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ScopedMountNode node;
ASSERT_EQ(0, mnt->Open(Path("/file"), O_WRONLY, &node));
// Reading should fail.
char buffer[10];
int bytes_read = 1; // Set to a non-zero value.
EXPECT_EQ(EACCES, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(0, bytes_read);
// Reopen as read-write.
ASSERT_EQ(0, mnt->Open(Path("/file"), O_RDWR, &node));
int bytes_written = 1; // Set to a non-zero value.
EXPECT_EQ(0, node->Write(3, "struct", 6, &bytes_written));
EXPECT_EQ(6, bytes_written);
EXPECT_EQ(0, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(9, bytes_read);
buffer[bytes_read] = 0;
EXPECT_STREQ("construct", buffer);
// Writing to a directory should fail.
EXPECT_EQ(0, mnt->Open(Path("/dir"), O_RDWR, &node));
EXPECT_EQ(EISDIR, node->Write(0, &buffer[0], sizeof(buffer), &bytes_read));
}
TEST_F(MountHtml5FsTest, GetStat) {
const int creation_time = 1000;
const int access_time = 2000;
const int modified_time = 3000;
const char contents[] = "contents";
// Create fake file.
FakeHtml5FsNode* fake_node;
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile(
"/file", contents, &fake_node));
fake_node->set_creation_time(creation_time);
fake_node->set_last_access_time(access_time);
fake_node->set_last_modified_time(modified_time);
// Create fake directory.
EXPECT_TRUE(
ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node));
fake_node->set_creation_time(creation_time);
fake_node->set_last_access_time(access_time);
fake_node->set_last_modified_time(modified_time);
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ScopedMountNode node;
ASSERT_EQ(0, mnt->Open(Path("/file"), O_RDONLY, &node));
struct stat statbuf;
EXPECT_EQ(0, node->GetStat(&statbuf));
EXPECT_EQ(S_IFREG | S_IWRITE | S_IREAD, statbuf.st_mode);
EXPECT_EQ(strlen(contents), statbuf.st_size);
EXPECT_EQ(access_time, statbuf.st_atime);
EXPECT_EQ(creation_time, statbuf.st_ctime);
EXPECT_EQ(modified_time, statbuf.st_mtime);
// Test Get* and Isa* methods.
size_t size;
EXPECT_EQ(0, node->GetSize(&size));
EXPECT_EQ(strlen(contents), size);
EXPECT_FALSE(node->IsaDir());
EXPECT_TRUE(node->IsaFile());
EXPECT_FALSE(node->IsaTTY());
// GetStat on a directory...
EXPECT_EQ(0, mnt->Open(Path("/dir"), O_RDONLY, &node));
EXPECT_EQ(0, node->GetStat(&statbuf));
EXPECT_EQ(S_IFDIR | S_IWRITE | S_IREAD, statbuf.st_mode);
EXPECT_EQ(0, statbuf.st_size);
EXPECT_EQ(access_time, statbuf.st_atime);
EXPECT_EQ(creation_time, statbuf.st_ctime);
EXPECT_EQ(modified_time, statbuf.st_mtime);
// Test Get* and Isa* methods.
EXPECT_EQ(0, node->GetSize(&size));
EXPECT_EQ(0, size);
EXPECT_TRUE(node->IsaDir());
EXPECT_FALSE(node->IsaFile());
EXPECT_FALSE(node->IsaTTY());
}
TEST_F(MountHtml5FsTest, FTruncate) {
const char contents[] = "contents";
EXPECT_TRUE(
ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ScopedMountNode node;
ASSERT_EQ(0, mnt->Open(Path("/file"), O_RDWR, &node));
char buffer[10] = {0};
int bytes_read = 0;
// First make the file shorter...
EXPECT_EQ(0, node->FTruncate(4));
EXPECT_EQ(0, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(4, bytes_read);
buffer[bytes_read] = 0;
EXPECT_STREQ("cont", buffer);
// Now make the file longer...
EXPECT_EQ(0, node->FTruncate(8));
EXPECT_EQ(0, node->Read(0, &buffer[0], sizeof(buffer), &bytes_read));
EXPECT_EQ(8, bytes_read);
buffer[bytes_read] = 0;
EXPECT_STREQ("cont\0\0\0\0", buffer);
// Ftruncate should fail for a directory.
EXPECT_EQ(0, mnt->Open(Path("/dir"), O_RDONLY, &node));
EXPECT_EQ(EISDIR, node->FTruncate(4));
}
TEST_F(MountHtml5FsTest, GetDents) {
const char contents[] = "contents";
EXPECT_TRUE(
ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
StringMap_t map;
ScopedRef<MountHtml5FsForTesting> mnt(
new MountHtml5FsForTesting(map, &ppapi_));
ScopedMountNode root;
ASSERT_EQ(0, mnt->Open(Path("/"), O_RDONLY, &root));
ScopedMountNode node;
ASSERT_EQ(0, mnt->Open(Path("/file"), O_RDWR, &node));
// Should fail for regular files.
const size_t kMaxDirents = 5;
dirent dirents[kMaxDirents];
int bytes_read = 1; // Set to a non-zero value.
memset(&dirents[0], 0, sizeof(dirents));
EXPECT_EQ(ENOTDIR,
node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
EXPECT_EQ(0, bytes_read);
// Should work with root directory.
// +2 to test a size that is not a multiple of sizeof(dirent).
// Expect it to round down.
memset(&dirents[0], 0, sizeof(dirents));
EXPECT_EQ(
0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read));
{
size_t num_dirents = bytes_read / sizeof(dirent);
EXPECT_EQ(3, num_dirents);
EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
std::multiset<std::string> dirnames;
for (int i = 0; i < num_dirents; ++i) {
EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
dirnames.insert(dirents[i].d_name);
}
EXPECT_EQ(1, dirnames.count("file"));
EXPECT_EQ(1, dirnames.count("."));
EXPECT_EQ(1, dirnames.count(".."));
}
// Add another file...
ASSERT_EQ(0, mnt->Open(Path("/file2"), O_CREAT, &node));
// Read the root directory again.
memset(&dirents[0], 0, sizeof(dirents));
EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
{
size_t num_dirents = bytes_read / sizeof(dirent);
EXPECT_EQ(4, num_dirents);
EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
std::multiset<std::string> dirnames;
for (int i = 0; i < num_dirents; ++i) {
EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
dirnames.insert(dirents[i].d_name);
}
EXPECT_EQ(1, dirnames.count("file"));
EXPECT_EQ(1, dirnames.count("file2"));
EXPECT_EQ(1, dirnames.count("."));
EXPECT_EQ(1, dirnames.count(".."));
}
}