//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03

// UNSUPPORTED: no-exceptions

#include <cassert>
#include <type_traits>
#include <utility>

#include "test_macros.h"

TEST_CONSTEXPR_CXX20 bool test() {
    // Make sure the transaction is rolled back if it is not marked as complete when
    // it goes out of scope.
    {
        bool rolled_back = false;
        {
            auto rollback = [&] { rolled_back = true; };
            std::__exception_guard<decltype(rollback)> g(rollback);
        }
        assert(rolled_back);
    }

    // Make sure the transaction is not rolled back if it is marked as complete when
    // it goes out of scope.
    {
        bool rolled_back = false;
        {
            auto rollback = [&] { rolled_back = true; };
            std::__exception_guard<decltype(rollback)> g(rollback);
            g.__complete();
        }
        assert(!rolled_back);
    }

    // Make sure that we will perform the right number of rollbacks when a transaction has
    // been moved around
    {
        // When we don't complete it (exactly 1 rollback should happen)
        {
            int rollbacks = 0;
            {
                auto rollback = [&] { ++rollbacks; };
                std::__exception_guard<decltype(rollback)> g(rollback);
                auto other = std::move(g);
            }
            assert(rollbacks == 1);
        }

        // When we do complete it (no rollbacks should happen)
        {
            int rollbacks = 0;
            {
                auto rollback = [&] { ++rollbacks; };
                std::__exception_guard<decltype(rollback)> g(rollback);
                auto other = std::move(g);
                other.__complete();
            }
            assert(rollbacks == 0);
        }
    }

    // Basic properties of the type
    {
        struct Rollback { void operator()() const { } };
        using Transaction = std::__exception_guard<Rollback>;

        static_assert(!std::is_default_constructible<Transaction>::value, "");

        static_assert(!std::is_copy_constructible<Transaction>::value, "");
        static_assert( std::is_move_constructible<Transaction>::value, "");

        static_assert(!std::is_copy_assignable<Transaction>::value, "");
        static_assert(!std::is_move_assignable<Transaction>::value, "");

        // Check noexcept-ness of a few operations
        {
            struct ThrowOnMove {
                ThrowOnMove(ThrowOnMove&&) noexcept(false) { }
                void operator()() const { }
            };
            using ThrowOnMoveTransaction = std::__exception_guard<ThrowOnMove>;

            ASSERT_NOEXCEPT(std::declval<Transaction>().__complete());
            static_assert( std::is_nothrow_move_constructible<Transaction>::value, "");
            static_assert(!std::is_nothrow_move_constructible<ThrowOnMoveTransaction>::value, "");
        }
    }

    return true;
}

void test_exceptions() {
#ifndef TEST_HAS_NO_EXCEPTIONS
    // Make sure the rollback is performed when an exception is thrown during the
    // lifetime of the transaction.
    {
        bool rolled_back = false;
        auto rollback = [&] { rolled_back = true; };
        try {
            std::__exception_guard<decltype(rollback)> g(rollback);
            throw 0;
        } catch (...) { }
        assert(rolled_back);
    }

    // Make sure we don't roll back if an exception is thrown but the transaction
    // has been marked as complete when that happens.
    {
        bool rolled_back = false;
        auto rollback = [&] { rolled_back = true; };
        try {
            std::__exception_guard<decltype(rollback)> g(rollback);
            g.__complete();
            throw 0;
        } catch (...) { }
        assert(!rolled_back);
    }

    // Make sure __exception_guard does not rollback if the transaction is marked as
    // completed within a destructor.
    {
        struct S {
            explicit S(bool& x) : x_(x) { }

            ~S() {
                auto rollback = [this]{ x_ = true; };
                std::__exception_guard<decltype(rollback)> g(rollback);
                g.__complete();
            }

            bool& x_;
        };

        bool rolled_back = false;
        try {
            S s(rolled_back);
            throw 0;
        } catch (...) {
            assert(!rolled_back);
        }
    }
#endif // TEST_HAS_NO_EXCEPTIONS
}

int main(int, char**) {
    test();
    test_exceptions();
#if TEST_STD_VER > 17
    static_assert(test(), "");
#endif
    return 0;
}
