//======================================================================
//-----------------------------------------------------------------------
/**
 * @file        iutest_failurecount_assertion.hpp
 * @brief       iris unit test 失敗数アサーション 定義 ファイル
 *
 * @author      t.shirayanagi
 * @par         copyright
 * Copyright (C) 2014-2019, Takazumi Shirayanagi\n
 * This software is released under the new BSD License,
 * see LICENSE
*/
//-----------------------------------------------------------------------
//======================================================================
#ifndef INCG_IRIS_IUTEST_FAILURECOUNT_ASSERTION_HPP_DB1A509F_DD9A_4F70_A926_131B0B0F5438_
#define INCG_IRIS_IUTEST_FAILURECOUNT_ASSERTION_HPP_DB1A509F_DD9A_4F70_A926_131B0B0F5438_

//======================================================================
// define
#if defined(_MSC_VER) && !defined(__clang__)
// /ZI オプションだと __LINE__ が __LINE__Var+N(Nは番号) になりコンパイルエラーになるための対応
#  ifndef __LINE__Var
#    define __LINE__Var 0
#  endif
#endif

/**
 * @ingroup IUTEST_EXPECT_
 * @brief   失敗数アサーション
 * @param   statements  = 複合ステートメント
 * @param   n = 失敗数上限値 (failed count < n)
*/
#define IUTEST_EXPECT_FAILURECOUNT_LT(statements, n) IUTEST_TEST_FAILURECOUNT_LT(statements, n, #statements, IUTEST_EXPECT_FAILURE)

/**
 * @ingroup IUTEST_INFORM_
 * @brief   失敗数アサーション
 * @param   statements  = 複合ステートメント
 * @param   n = 失敗数上限値 (failed count < n)
*/
#define IUTEST_INFORM_FAILURECOUNT_LT(statements, n) IUTEST_TEST_FAILURECOUNT_LT(statements, n, #statements, IUTEST_INFORM_FAILURE)

/**
 * @ingroup IUTEST_ASSUME_
 * @brief   失敗数アサーション
 * @param   statements  = 複合ステートメント
 * @param   n = 失敗数上限値 (failed count < n)
*/
#define IUTEST_ASSUME_FAILURECOUNT_LT(statements, n) IUTEST_TEST_FAILURECOUNT_LT(statements, n, #statements, IUTEST_ASSUME_FAILURE)

/**
 * @ingroup IUTEST_ASSERT_
 * @brief   失敗数アサーション
 * @param   statements  = 複合ステートメント
 * @param   n = 失敗数上限値 (failed count < n)
*/
#define IUTEST_ASSERT_FAILURECOUNT_LT(statements, n) IUTEST_TEST_FAILURECOUNT_LT(statements, n, #statements, IUTEST_ASSERT_FAILURE)

/**
 * @private
 * @{
*/

#if IUTEST_HAS_LAMBDA_STATEMENTS

#if IUTEST_HAS_EXCEPTIONS && IUTEST_USE_THROW_ON_ASSERTION_FAILURE
#  define IIUT_STATEMENTS_EXECUTER(statements)  [&](){ try {    \
    ::iutest::detail::ScopedDisableTestFailureBreak statements_failure_count_testflag_guard;    \
    statements;                                                 \
    } catch(...) {}                                             \
    }()
#else
#  define IIUT_STATEMENTS_EXECUTER(statements)                  \
    [&](){ ::iutest::detail::ScopedDisableTestFailureBreak statements_failure_count_testflag_guard; statements; }()
#endif

#define IUTEST_TEST_FAILURECOUNT_LT(statements, count, statements_str, on_failure)  \
    IUTEST_AMBIGUOUS_ELSE_BLOCKER_                                                  \
    if( const ::iutest::AssertionResult statemnets_ar = [&]() -> ::iutest::AssertionResult {    \
        ::iutest::detail::StatementsFailureCount statement_failure_checker;         \
        IIUT_STATEMENTS_EXECUTER(statements);                                       \
        return statement_failure_checker.GetResult(count, statements_str);          \
    }() )                                                                           \
        ;                                                                           \
    else                                                                            \
        on_failure(statemnets_ar.message())

#else

#if IUTEST_HAS_EXCEPTIONS && IUTEST_USE_THROW_ON_ASSERTION_FAILURE
#  define IIUT_STATEMENTS_EXECUTER(statements) struct IUTestStatementsExecuter {    \
    static void Execute() { ::iutest::detail::ScopedDisableTestFailureBreak guard;  \
    try { statements; } catch(...) {} }                                             \
    };                                                                              \
    IUTestStatementsExecuter::Execute()
#else
#  define IIUT_STATEMENTS_EXECUTER(statements) struct IUTestStatementsExecuter {    \
    static void Execute() { ::iutest::detail::ScopedDisableTestFailureBreak guard;  \
        statements; }                                                               \
    };                                                                              \
    IUTestStatementsExecuter::Execute()
#endif

#define IUTEST_TEST_FAILURECOUNT_LT(statements, count, statements_str, on_failure)  \
    IUTEST_AMBIGUOUS_ELSE_BLOCKER_                                                  \
    if( ::iutest::AssertionResult statemnets_ar = ::iutest::AssertionSuccess() ) {  \
        ::iutest::detail::StatementsFailureCount statement_failure_checker;         \
        IIUT_STATEMENTS_EXECUTER(statements);                                       \
        ::iutest::AssertionResult ar = statement_failure_checker.GetResult(count, statements_str);  \
        if( !ar ) {                                                                 \
            statemnets_ar << ar.message();                                          \
            goto IUTEST_PP_CAT(iutest_label_test_failurecount_lt_, __LINE__);       \
        }                                                                           \
    } else                                                                          \
        IUTEST_PP_CAT(iutest_label_test_failurecount_lt_, __LINE__):                \
        on_failure(statemnets_ar.message())

#endif

/**
 * @}
*/

namespace iutest {
namespace detail
{

//======================================================================
// class

/**
 * @brief   statements check
*/
class StatementsFailureCount : public NewTestPartResultCheckHelper::Collector< NoTestPartResultReporter >
{
public:
    AssertionResult GetResult(size_t cnt, const char* stetements_str)
    {
        const size_t num = count();
        size_t n=0;
        for( size_t i=0; i < num; ++i )
        {
            if( GetTestPartResult(i).failed() )
            {
                ++n;
            }
        }
        if( n < cnt )
        {
            return AssertionSuccess();
        }
        AssertionResult ar = AssertionFailure() << stetements_str << "\nExpected: failure count less than " << cnt << "\n  Actual: " << n;
        ar << "\nReported results:";
        for( size_t i=0; i < num; ++i )
        {
            ar << "\n" << GetTestPartResult(i).message();
        }
        return ar;
    }
};

/**
 * @brief   フラグ変更スコープ
*/
class ScopedDisableTestFailureBreak : public TestFlag::ScopedGuard
{
public:
    ScopedDisableTestFailureBreak()
    {
        IUTEST_FLAG(throw_on_failure) = false;
        IUTEST_FLAG(break_on_failure) = false;
    }
};

}   // end of namespace detail
}   // end of namespace iutest

#endif // INCG_IRIS_IUTEST_FAILURECOUNT_ASSERTION_HPP_DB1A509F_DD9A_4F70_A926_131B0B0F5438_
