// Copyright 2017 The Abseil 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
//
//      https://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 "absl/base/internal/exception_safety_testing.h"

#ifdef ABSL_HAVE_EXCEPTIONS

#include <cstddef>
#include <exception>
#include <iostream>
#include <list>
#include <type_traits>
#include <vector>

#include "gtest/gtest-spi.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"

namespace testing {

namespace {

using ::testing::exceptions_internal::SetCountdown;
using ::testing::exceptions_internal::TestException;
using ::testing::exceptions_internal::UnsetCountdown;

// EXPECT_NO_THROW can't inspect the thrown inspection in general.
template <typename F>
void ExpectNoThrow(const F& f) {
  try {
    f();
  } catch (const TestException& e) {
    ADD_FAILURE() << "Unexpected exception thrown from " << e.what();
  }
}

TEST(ThrowingValueTest, Throws) {
  SetCountdown();
  EXPECT_THROW(ThrowingValue<> bomb, TestException);

  // It's not guaranteed that every operator only throws *once*.  The default
  // ctor only throws once, though, so use it to make sure we only throw when
  // the countdown hits 0
  SetCountdown(2);
  ExpectNoThrow([]() { ThrowingValue<> bomb; });
  ExpectNoThrow([]() { ThrowingValue<> bomb; });
  EXPECT_THROW(ThrowingValue<> bomb, TestException);

  UnsetCountdown();
}

// Tests that an operation throws when the countdown is at 0, doesn't throw when
// the countdown doesn't hit 0, and doesn't modify the state of the
// ThrowingValue if it throws
template <typename F>
void TestOp(const F& f) {
  ExpectNoThrow(f);

  SetCountdown();
  EXPECT_THROW(f(), TestException);
  UnsetCountdown();
}

TEST(ThrowingValueTest, ThrowingCtors) {
  ThrowingValue<> bomb;

  TestOp([]() { ThrowingValue<> bomb(1); });
  TestOp([&]() { ThrowingValue<> bomb1 = bomb; });
  TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); });
}

TEST(ThrowingValueTest, ThrowingAssignment) {
  ThrowingValue<> bomb, bomb1;

  TestOp([&]() { bomb = bomb1; });
  TestOp([&]() { bomb = std::move(bomb1); });

  // Test that when assignment throws, the assignment should fail (lhs != rhs)
  // and strong guarantee fails (lhs != lhs_copy).
  {
    ThrowingValue<> lhs(39), rhs(42);
    ThrowingValue<> lhs_copy(lhs);
    SetCountdown();
    EXPECT_THROW(lhs = rhs, TestException);
    UnsetCountdown();
    EXPECT_NE(lhs, rhs);
    EXPECT_NE(lhs_copy, lhs);
  }
  {
    ThrowingValue<> lhs(39), rhs(42);
    ThrowingValue<> lhs_copy(lhs), rhs_copy(rhs);
    SetCountdown();
    EXPECT_THROW(lhs = std::move(rhs), TestException);
    UnsetCountdown();
    EXPECT_NE(lhs, rhs_copy);
    EXPECT_NE(lhs_copy, lhs);
  }
}

TEST(ThrowingValueTest, ThrowingComparisons) {
  ThrowingValue<> bomb1, bomb2;
  TestOp([&]() { return bomb1 == bomb2; });
  TestOp([&]() { return bomb1 != bomb2; });
  TestOp([&]() { return bomb1 < bomb2; });
  TestOp([&]() { return bomb1 <= bomb2; });
  TestOp([&]() { return bomb1 > bomb2; });
  TestOp([&]() { return bomb1 >= bomb2; });
}

TEST(ThrowingValueTest, ThrowingArithmeticOps) {
  ThrowingValue<> bomb1(1), bomb2(2);

  TestOp([&bomb1]() { +bomb1; });
  TestOp([&bomb1]() { -bomb1; });
  TestOp([&bomb1]() { ++bomb1; });
  TestOp([&bomb1]() { bomb1++; });
  TestOp([&bomb1]() { --bomb1; });
  TestOp([&bomb1]() { bomb1--; });

  TestOp([&]() { bomb1 + bomb2; });
  TestOp([&]() { bomb1 - bomb2; });
  TestOp([&]() { bomb1* bomb2; });
  TestOp([&]() { bomb1 / bomb2; });
  TestOp([&]() { bomb1 << 1; });
  TestOp([&]() { bomb1 >> 1; });
}

TEST(ThrowingValueTest, ThrowingLogicalOps) {
  ThrowingValue<> bomb1, bomb2;

  TestOp([&bomb1]() { !bomb1; });
  TestOp([&]() { bomb1&& bomb2; });
  TestOp([&]() { bomb1 || bomb2; });
}

TEST(ThrowingValueTest, ThrowingBitwiseOps) {
  ThrowingValue<> bomb1, bomb2;

  TestOp([&bomb1]() { ~bomb1; });
  TestOp([&]() { bomb1& bomb2; });
  TestOp([&]() { bomb1 | bomb2; });
  TestOp([&]() { bomb1 ^ bomb2; });
}

TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
  ThrowingValue<> bomb1(1), bomb2(2);

  TestOp([&]() { bomb1 += bomb2; });
  TestOp([&]() { bomb1 -= bomb2; });
  TestOp([&]() { bomb1 *= bomb2; });
  TestOp([&]() { bomb1 /= bomb2; });
  TestOp([&]() { bomb1 %= bomb2; });
  TestOp([&]() { bomb1 &= bomb2; });
  TestOp([&]() { bomb1 |= bomb2; });
  TestOp([&]() { bomb1 ^= bomb2; });
  TestOp([&]() { bomb1 *= bomb2; });
}

TEST(ThrowingValueTest, ThrowingStreamOps) {
  ThrowingValue<> bomb;

  TestOp([&]() {
    std::istringstream stream;
    stream >> bomb;
  });
  TestOp([&]() {
    std::stringstream stream;
    stream << bomb;
  });
}

// Tests the operator<< of ThrowingValue by forcing ConstructorTracker to emit
// a nonfatal failure that contains the string representation of the Thrower
TEST(ThrowingValueTest, StreamOpsOutput) {
  using ::testing::TypeSpec;
  exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);

  // Test default spec list (kEverythingThrows)
  EXPECT_NONFATAL_FAILURE(
      {
        using Thrower = ThrowingValue<TypeSpec{}>;
        auto thrower = Thrower(123);
        thrower.~Thrower();
      },
      "ThrowingValue<>(123)");

  // Test with one item in spec list (kNoThrowCopy)
  EXPECT_NONFATAL_FAILURE(
      {
        using Thrower = ThrowingValue<TypeSpec::kNoThrowCopy>;
        auto thrower = Thrower(234);
        thrower.~Thrower();
      },
      "ThrowingValue<kNoThrowCopy>(234)");

  // Test with multiple items in spec list (kNoThrowMove, kNoThrowNew)
  EXPECT_NONFATAL_FAILURE(
      {
        using Thrower =
            ThrowingValue<TypeSpec::kNoThrowMove | TypeSpec::kNoThrowNew>;
        auto thrower = Thrower(345);
        thrower.~Thrower();
      },
      "ThrowingValue<kNoThrowMove | kNoThrowNew>(345)");

  // Test with all items in spec list (kNoThrowCopy, kNoThrowMove, kNoThrowNew)
  EXPECT_NONFATAL_FAILURE(
      {
        using Thrower = ThrowingValue<static_cast<TypeSpec>(-1)>;
        auto thrower = Thrower(456);
        thrower.~Thrower();
      },
      "ThrowingValue<kNoThrowCopy | kNoThrowMove | kNoThrowNew>(456)");
}

template <typename F>
void TestAllocatingOp(const F& f) {
  ExpectNoThrow(f);

  SetCountdown();
  EXPECT_THROW(f(), exceptions_internal::TestBadAllocException);
  UnsetCountdown();
}

TEST(ThrowingValueTest, ThrowingAllocatingOps) {
  // make_unique calls unqualified operator new, so these exercise the
  // ThrowingValue overloads.
  TestAllocatingOp([]() { return absl::make_unique<ThrowingValue<>>(1); });
  TestAllocatingOp([]() { return absl::make_unique<ThrowingValue<>[]>(2); });
}

TEST(ThrowingValueTest, NonThrowingMoveCtor) {
  ThrowingValue<TypeSpec::kNoThrowMove> nothrow_ctor;

  SetCountdown();
  ExpectNoThrow([&nothrow_ctor]() {
    ThrowingValue<TypeSpec::kNoThrowMove> nothrow1 = std::move(nothrow_ctor);
  });
  UnsetCountdown();
}

TEST(ThrowingValueTest, NonThrowingMoveAssign) {
  ThrowingValue<TypeSpec::kNoThrowMove> nothrow_assign1, nothrow_assign2;

  SetCountdown();
  ExpectNoThrow([&nothrow_assign1, &nothrow_assign2]() {
    nothrow_assign1 = std::move(nothrow_assign2);
  });
  UnsetCountdown();
}

TEST(ThrowingValueTest, ThrowingCopyCtor) {
  ThrowingValue<> tv;

  TestOp([&]() { ThrowingValue<> tv_copy(tv); });
}

TEST(ThrowingValueTest, ThrowingCopyAssign) {
  ThrowingValue<> tv1, tv2;

  TestOp([&]() { tv1 = tv2; });
}

TEST(ThrowingValueTest, NonThrowingCopyCtor) {
  ThrowingValue<TypeSpec::kNoThrowCopy> nothrow_ctor;

  SetCountdown();
  ExpectNoThrow([&nothrow_ctor]() {
    ThrowingValue<TypeSpec::kNoThrowCopy> nothrow1(nothrow_ctor);
  });
  UnsetCountdown();
}

TEST(ThrowingValueTest, NonThrowingCopyAssign) {
  ThrowingValue<TypeSpec::kNoThrowCopy> nothrow_assign1, nothrow_assign2;

  SetCountdown();
  ExpectNoThrow([&nothrow_assign1, &nothrow_assign2]() {
    nothrow_assign1 = nothrow_assign2;
  });
  UnsetCountdown();
}

TEST(ThrowingValueTest, ThrowingSwap) {
  ThrowingValue<> bomb1, bomb2;
  TestOp([&]() { std::swap(bomb1, bomb2); });
}

TEST(ThrowingValueTest, NonThrowingSwap) {
  ThrowingValue<TypeSpec::kNoThrowMove> bomb1, bomb2;
  ExpectNoThrow([&]() { std::swap(bomb1, bomb2); });
}

TEST(ThrowingValueTest, NonThrowingAllocation) {
  ThrowingValue<TypeSpec::kNoThrowNew>* allocated;
  ThrowingValue<TypeSpec::kNoThrowNew>* array;

  ExpectNoThrow([&allocated]() {
    allocated = new ThrowingValue<TypeSpec::kNoThrowNew>(1);
    delete allocated;
  });
  ExpectNoThrow([&array]() {
    array = new ThrowingValue<TypeSpec::kNoThrowNew>[2];
    delete[] array;
  });
}

TEST(ThrowingValueTest, NonThrowingDelete) {
  auto* allocated = new ThrowingValue<>(1);
  auto* array = new ThrowingValue<>[2];

  SetCountdown();
  ExpectNoThrow([allocated]() { delete allocated; });
  SetCountdown();
  ExpectNoThrow([array]() { delete[] array; });

  UnsetCountdown();
}

TEST(ThrowingValueTest, NonThrowingPlacementDelete) {
  constexpr int kArrayLen = 2;
  // We intentionally create extra space to store the tag allocated by placement
  // new[].
  constexpr int kStorageLen = 4;

  alignas(ThrowingValue<>) unsigned char buf[sizeof(ThrowingValue<>)];
  alignas(ThrowingValue<>) unsigned char
      array_buf[sizeof(ThrowingValue<>[kStorageLen])];
  auto* placed = new (&buf) ThrowingValue<>(1);
  auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen];

  SetCountdown();
  ExpectNoThrow([placed, &buf]() {
    placed->~ThrowingValue<>();
    ThrowingValue<>::operator delete(placed, &buf);
  });

  SetCountdown();
  ExpectNoThrow([&, placed_array]() {
    for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>();
    ThrowingValue<>::operator delete[](placed_array, &array_buf);
  });

  UnsetCountdown();
}

TEST(ThrowingValueTest, NonThrowingDestructor) {
  auto* allocated = new ThrowingValue<>();

  SetCountdown();
  ExpectNoThrow([allocated]() { delete allocated; });
  UnsetCountdown();
}

TEST(ThrowingBoolTest, ThrowingBool) {
  ThrowingBool t = true;

  // Test that it's contextually convertible to bool
  if (t) {  // NOLINT(whitespace/empty_if_body)
  }
  EXPECT_TRUE(t);

  TestOp([&]() { (void)!t; });
}

TEST(ThrowingAllocatorTest, MemoryManagement) {
  // Just exercise the memory management capabilities under LSan to make sure we
  // don't leak.
  ThrowingAllocator<int> int_alloc;
  int* ip = int_alloc.allocate(1);
  int_alloc.deallocate(ip, 1);
  int* i_array = int_alloc.allocate(2);
  int_alloc.deallocate(i_array, 2);

  ThrowingAllocator<ThrowingValue<>> tv_alloc;
  ThrowingValue<>* ptr = tv_alloc.allocate(1);
  tv_alloc.deallocate(ptr, 1);
  ThrowingValue<>* tv_array = tv_alloc.allocate(2);
  tv_alloc.deallocate(tv_array, 2);
}

TEST(ThrowingAllocatorTest, CallsGlobalNew) {
  ThrowingAllocator<ThrowingValue<>, AllocSpec::kNoThrowAllocate> nothrow_alloc;
  ThrowingValue<>* ptr;

  SetCountdown();
  // This will only throw if ThrowingValue::new is called.
  ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });
  nothrow_alloc.deallocate(ptr, 1);

  UnsetCountdown();
}

TEST(ThrowingAllocatorTest, ThrowingConstructors) {
  ThrowingAllocator<int> int_alloc;
  int* ip = nullptr;

  SetCountdown();
  EXPECT_THROW(ip = int_alloc.allocate(1), TestException);
  ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });

  *ip = 1;
  SetCountdown();
  EXPECT_THROW(int_alloc.construct(ip, 2), TestException);
  EXPECT_EQ(*ip, 1);
  int_alloc.deallocate(ip, 1);

  UnsetCountdown();
}

TEST(ThrowingAllocatorTest, NonThrowingConstruction) {
  {
    ThrowingAllocator<int, AllocSpec::kNoThrowAllocate> int_alloc;
    int* ip = nullptr;

    SetCountdown();
    ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });

    SetCountdown();
    ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });

    EXPECT_EQ(*ip, 2);
    int_alloc.deallocate(ip, 1);

    UnsetCountdown();
  }

  {
    ThrowingAllocator<int> int_alloc;
    int* ip = nullptr;
    ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
    ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });
    EXPECT_EQ(*ip, 2);
    int_alloc.deallocate(ip, 1);
  }

  {
    ThrowingAllocator<ThrowingValue<>, AllocSpec::kNoThrowAllocate>
        nothrow_alloc;
    ThrowingValue<>* ptr;

    SetCountdown();
    ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });

    SetCountdown();
    ExpectNoThrow(
        [&]() { nothrow_alloc.construct(ptr, 2, testing::nothrow_ctor); });

    EXPECT_EQ(ptr->Get(), 2);
    nothrow_alloc.destroy(ptr);
    nothrow_alloc.deallocate(ptr, 1);

    UnsetCountdown();
  }

  {
    ThrowingAllocator<int> a;

    SetCountdown();
    ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = a; });

    SetCountdown();
    ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = std::move(a); });

    UnsetCountdown();
  }
}

TEST(ThrowingAllocatorTest, ThrowingAllocatorConstruction) {
  ThrowingAllocator<int> a;
  TestOp([]() { ThrowingAllocator<int> a; });
  TestOp([&]() { a.select_on_container_copy_construction(); });
}

TEST(ThrowingAllocatorTest, State) {
  ThrowingAllocator<int> a1, a2;
  EXPECT_NE(a1, a2);

  auto a3 = a1;
  EXPECT_EQ(a3, a1);
  int* ip = a1.allocate(1);
  EXPECT_EQ(a3, a1);
  a3.deallocate(ip, 1);
  EXPECT_EQ(a3, a1);
}

TEST(ThrowingAllocatorTest, InVector) {
  std::vector<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> v;
  for (int i = 0; i < 20; ++i) v.push_back({});
  for (int i = 0; i < 20; ++i) v.pop_back();
}

TEST(ThrowingAllocatorTest, InList) {
  std::list<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> l;
  for (int i = 0; i < 20; ++i) l.push_back({});
  for (int i = 0; i < 20; ++i) l.pop_back();
  for (int i = 0; i < 20; ++i) l.push_front({});
  for (int i = 0; i < 20; ++i) l.pop_front();
}

template <typename TesterInstance, typename = void>
struct NullaryTestValidator : public std::false_type {};

template <typename TesterInstance>
struct NullaryTestValidator<
    TesterInstance,
    absl::void_t<decltype(std::declval<TesterInstance>().Test())>>
    : public std::true_type {};

template <typename TesterInstance>
bool HasNullaryTest(const TesterInstance&) {
  return NullaryTestValidator<TesterInstance>::value;
}

void DummyOp(void*) {}

template <typename TesterInstance, typename = void>
struct UnaryTestValidator : public std::false_type {};

template <typename TesterInstance>
struct UnaryTestValidator<
    TesterInstance,
    absl::void_t<decltype(std::declval<TesterInstance>().Test(DummyOp))>>
    : public std::true_type {};

template <typename TesterInstance>
bool HasUnaryTest(const TesterInstance&) {
  return UnaryTestValidator<TesterInstance>::value;
}

TEST(ExceptionSafetyTesterTest, IncompleteTypesAreNotTestable) {
  using T = exceptions_internal::UninitializedT;
  auto op = [](T* t) {};
  auto inv = [](T*) { return testing::AssertionSuccess(); };
  auto fac = []() { return absl::make_unique<T>(); };

  // Test that providing operation and inveriants still does not allow for the
  // the invocation of .Test() and .Test(op) because it lacks a factory
  auto without_fac =
      testing::MakeExceptionSafetyTester().WithOperation(op).WithContracts(
          inv, testing::strong_guarantee);
  EXPECT_FALSE(HasNullaryTest(without_fac));
  EXPECT_FALSE(HasUnaryTest(without_fac));

  // Test that providing contracts and factory allows the invocation of
  // .Test(op) but does not allow for .Test() because it lacks an operation
  auto without_op = testing::MakeExceptionSafetyTester()
                        .WithContracts(inv, testing::strong_guarantee)
                        .WithFactory(fac);
  EXPECT_FALSE(HasNullaryTest(without_op));
  EXPECT_TRUE(HasUnaryTest(without_op));

  // Test that providing operation and factory still does not allow for the
  // the invocation of .Test() and .Test(op) because it lacks contracts
  auto without_inv =
      testing::MakeExceptionSafetyTester().WithOperation(op).WithFactory(fac);
  EXPECT_FALSE(HasNullaryTest(without_inv));
  EXPECT_FALSE(HasUnaryTest(without_inv));
}

struct ExampleStruct {};

std::unique_ptr<ExampleStruct> ExampleFunctionFactory() {
  return absl::make_unique<ExampleStruct>();
}

void ExampleFunctionOperation(ExampleStruct*) {}

testing::AssertionResult ExampleFunctionContract(ExampleStruct*) {
  return testing::AssertionSuccess();
}

struct {
  std::unique_ptr<ExampleStruct> operator()() const {
    return ExampleFunctionFactory();
  }
} example_struct_factory;

struct {
  void operator()(ExampleStruct*) const {}
} example_struct_operation;

struct {
  testing::AssertionResult operator()(ExampleStruct* example_struct) const {
    return ExampleFunctionContract(example_struct);
  }
} example_struct_contract;

auto example_lambda_factory = []() { return ExampleFunctionFactory(); };

auto example_lambda_operation = [](ExampleStruct*) {};

auto example_lambda_contract = [](ExampleStruct* example_struct) {
  return ExampleFunctionContract(example_struct);
};

// Testing that function references, pointers, structs with operator() and
// lambdas can all be used with ExceptionSafetyTester
TEST(ExceptionSafetyTesterTest, MixedFunctionTypes) {
  // function reference
  EXPECT_TRUE(testing::MakeExceptionSafetyTester()
                  .WithFactory(ExampleFunctionFactory)
                  .WithOperation(ExampleFunctionOperation)
                  .WithContracts(ExampleFunctionContract)
                  .Test());

  // function pointer
  EXPECT_TRUE(testing::MakeExceptionSafetyTester()
                  .WithFactory(&ExampleFunctionFactory)
                  .WithOperation(&ExampleFunctionOperation)
                  .WithContracts(&ExampleFunctionContract)
                  .Test());

  // struct
  EXPECT_TRUE(testing::MakeExceptionSafetyTester()
                  .WithFactory(example_struct_factory)
                  .WithOperation(example_struct_operation)
                  .WithContracts(example_struct_contract)
                  .Test());

  // lambda
  EXPECT_TRUE(testing::MakeExceptionSafetyTester()
                  .WithFactory(example_lambda_factory)
                  .WithOperation(example_lambda_operation)
                  .WithContracts(example_lambda_contract)
                  .Test());
}

struct NonNegative {
  bool operator==(const NonNegative& other) const { return i == other.i; }
  int i;
};

testing::AssertionResult CheckNonNegativeInvariants(NonNegative* g) {
  if (g->i >= 0) {
    return testing::AssertionSuccess();
  }
  return testing::AssertionFailure()
         << "i should be non-negative but is " << g->i;
}

struct {
  template <typename T>
  void operator()(T* t) const {
    (*t)();
  }
} invoker;

auto tester =
    testing::MakeExceptionSafetyTester().WithOperation(invoker).WithContracts(
        CheckNonNegativeInvariants);
auto strong_tester = tester.WithContracts(testing::strong_guarantee);

struct FailsBasicGuarantee : public NonNegative {
  void operator()() {
    --i;
    ThrowingValue<> bomb;
    ++i;
  }
};

TEST(ExceptionCheckTest, BasicGuaranteeFailure) {
  EXPECT_FALSE(tester.WithInitialValue(FailsBasicGuarantee{}).Test());
}

struct FollowsBasicGuarantee : public NonNegative {
  void operator()() {
    ++i;
    ThrowingValue<> bomb;
  }
};

TEST(ExceptionCheckTest, BasicGuarantee) {
  EXPECT_TRUE(tester.WithInitialValue(FollowsBasicGuarantee{}).Test());
}

TEST(ExceptionCheckTest, StrongGuaranteeFailure) {
  EXPECT_FALSE(strong_tester.WithInitialValue(FailsBasicGuarantee{}).Test());
  EXPECT_FALSE(strong_tester.WithInitialValue(FollowsBasicGuarantee{}).Test());
}

struct BasicGuaranteeWithExtraContracts : public NonNegative {
  // After operator(), i is incremented.  If operator() throws, i is set to 9999
  void operator()() {
    int old_i = i;
    i = kExceptionSentinel;
    ThrowingValue<> bomb;
    i = ++old_i;
  }

  static constexpr int kExceptionSentinel = 9999;
};
constexpr int BasicGuaranteeWithExtraContracts::kExceptionSentinel;

TEST(ExceptionCheckTest, BasicGuaranteeWithExtraContracts) {
  auto tester_with_val =
      tester.WithInitialValue(BasicGuaranteeWithExtraContracts{});
  EXPECT_TRUE(tester_with_val.Test());
  EXPECT_TRUE(
      tester_with_val
          .WithContracts([](BasicGuaranteeWithExtraContracts* o) {
            if (o->i == BasicGuaranteeWithExtraContracts::kExceptionSentinel) {
              return testing::AssertionSuccess();
            }
            return testing::AssertionFailure()
                   << "i should be "
                   << BasicGuaranteeWithExtraContracts::kExceptionSentinel
                   << ", but is " << o->i;
          })
          .Test());
}

struct FollowsStrongGuarantee : public NonNegative {
  void operator()() { ThrowingValue<> bomb; }
};

TEST(ExceptionCheckTest, StrongGuarantee) {
  EXPECT_TRUE(tester.WithInitialValue(FollowsStrongGuarantee{}).Test());
  EXPECT_TRUE(strong_tester.WithInitialValue(FollowsStrongGuarantee{}).Test());
}

struct HasReset : public NonNegative {
  void operator()() {
    i = -1;
    ThrowingValue<> bomb;
    i = 1;
  }

  void reset() { i = 0; }
};

testing::AssertionResult CheckHasResetContracts(HasReset* h) {
  h->reset();
  return testing::AssertionResult(h->i == 0);
}

TEST(ExceptionCheckTest, ModifyingChecker) {
  auto set_to_1000 = [](FollowsBasicGuarantee* g) {
    g->i = 1000;
    return testing::AssertionSuccess();
  };
  auto is_1000 = [](FollowsBasicGuarantee* g) {
    return testing::AssertionResult(g->i == 1000);
  };
  auto increment = [](FollowsStrongGuarantee* g) {
    ++g->i;
    return testing::AssertionSuccess();
  };

  EXPECT_FALSE(tester.WithInitialValue(FollowsBasicGuarantee{})
                   .WithContracts(set_to_1000, is_1000)
                   .Test());
  EXPECT_TRUE(strong_tester.WithInitialValue(FollowsStrongGuarantee{})
                  .WithContracts(increment)
                  .Test());
  EXPECT_TRUE(testing::MakeExceptionSafetyTester()
                  .WithInitialValue(HasReset{})
                  .WithContracts(CheckHasResetContracts)
                  .Test(invoker));
}

TEST(ExceptionSafetyTesterTest, ResetsCountdown) {
  auto test =
      testing::MakeExceptionSafetyTester()
          .WithInitialValue(ThrowingValue<>())
          .WithContracts([](ThrowingValue<>*) { return AssertionSuccess(); })
          .WithOperation([](ThrowingValue<>*) {});
  ASSERT_TRUE(test.Test());
  // If the countdown isn't reset because there were no exceptions thrown, then
  // this will fail with a termination from an unhandled exception
  EXPECT_TRUE(test.Test());
}

struct NonCopyable : public NonNegative {
  NonCopyable(const NonCopyable&) = delete;
  NonCopyable() : NonNegative{0} {}

  void operator()() { ThrowingValue<> bomb; }
};

TEST(ExceptionCheckTest, NonCopyable) {
  auto factory = []() { return absl::make_unique<NonCopyable>(); };
  EXPECT_TRUE(tester.WithFactory(factory).Test());
  EXPECT_TRUE(strong_tester.WithFactory(factory).Test());
}

struct NonEqualityComparable : public NonNegative {
  void operator()() { ThrowingValue<> bomb; }

  void ModifyOnThrow() {
    ++i;
    ThrowingValue<> bomb;
    static_cast<void>(bomb);
    --i;
  }
};

TEST(ExceptionCheckTest, NonEqualityComparable) {
  auto nec_is_strong = [](NonEqualityComparable* nec) {
    return testing::AssertionResult(nec->i == NonEqualityComparable().i);
  };
  auto strong_nec_tester = tester.WithInitialValue(NonEqualityComparable{})
                               .WithContracts(nec_is_strong);

  EXPECT_TRUE(strong_nec_tester.Test());
  EXPECT_FALSE(strong_nec_tester.Test(
      [](NonEqualityComparable* n) { n->ModifyOnThrow(); }));
}

template <typename T>
struct ExhaustivenessTester {
  void operator()() {
    successes |= 1;
    T b1;
    static_cast<void>(b1);
    successes |= (1 << 1);
    T b2;
    static_cast<void>(b2);
    successes |= (1 << 2);
    T b3;
    static_cast<void>(b3);
    successes |= (1 << 3);
  }

  bool operator==(const ExhaustivenessTester<ThrowingValue<>>&) const {
    return true;
  }

  static unsigned char successes;
};

struct {
  template <typename T>
  testing::AssertionResult operator()(ExhaustivenessTester<T>*) const {
    return testing::AssertionSuccess();
  }
} CheckExhaustivenessTesterContracts;

template <typename T>
unsigned char ExhaustivenessTester<T>::successes = 0;

TEST(ExceptionCheckTest, Exhaustiveness) {
  auto exhaust_tester = testing::MakeExceptionSafetyTester()
                            .WithContracts(CheckExhaustivenessTesterContracts)
                            .WithOperation(invoker);

  EXPECT_TRUE(
      exhaust_tester.WithInitialValue(ExhaustivenessTester<int>{}).Test());
  EXPECT_EQ(ExhaustivenessTester<int>::successes, 0xF);

  EXPECT_TRUE(
      exhaust_tester.WithInitialValue(ExhaustivenessTester<ThrowingValue<>>{})
          .WithContracts(testing::strong_guarantee)
          .Test());
  EXPECT_EQ(ExhaustivenessTester<ThrowingValue<>>::successes, 0xF);
}

struct LeaksIfCtorThrows : private exceptions_internal::TrackedObject {
  LeaksIfCtorThrows() : TrackedObject(ABSL_PRETTY_FUNCTION) {
    ++counter;
    ThrowingValue<> v;
    static_cast<void>(v);
    --counter;
  }
  LeaksIfCtorThrows(const LeaksIfCtorThrows&) noexcept
      : TrackedObject(ABSL_PRETTY_FUNCTION) {}
  static int counter;
};
int LeaksIfCtorThrows::counter = 0;

TEST(ExceptionCheckTest, TestLeakyCtor) {
  testing::TestThrowingCtor<LeaksIfCtorThrows>();
  EXPECT_EQ(LeaksIfCtorThrows::counter, 1);
  LeaksIfCtorThrows::counter = 0;
}

struct Tracked : private exceptions_internal::TrackedObject {
  Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {}
};

TEST(ConstructorTrackerTest, CreatedBefore) {
  Tracked a, b, c;
  exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
}

TEST(ConstructorTrackerTest, CreatedAfter) {
  exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
  Tracked a, b, c;
}

TEST(ConstructorTrackerTest, NotDestroyedAfter) {
  alignas(Tracked) unsigned char storage[sizeof(Tracked)];
  EXPECT_NONFATAL_FAILURE(
      {
        exceptions_internal::ConstructorTracker ct(
            exceptions_internal::countdown);
        new (&storage) Tracked();
      },
      "not destroyed");
}

TEST(ConstructorTrackerTest, DestroyedTwice) {
  exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
  EXPECT_NONFATAL_FAILURE(
      {
        Tracked t;
        t.~Tracked();
      },
      "re-destroyed");
}

TEST(ConstructorTrackerTest, ConstructedTwice) {
  exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
  alignas(Tracked) unsigned char storage[sizeof(Tracked)];
  EXPECT_NONFATAL_FAILURE(
      {
        new (&storage) Tracked();
        new (&storage) Tracked();
        reinterpret_cast<Tracked*>(&storage)->~Tracked();
      },
      "re-constructed");
}

TEST(ThrowingValueTraitsTest, RelationalOperators) {
  ThrowingValue<> a, b;
  EXPECT_TRUE((std::is_convertible<decltype(a == b), bool>::value));
  EXPECT_TRUE((std::is_convertible<decltype(a != b), bool>::value));
  EXPECT_TRUE((std::is_convertible<decltype(a < b), bool>::value));
  EXPECT_TRUE((std::is_convertible<decltype(a <= b), bool>::value));
  EXPECT_TRUE((std::is_convertible<decltype(a > b), bool>::value));
  EXPECT_TRUE((std::is_convertible<decltype(a >= b), bool>::value));
}

TEST(ThrowingAllocatorTraitsTest, Assignablility) {
  EXPECT_TRUE(absl::is_move_assignable<ThrowingAllocator<int>>::value);
  EXPECT_TRUE(absl::is_copy_assignable<ThrowingAllocator<int>>::value);
  EXPECT_TRUE(std::is_nothrow_move_assignable<ThrowingAllocator<int>>::value);
  EXPECT_TRUE(std::is_nothrow_copy_assignable<ThrowingAllocator<int>>::value);
}

}  // namespace

}  // namespace testing

#endif  // ABSL_HAVE_EXCEPTIONS
