// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "util/mac/xattr.h"

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <limits>

#include "base/files/scoped_file.h"
#include "base/posix/eintr_wrapper.h"
#include "gtest/gtest.h"
#include "test/errors.h"
#include "test/scoped_temp_dir.h"

namespace crashpad {
namespace test {
namespace {

class Xattr : public testing::Test {
 protected:
  // testing::Test:

  void SetUp() override {
    path_ = temp_dir_.path().Append("xattr_file");
    base::ScopedFD tmp(
        HANDLE_EINTR(open(path_.value().c_str(),
                          O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY | O_CLOEXEC,
                          0644)));
    EXPECT_GE(tmp.get(), 0) << ErrnoMessage("open");
  }

  void TearDown() override {
    EXPECT_EQ(unlink(path_.value().c_str()), 0) << ErrnoMessage("unlink");
  }

  const base::FilePath& path() const { return path_; }

 private:
  ScopedTempDir temp_dir_;
  base::FilePath path_;
};

constexpr char kKey[] = "org.chromium.crashpad.test";

TEST_F(Xattr, ReadNonExistentXattr) {
  std::string value;
  EXPECT_EQ(ReadXattr(path(), kKey, &value), XattrStatus::kNoAttribute);
}

TEST_F(Xattr, WriteAndReadString) {
  std::string value = "hello world";
  EXPECT_TRUE(WriteXattr(path(), kKey, value));

  std::string actual;
  EXPECT_EQ(ReadXattr(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, value);
}

TEST_F(Xattr, WriteAndReadVeryLongString) {
  std::string value(533, 'A');
  EXPECT_TRUE(WriteXattr(path(), kKey, value));

  std::string actual;
  EXPECT_EQ(ReadXattr(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, value);
}

TEST_F(Xattr, WriteAndReadBool) {
  EXPECT_TRUE(WriteXattrBool(path(), kKey, true));
  bool actual = false;
  EXPECT_EQ(ReadXattrBool(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_TRUE(actual);

  EXPECT_TRUE(WriteXattrBool(path(), kKey, false));
  EXPECT_EQ(ReadXattrBool(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_FALSE(actual);
}

TEST_F(Xattr, WriteAndReadInt) {
  int expected = 42;
  int actual;

  EXPECT_TRUE(WriteXattrInt(path(), kKey, expected));
  EXPECT_EQ(ReadXattrInt(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, expected);

  expected = std::numeric_limits<int>::max();
  EXPECT_TRUE(WriteXattrInt(path(), kKey, expected));
  EXPECT_EQ(ReadXattrInt(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, expected);
}

TEST_F(Xattr, WriteAndReadTimeT) {
  time_t expected = time(nullptr);
  time_t actual;

  EXPECT_TRUE(WriteXattrTimeT(path(), kKey, expected));
  EXPECT_EQ(ReadXattrTimeT(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, expected);

  expected = std::numeric_limits<time_t>::max();
  EXPECT_TRUE(WriteXattrTimeT(path(), kKey, expected));
  EXPECT_EQ(ReadXattrTimeT(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, expected);
}

TEST_F(Xattr, RemoveAndRead) {
  const std::string value = "hello world";
  EXPECT_TRUE(WriteXattr(path(), kKey, value));

  std::string actual;
  EXPECT_EQ(ReadXattr(path(), kKey, &actual), XattrStatus::kOK);
  EXPECT_EQ(actual, value);

  EXPECT_EQ(RemoveXattr(path(), kKey), XattrStatus::kOK);
  EXPECT_EQ(ReadXattr(path(), kKey, &actual), XattrStatus::kNoAttribute);

  EXPECT_EQ(RemoveXattr(path(), kKey), XattrStatus::kNoAttribute);
}

}  // namespace
}  // namespace test
}  // namespace crashpad
