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