/*
 * 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.)
 * 
 * See http://www.boost.org/libs/iostreams for documentation.
 *
 * Tests the function templates boost::iostreams::detail::execute_all and
 * boost::iostreams::detail::execute_foreach
 *
 * File:        libs/iostreams/test/execute_test.cpp
 * Date:        Thu Dec 06 13:21:54 MST 2007
 * Copyright:   2007-2008 CodeRage, LLC
 * Author:      Jonathan Turkanis
 * Contact:     turkanis at coderage dot com
 */

#include <boost/iostreams/detail/execute.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/test/unit_test.hpp>  

using namespace std;
using namespace boost;
using namespace boost::iostreams;
using namespace boost::iostreams::detail;
using boost::unit_test::test_suite;

// Function object that sets a boolean flag and returns a value
// specified at construction
template<typename Result>
class operation {
public:
    typedef Result result_type;
    explicit operation(Result r, bool& executed) 
        : r_(r), executed_(executed) 
        { }
    Result operator()() const 
    { 
        executed_ = true;
        return r_; 
    }
private:
    operation& operator=(const operation&);
    Result  r_;
    bool&   executed_;  
};

// Specialization for void return
template<>
class operation<void> {
public:
    typedef void result_type;
    explicit operation(bool& executed) : executed_(executed) { }
    void operator()() const { executed_ = true; }
private:
    operation& operator=(const operation&);
    bool& executed_; 
};

// Simple exception class with error code built in to type
template<int Code>
struct error { };

// Function object that sets a boolean flag and throws an exception
template<int Code>
class thrower {
public:
    typedef void result_type;
    explicit thrower(bool& executed) : executed_(executed) { }
    void operator()() const 
    { 
        executed_ = true;
        throw error<Code>(); 
    }
private:
    thrower& operator=(const thrower&);
    bool& executed_; 
};

// Function object for use by foreach_test
class foreach_func {
public:
    typedef void result_type;
    explicit foreach_func(int& count) : count_(count) { }
    void operator()(int x) const
    {
        ++count_;
        switch (x) {
        case 0: throw error<0>();
        case 1: throw error<1>();
        case 2: throw error<2>();
        case 3: throw error<3>();
        case 4: throw error<4>();
        case 5: throw error<5>();
        case 6: throw error<6>();
        case 7: throw error<7>();
        case 8: throw error<8>();
        case 9: throw error<9>();
        default:
            break;
        }
    }
private:
    foreach_func& operator=(const foreach_func&);
    int&  count_; // Number of times operator() has been called
};

void success_test()
{
    // Test returning an int
    {
        bool executed = false;
        BOOST_CHECK(execute_all(operation<int>(9, executed)) == 9);
        BOOST_CHECK(executed);
    }

    // Test returning void
    {
        bool executed = false;
        execute_all(operation<void>(executed));
        BOOST_CHECK(executed);
    }

    // Test returning an int with one cleanup operation
    {
        bool executed = false, cleaned_up = false;
        BOOST_CHECK(
            execute_all(
                operation<int>(9, executed),
                operation<void>(cleaned_up)
            ) == 9
        );
        BOOST_CHECK(executed && cleaned_up);
    }

    // Test returning void with one cleanup operation
    {
        bool executed = false, cleaned_up = false;
        execute_all(
            operation<void>(executed),
            operation<void>(cleaned_up)
        );
        BOOST_CHECK(executed && cleaned_up);
    }

    // Test returning an int with two cleanup operations
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK(
            execute_all(
                operation<int>(9, executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2)
            ) == 9
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test returning void with two cleanup operations
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        execute_all(
            operation<void>(executed),
            operation<void>(cleaned_up1),
            operation<void>(cleaned_up2)
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test returning an int with three cleanup operations
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK(
            execute_all(
                operation<int>(9, executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2),
                operation<void>(cleaned_up3)
            ) == 9
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test returning void with three cleanup operations
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        execute_all(
            operation<void>(executed),
            operation<void>(cleaned_up1),
            operation<void>(cleaned_up2),
            operation<void>(cleaned_up3)
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }
}

void operation_throws_test()
{
    // Test primary operation throwing with no cleanup operations
    {
        bool executed = false;
        BOOST_CHECK_THROW(
            execute_all(thrower<0>(executed)),
            error<0>
        );
        BOOST_CHECK(executed);
    }

    // Test primary operation throwing with one cleanup operation
    {
        bool executed = false, cleaned_up = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up);
    }

    // Test primary operation throwing with two cleanup operations
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2)
            ), 
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test primary operation throwing with three cleanup operations
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2),
                operation<void>(cleaned_up3)
            ), 
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }
}

void cleanup_throws_test()
{
    // Test single cleanup operation that throws
    {
        bool executed = false, cleaned_up = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up);
    }

    // Test fist of two cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                operation<void>(cleaned_up2)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test second of two cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                operation<void>(cleaned_up1),
                thrower<2>(cleaned_up2)
            ),
            error<2>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test first of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                operation<void>(cleaned_up2),
                operation<void>(cleaned_up3)
            ), 
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test second of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                operation<void>(cleaned_up1),
                thrower<2>(cleaned_up2),
                operation<void>(cleaned_up3)
            ), 
            error<2>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test third of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2),
                thrower<3>(cleaned_up3)
            ), 
            error<3>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }
}

void multiple_exceptions_test()
{
    // Test primary operation and cleanup operation throwing
    {
        bool executed = false, cleaned_up = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                thrower<1>(cleaned_up)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up);
    }

    // Test primary operation and first of two cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                thrower<1>(cleaned_up1),
                operation<void>(cleaned_up2)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test primary operation and second of two cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up1),
                thrower<2>(cleaned_up2)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test two cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, cleaned_up2 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                thrower<2>(cleaned_up2)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2);
    }

    // Test primary operation and first of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                thrower<1>(cleaned_up1),
                operation<void>(cleaned_up2),
                operation<void>(cleaned_up3)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test primary operation and second of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up1),
                thrower<2>(cleaned_up2),
                operation<void>(cleaned_up3)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test primary operation and third of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                thrower<0>(executed),
                operation<void>(cleaned_up1),
                operation<void>(cleaned_up2),
                thrower<3>(cleaned_up3)
            ),
            error<0>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test first and second of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                thrower<2>(cleaned_up2),
                operation<void>(cleaned_up3)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test first and third of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                operation<void>(cleaned_up2),
                thrower<3>(cleaned_up3)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test second and third of three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                operation<void>(cleaned_up1),
                thrower<2>(cleaned_up2),
                thrower<3>(cleaned_up3)
            ),
            error<2>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }

    // Test three cleanup operations throwing
    {
        bool executed = false, cleaned_up1 = false, 
             cleaned_up2 = false, cleaned_up3 = false;
        BOOST_CHECK_THROW(
            execute_all(
                operation<void>(executed),
                thrower<1>(cleaned_up1),
                thrower<2>(cleaned_up2),
                thrower<3>(cleaned_up3)
            ),
            error<1>
        );
        BOOST_CHECK(executed && cleaned_up1 && cleaned_up2 && cleaned_up3);
    }
}

#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof(ar[0]))

void foreach_test()
{
    // Test case where neither of two operations throws
    {
        int count = 0;
        int seq[] = {-1, -1};
        BOOST_CHECK_NO_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count))
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where first of two operations throws
    {
        int count = 0;
        int seq[] = {0, -1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where second of two operations throws
    {
        int count = 0;
        int seq[] = {-1, 1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<1>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where both of two operations throw
    {
        int count = 0;
        int seq[] = {0, 1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where none of three operations throws
    {
        int count = 0;
        int seq[] = {-1, -1, -1};
        BOOST_CHECK_NO_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count))
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where first of three operations throw
    {
        int count = 0;
        int seq[] = {0, -1, -1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where second of three operations throw
    {
        int count = 0;
        int seq[] = {-1, 1, -1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<1>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where third of three operations throw
    {
        int count = 0;
        int seq[] = {-1, -1, 2};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<2>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where first and second of three operations throw
    {
        int count = 0;
        int seq[] = {0, 1, -1};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where first and third of three operations throw
    {
        int count = 0;
        int seq[] = {0, -1, 2};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where second and third of three operations throw
    {
        int count = 0;
        int seq[] = {-1, 1, 2};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<1>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }

    // Test case where three of three operations throw
    {
        int count = 0;
        int seq[] = {0, 1, 2};
        BOOST_CHECK_THROW(
            execute_foreach(seq, seq + ARRAY_SIZE(seq), foreach_func(count)),
            error<0>
        );
        BOOST_CHECK(count == ARRAY_SIZE(seq));
    }
}

test_suite* init_unit_test_suite(int, char* []) 
{
    test_suite* test = BOOST_TEST_SUITE("execute test");
    test->add(BOOST_TEST_CASE(&success_test));
    test->add(BOOST_TEST_CASE(&operation_throws_test));
    test->add(BOOST_TEST_CASE(&cleanup_throws_test));
    test->add(BOOST_TEST_CASE(&multiple_exceptions_test));
    test->add(BOOST_TEST_CASE(&foreach_test));
    return test;
}
