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