// Copyright 2018 The gVisor Authors.
//
// 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.

#ifndef GVISOR_TEST_UTIL_POSIX_ERROR_H_
#define GVISOR_TEST_UTIL_POSIX_ERROR_H_

#include <string>

#include "gmock/gmock.h"
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
#include "absl/types/variant.h"
#include "test/util/logging.h"

namespace gvisor {
namespace testing {

class ABSL_MUST_USE_RESULT PosixError {
 public:
  PosixError() {}
  explicit PosixError(int errno_value) : errno_(errno_value) {}
  PosixError(int errno_value, std::string msg)
      : errno_(errno_value), msg_(std::move(msg)) {}

  PosixError(PosixError&& other) = default;
  PosixError& operator=(PosixError&& other) = default;
  PosixError(const PosixError&) = default;
  PosixError& operator=(const PosixError&) = default;

  bool ok() const { return errno_ == 0; }

  // Returns a reference to *this to make matchers compatible with
  // PosixErrorOr.
  const PosixError& error() const { return *this; }

  int errno_value() const { return errno_; }
  std::string message() const { return msg_; }

  // ToString produces a full string representation of this posix error
  // including the printable representation of the errno and the error message.
  std::string ToString() const;

  // Ignores any errors. This method does nothing except potentially suppress
  // complaints from any tools that are checking that errors are not dropped on
  // the floor.
  void IgnoreError() const {}

 private:
  int errno_ = 0;
  std::string msg_;
};

template <typename T>
class ABSL_MUST_USE_RESULT PosixErrorOr {
 public:
  // A PosixErrorOr will check fail if it is constructed with NoError().
  PosixErrorOr(const PosixError& error);
  PosixErrorOr(const T& value);
  PosixErrorOr(T&& value);

  PosixErrorOr(PosixErrorOr&& other) = default;
  PosixErrorOr& operator=(PosixErrorOr&& other) = default;
  PosixErrorOr(const PosixErrorOr&) = default;
  PosixErrorOr& operator=(const PosixErrorOr&) = default;

  // Conversion copy/move constructor, T must be convertible from U.
  template <typename U>
  friend class PosixErrorOr;

  template <typename U>
  PosixErrorOr(PosixErrorOr<U> other);

  template <typename U>
  PosixErrorOr& operator=(PosixErrorOr<U> other);

  // Returns true if this PosixErrorOr contains some T.
  bool ok() const;

  // Return a copy of the contained PosixError or NoError().
  PosixError error() const;

  // Returns a reference to our current value, or CHECK-fails if !this->ok().
  const T& ValueOrDie() const&;
  T& ValueOrDie() &;
  const T&& ValueOrDie() const&&;
  T&& ValueOrDie() &&;

  // Ignores any errors. This method does nothing except potentially suppress
  // complaints from any tools that are checking that errors are not dropped on
  // the floor.
  void IgnoreError() const {}

 private:
  absl::variant<T, PosixError> value_;

  friend class PosixErrorIsMatcherCommonImpl;
};

template <typename T>
PosixErrorOr<T>::PosixErrorOr(const PosixError& error) : value_(error) {
  TEST_CHECK_MSG(
      !error.ok(),
      "Constructing PosixErrorOr with NoError, eg. errno 0 is not allowed.");
}

template <typename T>
PosixErrorOr<T>::PosixErrorOr(const T& value) : value_(value) {}

template <typename T>
PosixErrorOr<T>::PosixErrorOr(T&& value) : value_(std::move(value)) {}

// Conversion copy/move constructor, T must be convertible from U.
template <typename T>
template <typename U>
inline PosixErrorOr<T>::PosixErrorOr(PosixErrorOr<U> other) {
  if (absl::holds_alternative<U>(other.value_)) {
    // T is convertible from U.
    value_ = absl::get<U>(std::move(other.value_));
  } else if (absl::holds_alternative<PosixError>(other.value_)) {
    value_ = absl::get<PosixError>(std::move(other.value_));
  } else {
    TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
  }
}

template <typename T>
template <typename U>
inline PosixErrorOr<T>& PosixErrorOr<T>::operator=(PosixErrorOr<U> other) {
  if (absl::holds_alternative<U>(other.value_)) {
    // T is convertible from U.
    value_ = absl::get<U>(std::move(other.value_));
  } else if (absl::holds_alternative<PosixError>(other.value_)) {
    value_ = absl::get<PosixError>(std::move(other.value_));
  } else {
    TEST_CHECK_MSG(false, "PosixErrorOr does not contain PosixError or value");
  }
  return *this;
}

template <typename T>
PosixError PosixErrorOr<T>::error() const {
  if (!absl::holds_alternative<PosixError>(value_)) {
    return PosixError();
  }
  return absl::get<PosixError>(value_);
}

template <typename T>
bool PosixErrorOr<T>::ok() const {
  return absl::holds_alternative<T>(value_);
}

template <typename T>
const T& PosixErrorOr<T>::ValueOrDie() const& {
  TEST_CHECK(absl::holds_alternative<T>(value_));
  return absl::get<T>(value_);
}

template <typename T>
T& PosixErrorOr<T>::ValueOrDie() & {
  TEST_CHECK(absl::holds_alternative<T>(value_));
  return absl::get<T>(value_);
}

template <typename T>
const T&& PosixErrorOr<T>::ValueOrDie() const&& {
  TEST_CHECK(absl::holds_alternative<T>(value_));
  return std::move(absl::get<T>(value_));
}

template <typename T>
T&& PosixErrorOr<T>::ValueOrDie() && {
  TEST_CHECK(absl::holds_alternative<T>(value_));
  return std::move(absl::get<T>(value_));
}

extern ::std::ostream& operator<<(::std::ostream& os, const PosixError& e);

template <typename T>
::std::ostream& operator<<(::std::ostream& os, const PosixErrorOr<T>& e) {
  os << e.error();
  return os;
}

// NoError is a PosixError that represents a successful state, i.e. No Error.
inline PosixError NoError() { return PosixError(); }

// Monomorphic implementation of matcher IsPosixErrorOk() for a given type T.
// T can be PosixError, PosixErrorOr<>, or a reference to either of them.
template <typename T>
class MonoPosixErrorIsOkMatcherImpl : public ::testing::MatcherInterface<T> {
 public:
  void DescribeTo(std::ostream* os) const override { *os << "is OK"; }
  void DescribeNegationTo(std::ostream* os) const override {
    *os << "is not OK";
  }
  bool MatchAndExplain(T actual_value,
                       ::testing::MatchResultListener*) const override {
    return actual_value.ok();
  }
};

// Implements IsPosixErrorOkMatcher() as a polymorphic matcher.
class IsPosixErrorOkMatcher {
 public:
  template <typename T>
  operator ::testing::Matcher<T>() const {  // NOLINT
    return MakeMatcher(new MonoPosixErrorIsOkMatcherImpl<T>());
  }
};

// Monomorphic implementation of a matcher for a PosixErrorOr.
template <typename PosixErrorOrType>
class IsPosixErrorOkAndHoldsMatcherImpl
    : public ::testing::MatcherInterface<PosixErrorOrType> {
 public:
  using ValueType = typename std::remove_reference<decltype(
      std::declval<PosixErrorOrType>().ValueOrDie())>::type;

  template <typename InnerMatcher>
  explicit IsPosixErrorOkAndHoldsMatcherImpl(InnerMatcher&& inner_matcher)
      : inner_matcher_(::testing::SafeMatcherCast<const ValueType&>(
            std::forward<InnerMatcher>(inner_matcher))) {}

  void DescribeTo(std::ostream* os) const override {
    *os << "is OK and has a value that ";
    inner_matcher_.DescribeTo(os);
  }

  void DescribeNegationTo(std::ostream* os) const override {
    *os << "isn't OK or has a value that ";
    inner_matcher_.DescribeNegationTo(os);
  }

  bool MatchAndExplain(
      PosixErrorOrType actual_value,
      ::testing::MatchResultListener* listener) const override {
    // We can't extract the value if it doesn't contain one.
    if (!actual_value.ok()) {
      return false;
    }

    ::testing::StringMatchResultListener inner_listener;
    const bool matches = inner_matcher_.MatchAndExplain(
        actual_value.ValueOrDie(), &inner_listener);
    const std::string inner_explanation = inner_listener.str();
    *listener << "has a value "
              << ::testing::PrintToString(actual_value.ValueOrDie());

    if (!inner_explanation.empty()) {
      *listener << " " << inner_explanation;
    }
    return matches;
  }

 private:
  const ::testing::Matcher<const ValueType&> inner_matcher_;
};

// Implements IsOkAndHolds() as a polymorphic matcher.
template <typename InnerMatcher>
class IsPosixErrorOkAndHoldsMatcher {
 public:
  explicit IsPosixErrorOkAndHoldsMatcher(InnerMatcher inner_matcher)
      : inner_matcher_(std::move(inner_matcher)) {}

  // Converts this polymorphic matcher to a monomorphic one of the given type.
  // PosixErrorOrType can be either PosixErrorOr<T> or a reference to
  // PosixErrorOr<T>.
  template <typename PosixErrorOrType>
  operator ::testing::Matcher<PosixErrorOrType>() const {  // NOLINT
    return ::testing::MakeMatcher(
        new IsPosixErrorOkAndHoldsMatcherImpl<PosixErrorOrType>(
            inner_matcher_));
  }

 private:
  const InnerMatcher inner_matcher_;
};

// PosixErrorIs() is a polymorphic matcher.  This class is the common
// implementation of it shared by all types T where PosixErrorIs() can be
// used as a Matcher<T>.
class PosixErrorIsMatcherCommonImpl {
 public:
  PosixErrorIsMatcherCommonImpl(
      ::testing::Matcher<int> code_matcher,
      ::testing::Matcher<const std::string&> message_matcher)
      : code_matcher_(std::move(code_matcher)),
        message_matcher_(std::move(message_matcher)) {}

  void DescribeTo(std::ostream* os) const;

  void DescribeNegationTo(std::ostream* os) const;

  bool MatchAndExplain(const PosixError& error,
                       ::testing::MatchResultListener* result_listener) const;

  template <typename T>
  bool MatchAndExplain(const PosixErrorOr<T>& error_or,
                       ::testing::MatchResultListener* result_listener) const {
    if (error_or.ok()) {
      *result_listener << "has a value "
                       << ::testing::PrintToString(error_or.ValueOrDie());
      return false;
    }

    return MatchAndExplain(error_or.error(), result_listener);
  }

 private:
  const ::testing::Matcher<int> code_matcher_;
  const ::testing::Matcher<const std::string&> message_matcher_;
};

// Monomorphic implementation of matcher PosixErrorIs() for a given type
// T.  T can be PosixError, PosixErrorOr<>, or a reference to either of them.
template <typename T>
class MonoPosixErrorIsMatcherImpl : public ::testing::MatcherInterface<T> {
 public:
  explicit MonoPosixErrorIsMatcherImpl(
      PosixErrorIsMatcherCommonImpl common_impl)
      : common_impl_(std::move(common_impl)) {}

  void DescribeTo(std::ostream* os) const override {
    common_impl_.DescribeTo(os);
  }

  void DescribeNegationTo(std::ostream* os) const override {
    common_impl_.DescribeNegationTo(os);
  }

  bool MatchAndExplain(
      T actual_value,
      ::testing::MatchResultListener* result_listener) const override {
    return common_impl_.MatchAndExplain(actual_value, result_listener);
  }

 private:
  PosixErrorIsMatcherCommonImpl common_impl_;
};

inline ::testing::Matcher<int> ToErrorCodeMatcher(
    const ::testing::Matcher<int>& m) {
  return m;
}

// Implements PosixErrorIs() as a polymorphic matcher.
class PosixErrorIsMatcher {
 public:
  template <typename ErrorCodeMatcher>
  PosixErrorIsMatcher(ErrorCodeMatcher&& code_matcher,
                      ::testing::Matcher<const std::string&> message_matcher)
      : common_impl_(
            ToErrorCodeMatcher(std::forward<ErrorCodeMatcher>(code_matcher)),
            std::move(message_matcher)) {}

  // Converts this polymorphic matcher to a monomorphic matcher of the
  // given type.  T can be StatusOr<>, Status, or a reference to
  // either of them.
  template <typename T>
  operator ::testing::Matcher<T>() const {  // NOLINT
    return MakeMatcher(new MonoPosixErrorIsMatcherImpl<T>(common_impl_));
  }

 private:
  const PosixErrorIsMatcherCommonImpl common_impl_;
};

// Returns a gMock matcher that matches a PosixError or PosixErrorOr<> whose
// whose error code matches code_matcher, and whose error message matches
// message_matcher.
template <typename ErrorCodeMatcher>
PosixErrorIsMatcher PosixErrorIs(
    ErrorCodeMatcher&& code_matcher,
    ::testing::Matcher<const std::string&> message_matcher) {
  return PosixErrorIsMatcher(std::forward<ErrorCodeMatcher>(code_matcher),
                             std::move(message_matcher));
}

// Returns a gMock matcher that matches a PosixErrorOr<> which is ok() and
// value matches the inner matcher.
template <typename InnerMatcher>
IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>
IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) {
  return IsPosixErrorOkAndHoldsMatcher<typename std::decay<InnerMatcher>::type>(
      std::forward<InnerMatcher>(inner_matcher));
}

// Internal helper for concatenating macro values.
#define POSIX_ERROR_IMPL_CONCAT_INNER_(x, y) x##y
#define POSIX_ERROR_IMPL_CONCAT_(x, y) POSIX_ERROR_IMPL_CONCAT_INNER_(x, y)

#define POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(posixerroror, lhs, rexpr) \
  auto posixerroror = (rexpr);                                       \
  if (!posixerroror.ok()) {                                          \
    return (posixerroror.error());                                   \
  }                                                                  \
  lhs = std::move(posixerroror).ValueOrDie()

#define EXPECT_NO_ERRNO(expression) \
  EXPECT_THAT(expression, IsPosixErrorOkMatcher())
#define ASSERT_NO_ERRNO(expression) \
  ASSERT_THAT(expression, IsPosixErrorOkMatcher())

#define ASSIGN_OR_RETURN_ERRNO(lhs, rexpr) \
  POSIX_ERROR_IMPL_ASSIGN_OR_RETURN_(      \
      POSIX_ERROR_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr)

#define RETURN_IF_ERRNO(s) \
  do {                     \
    if (!s.ok()) {         \
      return s;            \
    }                      \
  } while (false);

#define ASSERT_NO_ERRNO_AND_VALUE(expr)   \
  ({                                      \
    auto _expr_result = (expr);           \
    ASSERT_NO_ERRNO(_expr_result);        \
    std::move(_expr_result).ValueOrDie(); \
  })

}  // namespace testing
}  // namespace gvisor

#endif  // GVISOR_TEST_UTIL_POSIX_ERROR_H_