c91eeaf5a9efd731eb6320511b778daba001a222
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / tests / nacl_io_test / html5_fs_test.cc
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
30 using namespace nacl_io;
31 using namespace sdk_util;
32
33 using ::testing::_;
34 using ::testing::DoAll;
35 using ::testing::Invoke;
36 using ::testing::Mock;
37 using ::testing::Return;
38
39 namespace {
40
41 class 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
52 class Html5FsTest : public ::testing::Test {
53  public:
54   Html5FsTest();
55
56  protected:
57   FakePepperInterfaceHtml5Fs ppapi_html5_;
58   PepperInterfaceMock ppapi_mock_;
59   PepperInterfaceDelegate ppapi_;
60 };
61
62 Html5FsTest::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
75 TEST_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
121 TEST_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
131 TEST_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
149 TEST_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.
163 TEST_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.
178 TEST_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
191 TEST_F(Html5FsTest, OpenForCreate) {
192   StringMap_t map;
193   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
194
195   Path path("/foo");
196   EXPECT_EQ(ENOENT, fs->Access(path, F_OK));
197
198   ScopedNode node;
199   ASSERT_EQ(0, fs->Open(path, O_CREAT | O_RDWR, &node));
200
201   // Write some data.
202   char contents[] = "contents";
203   int bytes_written = 0;
204   EXPECT_EQ(0, node->Write(HandleAttr(), &contents[0], strlen(contents),
205                            &bytes_written));
206   EXPECT_EQ(strlen(contents), bytes_written);
207
208   // Create again.
209   ASSERT_EQ(0, fs->Open(path, O_CREAT, &node));
210
211   // Check that the file still has data.
212   size_t size;
213   EXPECT_EQ(0, node->GetSize(&size));
214   EXPECT_EQ(strlen(contents), size);
215
216   // Open exclusively.
217   EXPECT_EQ(EEXIST, fs->Open(path, O_CREAT | O_EXCL, &node));
218
219   // Try to truncate without write access.
220   EXPECT_EQ(EINVAL, fs->Open(path, O_CREAT | O_TRUNC, &node));
221
222   // Open and truncate.
223   ASSERT_EQ(0, fs->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node));
224
225   // File should be empty.
226   EXPECT_EQ(0, node->GetSize(&size));
227   EXPECT_EQ(0, size);
228 }
229
230 TEST_F(Html5FsTest, Read) {
231   const char contents[] = "contents";
232   ASSERT_TRUE(
233       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
234   ASSERT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
235   StringMap_t map;
236   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
237
238   ScopedNode node;
239   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
240
241   char buffer[10] = {0};
242   int bytes_read = 0;
243   HandleAttr attr;
244   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
245   ASSERT_EQ(strlen(contents), bytes_read);
246   ASSERT_STREQ(contents, buffer);
247
248   // Read nothing past the end of the file.
249   attr.offs = 100;
250   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
251   ASSERT_EQ(0, bytes_read);
252
253   // Read part of the data.
254   attr.offs = 4;
255   ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
256   ASSERT_EQ(strlen(contents) - 4, bytes_read);
257   buffer[bytes_read] = 0;
258   ASSERT_STREQ("ents", buffer);
259
260   // Writing should fail.
261   int bytes_written = 1;  // Set to a non-zero value.
262   attr.offs = 0;
263   ASSERT_EQ(EACCES,
264             node->Write(attr, &buffer[0], sizeof(buffer), &bytes_written));
265   ASSERT_EQ(0, bytes_written);
266
267   // Reading from a directory should fail.
268   ASSERT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
269   ASSERT_EQ(EISDIR, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
270 }
271
272 TEST_F(Html5FsTest, Write) {
273   const char contents[] = "contents";
274   EXPECT_TRUE(
275       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
276   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
277
278   StringMap_t map;
279   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
280
281   ScopedNode node;
282   ASSERT_EQ(0, fs->Open(Path("/file"), O_WRONLY, &node));
283
284   // Reading should fail.
285   char buffer[10];
286   int bytes_read = 1;  // Set to a non-zero value.
287   HandleAttr attr;
288   EXPECT_EQ(EACCES, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
289   EXPECT_EQ(0, bytes_read);
290
291   // Reopen as read-write.
292   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
293
294   int bytes_written = 1;  // Set to a non-zero value.
295   attr.offs = 3;
296   EXPECT_EQ(0, node->Write(attr, "struct", 6, &bytes_written));
297   EXPECT_EQ(6, bytes_written);
298
299   attr.offs = 0;
300   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
301   EXPECT_EQ(9, bytes_read);
302   buffer[bytes_read] = 0;
303   EXPECT_STREQ("construct", buffer);
304
305   // Writing to a directory should fail.
306   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDWR, &node));
307   EXPECT_EQ(EISDIR, node->Write(attr, &buffer[0], sizeof(buffer), &bytes_read));
308 }
309
310 TEST_F(Html5FsTest, GetStat) {
311   const int creation_time = 1000;
312   const int access_time = 2000;
313   const int modified_time = 3000;
314   const char contents[] = "contents";
315
316   // Create fake file.
317   FakeHtml5FsNode* fake_node;
318   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile(
319       "/file", contents, &fake_node));
320   fake_node->set_creation_time(creation_time);
321   fake_node->set_last_access_time(access_time);
322   fake_node->set_last_modified_time(modified_time);
323
324   // Create fake directory.
325   EXPECT_TRUE(
326       ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node));
327   fake_node->set_creation_time(creation_time);
328   fake_node->set_last_access_time(access_time);
329   fake_node->set_last_modified_time(modified_time);
330
331   StringMap_t map;
332   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
333
334   ScopedNode node;
335   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
336
337   struct stat statbuf;
338   EXPECT_EQ(0, node->GetStat(&statbuf));
339   EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
340   EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
341             S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
342   EXPECT_EQ(strlen(contents), statbuf.st_size);
343   EXPECT_EQ(access_time, statbuf.st_atime);
344   EXPECT_EQ(creation_time, statbuf.st_ctime);
345   EXPECT_EQ(modified_time, statbuf.st_mtime);
346
347   // Test Get* and Isa* methods.
348   size_t size;
349   EXPECT_EQ(0, node->GetSize(&size));
350   EXPECT_EQ(strlen(contents), size);
351   EXPECT_FALSE(node->IsaDir());
352   EXPECT_TRUE(node->IsaFile());
353   EXPECT_FALSE(node->IsaTTY());
354
355   // GetStat on a directory...
356   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
357   EXPECT_EQ(0, node->GetStat(&statbuf));
358   EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT);
359   EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH |
360             S_IWUSR | S_IWGRP | S_IWOTH, statbuf.st_mode & ~S_IFMT);
361   EXPECT_EQ(0, statbuf.st_size);
362   EXPECT_EQ(access_time, statbuf.st_atime);
363   EXPECT_EQ(creation_time, statbuf.st_ctime);
364   EXPECT_EQ(modified_time, statbuf.st_mtime);
365
366   // Test Get* and Isa* methods.
367   EXPECT_EQ(0, node->GetSize(&size));
368   EXPECT_EQ(0, size);
369   EXPECT_TRUE(node->IsaDir());
370   EXPECT_FALSE(node->IsaFile());
371   EXPECT_FALSE(node->IsaTTY());
372 }
373
374 TEST_F(Html5FsTest, FTruncate) {
375   const char contents[] = "contents";
376   EXPECT_TRUE(
377       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
378   EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
379
380   StringMap_t map;
381   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
382
383   ScopedNode node;
384   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
385
386   HandleAttr attr;
387   char buffer[10] = {0};
388   int bytes_read = 0;
389
390   // First make the file shorter...
391   EXPECT_EQ(0, node->FTruncate(4));
392   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
393   EXPECT_EQ(4, bytes_read);
394   buffer[bytes_read] = 0;
395   EXPECT_STREQ("cont", buffer);
396
397   // Now make the file longer...
398   EXPECT_EQ(0, node->FTruncate(8));
399   EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
400   EXPECT_EQ(8, bytes_read);
401   buffer[bytes_read] = 0;
402   EXPECT_STREQ("cont\0\0\0\0", buffer);
403
404   // Ftruncate should fail for a directory.
405   EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
406   EXPECT_EQ(EISDIR, node->FTruncate(4));
407 }
408
409 TEST_F(Html5FsTest, GetDents) {
410   const char contents[] = "contents";
411   EXPECT_TRUE(
412       ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
413
414   StringMap_t map;
415   ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
416
417   ScopedNode root;
418   ASSERT_EQ(0, fs->Open(Path("/"), O_RDONLY, &root));
419
420   ScopedNode node;
421   ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
422
423   // Should fail for regular files.
424   const size_t kMaxDirents = 5;
425   dirent dirents[kMaxDirents];
426   int bytes_read = 1;  // Set to a non-zero value.
427
428   memset(&dirents[0], 0, sizeof(dirents));
429   EXPECT_EQ(ENOTDIR,
430             node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
431   EXPECT_EQ(0, bytes_read);
432
433   // Should work with root directory.
434   // +2 to test a size that is not a multiple of sizeof(dirent).
435   // Expect it to round down.
436   memset(&dirents[0], 0, sizeof(dirents));
437   EXPECT_EQ(
438       0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read));
439
440   {
441     size_t num_dirents = bytes_read / sizeof(dirent);
442     EXPECT_EQ(3, num_dirents);
443     EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
444
445     std::multiset<std::string> dirnames;
446     for (size_t i = 0; i < num_dirents; ++i) {
447       EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
448       EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
449       dirnames.insert(dirents[i].d_name);
450     }
451
452     EXPECT_EQ(1, dirnames.count("file"));
453     EXPECT_EQ(1, dirnames.count("."));
454     EXPECT_EQ(1, dirnames.count(".."));
455   }
456
457   // Add another file...
458   ASSERT_EQ(0, fs->Open(Path("/file2"), O_CREAT, &node));
459
460   // Read the root directory again.
461   memset(&dirents[0], 0, sizeof(dirents));
462   EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
463
464   {
465     size_t num_dirents = bytes_read / sizeof(dirent);
466     EXPECT_EQ(4, num_dirents);
467     EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
468
469     std::multiset<std::string> dirnames;
470     for (size_t i = 0; i < num_dirents; ++i) {
471       EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
472       EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
473       dirnames.insert(dirents[i].d_name);
474     }
475
476     EXPECT_EQ(1, dirnames.count("file"));
477     EXPECT_EQ(1, dirnames.count("file2"));
478     EXPECT_EQ(1, dirnames.count("."));
479     EXPECT_EQ(1, dirnames.count(".."));
480   }
481 }