/***************************************************************************
 *
 * 21.string.exceptions.cpp - test string members that throw exceptions
 *
 * $Id$
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 1994-2008 Rogue Wave Software.
 *
 **************************************************************************/

#include <cassert>
#include <cstddef>
#include <stdexcept>
#include <string>

#include <rw_driver.h>

// uses setjmp() and longjmp() to verify correct function if exception
// exception support is disabled
#ifdef _RWSTD_NO_EXCEPTIONS
#  include <csetjmp>

int     id;   // "thrown" from user_throw () defined below
jmp_buf env;  // used to implement fake exception handling

#  undef try
#  define try           if (0 == (id = setjmp (env)))

#  undef catch
#  define catch(ignore) else if (0 != id)
#endif   // _RWSTD_NO_EXCEPTIONS


// use a type that string isn't instantiated/specialized on by the library
// also use a native type to avoid having to specialize char_traits
typedef int Char;

typedef std::char_traits<Char> CharTraits;
typedef std::allocator<Char> CharAllocator;

typedef std::basic_string<Char, CharTraits, CharAllocator> String;


_RWSTD_NAMESPACE (std) {

// specialize in order to easily induce exceptional conditions
_RWSTD_SPECIALIZED_CLASS
String::size_type String::max_size () const
{
    return _RWSTD_NEW_CAPACITY (String, this, 0);
}

}   // namespace std


#ifndef _RWSTD_NO_REPLACEABLE_NEW_DELETE
#  include <rw_new.h>
#else
   // can't check for memory leaks using the replacement
   // operators new and delete
#  define rwt_check_leaks(psize, ignore) \
          ((psize) ? *(std::size_t*)(psize) = 0 : 0)
#endif


/* static */ void
user_throw (int ex_id, char *what)
{
    _RW::__rw_free_what_buf (what);

#ifndef _RWSTD_NO_EXCEPTIONS

    throw ex_id;

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    longjmp (env, ex_id);

#endif   // _RWSTD_NO_EXCEPTIONS
}

/**************************************************************************/

static void
test_simple_throw ()
{
    rw_info (0, __FILE__, __LINE__, "exception handling test setup");

    // establish a chekpoint for memory leaks
    rwt_check_leaks (0, 0);

#ifdef _RWSTD_NO_EXCEPTIONS

    // prevent library from aborting if exception support is disabled
    _RW::__rw_throw_proc = user_throw;

#endif   // _RWSTD_NO_EXCEPTIONS

    int thrown = 0;

    try {
        // throw and catch a bogus exception in order to initialize
        // data structures internal to the library to prevent any
        // memory allocation from throwing off memory leak detection
        _RWSTD_REQUIRES (0, (_RWSTD_ERROR_OUT_OF_RANGE,
                             _RWSTD_FUNC ("test_simple_throw ()"), 0, 0));
    }

#ifndef _RWSTD_NO_EXCEPTIONS

    catch (std::out_of_range&) {
        thrown = 1;
    }

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    catch (int id) {
        thrown = id == _RWSTD_ERROR_OUT_OF_RANGE;
    }

#endif   // _RWSTD_NO_EXCEPTIONS

    catch (...) {
        thrown = -1;
    }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "_RWSTD_REQUIRES (_RWSTD_ERROR_OUT_OF_RANGE) failed to "
               "throw std::out_of_range");
}

/**************************************************************************/

// const objects used throughout remaining test cases

const Char s[] = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'a', 'b', 'c', 'd', 'e', 'f', '\0'
};

const String s0 (s);

/**************************************************************************/

// constructor tests

static void
test_size_ctor ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.1, p4 (size)");

    // establish a chekpoint for memory leaks
    rwt_check_leaks (0, 0);

    int thrown = 0;

    try {
        // throws std::out_of_range if pos > str.size ()
        String s1 (s0, s0.size () + 1);
    }
#ifndef _RWSTD_NO_EXCEPTIONS

    catch (std::out_of_range&) {
        thrown = 1;
    }

#else   // if defined (_RWSTD_NO_EXCEPTIONS)

    catch (int id) {
        thrown = id == _RWSTD_ERROR_OUT_OF_RANGE;
    }

#endif   // _RWSTD_NO_EXCEPTIONS

    catch (...) {
        thrown = -1;
    }

    std::size_t nbytes; /* uninitialized */
    std::size_t nblocks = rwt_check_leaks (&nbytes, 0);
    _RWSTD_UNUSED (nblocks);

    rw_assert (1 == thrown, __FILE__, __LINE__,
               ("string::string (const string&, size_type, size_type, "
                "const allocator_type&) failed to throw std::out_of_range"));

    rw_assert (s == s0, __FILE__, __LINE__,
               "original const string modified");

    rw_assert (0 == nbytes, __FILE__, __LINE__,
               "string::string (const string&, size_type, size_type, "
               "const allocator_type&) leaked %u bytes", nbytes);
}

static void
test_npos_ctor ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.1, p4 (npos)");

    _RW::__rw_throw_proc = user_throw;

    // establish a chekpoint for memory leaks
    rwt_check_leaks (0, 0);

    int thrown = 0;

    try {
        // throws std::out_of_range if pos > str.size ()
        String s1 (s0, String::npos);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    std::size_t nbytes; /* uninitialized */
    std::size_t nblocks = rwt_check_leaks (&nbytes, 0);
    _RWSTD_UNUSED (nblocks);

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::string (const string&, size_type, size_type, "
               "const allocator_type&) failed to use __rw::__rw_throw()");

    rw_assert (0 == nbytes, __FILE__, __LINE__,
               "string::string (const string&, size_type, size_type, "
               "const allocator_type&) leaked %u bytes", nbytes);

    rw_assert (s == s0, __FILE__, __LINE__,
               "original const string modified");
}

static void
test_max_size_ctor1 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.1, p7");

    // establish a chekpoint for memory leaks
    rwt_check_leaks (0, 0);

    int thrown = 0;

    try {
        // throws std::out_of_range if n > max_size () (*)
        // (*) see also lwg issue 83
        String s1 (s0.data (), s0.max_size () + 1);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    std::size_t nbytes; /* uninitialized */
    std::size_t nblocks = rwt_check_leaks (&nbytes, 0);
    _RWSTD_UNUSED (nblocks);

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::string (const char_type*, size_type) "
               "failed to use __rw::__rw_throw()");

    rw_assert (0 == nbytes, __FILE__, __LINE__,
               "string::string (const char_type*, size_type)"
               "leaked %u bytes", nbytes);

    rw_assert (s == s0, __FILE__, __LINE__,
               "original const string modified");
}

static void
test_max_size_ctor2 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.1, p13");

    int thrown = 0;

    try {
        // throws std::length_error if n > max_size () (*)
        // (*) see also lwg issue 83
        String s1 (s0.max_size () + 1, Char ());
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::string (size_type, char_type) "
               "failed to use __rw::__rw_throw()");
}

static void
test_len_ctor ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.1, p15");

    int thrown = 0;

    try {
        // same as 21.3.1, p13 for integral arguments
        String s1 = String (long (String::npos), long (' '));
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::string (InputIterator, InputIterator)"
               "failed to use __rw::__rw_throw()");
}

/**************************************************************************/

// member functions

static void
test_resize ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.3, p6");

    String s1 (s0);

    // save data, size and capacity of a constructed string
    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    rw_assert (s1.size () == s0.size () && s1 == s0, __FILE__, __LINE__,
               "std::string::string (const string&)");

    int thrown = 0;

    try {
        // throws std::length_error if n > max_size ()
        s1.resize (s1.max_size () + 1U /* , char () */);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::resize (size_type, char_type)"
               "failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::resize (size_type, char_type) modified *this");
}

static void
test_reserve ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.3, p12");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::length_error if n > max_size ()
        s1.reserve (s1.max_size () + 1U);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::reserve (size_type)"
               "failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::reserve (size_type) modified *this");
}

static void
test_at ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.4, p3");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if pos > size ()
        s1.at (s1.size ());
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::at (size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::at (size_type) modified *this");
}

static void
test_append ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.2, p3");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if pos > size ()
        s1.append (s1, s1.size () + 1, 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::append (const string&, size_type, size_type) "
               "failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::append (const string&, size_type, size_type) "
               "modified *this");
}

static void
test_assign ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.3, p3");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if pos > size ()
        s1.assign (s1, s1.size () + 1, 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::assign (const string&, size_type, size_type) "
               "failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::assign (const string&, size_type, size_type) "
               "modified *this");
}

static void
test_insert1 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.4, p3 (pos1 > size ())");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if:
        // 1)    pos1 > size ()       <-- testing
        // 2) or pos2 > str.size ()
        s1.insert (s1.size () + 1, s1, 0, 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::insert (size_type, const string&, size_type, "
               "size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::insert (size_type, const string&, size_type, "
               "modified *this");
}

static void
test_insert2 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.4, p3 (pos2 > str.size ())");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if:
        // 1)    pos1 > size ()
        // 2) or pos2 > str.size ()   <-- testing
        s1.insert (s1.size (), s1, s1.size () + 1, 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::insert (size_type, const string&, size_type, "
               "size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::insert (size_type, const string&, size_type, "
               "modified *this");
}

static void
test_erase ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.5, p2");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if pos > size ()
        s1.erase (s1.size () + 1 /* , String::npos */);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::erase (size_type, size_type) "
               "failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::erase (size_type, size_type) modified *this");
}

static void
test_replace1 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.6, p3 (pos1 > size ())");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if:
        // 1)    pos1 > size ()      <-- testing
        // 2) or pos2 > str.size ()
        s1.replace (s1.size () + 1, 0, s0, s0.size (), 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) modified *this");
}

static void
test_replace2 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.6, p3 (pos2 > str.size ())");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    try {
        // throws std::out_of_range if:
        // 1)    pos1 > size ()
        // 2) or pos2 > str.size ()   <-- testing
        s1.replace (s1.size (), 0, s1, s1.size () + 1, 0);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) modified *this");
}

static void
test_replace3 ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.6, p5");

    int thrown = 0;

    String s1 (s0);

    const String::const_pointer s1_data = s1.data ();
    const String::size_type     s1_size = s1.size ();
    const String::size_type     s1_cap  = s1.capacity ();

    // establish a chekpoint for memory leaks
    rwt_check_leaks (0, 0);

    try {
        // make sure max_size() isn't too big
        assert (s1.max_size () == _RWSTD_NEW_CAPACITY (String, &s1, 0));

        thrown = -1;

        // must not throw
        String s2 (s1.max_size () - s1.size () + 2, Char ());

        thrown = 0;

        // throws std::length_error if:
        //               size () - xlen >= max_size () - rlen (*)
        // where xlen = min (n1, this->size () - pos1)
        // and   rlen = min (n2, str.size () - pos2)
        // (*) see also lwg issue 86
        s1.replace (0, 1, s2, 0, s2.size ());
    }
    catch (int id) {
        thrown = 0 == thrown && _RWSTD_ERROR_LENGTH_ERROR == id;
    }
    catch (...) { /* empty */ }

    std::size_t nbytes; /* uninitialized */
    std::size_t nblocks = rwt_check_leaks (&nbytes, 0);
    _RWSTD_UNUSED (nblocks);

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) failed to use __rw::__rw_throw()");

    // verify that string wasn't modified
    rw_assert (s1_data == s1.data () && s1_size == s1.size ()
               && s1_cap == s1.capacity (), __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) modified *this");

    // tests not only replace() but also string ctor (s2 above)
    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::replace (size_type, size_type, const string&, "
               "size_type, size_type) leaked %u bytes", nbytes);
}

static void
test_copy ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.5.7, p2");

    int thrown = 0;

    Char c = '\1';

    String s1 (s0);

    try {
        // throws std::out_of_range if pos > size ()
        s1.copy (&c, 1, s1.size () + 1);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::copy (pointer, size_type, size_type) "
               "failed to use __rw::__rw_throw()");

    // verify that destination buffer wasn't modified
    rw_assert ('\1' == c, __FILE__, __LINE__,
               "string::copy (pointer, size_type, size_type) "
               "modified buffer");
}

static void
test_substr ()
{
    rw_info (0, __FILE__, __LINE__, "21.3.6.7, p2");

    int thrown = 0;

    String s1 (s0);

    try {
        // throws std::out_of_range if pos > size ()
        s1.substr (s1.size () + 1 /* , String::npos */);
    }
    catch (int id) {
        thrown = _RWSTD_ERROR_OUT_OF_RANGE == id;
    }
    catch (...) { /* empty */ }

    rw_assert (1 == thrown, __FILE__, __LINE__,
               "string::substr (size_type, size_type) "
               "failed to use __rw::__rw_throw()");
}

/**************************************************************************/

static int
run_test (int /*unused*/, char* /*unused*/ [])
{
    test_simple_throw ();

    // constructors
    test_size_ctor ();
    test_npos_ctor ();

    test_max_size_ctor1 ();
    test_max_size_ctor2 ();

    test_len_ctor ();

    // member functions
    test_resize ();
    test_reserve ();
    test_at ();
    test_append ();
    test_assign ();

    test_insert1 ();
    test_insert2 ();

    test_erase ();

    test_replace1 ();
    test_replace2 ();
    test_replace3 ();

    test_copy ();
    test_substr ();

    return 0;
}

/*extern*/ int
main (int argc, char* argv [])
{
    return rw_test (argc, argv, __FILE__,
                    "lib.basic.string",
                    "exception saftety",
                    run_test, "", 0);
}

