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