/*
 *          Copyright Andrey Semashev 2007 - 2015.
 * Distributed under the Boost Software License, Version 1.0.
 *    (See accompanying file LICENSE_1_0.txt or copy at
 *          http://www.boost.org/LICENSE_1_0.txt)
 */
/*!
 * \file   util_exception_handler.cpp
 * \author Andrey Semashev
 * \date   13.07.2009
 *
 * \brief  This header contains tests for the exception handler functional objects.
 */

#define BOOST_TEST_MODULE util_exception_handler

#include <string>
#include <typeinfo>
#include <stdexcept>
#include <boost/mpl/vector.hpp>
#include <boost/smart_ptr/scoped_ptr.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/log/utility/exception_handler.hpp>

namespace logging = boost::log;

namespace {

    struct my_handler1
    {
        typedef void result_type;

        std::type_info const*& m_pExceptionType;

        my_handler1(std::type_info const*& p) : m_pExceptionType(p) {}

        void operator() (std::exception&) const
        {
            m_pExceptionType = &typeid(std::exception);
        }
        void operator() (std::runtime_error&) const
        {
            m_pExceptionType = &typeid(std::runtime_error);
        }
    };

    struct my_handler2
    {
        typedef void result_type;
        typedef boost::mpl::vector< std::runtime_error, std::exception >::type exception_types;

        std::type_info const*& m_pExceptionType;

        explicit my_handler2(std::type_info const*& p) : m_pExceptionType(p) {}

        void operator() (std::exception&) const
        {
            m_pExceptionType = &typeid(std::exception);
        }
        void operator() (std::runtime_error&) const
        {
            m_pExceptionType = &typeid(std::runtime_error);
        }
    };

    struct my_handler1_nothrow
    {
        typedef void result_type;

        std::type_info const*& m_pExceptionType;

        my_handler1_nothrow(std::type_info const*& p) : m_pExceptionType(p) {}

        void operator() (std::exception&) const
        {
            m_pExceptionType = &typeid(std::exception);
        }
        void operator() (std::runtime_error&) const
        {
            m_pExceptionType = &typeid(std::runtime_error);
        }
        void operator() () const
        {
            m_pExceptionType = &typeid(void);
        }
    };

    struct my_handler2_nothrow
    {
        typedef void result_type;
        typedef boost::mpl::vector< std::runtime_error, std::exception >::type exception_types;

        std::type_info const*& m_pExceptionType;

        explicit my_handler2_nothrow(std::type_info const*& p) : m_pExceptionType(p) {}

        void operator() (std::exception&) const
        {
            m_pExceptionType = &typeid(std::exception);
        }
        void operator() (std::runtime_error&) const
        {
            m_pExceptionType = &typeid(std::runtime_error);
        }
        void operator() () const
        {
            m_pExceptionType = &typeid(void);
        }
    };

    struct my_exception {};

    struct my_function0
    {
        struct impl_base
        {
            virtual ~impl_base() {}
            virtual void invoke() = 0;
        };

        template< typename T >
        struct impl : public impl_base
        {
            T m_Fun;

            explicit impl(T const& fun) : m_Fun(fun) {}
            void invoke() { m_Fun(); }
        };

    private:
        boost::scoped_ptr< impl_base > m_pImpl;

    public:
        template< typename T >
        my_function0& operator= (T const& fun)
        {
            m_pImpl.reset(new impl< T >(fun));
            return *this;
        }

        void operator() () const
        {
            m_pImpl->invoke();
        }
    };

} // namespace

// Tests for handler with explicit exception types specification
BOOST_AUTO_TEST_CASE(explicit_exception_types)
{
    std::type_info const* pExceptionType = 0;
    my_function0 handler;
    handler = logging::make_exception_handler<
        std::runtime_error,
        std::exception
    >(my_handler1(pExceptionType));

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::runtime_error));
    pExceptionType = 0;

    try
    {
        throw std::logic_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;

    try
    {
        throw my_exception();
    }
    catch (...)
    {
        BOOST_CHECK_THROW(handler(), my_exception);
    }
    BOOST_REQUIRE(pExceptionType == 0);

    // Verify that exception types are checked in the specified order
    handler = logging::make_exception_handler<
        std::exception,
        std::runtime_error
    >(my_handler1(pExceptionType));

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;
}

// Tests for handler with explicit exception types specification (no-throw version)
BOOST_AUTO_TEST_CASE(explicit_exception_types_nothrow)
{
    std::type_info const* pExceptionType = 0;
    my_function0 handler;
    handler = logging::make_exception_handler<
        std::runtime_error,
        std::exception
    >(my_handler1_nothrow(pExceptionType), std::nothrow);

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::runtime_error));
    pExceptionType = 0;

    try
    {
        throw std::logic_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;

    try
    {
        throw my_exception();
    }
    catch (...)
    {
        BOOST_CHECK_NO_THROW(handler());
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(void));
    pExceptionType = 0;

    // Verify that exception types are checked in the specified order
    handler = logging::make_exception_handler<
        std::exception,
        std::runtime_error
    >(my_handler1_nothrow(pExceptionType), std::nothrow);

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;
}

// Tests for handler with self-contained exception types
BOOST_AUTO_TEST_CASE(self_contained_exception_types)
{
    std::type_info const* pExceptionType = 0;
    my_function0 handler;
    handler = logging::make_exception_handler(my_handler2(pExceptionType));

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::runtime_error));
    pExceptionType = 0;

    try
    {
        throw std::logic_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;

    try
    {
        throw my_exception();
    }
    catch (...)
    {
        BOOST_CHECK_THROW(handler(), my_exception);
    }
    BOOST_REQUIRE(pExceptionType == 0);
}

// Tests for handler with self-contained exception types (no-throw version)
BOOST_AUTO_TEST_CASE(self_contained_exception_types_nothrow)
{
    std::type_info const* pExceptionType = 0;
    my_function0 handler;
    handler = logging::make_exception_handler(my_handler2_nothrow(pExceptionType), std::nothrow);

    try
    {
        throw std::runtime_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::runtime_error));
    pExceptionType = 0;

    try
    {
        throw std::logic_error("error");
    }
    catch (...)
    {
        handler();
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(std::exception));
    pExceptionType = 0;

    try
    {
        throw my_exception();
    }
    catch (...)
    {
        BOOST_CHECK_NO_THROW(handler());
    }
    BOOST_REQUIRE(pExceptionType != 0);
    BOOST_CHECK(*pExceptionType == typeid(void));
    pExceptionType = 0;
}
