//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03
// UNSUPPORTED: libcpp-hardening-mode=none
// XFAIL: availability-verbose_abort-missing

#include <cassert>
#include <cstdio>
#include <string>

#include "check_assertion.h"

template <class Func>
bool TestDeathTest(
    Outcome expected_outcome, DeathCause expected_cause, const char* stmt, Func&& func, const Matcher& matcher) {
  auto get_matcher = [&] {
#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
    return matcher;
#else
    (void)matcher;
    return MakeAnyMatcher();
#endif
  };

  DeathTest test_case;
  DeathTestResult test_result = test_case.Run(std::array<DeathCause, 1>{expected_cause}, func, get_matcher());
  std::string maybe_failure_description;

  Outcome outcome = test_result.outcome();
  if (expected_outcome != outcome) {
    maybe_failure_description +=
        std::string("Test outcome was different from expected; expected ") + ToString(expected_outcome) +
        ", got: " + ToString(outcome);
  }

  DeathCause cause = test_result.cause();
  if (expected_cause != cause) {
    auto failure_description =
        std::string("Cause of death was different from expected; expected ") + ToString(expected_cause) +
        ", got: " + ToString(cause);
    if (maybe_failure_description.empty()) {
      maybe_failure_description = failure_description;
    } else {
      maybe_failure_description += std::string("; ") + failure_description;
    }
  }

  if (!maybe_failure_description.empty()) {
    test_case.PrintFailureDetails(maybe_failure_description, stmt, test_result.cause());
    return false;
  }

  return true;
}

// clang-format off

#define TEST_DEATH_TEST(outcome, cause, ...)                   \
  assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, MakeAnyMatcher()) ))
#define TEST_DEATH_TEST_MATCHES(outcome, cause, matcher, ...)  \
  assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) ))

// clang-format on

#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
DeathCause assertion_death_cause = DeathCause::VerboseAbort;
#else
DeathCause assertion_death_cause = DeathCause::Trap;
#endif

int main(int, char**) {
  auto fail_assert     = [] { _LIBCPP_ASSERT(false, "Some message"); };
  Matcher good_matcher = MakeAssertionMessageMatcher("Some message");
  Matcher bad_matcher  = MakeAssertionMessageMatcher("Bad expected message");

  // Test the implementation of death tests. We're bypassing the assertions added by the actual `EXPECT_DEATH` macros
  // which allows us to test failure cases (where the assertion would fail) as well.
  {
    // Success -- `std::terminate`.
    TEST_DEATH_TEST(Outcome::Success, DeathCause::StdTerminate, std::terminate());

    // Success -- trapping.
    TEST_DEATH_TEST(Outcome::Success, DeathCause::Trap, __builtin_trap());

    // Success -- assertion failure with any matcher.
    TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, MakeAnyMatcher(), fail_assert());

    // Success -- assertion failure with a specific matcher.
    TEST_DEATH_TEST_MATCHES(Outcome::Success, assertion_death_cause, good_matcher, fail_assert());

#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
    // Failure -- error message doesn't match.
    TEST_DEATH_TEST_MATCHES(Outcome::UnexpectedErrorMessage, assertion_death_cause, bad_matcher, fail_assert());
#endif

    // Invalid cause -- child did not die.
    TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0));

    // Invalid cause --  unknown.
    TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::Unknown, std::exit(13));
  }

  // Test the `EXPECT_DEATH` macros themselves. Since they assert success, we can only test successful cases.
  {
    auto invoke_verbose_abort = [] { _LIBCPP_VERBOSE_ABORT("contains some message"); };
    auto invoke_abort         = [] { std::abort(); };

    auto simple_matcher = [](const std::string& text) {
      bool success = text.find("some") != std::string::npos;
      return MatchResult(success, "");
    };

    EXPECT_ANY_DEATH(_LIBCPP_VERBOSE_ABORT(""));
    EXPECT_ANY_DEATH(std::abort());
    EXPECT_ANY_DEATH(std::terminate());
    EXPECT_DEATH(invoke_verbose_abort());
    EXPECT_DEATH_MATCHES(MakeAnyMatcher(), invoke_verbose_abort());
    EXPECT_DEATH_MATCHES(simple_matcher, invoke_verbose_abort());
    EXPECT_STD_ABORT(invoke_abort());
    EXPECT_STD_TERMINATE([] { std::terminate(); });
    TEST_LIBCPP_ASSERT_FAILURE(fail_assert(), "Some message");
  }

  return 0;
}
