chromium/native_client_sdk/src/tests/nacl_io_test/js_fs_test.cc

// Copyright 2014 The Chromium Authors
// 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 <pthread.h>

#include <set>
#include <string>

#include <gmock/gmock.h>
#include <ppapi/c/pp_errors.h>
#include <ppapi/c/pp_instance.h>

#include "fake_ppapi/fake_messaging_interface.h"
#include "fake_ppapi/fake_pepper_interface.h"
#include "nacl_io/ioctl.h"
#include "nacl_io/jsfs/js_fs.h"
#include "nacl_io/jsfs/js_fs_node.h"
#include "nacl_io/kernel_intercept.h"
#include "nacl_io/kernel_proxy.h"
#include "nacl_io/log.h"
#include "nacl_io/osdirent.h"
#include "nacl_io/osunistd.h"
#include "sdk_util/auto_lock.h"
#include "sdk_util/scoped_ref.h"
#include "sdk_util/simple_lock.h"

using namespace nacl_io;
using namespace sdk_util;

namespace {

class JsFsForTesting : public JsFs {
 public:
  JsFsForTesting(PepperInterface* ppapi) {
    FsInitArgs args;
    args.ppapi = ppapi;
    Error error = Init(args);
    EXPECT_EQ(0, error);
  }
};

class FakeMessagingInterfaceJsFs : public MessagingInterface {
 public:
  explicit FakeMessagingInterfaceJsFs(VarInterface* var_interface)
      : var_interface_(var_interface), has_message_(false) {
    pthread_cond_init(&cond_, NULL);
  }

  ~FakeMessagingInterfaceJsFs() { pthread_cond_destroy(&cond_); }

  virtual void PostMessage(PP_Instance instance, PP_Var message) {
    var_interface_->AddRef(message);

    AUTO_LOCK(lock_);
    ASSERT_FALSE(has_message_);

    message_ = message;
    has_message_ = true;
    pthread_cond_signal(&cond_);
  }

  PP_Var WaitForMessage() {
    AUTO_LOCK(lock_);
    while (!has_message_) {
      pthread_cond_wait(&cond_, lock_.mutex());
    }

    has_message_ = false;
    return message_;
  }

 private:
  VarInterface* var_interface_;
  SimpleLock lock_;
  pthread_cond_t cond_;
  PP_Var message_;
  bool has_message_;
};

class FakePepperInterfaceJsFs : public FakePepperInterface {
 public:
  FakePepperInterfaceJsFs() : messaging_interface_(GetVarInterface()) {}

  virtual nacl_io::MessagingInterface* GetMessagingInterface() {
    return &messaging_interface_;
  }

 private:
  FakeMessagingInterfaceJsFs messaging_interface_;
};

class JsFsTest : public ::testing::Test {
 public:
  void SetUp() {
    ASSERT_EQ(0, ki_push_state_for_testing());
    ASSERT_EQ(0, ki_init_interface(&kp_, &ppapi_));
    fs_.reset(new JsFsForTesting(&ppapi_));

    js_thread_started_ = false;
  }

  void TearDown() {
    if (js_thread_started_)
      pthread_join(js_thread_, NULL);

    for (RequestResponses::iterator it = request_responses_.begin(),
                                    end = request_responses_.end();
         it != end;
         ++it) {
      ppapi_.GetVarInterface()->Release(it->request);
      ppapi_.GetVarInterface()->Release(it->response);
    }


    ki_uninit();
  }

  void StartJsThread() {
    ASSERT_EQ(0, pthread_create(&js_thread_, NULL, JsThreadMainThunk, this));
    js_thread_started_ = true;
  }

  static void* JsThreadMainThunk(void* arg) {
    static_cast<JsFsTest*>(arg)->JsThreadMain();
    return NULL;
  }

  PP_Var WaitForRequest() {
    FakeMessagingInterfaceJsFs* messaging_if =
        static_cast<FakeMessagingInterfaceJsFs*>(
            ppapi_.GetMessagingInterface());
    return messaging_if->WaitForMessage();
  }

  void JsThreadMain() {
    for (RequestResponses::iterator it = request_responses_.begin(),
                                    end = request_responses_.end();
         it != end;
         ++it) {
      PP_Var request = WaitForRequest();
      EXPECT_TRUE(VarsAreEqual(it->request, request))
          << "Vars are not equal: " << VarToString(it->request)
          << " != " << VarToString(request);
      ppapi_.GetVarInterface()->Release(request);
      EXPECT_EQ(0,
                fs_->Filesystem_Ioctl(NACL_IOC_HANDLEMESSAGE, &it->response));
      // Passed ownership of response_ to filesystem, so set this to undefined
      // so it isn't double-released in TearDown().
      it->response = PP_MakeUndefined();
    }
  }

  void Expect(PP_Var request, PP_Var response) {
    RequestResponse rr;
    // Pass ownership of both vars from caller to callee.
    rr.request = request;
    rr.response = response;
    request_responses_.push_back(rr);
  }

  bool CreateDict(PP_Var* out_var) {
    *out_var = ppapi_.GetVarDictionaryInterface()->Create();
    return out_var->type == PP_VARTYPE_DICTIONARY;
  }

  bool SetDictKeyValue(PP_Var* var, const char* key, int32_t value) {
    return SetDictKeyValue(var, key, PP_MakeInt32(value));
  }

  bool SetDictKeyValue(PP_Var* var, const char* key, size_t value) {
    return SetDictKeyValue(var, key, PP_MakeInt32(static_cast<size_t>(value)));
  }

  bool SetDictKeyValue(PP_Var* var, const char* key, int64_t value) {
    VarArrayInterface* array_if = ppapi_.GetVarArrayInterface();
    PP_Var value_var = array_if->Create();
    return array_if->Set(value_var, 0, PP_MakeInt32(value >> 32)) &&
           array_if->Set(value_var, 1, PP_MakeInt32(value & 0xffffffff)) &&
           SetDictKeyValue(var, key, value_var);
  }

  bool SetDictKeyValue(PP_Var* var, const char* key, const char* value) {
    VarInterface* var_if = ppapi_.GetVarInterface();
    PP_Var value_var = var_if->VarFromUtf8(value, strlen(value));
    return SetDictKeyValue(var, key, value_var);
  }

  bool SetDictKeyValue(PP_Var* var, const char* key, PP_Var value_var) {
    VarDictionaryInterface* dict_if = ppapi_.GetVarDictionaryInterface();
    VarInterface* var_if = ppapi_.GetVarInterface();
    PP_Var key_var = var_if->VarFromUtf8(key, strlen(key));
    PP_Bool result = dict_if->Set(*var, key_var, value_var);
    var_if->Release(key_var);
    var_if->Release(value_var);
    return result == PP_TRUE;
  }

  bool CreateArray(PP_Var* out_var) {
    *out_var = ppapi_.GetVarArrayInterface()->Create();
    return out_var->type == PP_VARTYPE_ARRAY;
  }

  bool SetArrayValue(PP_Var* var, uint32_t i, int32_t value) {
    return SetArrayValue(var, i, PP_MakeInt32(value));
  }

  bool SetArrayValue(PP_Var* var, uint32_t i, const char* value) {
    VarInterface* var_if = ppapi_.GetVarInterface();
    PP_Var value_var = var_if->VarFromUtf8(value, strlen(value));
    return SetArrayValue(var, i, value_var);
  }

  bool SetArrayValue(PP_Var* var, uint32_t i, int64_t value) {
    VarArrayInterface* array_if = ppapi_.GetVarArrayInterface();
    PP_Var value_var = array_if->Create();
    return array_if->Set(value_var, 0, PP_MakeInt32(value >> 32)) &&
           array_if->Set(value_var, 1, PP_MakeInt32(value & 0xffffffff)) &&
           SetArrayValue(var, i, value_var);
  }

  bool SetArrayValue(PP_Var* var, uint32_t i, PP_Var value_var) {
    VarArrayInterface* array_if = ppapi_.GetVarArrayInterface();
    VarInterface* var_if = ppapi_.GetVarInterface();
    PP_Bool result = array_if->Set(*var, i, value_var);
    var_if->Release(value_var);
    return result == PP_TRUE;
  }

  std::string VarToString(PP_Var var) {
    VarDictionaryInterface* dict_if = ppapi_.GetVarDictionaryInterface();
    VarArrayInterface* array_if = ppapi_.GetVarArrayInterface();
    VarInterface* var_if = ppapi_.GetVarInterface();
    VarArrayBufferInterface* array_buffer_if =
        ppapi_.GetVarArrayBufferInterface();

    switch (var.type) {
      case PP_VARTYPE_UNDEFINED:
        return "undefined";
      case PP_VARTYPE_NULL:
        return "null";
      case PP_VARTYPE_BOOL:
        return var.value.as_bool ? "true" : "false";
      case PP_VARTYPE_INT32: {
        char buffer[32];
        snprintf(buffer, 32, "%d", var.value.as_int);
        return buffer;
      }
      case PP_VARTYPE_DOUBLE: {
        char buffer[32];
        snprintf(buffer, 32, "%g", var.value.as_double);
        return buffer;
      }
      case PP_VARTYPE_STRING: {
        uint32_t var_len;
        const char* var_str = var_if->VarToUtf8(var, &var_len);
        std::string result("\"");
        result += std::string(var_str, var_len);
        result += "\"";
        return result;
      }
      case PP_VARTYPE_ARRAY: {
        std::string result("[");
        uint32_t var_len = array_if->GetLength(var);

        for (uint32_t i = 0; i < var_len; ++i) {
          PP_Var var_item = array_if->Get(var, i);
          result += VarToString(var_item);
          var_if->Release(var_item);
          if (i != var_len - 1)
            result += ", ";
        }
        result += "]";
        return result;
      }
      case PP_VARTYPE_DICTIONARY: {
        std::string result("{");
        PP_Var var_keys = dict_if->GetKeys(var);
        uint32_t var_len = array_if->GetLength(var_keys);

        for (uint32_t i = 0; i < var_len; ++i) {
          PP_Var key = array_if->Get(var_keys, i);
          result += VarToString(key);
          result += ": ";
          PP_Var var_value = dict_if->Get(var, key);
          result += VarToString(var_value);
          var_if->Release(key);
          var_if->Release(var_value);
          if (i != var_len - 1)
            result += ", ";
        }
        result += "}";
        var_if->Release(var_keys);
        return result;
      }
      case PP_VARTYPE_ARRAY_BUFFER: {
        uint32_t var_len;
        if (!array_buffer_if->ByteLength(var, &var_len)) {
          LOG_ERROR("Unable to get byte length of var.");
          return "undefined";
        }

        std::string result("new Uint8Array([");

        void* var_ptr = array_buffer_if->Map(var);
        for (uint32_t i = 0; i < var_len; ++i) {
          char buffer[8];
          snprintf(buffer, 8, "%d", static_cast<uint8_t*>(var_ptr)[i]);
          result += buffer;
          if (i != var_len - 1)
            result += ", ";
        }
        result += "])";
        array_buffer_if->Unmap(var);
        return result;
      }

      default:
        ADD_FAILURE() << "Unexpected var type: " << var.type;
        return "undefined";
    }
  }

  bool VarsAreEqual(PP_Var expected, PP_Var var) {
    if (expected.type != var.type)
      return false;

    VarDictionaryInterface* dict_if = ppapi_.GetVarDictionaryInterface();
    VarArrayInterface* array_if = ppapi_.GetVarArrayInterface();
    VarInterface* var_if = ppapi_.GetVarInterface();
    VarArrayBufferInterface* array_buffer_if =
        ppapi_.GetVarArrayBufferInterface();

    switch (var.type) {
      case PP_VARTYPE_UNDEFINED:
      case PP_VARTYPE_NULL:
        return true;
      case PP_VARTYPE_BOOL:
        return expected.value.as_bool == var.value.as_bool;
      case PP_VARTYPE_INT32:
        return expected.value.as_int == var.value.as_int;
      case PP_VARTYPE_DOUBLE:
        return expected.value.as_double == var.value.as_double;
      case PP_VARTYPE_STRING: {
        uint32_t var_len;
        uint32_t expected_len;
        const char* var_str = var_if->VarToUtf8(var, &var_len);
        const char* expected_str = var_if->VarToUtf8(expected, &expected_len);

        if (expected_len != var_len)
          return false;

        return memcmp(expected_str, var_str, var_len) == 0;
      }
      case PP_VARTYPE_ARRAY: {
        uint32_t var_len = array_if->GetLength(var);
        uint32_t expected_len = array_if->GetLength(expected);

        if (expected_len != var_len)
          return false;

        for (uint32_t i = 0; i < var_len; ++i) {
          PP_Var var_item = array_if->Get(var, i);
          PP_Var expected_item = array_if->Get(expected, i);
          bool equal = VarsAreEqual(expected_item, var_item);
          var_if->Release(var_item);
          var_if->Release(expected_item);

          if (!equal)
            return false;
        }

        return true;
      }
      case PP_VARTYPE_DICTIONARY: {
        PP_Var var_keys = dict_if->GetKeys(var);
        PP_Var expected_keys = dict_if->GetKeys(expected);

        uint32_t var_len = array_if->GetLength(var_keys);
        uint32_t expected_len = array_if->GetLength(expected_keys);

        bool result = true;

        if (expected_len == var_len) {
          for (uint32_t i = 0; i < var_len; ++i) {
            PP_Var key = array_if->Get(var_keys, i);
            PP_Var var_value = dict_if->Get(var, key);
            PP_Var expected_value = dict_if->Get(expected, key);
            bool equal = VarsAreEqual(expected_value, var_value);
            var_if->Release(key);
            var_if->Release(var_value);
            var_if->Release(expected_value);

            if (!equal) {
              result = false;
              break;
            }
          }
        } else {
          result = false;
        }

        var_if->Release(var_keys);
        var_if->Release(expected_keys);
        return result;
      }
      case PP_VARTYPE_ARRAY_BUFFER: {
        uint32_t var_len;
        if (!array_buffer_if->ByteLength(var, &var_len))
          return false;

        uint32_t expected_len;
        if (!array_buffer_if->ByteLength(expected, &expected_len))
          return false;

        if (expected_len != var_len)
          return false;

        void* var_ptr = array_buffer_if->Map(var);
        void* expected_ptr = array_buffer_if->Map(expected);
        bool equal = memcmp(var_ptr, expected_ptr, var_len) == 0;
        array_buffer_if->Unmap(var);
        array_buffer_if->Unmap(expected);

        return equal;
      }

      default:
        ADD_FAILURE() << "Unexpected var type: " << var.type;
        return false;
    }
  }

  PP_Var CreateDummyArrayBuffer(uint32_t length) {
    VarArrayBufferInterface* array_buffer_if =
        ppapi_.GetVarArrayBufferInterface();
    PP_Var var = array_buffer_if->Create(length);
    uint8_t* data = static_cast<uint8_t*>(array_buffer_if->Map(var));
    FillDummyBuffer(data, length);
    array_buffer_if->Unmap(var);
    return var;
  }

  void FillDummyBuffer(uint8_t* buf, size_t buf_len) {
    for (uint32_t i = 0; i < buf_len; ++i) {
      buf[i] = i & 255;
    }
  }

  bool EqualsDummyArrayBuffer(uint8_t* buf, size_t buf_len) {
    for (uint32_t i = 0; i < buf_len; ++i) {
      if (buf[i] != (i & 255)) {
        LOG_ERROR("Byte %d of ArrayBuffer doesn't match: %d != %d.",
                  i,
                  buf[i],
                  i & 255);
        return false;
      }
    }
    return true;
  }

 protected:
  FakePepperInterfaceJsFs ppapi_;
  ScopedRef<JsFsForTesting> fs_;
  KernelProxy kp_;
  pthread_t js_thread_;
  bool js_thread_started_;

  struct RequestResponse {
    PP_Var request;
    PP_Var response;
  };

  typedef std::vector<RequestResponse> RequestResponses;
  RequestResponses request_responses_;
};

class JsFsNodeTest : public JsFsTest {
 public:
  static const int fd;

  virtual void SetUp() {
    JsFsTest::SetUp();

    PP_Var expected;
    ASSERT_EQ(true, CreateDict(&expected));
    ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
    ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "open"));
    ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));
    ASSERT_EQ(true, SetDictKeyValue(&expected, "oflag", O_RDONLY));

    PP_Var response;
    ASSERT_EQ(true, CreateDict(&response));
    ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
    ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
    ASSERT_EQ(true, SetDictKeyValue(&response, "fd", fd));

    Expect(expected, response);
  }

  virtual void TearDown() {
    JsFsTest::TearDown();
  }

  void OpenNode() {
    EXPECT_EQ(0, fs_->Open(Path("/foo"), O_RDONLY, &node_));
    EXPECT_EQ(fd, sdk_util::static_scoped_ref_cast<JsFsNode>(node_)->fd());
  }

 protected:
  ScopedNode node_;
};

const int JsFsNodeTest::fd = 123;

}  // namespace

TEST_F(JsFsTest, Open) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "open"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "oflag", O_RDONLY));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
  ASSERT_EQ(true, SetDictKeyValue(&response, "fd", 123));

  Expect(expected, response);
  StartJsThread();

  ScopedNode node;
  EXPECT_EQ(0, fs_->Open(Path("/foo"), O_RDONLY, &node));
  EXPECT_EQ(123, sdk_util::static_scoped_ref_cast<JsFsNode>(node)->fd());
}

TEST_F(JsFsTest, Unlink) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "unlink"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();

  EXPECT_EQ(0, fs_->Unlink(Path("/foo")));
}

TEST_F(JsFsTest, Mkdir) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "mkdir"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "mode", 0644));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();

  EXPECT_EQ(0, fs_->Mkdir(Path("/foo"), 0644));
}

TEST_F(JsFsTest, Rmdir) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "rmdir"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();

  EXPECT_EQ(0, fs_->Rmdir(Path("/foo")));
}

TEST_F(JsFsTest, Remove) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "remove"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "path", "/foo"));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();

  EXPECT_EQ(0, fs_->Remove(Path("/foo")));
}

TEST_F(JsFsTest, Rename) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "rename"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "old", "/foo"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "new", "/bar"));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();

  EXPECT_EQ(0, fs_->Rename(Path("/foo"), Path("/bar")));
}

TEST_F(JsFsNodeTest, GetStat) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "fstat"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_ino", (int64_t) 1));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_mode", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_nlink", 3));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_uid", 4));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_gid", 5));
#ifdef __APPLE__
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_rdev", (dev_t) 6));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_size", (off_t) 7));
#else
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_rdev", (int64_t) 6));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_size", (int64_t) 7));
#endif
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_blksize", 8));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_blocks", 9));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_atime", (int64_t) 10));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_mtime", (int64_t) 11));
  ASSERT_EQ(true, SetDictKeyValue(&response, "st_ctime", (int64_t) 12));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  struct stat statbuf;
  EXPECT_EQ(0, node_->GetStat(&statbuf));
  EXPECT_EQ(fs_->dev(), statbuf.st_dev);
  EXPECT_EQ(1, statbuf.st_ino);
  EXPECT_EQ(2, statbuf.st_mode);
  EXPECT_EQ(3, statbuf.st_nlink);
  EXPECT_EQ(4, statbuf.st_uid);
  EXPECT_EQ(5, statbuf.st_gid);
  EXPECT_EQ(6, statbuf.st_rdev);
  EXPECT_EQ(7, statbuf.st_size);
  EXPECT_EQ(8, statbuf.st_blksize);
  EXPECT_EQ(9, statbuf.st_blocks);
  EXPECT_EQ(10, statbuf.st_atime);
  EXPECT_EQ(11, statbuf.st_mtime);
  EXPECT_EQ(12, statbuf.st_ctime);
}

TEST_F(JsFsNodeTest, FSync) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "fsync"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  EXPECT_EQ(0, node_->FSync());
}

TEST_F(JsFsNodeTest, FTruncate) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "ftruncate"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "length", 0));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  EXPECT_EQ(0, node_->FTruncate(0));
}

TEST_F(JsFsNodeTest, Read) {
  const size_t kReadLength = 100;

  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "pread"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));
  ASSERT_EQ(true,
            SetDictKeyValue(&expected, "nbyte", static_cast<int>(kReadLength)));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "offset", 200));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
  ASSERT_EQ(
      true,
      SetDictKeyValue(&response, "buf", CreateDummyArrayBuffer(kReadLength)));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  HandleAttr attr;
  attr.offs = 200;
  uint8_t buf[kReadLength];
  int bytes_read;
  EXPECT_EQ(0, node_->Read(attr, buf, kReadLength, &bytes_read));
  EXPECT_EQ(kReadLength, bytes_read);
  EXPECT_TRUE(EqualsDummyArrayBuffer(buf, kReadLength));
}

TEST_F(JsFsNodeTest, Write) {
  const size_t kWriteLength = 100;

  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "pwrite"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));
  ASSERT_EQ(
      true,
      SetDictKeyValue(&expected, "buf", CreateDummyArrayBuffer(kWriteLength)));
  ASSERT_EQ(
      true,
      SetDictKeyValue(&expected, "nbyte", static_cast<int>(kWriteLength)));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "offset", 200));

  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
  ASSERT_EQ(true, SetDictKeyValue(&response, "nwrote", kWriteLength));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  HandleAttr attr;
  attr.offs = 200;

  uint8_t buf[kWriteLength];
  FillDummyBuffer(buf, kWriteLength);

  int bytes_written;
  EXPECT_EQ(0, node_->Write(attr, buf, kWriteLength, &bytes_written));
  EXPECT_EQ(kWriteLength, bytes_written);
}

TEST_F(JsFsNodeTest, GetDents) {
  PP_Var expected;
  ASSERT_EQ(true, CreateDict(&expected));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "cmd", "getdents"));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "fildes", fd));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "offs", 0));
  ASSERT_EQ(true, SetDictKeyValue(&expected, "count", 2));

  PP_Var entry0;
  ASSERT_EQ(true, CreateDict(&entry0));
  ASSERT_EQ(true, SetDictKeyValue(&entry0, "d_ino", 2));
  ASSERT_EQ(true, SetDictKeyValue(&entry0, "d_name", "."));
  PP_Var entry1;
  ASSERT_EQ(true, CreateDict(&entry1));
  ASSERT_EQ(true, SetDictKeyValue(&entry1, "d_ino", 3));
  ASSERT_EQ(true, SetDictKeyValue(&entry1, "d_name", ".."));
  PP_Var array;
  ASSERT_EQ(true, CreateArray(&array));
  ASSERT_EQ(true, SetArrayValue(&array, 0, entry0));
  ASSERT_EQ(true, SetArrayValue(&array, 1, entry1));
  PP_Var response;
  ASSERT_EQ(true, CreateDict(&response));
  ASSERT_EQ(true, SetDictKeyValue(&response, "id", 2));
  ASSERT_EQ(true, SetDictKeyValue(&response, "error", 0));
  ASSERT_EQ(true, SetDictKeyValue(&response, "dirents", array));

  Expect(expected, response);
  StartJsThread();
  OpenNode();

  dirent buf[2];
  int bytes_written;
  EXPECT_EQ(0, node_->GetDents(0, buf, sizeof(dirent) * 2, &bytes_written));
  EXPECT_EQ(sizeof(dirent) * 2, bytes_written);
  EXPECT_EQ(2, buf[0].d_ino);
  EXPECT_EQ(sizeof(dirent), buf[0].d_reclen);
  EXPECT_STREQ(".", buf[0].d_name);
  EXPECT_EQ(3, buf[1].d_ino);
  EXPECT_EQ(sizeof(dirent), buf[1].d_reclen);
  EXPECT_STREQ("..", buf[1].d_name);
}